Skip to content

Commit df0c1f6

Browse files
authored
refactor(core): migrate MessageV2 tool state schemas to Effect Schema (#23752)
1 parent d6dea3f commit df0c1f6

1 file changed

Lines changed: 74 additions & 68 deletions

File tree

packages/opencode/src/session/message-v2.ts

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import { isMedia } from "@/util/media"
1515
import type { SystemError } from "bun"
1616
import type { Provider } from "@/provider"
1717
import { ModelID, ProviderID } from "@/provider/schema"
18-
import { Effect, Schema } from "effect"
19-
import { zod } from "@/util/effect-zod"
18+
import { Effect, Schema, Types } from "effect"
19+
import { zod, ZodOverride } from "@/util/effect-zod"
20+
import { withStatics } from "@/util/schema"
2021
import { EffectLogger } from "@/effect"
2122

2223
/** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */
@@ -272,79 +273,84 @@ export const StepFinishPart = PartBase.extend({
272273
})
273274
export type StepFinishPart = z.infer<typeof StepFinishPart>
274275

275-
export const ToolStatePending = z
276-
.object({
277-
status: z.literal("pending"),
278-
input: z.record(z.string(), z.any()),
279-
raw: z.string(),
280-
})
281-
.meta({
282-
ref: "ToolStatePending",
283-
})
284-
285-
export type ToolStatePending = z.infer<typeof ToolStatePending>
276+
export const ToolStatePending = Schema.Struct({
277+
status: Schema.Literal("pending"),
278+
input: Schema.Record(Schema.String, Schema.Any),
279+
raw: Schema.String,
280+
})
281+
.annotate({ identifier: "ToolStatePending" })
282+
.pipe(withStatics((s) => ({ zod: zod(s) })))
283+
export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>>
284+
285+
export const ToolStateRunning = Schema.Struct({
286+
status: Schema.Literal("running"),
287+
input: Schema.Record(Schema.String, Schema.Any),
288+
title: Schema.optional(Schema.String),
289+
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
290+
time: Schema.Struct({
291+
start: Schema.Number,
292+
}),
293+
})
294+
.annotate({ identifier: "ToolStateRunning" })
295+
.pipe(withStatics((s) => ({ zod: zod(s) })))
296+
export type ToolStateRunning = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateRunning>>
297+
298+
export const ToolStateCompleted = Schema.Struct({
299+
status: Schema.Literal("completed"),
300+
input: Schema.Record(Schema.String, Schema.Any),
301+
output: Schema.String,
302+
title: Schema.String,
303+
metadata: Schema.Record(Schema.String, Schema.Any),
304+
time: Schema.Struct({
305+
start: Schema.Number,
306+
end: Schema.Number,
307+
compacted: Schema.optional(Schema.Number),
308+
}),
309+
// FilePart is still Zod-first this slice; bridge via ZodOverride so the
310+
// derived Zod + JSON Schema still emit `$ref: FilePart` array items.
311+
attachments: Schema.optional(Schema.Any.annotate({ [ZodOverride]: FilePart.array() })),
312+
})
313+
.annotate({ identifier: "ToolStateCompleted" })
314+
.pipe(withStatics((s) => ({ zod: zod(s) })))
315+
export type ToolStateCompleted = Omit<
316+
Types.DeepMutable<Schema.Schema.Type<typeof ToolStateCompleted>>,
317+
"attachments"
318+
> & {
319+
attachments?: FilePart[]
320+
}
286321

287-
export const ToolStateRunning = z
288-
.object({
289-
status: z.literal("running"),
290-
input: z.record(z.string(), z.any()),
291-
title: z.string().optional(),
292-
metadata: z.record(z.string(), z.any()).optional(),
293-
time: z.object({
294-
start: z.number(),
295-
}),
296-
})
297-
.meta({
298-
ref: "ToolStateRunning",
299-
})
300-
export type ToolStateRunning = z.infer<typeof ToolStateRunning>
301-
302-
export const ToolStateCompleted = z
303-
.object({
304-
status: z.literal("completed"),
305-
input: z.record(z.string(), z.any()),
306-
output: z.string(),
307-
title: z.string(),
308-
metadata: z.record(z.string(), z.any()),
309-
time: z.object({
310-
start: z.number(),
311-
end: z.number(),
312-
compacted: z.number().optional(),
313-
}),
314-
attachments: FilePart.array().optional(),
315-
})
316-
.meta({
317-
ref: "ToolStateCompleted",
318-
})
319-
export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>
320-
321-
export const ToolStateError = z
322-
.object({
323-
status: z.literal("error"),
324-
input: z.record(z.string(), z.any()),
325-
error: z.string(),
326-
metadata: z.record(z.string(), z.any()).optional(),
327-
time: z.object({
328-
start: z.number(),
329-
end: z.number(),
330-
}),
331-
})
332-
.meta({
333-
ref: "ToolStateError",
334-
})
335-
export type ToolStateError = z.infer<typeof ToolStateError>
322+
export const ToolStateError = Schema.Struct({
323+
status: Schema.Literal("error"),
324+
input: Schema.Record(Schema.String, Schema.Any),
325+
error: Schema.String,
326+
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
327+
time: Schema.Struct({
328+
start: Schema.Number,
329+
end: Schema.Number,
330+
}),
331+
})
332+
.annotate({ identifier: "ToolStateError" })
333+
.pipe(withStatics((s) => ({ zod: zod(s) })))
334+
export type ToolStateError = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateError>>
336335

337-
export const ToolState = z
338-
.discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
339-
.meta({
340-
ref: "ToolState",
341-
})
336+
const _ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).annotate({
337+
discriminator: "status",
338+
identifier: "ToolState",
339+
})
340+
// Cast the derived zod so downstream z.infer sees the same mutable shape that
341+
// our exported TS types expose (the pre-migration Zod inferences were mutable).
342+
export const ToolState = Object.assign(_ToolState, {
343+
zod: zod(_ToolState) as unknown as z.ZodType<
344+
ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
345+
>,
346+
})
347+
export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
342348

343349
export const ToolPart = PartBase.extend({
344350
type: z.literal("tool"),
345351
callID: z.string(),
346352
tool: z.string(),
347-
state: ToolState,
353+
state: ToolState.zod,
348354
metadata: z.record(z.string(), z.any()).optional(),
349355
}).meta({
350356
ref: "ToolPart",

0 commit comments

Comments
 (0)