diff options
| author | Kit Langton <[email protected]> | 2026-04-21 17:47:50 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-21 17:47:50 -0400 |
| commit | df0c1f649c306bd1c125c0b532bd17b76bf888c0 (patch) | |
| tree | 9b08ff24b21b1e8613777a6f0226271129b91b5d | |
| parent | d6dea3f3e00598a734d2fb61dbc9d74ccbd1781c (diff) | |
| download | opencode-df0c1f649c306bd1c125c0b532bd17b76bf888c0.tar.gz opencode-df0c1f649c306bd1c125c0b532bd17b76bf888c0.zip | |
refactor(core): migrate MessageV2 tool state schemas to Effect Schema (#23752)
| -rw-r--r-- | packages/opencode/src/session/message-v2.ts | 142 |
1 files changed, 74 insertions, 68 deletions
diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 83477d12b..04cb15ef8 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -15,8 +15,9 @@ import { isMedia } from "@/util/media" import type { SystemError } from "bun" import type { Provider } from "@/provider" import { ModelID, ProviderID } from "@/provider/schema" -import { Effect, Schema } from "effect" -import { zod } from "@/util/effect-zod" +import { Effect, Schema, Types } from "effect" +import { zod, ZodOverride } from "@/util/effect-zod" +import { withStatics } from "@/util/schema" import { EffectLogger } from "@/effect" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ @@ -272,79 +273,84 @@ export const StepFinishPart = PartBase.extend({ }) export type StepFinishPart = z.infer<typeof StepFinishPart> -export const ToolStatePending = z - .object({ - status: z.literal("pending"), - input: z.record(z.string(), z.any()), - raw: z.string(), - }) - .meta({ - ref: "ToolStatePending", - }) - -export type ToolStatePending = z.infer<typeof ToolStatePending> +export const ToolStatePending = Schema.Struct({ + status: Schema.Literal("pending"), + input: Schema.Record(Schema.String, Schema.Any), + raw: Schema.String, +}) + .annotate({ identifier: "ToolStatePending" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>> + +export const ToolStateRunning = Schema.Struct({ + status: Schema.Literal("running"), + input: Schema.Record(Schema.String, Schema.Any), + title: Schema.optional(Schema.String), + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: Schema.Number, + }), +}) + .annotate({ identifier: "ToolStateRunning" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStateRunning = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateRunning>> + +export const ToolStateCompleted = Schema.Struct({ + status: Schema.Literal("completed"), + input: Schema.Record(Schema.String, Schema.Any), + output: Schema.String, + title: Schema.String, + metadata: Schema.Record(Schema.String, Schema.Any), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + compacted: Schema.optional(Schema.Number), + }), + // FilePart is still Zod-first this slice; bridge via ZodOverride so the + // derived Zod + JSON Schema still emit `$ref: FilePart` array items. + attachments: Schema.optional(Schema.Any.annotate({ [ZodOverride]: FilePart.array() })), +}) + .annotate({ identifier: "ToolStateCompleted" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStateCompleted = Omit< + Types.DeepMutable<Schema.Schema.Type<typeof ToolStateCompleted>>, + "attachments" +> & { + attachments?: FilePart[] +} -export const ToolStateRunning = z - .object({ - status: z.literal("running"), - input: z.record(z.string(), z.any()), - title: z.string().optional(), - metadata: z.record(z.string(), z.any()).optional(), - time: z.object({ - start: z.number(), - }), - }) - .meta({ - ref: "ToolStateRunning", - }) -export type ToolStateRunning = z.infer<typeof ToolStateRunning> - -export const ToolStateCompleted = z - .object({ - status: z.literal("completed"), - input: z.record(z.string(), z.any()), - output: z.string(), - title: z.string(), - metadata: z.record(z.string(), z.any()), - time: z.object({ - start: z.number(), - end: z.number(), - compacted: z.number().optional(), - }), - attachments: FilePart.array().optional(), - }) - .meta({ - ref: "ToolStateCompleted", - }) -export type ToolStateCompleted = z.infer<typeof ToolStateCompleted> - -export const ToolStateError = z - .object({ - status: z.literal("error"), - input: z.record(z.string(), z.any()), - error: z.string(), - metadata: z.record(z.string(), z.any()).optional(), - time: z.object({ - start: z.number(), - end: z.number(), - }), - }) - .meta({ - ref: "ToolStateError", - }) -export type ToolStateError = z.infer<typeof ToolStateError> +export const ToolStateError = Schema.Struct({ + status: Schema.Literal("error"), + input: Schema.Record(Schema.String, Schema.Any), + error: Schema.String, + metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), + time: Schema.Struct({ + start: Schema.Number, + end: Schema.Number, + }), +}) + .annotate({ identifier: "ToolStateError" }) + .pipe(withStatics((s) => ({ zod: zod(s) }))) +export type ToolStateError = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateError>> -export const ToolState = z - .discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]) - .meta({ - ref: "ToolState", - }) +const _ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).annotate({ + discriminator: "status", + identifier: "ToolState", +}) +// Cast the derived zod so downstream z.infer sees the same mutable shape that +// our exported TS types expose (the pre-migration Zod inferences were mutable). +export const ToolState = Object.assign(_ToolState, { + zod: zod(_ToolState) as unknown as z.ZodType< + ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError + >, +}) +export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError export const ToolPart = PartBase.extend({ type: z.literal("tool"), callID: z.string(), tool: z.string(), - state: ToolState, + state: ToolState.zod, metadata: z.record(z.string(), z.any()).optional(), }).meta({ ref: "ToolPart", |
