refactor(core): migrate MessageV2 errors to Schema-backed named errors (#23764)

This commit is contained in:
Kit Langton
2026-04-21 23:40:32 -04:00
committed by GitHub
parent 1593c3ed16
commit ed802fd121
3 changed files with 86 additions and 33 deletions

View File

@@ -985,7 +985,8 @@ export const GithubRunCommand = cmd({
const err = result.info.error
console.error("Agent error:", err)
if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files))
throw new Error(`${err.name}: ${err.data?.message || ""}`)
const message = "message" in err.data ? err.data.message : ""
throw new Error(`${err.name}: ${message}`)
}
const text = extractResponseText(result.parts)
@@ -1014,7 +1015,8 @@ export const GithubRunCommand = cmd({
const err = summary.info.error
console.error("Summary agent error:", err)
if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files))
throw new Error(`${err.name}: ${err.data?.message || ""}`)
const message = "message" in err.data ? err.data.message : ""
throw new Error(`${err.name}: ${message}`)
}
const summaryText = extractResponseText(summary.parts)

View File

@@ -18,6 +18,7 @@ import { ModelID, ProviderID } from "@/provider/schema"
import { Effect, Schema, Types } from "effect"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
import { EffectLogger } from "@/effect"
/** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */
@@ -30,38 +31,29 @@ interface FetchDecompressionError extends Error {
export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached image(s) from tool result:"
export { isMedia }
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
export const StructuredOutputError = NamedError.create(
"StructuredOutputError",
z.object({
message: z.string(),
retries: z.number(),
}),
)
export const AuthError = NamedError.create(
"ProviderAuthError",
z.object({
providerID: z.string(),
message: z.string(),
}),
)
export const APIError = NamedError.create(
"APIError",
z.object({
message: z.string(),
statusCode: z.number().optional(),
isRetryable: z.boolean(),
responseHeaders: z.record(z.string(), z.string()).optional(),
responseBody: z.string().optional(),
metadata: z.record(z.string(), z.string()).optional(),
}),
)
export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
export const AbortedError = namedSchemaError("MessageAbortedError", { message: Schema.String })
export const StructuredOutputError = namedSchemaError("StructuredOutputError", {
message: Schema.String,
retries: Schema.Number,
})
export const AuthError = namedSchemaError("ProviderAuthError", {
providerID: Schema.String,
message: Schema.String,
})
export const APIError = namedSchemaError("APIError", {
message: Schema.String,
statusCode: Schema.optional(Schema.Number),
isRetryable: Schema.Boolean,
responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)),
responseBody: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
})
export type APIError = z.infer<typeof APIError.Schema>
export const ContextOverflowError = NamedError.create(
"ContextOverflowError",
z.object({ message: z.string(), responseBody: z.string().optional() }),
)
export const ContextOverflowError = namedSchemaError("ContextOverflowError", {
message: Schema.String,
responseBody: Schema.optional(Schema.String),
})
export class OutputFormatText extends Schema.Class<OutputFormatText>("OutputFormatText")({
type: Schema.Literal("text"),

View File

@@ -0,0 +1,59 @@
import { Schema } from "effect"
import z from "zod"
import { zod } from "@/util/effect-zod"
/**
* Create a Schema-backed NamedError-shaped class.
*
* Drop-in replacement for `NamedError.create(tag, zodShape)` but backed by
* `Schema.Struct` under the hood. The wire shape emitted by the derived
* `.Schema` is still `{ name: tag, data: {...fields} }` so the generated
* OpenAPI/SDK output is byte-identical to the original NamedError schema.
*
* Preserves the existing surface:
* - static `Schema` (Zod schema of the wire shape)
* - static `isInstance(x)`
* - instance `toObject()` returning `{ name, data }`
* - `new X({ ...data }, { cause })`
*/
export function namedSchemaError<Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) {
// Wire shape matches the original NamedError output so the SDK stays stable.
const dataSchema = Schema.Struct(fields)
const wire = z
.object({
name: z.literal(tag),
data: zod(dataSchema),
})
.meta({ ref: tag })
type Data = Schema.Schema.Type<typeof dataSchema>
class NamedSchemaError extends Error {
static readonly Schema = wire
static readonly tag = tag
public static isInstance(input: unknown): input is NamedSchemaError {
return (
typeof input === "object" &&
input !== null &&
"name" in input &&
(input as { name: unknown }).name === tag
)
}
public override readonly name: Tag = tag
public readonly data: Data
constructor(data: Data, options?: ErrorOptions) {
super(tag, options)
this.data = data
}
toObject(): { name: Tag; data: Data } {
return { name: tag, data: this.data }
}
}
Object.defineProperty(NamedSchemaError, "name", { value: tag })
return NamedSchemaError
}