diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 42b94ffcc5..ba2a8c7446 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,7 +1,7 @@ import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin" import { Auth } from "@/auth" import { InstanceState } from "@/effect/instance-state" -import { namedSchemaError } from "@/util/named-schema-error" +import { NamedError } from "@opencode-ai/core/util/error" import { optionalOmitUndefined } from "@opencode-ai/core/schema" import { Plugin } from "../plugin" import { ProviderID } from "./schema" @@ -64,13 +64,13 @@ export const CallbackInput = Schema.Struct({ }) export type CallbackInput = Schema.Schema.Type -export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID }) +export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", { providerID: ProviderID }) -export const OauthCodeMissing = namedSchemaError("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) +export const OauthCodeMissing = NamedError.create("ProviderAuthOauthCodeMissing", { providerID: ProviderID }) -export const OauthCallbackFailed = namedSchemaError("ProviderAuthOauthCallbackFailed", {}) +export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", {}) -export const ValidationFailed = namedSchemaError("ProviderAuthValidationFailed", { +export const ValidationFailed = NamedError.create("ProviderAuthValidationFailed", { field: Schema.String, message: Schema.String, }) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 236f14de75..f381e848d8 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -13,7 +13,7 @@ import { Auth } from "../auth" import { Env } from "../env" import { InstallationVersion } from "@opencode-ai/core/installation/version" import { Flag } from "@opencode-ai/core/flag/flag" -import { namedSchemaError } from "@/util/named-schema-error" +import { NamedError } from "@opencode-ai/core/util/error" import { iife } from "@/util/iife" import { Global } from "@opencode-ai/core/global" import path from "path" @@ -1749,13 +1749,13 @@ export function parseModel(model: string) { } } -export const ModelNotFoundError = namedSchemaError("ProviderModelNotFoundError", { +export const ModelNotFoundError = NamedError.create("ProviderModelNotFoundError", { providerID: ProviderID, modelID: ModelID, suggestions: Schema.optional(Schema.Array(Schema.String)), }) -export const InitError = namedSchemaError("ProviderInitError", { +export const InitError = NamedError.create("ProviderInitError", { providerID: ProviderID, }) diff --git a/packages/opencode/src/session/message-error.ts b/packages/opencode/src/session/message-error.ts new file mode 100644 index 0000000000..bf40d45be0 --- /dev/null +++ b/packages/opencode/src/session/message-error.ts @@ -0,0 +1,14 @@ +import { Schema } from "effect" +import { NamedError } from "@opencode-ai/core/util/error" + +export const OutputLengthError = NamedError.create("MessageOutputLengthError", {}) + +export const AuthError = NamedError.create("ProviderAuthError", { + providerID: Schema.String, + message: Schema.String, +}) + +export const Shared = [AuthError.EffectSchema, NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema] as const +export const SharedSchema = Schema.Union(Shared) + +export * as MessageError from "./message-error" diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index f797e2dc3d..626261d0f6 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -23,8 +23,10 @@ import type { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "@/provider/schema" import { Effect, Schema, Types } from "effect" import { NonNegativeInt } from "@opencode-ai/core/schema" -import { namedSchemaError } from "@/util/named-schema-error" import * as EffectLogger from "@opencode-ai/core/effect/logger" +import { MessageError } from "./message-error" +import { AuthError, OutputLengthError } from "./message-error" +export { AuthError, OutputLengthError } from "./message-error" /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */ interface FetchDecompressionError extends Error { @@ -36,17 +38,12 @@ interface FetchDecompressionError extends Error { export const SYNTHETIC_ATTACHMENT_PROMPT = "Attached media from tool result:" export { isMedia } -export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) -export const AbortedError = namedSchemaError("MessageAbortedError", { message: Schema.String }) -export const StructuredOutputError = namedSchemaError("StructuredOutputError", { +export const AbortedError = NamedError.create("MessageAbortedError", { message: Schema.String }) +export const StructuredOutputError = NamedError.create("StructuredOutputError", { message: Schema.String, retries: NonNegativeInt, }) -export const AuthError = namedSchemaError("ProviderAuthError", { - providerID: Schema.String, - message: Schema.String, -}) -export const APIError = namedSchemaError("APIError", { +export const APIError = NamedError.create("APIError", { message: Schema.String, statusCode: Schema.optional(NonNegativeInt), isRetryable: Schema.Boolean, @@ -55,7 +52,7 @@ export const APIError = namedSchemaError("APIError", { metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)), }) export type APIError = Schema.Schema.Type -export const ContextOverflowError = namedSchemaError("ContextOverflowError", { +export const ContextOverflowError = NamedError.create("ContextOverflowError", { message: Schema.String, responseBody: Schema.optional(Schema.String), }) @@ -381,9 +378,7 @@ export type Part = | CompactionPart const AssistantErrorSchema = Schema.Union([ - AuthError.EffectSchema, - NamedError.Unknown.EffectSchema, - OutputLengthError.EffectSchema, + ...MessageError.Shared, AbortedError.EffectSchema, StructuredOutputError.EffectSchema, ContextOverflowError.EffectSchema, diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index 6a859ffaa4..39c842f94b 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -2,14 +2,9 @@ import { Schema } from "effect" import { SessionID } from "./schema" import { ModelID, ProviderID } from "../provider/schema" import { NonNegativeInt } from "@opencode-ai/core/schema" -import { namedSchemaError } from "@/util/named-schema-error" -import { NamedError } from "@opencode-ai/core/util/error" - -export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}) -export const AuthError = namedSchemaError("ProviderAuthError", { - providerID: Schema.String, - message: Schema.String, -}) +import { MessageError } from "./message-error" +import { AuthError, OutputLengthError } from "./message-error" +export { AuthError, OutputLengthError } from "./message-error" export const ToolCall = Schema.Struct({ state: Schema.Literal("call"), @@ -105,9 +100,7 @@ export const Info = Schema.Struct({ created: NonNegativeInt, completed: Schema.optional(NonNegativeInt), }), - error: Schema.optional( - Schema.Union([AuthError.EffectSchema, NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema]), - ), + error: Schema.optional(MessageError.SharedSchema), sessionID: SessionID, tool: Schema.Record( Schema.String, diff --git a/packages/opencode/src/util/named-schema-error.ts b/packages/opencode/src/util/named-schema-error.ts deleted file mode 100644 index cc02c3731a..0000000000 --- a/packages/opencode/src/util/named-schema-error.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Schema } from "effect" -import { NamedError } from "@opencode-ai/core/util/error" - -/** - * Create a Schema-backed NamedError-shaped class. - */ -export function namedSchemaError(tag: Tag, fields: Fields) { - return NamedError.create(tag, fields) -} diff --git a/packages/opencode/test/util/error.test.ts b/packages/opencode/test/util/error.test.ts index bc966133ab..8d077b1f26 100644 --- a/packages/opencode/test/util/error.test.ts +++ b/packages/opencode/test/util/error.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test" import { Schema } from "effect" import { NamedError } from "@opencode-ai/core/util/error" import { errorData, errorFormat, errorMessage } from "../../src/util/error" -import { namedSchemaError } from "../../src/util/named-schema-error" import { UI } from "../../src/cli/ui" +import { MessageError } from "../../src/session/message-error" describe("util.error", () => { test("formats native Error instances", () => { @@ -53,12 +53,11 @@ describe("util.error", () => { expect(String(data.formatted)).toContain("ResolveMessage") }) - test("named schema errors are real NamedError instances", () => { - const ExampleError = namedSchemaError("ExampleError", { message: Schema.String }) - const error = new ExampleError({ message: "boom" }) + test("schema-backed named errors are real NamedError instances", () => { + const error = new MessageError.AuthError({ providerID: "anthropic", message: "boom" }) expect(error).toBeInstanceOf(NamedError) - expect(error.toObject()).toEqual({ name: "ExampleError", data: { message: "boom" } }) + expect(error.toObject()).toEqual({ name: "ProviderAuthError", data: { providerID: "anthropic", message: "boom" } }) }) test("void named errors accept JSON without data", () => {