fix(core): clean up Effect audit patterns (#35174)
This commit is contained in:
parent
08293e74a0
commit
1b88ff8d53
18 changed files with 118 additions and 62 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import nodePath from "path"
|
||||
import { customType } from "drizzle-orm/sqlite-core"
|
||||
import { Schema } from "effect"
|
||||
import { AbsolutePath } from "../schema"
|
||||
|
||||
function storagePath(input: string) {
|
||||
|
|
@ -74,6 +75,8 @@ export const pathColumn = customType<{
|
|||
},
|
||||
})
|
||||
|
||||
const decodeAbsoluteArray = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Array(Schema.String)))
|
||||
|
||||
export const absoluteArrayColumn = customType<{
|
||||
data: AbsolutePath[]
|
||||
driverData: string
|
||||
|
|
@ -86,6 +89,6 @@ export const absoluteArrayColumn = customType<{
|
|||
return JSON.stringify(input.map(absolute))
|
||||
},
|
||||
fromDriver(input) {
|
||||
return (JSON.parse(input) as string[]).map((item) => AbsolutePath.make(toPlatform(absolute(item))))
|
||||
return decodeAbsoluteArray(input).map((item) => AbsolutePath.make(toPlatform(absolute(item))))
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -113,7 +113,8 @@ const layer = Layer.effect(
|
|||
)
|
||||
}
|
||||
|
||||
const config = (yield* (yield* Config.Service).entries())
|
||||
const configService = yield* Config.Service
|
||||
const config = (yield* configService.entries())
|
||||
.filter((entry): entry is Config.Document => entry.type === "document")
|
||||
.flatMap((item) => item.info.watcher?.ignore ?? [])
|
||||
yield* Effect.forkScoped(
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const Cost = Schema.Struct({
|
|||
const ReasoningOption = Schema.Union([
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("effort"),
|
||||
values: Schema.Array(Schema.String),
|
||||
values: Schema.Array(Schema.Union([Schema.String, Schema.Null])),
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("toggle"),
|
||||
|
|
@ -125,6 +125,10 @@ export const Provider = Schema.Struct({
|
|||
|
||||
export type Provider = Schema.Schema.Type<typeof Provider>
|
||||
|
||||
const Providers = Schema.Record(Schema.String, Provider)
|
||||
const decodeProviders = Schema.decodeUnknownEffect(Schema.fromJsonString(Providers))
|
||||
const decodeProvidersUnknown = Schema.decodeUnknownEffect(Providers)
|
||||
|
||||
export const Event = ModelsDev.Event
|
||||
|
||||
declare const OPENCODE_MODELS_DEV: Record<string, Provider> | undefined
|
||||
|
|
@ -176,6 +180,7 @@ const layer = Layer.effect(
|
|||
})
|
||||
|
||||
const loadFromDisk = fs.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).pipe(
|
||||
Effect.flatMap(decodeProvidersUnknown),
|
||||
Effect.catch((error) => {
|
||||
if (
|
||||
Flag.OPENCODE_MODELS_PATH === undefined &&
|
||||
|
|
@ -186,11 +191,17 @@ const layer = Layer.effect(
|
|||
}
|
||||
return Effect.succeed(undefined)
|
||||
}),
|
||||
Effect.map((v) => v as Record<string, Provider> | undefined),
|
||||
)
|
||||
|
||||
const loadSnapshot = Effect.sync(() =>
|
||||
typeof OPENCODE_MODELS_DEV === "undefined" ? undefined : OPENCODE_MODELS_DEV,
|
||||
).pipe(
|
||||
Effect.flatMap((snapshot) =>
|
||||
snapshot === undefined ? Effect.succeed(undefined) : decodeProvidersUnknown(snapshot),
|
||||
),
|
||||
Effect.catch((cause) =>
|
||||
Effect.logWarning("bundled models snapshot failed schema decode", { cause }).pipe(Effect.as(undefined)),
|
||||
),
|
||||
)
|
||||
|
||||
const fetchAndWrite = Effect.fn("ModelsDev.fetchAndWrite")(function* () {
|
||||
|
|
@ -221,7 +232,7 @@ const layer = Layer.effect(
|
|||
return yield* fetchAndWrite()
|
||||
}),
|
||||
)
|
||||
return JSON.parse(text) as Record<string, Provider>
|
||||
return yield* decodeProviders(text)
|
||||
}).pipe(Effect.withSpan("ModelsDev.populate"), Effect.orDie)
|
||||
|
||||
const [cachedGet, invalidate] = yield* Effect.cachedInvalidateWithTTL(populate, Duration.infinity)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createServer } from "node:http"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { Deferred, Effect, Semaphore, Stream } from "effect"
|
||||
import { Deferred, Effect, Option, Schema, Semaphore, Stream } from "effect"
|
||||
import type { Scope } from "effect"
|
||||
import { Credential } from "../../credential"
|
||||
import { EventV2 } from "../../event"
|
||||
|
|
@ -32,11 +32,16 @@ type TokenResponse = {
|
|||
expires_in?: number
|
||||
}
|
||||
|
||||
type Claims = {
|
||||
chatgpt_account_id?: string
|
||||
organizations?: Array<{ id: string }>
|
||||
"https://api.openai.com/auth"?: { chatgpt_account_id?: string }
|
||||
}
|
||||
const Claims = Schema.fromJsonString(
|
||||
Schema.Struct({
|
||||
chatgpt_account_id: Schema.optional(Schema.String),
|
||||
organizations: Schema.optional(Schema.Array(Schema.Struct({ id: Schema.String }))),
|
||||
"https://api.openai.com/auth": Schema.optional(
|
||||
Schema.Struct({ chatgpt_account_id: Schema.optional(Schema.String) }),
|
||||
),
|
||||
}),
|
||||
)
|
||||
const decodeClaims = Schema.decodeUnknownOption(Claims)
|
||||
|
||||
const browser = {
|
||||
integrationID: Integration.ID.make("openai"),
|
||||
|
|
@ -315,14 +320,11 @@ function extractAccountID(tokens: TokenResponse) {
|
|||
function claim(token: string) {
|
||||
const part = token.split(".")[1]
|
||||
if (!part) return
|
||||
try {
|
||||
const claims = JSON.parse(Buffer.from(part, "base64url").toString()) as Claims
|
||||
return (
|
||||
claims.chatgpt_account_id ??
|
||||
claims["https://api.openai.com/auth"]?.chatgpt_account_id ??
|
||||
claims.organizations?.[0]?.id
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const claims = Option.getOrUndefined(decodeClaims(Buffer.from(part, "base64url").toString()))
|
||||
if (!claims) return
|
||||
return (
|
||||
claims.chatgpt_account_id ??
|
||||
claims["https://api.openai.com/auth"]?.chatgpt_account_id ??
|
||||
claims.organizations?.[0]?.id
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,17 +19,18 @@ export const SapAICorePlugin = define({
|
|||
const installedPath = evt.package.startsWith("file://")
|
||||
? evt.package
|
||||
: (yield* npm.add(evt.package).pipe(Effect.orDie)).entrypoint
|
||||
if (!installedPath) throw new Error(`Package ${evt.package} has no import entrypoint`)
|
||||
if (!installedPath) return yield* Effect.die(new Error(`Package ${evt.package} has no import entrypoint`))
|
||||
|
||||
const mod = yield* Effect.promise(async () => {
|
||||
return (await import(
|
||||
installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href
|
||||
)) as Record<string, (options: any) => any>
|
||||
}).pipe(Effect.orDie)
|
||||
const mod: Record<string, unknown> = yield* Effect.promise(
|
||||
() => import(installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href),
|
||||
)
|
||||
const match = Object.keys(mod).find((name) => name.startsWith("create"))
|
||||
if (!match) throw new Error(`Package ${evt.package} has no provider factory export`)
|
||||
if (!match) return yield* Effect.die(new Error(`Package ${evt.package} has no provider factory export`))
|
||||
const factory = mod[match]
|
||||
if (typeof factory !== "function")
|
||||
return yield* Effect.die(new Error(`Package ${evt.package} provider factory export is not callable`))
|
||||
|
||||
evt.sdk = mod[match](
|
||||
evt.sdk = factory(
|
||||
serviceKey
|
||||
? { deploymentId: process.env.AICORE_DEPLOYMENT_ID, resourceGroup: process.env.AICORE_RESOURCE_GROUP }
|
||||
: {},
|
||||
|
|
|
|||
|
|
@ -202,7 +202,8 @@ const layer = Layer.effect(
|
|||
const copyDirectory = yield* canonical(input.directory)
|
||||
const stored = yield* directories.get({ projectID: input.projectID, directory: copyDirectory })
|
||||
if (!stored?.strategy) return yield* new InvalidDirectoryError({ directory: copyDirectory })
|
||||
yield* (yield* getStrategy(StrategyID.make(stored.strategy))).remove({
|
||||
const strategy = yield* getStrategy(StrategyID.make(stored.strategy))
|
||||
yield* strategy.remove({
|
||||
directory: copyDirectory,
|
||||
force: input.force,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const RawMatch = Schema.Struct({
|
|||
),
|
||||
}),
|
||||
})
|
||||
const decodeJsonRecord = Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)
|
||||
|
||||
type RawMatchData = (typeof RawMatch.Type)["data"]
|
||||
|
||||
|
|
@ -232,10 +233,7 @@ const layer = Layer.effect(
|
|||
parse: (line) =>
|
||||
(Buffer.byteLength(line, "utf8") > MAX_RECORD_BYTES
|
||||
? Effect.fail(failure(`Ripgrep JSON record exceeded ${MAX_RECORD_BYTES} bytes`))
|
||||
: Effect.try({
|
||||
try: () => JSON.parse(line) as unknown,
|
||||
catch: (cause) => failure("Invalid ripgrep JSON output", cause),
|
||||
})
|
||||
: decodeJsonRecord(line).pipe(Effect.mapError((cause) => failure("Invalid ripgrep JSON output", cause)))
|
||||
).pipe(
|
||||
Effect.flatMap((json) => {
|
||||
if (!json || typeof json !== "object" || !("type" in json) || json.type !== "match")
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ function insertMessage(db: DatabaseService, event: SessionEvent.DurableEvent, me
|
|||
const layer = Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
const { db } = yield* Database.Service
|
||||
const db = (yield* Database.Service).db
|
||||
yield* events.project(SessionV1.Event.Created, (event) =>
|
||||
Effect.gen(function* () {
|
||||
const stored = yield* db
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
type Model,
|
||||
type ProviderMetadata,
|
||||
} from "@opencode-ai/llm"
|
||||
import { Option, Schema } from "effect"
|
||||
import { SessionMessage } from "../message"
|
||||
import type { FileAttachment } from "../prompt"
|
||||
|
||||
|
|
@ -18,14 +19,12 @@ const media = (file: FileAttachment): ContentPart => ({
|
|||
metadata: file.description === undefined ? undefined : { description: file.description },
|
||||
})
|
||||
|
||||
const toolInput = (tool: SessionMessage.AssistantTool) => {
|
||||
if (tool.state.status !== "pending") return tool.state.input
|
||||
try {
|
||||
return JSON.parse(tool.state.input) as unknown
|
||||
} catch {
|
||||
return tool.state.input
|
||||
}
|
||||
}
|
||||
const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString)
|
||||
|
||||
const toolInput = (tool: SessionMessage.AssistantTool) =>
|
||||
tool.state.status === "pending"
|
||||
? Option.getOrElse(decodeToolInput(tool.state.input), () => tool.state.input)
|
||||
: tool.state.input
|
||||
|
||||
const toolCall = (tool: SessionMessage.AssistantTool, providerMetadata: ProviderMetadata | undefined): ContentPart =>
|
||||
ToolCallPart.make({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue