refactor(core): migrate MessageV2 errors to Schema-backed named errors (#23764)
This commit is contained in:
parent
1593c3ed16
commit
ed802fd121
3 changed files with 86 additions and 33 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
59
packages/opencode/src/util/named-schema-error.ts
Normal file
59
packages/opencode/src/util/named-schema-error.ts
Normal 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
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue