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
commit ed802fd121
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 33 deletions

View file

@ -985,7 +985,8 @@ export const GithubRunCommand = cmd({
const err = result.info.error const err = result.info.error
console.error("Agent error:", err) console.error("Agent error:", err)
if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files)) 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) const text = extractResponseText(result.parts)
@ -1014,7 +1015,8 @@ export const GithubRunCommand = cmd({
const err = summary.info.error const err = summary.info.error
console.error("Summary agent error:", err) console.error("Summary agent error:", err)
if (err.name === "ContextOverflowError") throw new Error(formatPromptTooLargeError(files)) 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) const summaryText = extractResponseText(summary.parts)

View file

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