summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-21 23:40:32 -0400
committerGitHub <[email protected]>2026-04-21 23:40:32 -0400
commited802fd121c46dd4efc3b87e7bf4865b630ccb21 (patch)
tree49612ab35d549f685a5821a665232d20654a67eb
parent1593c3ed16369001f24252d0091092da8db26bf3 (diff)
downloadopencode-ed802fd121c46dd4efc3b87e7bf4865b630ccb21.tar.gz
opencode-ed802fd121c46dd4efc3b87e7bf4865b630ccb21.zip
refactor(core): migrate MessageV2 errors to Schema-backed named errors (#23764)
-rw-r--r--packages/opencode/src/cli/cmd/github.ts6
-rw-r--r--packages/opencode/src/session/message-v2.ts54
-rw-r--r--packages/opencode/src/util/named-schema-error.ts59
3 files changed, 86 insertions, 33 deletions
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index ed1ca2124..fe8e233dd 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -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)
diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts
index aceecd9b8..123f7b540 100644
--- a/packages/opencode/src/session/message-v2.ts
+++ b/packages/opencode/src/session/message-v2.ts
@@ -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"),
diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts
new file mode 100644
index 000000000..5fcc93cba
--- /dev/null
+++ b/packages/opencode/src/util/named-schema-error.ts
@@ -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
+}