refactor(core): migrate MessageV2 tool state schemas to Effect Schema (#23752)

This commit is contained in:
Kit Langton
2026-04-21 17:47:50 -04:00
committed by GitHub
parent d6dea3f3e0
commit df0c1f649c
+72 -66
View File
@@ -15,8 +15,9 @@ import { isMedia } from "@/util/media"
import type { SystemError } from "bun" import type { SystemError } from "bun"
import type { Provider } from "@/provider" import type { Provider } from "@/provider"
import { ModelID, ProviderID } from "@/provider/schema" import { ModelID, ProviderID } from "@/provider/schema"
import { Effect, Schema } from "effect" import { Effect, Schema, Types } from "effect"
import { zod } from "@/util/effect-zod" import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { EffectLogger } from "@/effect" import { EffectLogger } from "@/effect"
/** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ /** 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 type StepFinishPart = z.infer<typeof StepFinishPart>
export const ToolStatePending = z export const ToolStatePending = Schema.Struct({
.object({ status: Schema.Literal("pending"),
status: z.literal("pending"), input: Schema.Record(Schema.String, Schema.Any),
input: z.record(z.string(), z.any()), raw: Schema.String,
raw: z.string(), })
}) .annotate({ identifier: "ToolStatePending" })
.meta({ .pipe(withStatics((s) => ({ zod: zod(s) })))
ref: "ToolStatePending", export type ToolStatePending = Types.DeepMutable<Schema.Schema.Type<typeof ToolStatePending>>
})
export type ToolStatePending = z.infer<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 ToolStateRunning = z export const ToolStateCompleted = Schema.Struct({
.object({ status: Schema.Literal("completed"),
status: z.literal("running"), input: Schema.Record(Schema.String, Schema.Any),
input: z.record(z.string(), z.any()), output: Schema.String,
title: z.string().optional(), title: Schema.String,
metadata: z.record(z.string(), z.any()).optional(), metadata: Schema.Record(Schema.String, Schema.Any),
time: z.object({ time: Schema.Struct({
start: z.number(), start: Schema.Number,
}), end: Schema.Number,
}) compacted: Schema.optional(Schema.Number),
.meta({ }),
ref: "ToolStateRunning", // FilePart is still Zod-first this slice; bridge via ZodOverride so the
}) // derived Zod + JSON Schema still emit `$ref: FilePart` array items.
export type ToolStateRunning = z.infer<typeof ToolStateRunning> 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 ToolStateCompleted = z export const ToolStateError = Schema.Struct({
.object({ status: Schema.Literal("error"),
status: z.literal("completed"), input: Schema.Record(Schema.String, Schema.Any),
input: z.record(z.string(), z.any()), error: Schema.String,
output: z.string(), metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
title: z.string(), time: Schema.Struct({
metadata: z.record(z.string(), z.any()), start: Schema.Number,
time: z.object({ end: Schema.Number,
start: z.number(), }),
end: z.number(), })
compacted: z.number().optional(), .annotate({ identifier: "ToolStateError" })
}), .pipe(withStatics((s) => ({ zod: zod(s) })))
attachments: FilePart.array().optional(), export type ToolStateError = Types.DeepMutable<Schema.Schema.Type<typeof ToolStateError>>
})
.meta({
ref: "ToolStateCompleted",
})
export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>
export const ToolStateError = z const _ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).annotate({
.object({ discriminator: "status",
status: z.literal("error"), identifier: "ToolState",
input: z.record(z.string(), z.any()), })
error: z.string(), // Cast the derived zod so downstream z.infer sees the same mutable shape that
metadata: z.record(z.string(), z.any()).optional(), // our exported TS types expose (the pre-migration Zod inferences were mutable).
time: z.object({ export const ToolState = Object.assign(_ToolState, {
start: z.number(), zod: zod(_ToolState) as unknown as z.ZodType<
end: z.number(), ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
}), >,
}) })
.meta({ export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
ref: "ToolStateError",
})
export type ToolStateError = z.infer<typeof ToolStateError>
export const ToolState = z
.discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
.meta({
ref: "ToolState",
})
export const ToolPart = PartBase.extend({ export const ToolPart = PartBase.extend({
type: z.literal("tool"), type: z.literal("tool"),
callID: z.string(), callID: z.string(),
tool: z.string(), tool: z.string(),
state: ToolState, state: ToolState.zod,
metadata: z.record(z.string(), z.any()).optional(), metadata: z.record(z.string(), z.any()).optional(),
}).meta({ }).meta({
ref: "ToolPart", ref: "ToolPart",