mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-13 15:44:56 +00:00
Remove Zod from named errors (#26982)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import z from "zod"
|
||||
import { Schema } from "effect"
|
||||
|
||||
export abstract class NamedError extends Error {
|
||||
abstract schema(): z.core.$ZodType
|
||||
abstract toObject(): { name: string; data: any }
|
||||
abstract schema(): Schema.Top
|
||||
abstract toObject(): { name: string; data: unknown }
|
||||
|
||||
static hasName(error: unknown, name: string): boolean {
|
||||
return (
|
||||
@@ -10,30 +10,42 @@ export abstract class NamedError extends Error {
|
||||
)
|
||||
}
|
||||
|
||||
static create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data) {
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.literal(name),
|
||||
data,
|
||||
})
|
||||
.meta({
|
||||
ref: name,
|
||||
})
|
||||
static create<Name extends string, Fields extends Schema.Struct.Fields>(
|
||||
name: Name,
|
||||
fields: Fields,
|
||||
): ReturnType<typeof NamedError.createSchemaClass<Name, Schema.Struct<Fields>>>
|
||||
static create<Name extends string, DataSchema extends Schema.Top>(
|
||||
name: Name,
|
||||
data: DataSchema,
|
||||
): ReturnType<typeof NamedError.createSchemaClass<Name, DataSchema>>
|
||||
static create<Name extends string>(name: Name, data: Schema.Top | Schema.Struct.Fields) {
|
||||
return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data))
|
||||
}
|
||||
|
||||
private static createSchemaClass<Name extends string, DataSchema extends Schema.Top>(name: Name, data: DataSchema) {
|
||||
const schema = Schema.Struct({
|
||||
name: Schema.Literal(name),
|
||||
data,
|
||||
}).annotate({ identifier: name })
|
||||
type Data = Schema.Schema.Type<DataSchema>
|
||||
|
||||
const result = class extends NamedError {
|
||||
public static readonly Schema = schema
|
||||
public static readonly EffectSchema = schema
|
||||
public static readonly tag = name
|
||||
|
||||
public override readonly name = name as Name
|
||||
public override readonly name = name
|
||||
|
||||
constructor(
|
||||
public readonly data: z.input<Data>,
|
||||
public readonly data: Data,
|
||||
options?: ErrorOptions,
|
||||
) {
|
||||
super(name, options)
|
||||
this.name = name
|
||||
}
|
||||
|
||||
static isInstance(input: any): input is InstanceType<typeof result> {
|
||||
return typeof input === "object" && "name" in input && input.name === name
|
||||
static isInstance(input: unknown): input is InstanceType<typeof result> {
|
||||
return NamedError.hasName(input, name)
|
||||
}
|
||||
|
||||
schema() {
|
||||
@@ -51,10 +63,7 @@ export abstract class NamedError extends Error {
|
||||
return result
|
||||
}
|
||||
|
||||
public static readonly Unknown = NamedError.create(
|
||||
"UnknownError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
public static readonly Unknown = NamedError.create("UnknownError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { DateTime } from "luxon"
|
||||
import { createStore } from "solid-js/store"
|
||||
import z from "zod"
|
||||
import NotFound from "../[...404]"
|
||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
||||
@@ -33,13 +32,28 @@ const ClientOnlyWorkerPoolProvider = clientOnly(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const SessionDataMissingError = NamedError.create(
|
||||
"SessionDataMissingError",
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
message: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
class SessionDataMissingError extends NamedError {
|
||||
public override readonly name = "SessionDataMissingError"
|
||||
|
||||
constructor(
|
||||
public readonly data: { sessionID: string; message?: string },
|
||||
options?: ErrorOptions,
|
||||
) {
|
||||
super("SessionDataMissingError", options)
|
||||
}
|
||||
|
||||
static isInstance(input: unknown): input is SessionDataMissingError {
|
||||
return NamedError.hasName(input, "SessionDataMissingError")
|
||||
}
|
||||
|
||||
schema(): never {
|
||||
throw new Error("SessionDataMissingError does not expose a schema")
|
||||
}
|
||||
|
||||
toObject() {
|
||||
return { name: this.name, data: this.data }
|
||||
}
|
||||
}
|
||||
|
||||
const getData = query(async (shareID) => {
|
||||
"use server"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import z from "zod"
|
||||
import { EOL } from "os"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Schema } from "effect"
|
||||
import { logo as glyphs } from "./logo"
|
||||
|
||||
const wordmark = [
|
||||
@@ -10,7 +10,7 @@ const wordmark = [
|
||||
`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`,
|
||||
]
|
||||
|
||||
export const CancelledError = NamedError.create("UICancelledError", z.void())
|
||||
export const CancelledError = NamedError.create("UICancelledError", Schema.optional(Schema.Void))
|
||||
|
||||
export const Style = {
|
||||
TEXT_HIGHLIGHT: "\x1b[96m",
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as Log from "@opencode-ai/core/util/log"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import os from "os"
|
||||
import z from "zod"
|
||||
import { mergeDeep } from "remeda"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import fsNode from "fs/promises"
|
||||
@@ -357,14 +356,11 @@ function writableGlobal(info: Info) {
|
||||
return next
|
||||
}
|
||||
|
||||
export const ConfigDirectoryTypoError = NamedError.create(
|
||||
"ConfigDirectoryTypoError",
|
||||
z.object({
|
||||
path: z.string(),
|
||||
dir: z.string(),
|
||||
suggestion: z.string(),
|
||||
}),
|
||||
)
|
||||
export const ConfigDirectoryTypoError = NamedError.create("ConfigDirectoryTypoError", {
|
||||
path: Schema.String,
|
||||
dir: Schema.String,
|
||||
suggestion: Schema.String,
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
export * as ConfigError from "./error"
|
||||
|
||||
import z from "zod"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Schema } from "effect"
|
||||
|
||||
export const JsonError = NamedError.create(
|
||||
"ConfigJsonError",
|
||||
z.object({
|
||||
path: z.string(),
|
||||
message: z.string().optional(),
|
||||
const Issue = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
message: Schema.String,
|
||||
path: Schema.Array(Schema.String),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Unknown)],
|
||||
)
|
||||
|
||||
export const InvalidError = NamedError.create(
|
||||
"ConfigInvalidError",
|
||||
z.object({
|
||||
path: z.string(),
|
||||
issues: z.custom<z.core.$ZodIssue[]>().optional(),
|
||||
message: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
export const JsonError = NamedError.create("ConfigJsonError", {
|
||||
path: Schema.String,
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
export const InvalidError = NamedError.create("ConfigInvalidError", {
|
||||
path: Schema.String,
|
||||
issues: Schema.optional(Schema.Array(Issue)),
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import matter from "gray-matter"
|
||||
import { z } from "zod"
|
||||
import { Schema } from "effect"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
|
||||
@@ -88,12 +88,9 @@ export async function parse(filePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export const FrontmatterError = NamedError.create(
|
||||
"ConfigFrontmatterError",
|
||||
z.object({
|
||||
path: z.string(),
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const FrontmatterError = NamedError.create("ConfigFrontmatterError", {
|
||||
path: Schema.String,
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export * as ConfigMarkdown from "./markdown"
|
||||
|
||||
@@ -2,7 +2,6 @@ export * as ConfigParse from "./parse"
|
||||
|
||||
import { type ParseError as JsoncParseError, parse as parseJsoncImpl, printParseErrorCode } from "jsonc-parser"
|
||||
import { Cause, Exit, Schema as EffectSchema, SchemaIssue } from "effect"
|
||||
import type z from "zod"
|
||||
import type { DeepMutable } from "@opencode-ai/core/schema"
|
||||
import { InvalidError, JsonError } from "./error"
|
||||
|
||||
@@ -48,7 +47,7 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
|
||||
keys: extra,
|
||||
path: [],
|
||||
message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`,
|
||||
} as z.core.$ZodIssue,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -61,8 +60,12 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
|
||||
{
|
||||
path: source,
|
||||
issues: EffectSchema.isSchemaError(error)
|
||||
? (SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues as z.core.$ZodIssue[])
|
||||
: ([{ code: "custom", message: String(error), path: [] }] as z.core.$ZodIssue[]),
|
||||
? SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues.map((issue) => ({
|
||||
...issue,
|
||||
message: issue.message,
|
||||
path: issue.path?.map(String) ?? [],
|
||||
}))
|
||||
: [{ message: String(error), path: [] }],
|
||||
},
|
||||
{ cause: error },
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import z from "zod"
|
||||
import { Schema } from "effect"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
@@ -24,14 +23,11 @@ export const Event = {
|
||||
),
|
||||
}
|
||||
|
||||
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({}))
|
||||
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})
|
||||
|
||||
export const InstallFailedError = NamedError.create(
|
||||
"InstallFailedError",
|
||||
z.object({
|
||||
stderr: z.string(),
|
||||
}),
|
||||
)
|
||||
export const InstallFailedError = NamedError.create("InstallFailedError", {
|
||||
stderr: Schema.String,
|
||||
})
|
||||
|
||||
export function ide() {
|
||||
if (process.env["TERM_PROGRAM"] === "vscode") {
|
||||
|
||||
@@ -39,6 +39,7 @@ import { PluginCommand } from "./cli/cmd/plug"
|
||||
import { Heap } from "./cli/heap"
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite"
|
||||
import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
|
||||
import { isRecord } from "@/util/record"
|
||||
|
||||
const processMetadata = ensureProcessMetadata("main")
|
||||
|
||||
@@ -203,13 +204,6 @@ try {
|
||||
}
|
||||
} catch (e) {
|
||||
let data: Record<string, any> = {}
|
||||
if (e instanceof NamedError) {
|
||||
const obj = e.toObject()
|
||||
Object.assign(data, {
|
||||
...obj.data,
|
||||
})
|
||||
}
|
||||
|
||||
if (e instanceof Error) {
|
||||
Object.assign(data, {
|
||||
name: e.name,
|
||||
@@ -219,6 +213,16 @@ try {
|
||||
})
|
||||
}
|
||||
|
||||
if (e instanceof NamedError) {
|
||||
const obj = e.toObject()
|
||||
if (isRecord(obj.data)) {
|
||||
for (const [key, value] of Object.entries(obj.data)) {
|
||||
if (key === "name" || key === "stack" || key === "cause") continue
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e instanceof ResolveMessage) {
|
||||
Object.assign(data, {
|
||||
name: e.name,
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Process } from "@/util/process"
|
||||
import { LANGUAGE_EXTENSIONS } from "./language"
|
||||
import z from "zod"
|
||||
import { Schema } from "effect"
|
||||
import type * as LSPServer from "./server"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
@@ -32,12 +31,9 @@ export type Info = NonNullable<Awaited<ReturnType<typeof create>>>
|
||||
|
||||
export type Diagnostic = VSCodeDiagnostic
|
||||
|
||||
export const InitializeError = NamedError.create(
|
||||
"LSPInitializeError",
|
||||
z.object({
|
||||
serverID: z.string(),
|
||||
}),
|
||||
)
|
||||
export const InitializeError = NamedError.create("LSPInitializeError", {
|
||||
serverID: Schema.String,
|
||||
})
|
||||
|
||||
export const Event = {
|
||||
Diagnostics: BusEvent.define(
|
||||
|
||||
@@ -68,12 +68,9 @@ export const BrowserOpenFailed = BusEvent.define(
|
||||
}),
|
||||
)
|
||||
|
||||
export const Failed = NamedError.create(
|
||||
"MCPFailed",
|
||||
z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
)
|
||||
export const Failed = NamedError.create("MCPFailed", {
|
||||
name: Schema.String,
|
||||
})
|
||||
|
||||
type MCPClient = Client
|
||||
|
||||
|
||||
@@ -382,9 +382,7 @@ export type Part =
|
||||
|
||||
const AssistantErrorSchema = Schema.Union([
|
||||
AuthError.EffectSchema,
|
||||
Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({
|
||||
identifier: "UnknownError",
|
||||
}),
|
||||
NamedError.Unknown.EffectSchema,
|
||||
OutputLengthError.EffectSchema,
|
||||
AbortedError.EffectSchema,
|
||||
StructuredOutputError.EffectSchema,
|
||||
|
||||
@@ -3,6 +3,7 @@ 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", {
|
||||
@@ -10,26 +11,6 @@ export const AuthError = namedSchemaError("ProviderAuthError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
const AuthErrorEffect = Schema.Struct({
|
||||
name: Schema.Literal("ProviderAuthError"),
|
||||
data: Schema.Struct({
|
||||
providerID: Schema.String,
|
||||
message: Schema.String,
|
||||
}),
|
||||
})
|
||||
|
||||
const OutputLengthErrorEffect = Schema.Struct({
|
||||
name: Schema.Literal("MessageOutputLengthError"),
|
||||
data: Schema.Struct({}),
|
||||
})
|
||||
|
||||
const UnknownErrorEffect = Schema.Struct({
|
||||
name: Schema.Literal("UnknownError"),
|
||||
data: Schema.Struct({
|
||||
message: Schema.String,
|
||||
}),
|
||||
})
|
||||
|
||||
export const ToolCall = Schema.Struct({
|
||||
state: Schema.Literal("call"),
|
||||
step: Schema.optional(NonNegativeInt),
|
||||
@@ -124,7 +105,9 @@ export const Info = Schema.Struct({
|
||||
created: NonNegativeInt,
|
||||
completed: Schema.optional(NonNegativeInt),
|
||||
}),
|
||||
error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])),
|
||||
error: Schema.optional(
|
||||
Schema.Union([AuthError.EffectSchema, NamedError.Unknown.EffectSchema, OutputLengthError.EffectSchema]),
|
||||
),
|
||||
sessionID: SessionID,
|
||||
tool: Schema.Record(
|
||||
Schema.String,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Cause, Clock, Duration, Effect, Schedule } from "effect"
|
||||
import { MessageV2 } from "./message-v2"
|
||||
import { iife } from "@/util/iife"
|
||||
import { isRecord } from "@/util/record"
|
||||
|
||||
export type Err = ReturnType<NamedError["toObject"]>
|
||||
|
||||
@@ -121,7 +122,7 @@ export function retryable(error: Err, provider: string) {
|
||||
}
|
||||
|
||||
// Check for rate limit patterns in plain text error messages
|
||||
const msg = error.data?.message
|
||||
const msg = isRecord(error.data) ? error.data.message : undefined
|
||||
if (typeof msg === "string") {
|
||||
const lower = msg.toLowerCase()
|
||||
if (
|
||||
@@ -133,7 +134,7 @@ export function retryable(error: Err, provider: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const json = parseJSON(error.data?.message)
|
||||
const json = parseJSON(msg)
|
||||
if (!json || typeof json !== "object") return undefined
|
||||
const code = typeof json.code === "string" ? json.code : ""
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import z from "zod"
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import type { Agent } from "@/agent/agent"
|
||||
@@ -16,6 +15,7 @@ import { Glob } from "@opencode-ai/core/util/glob"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Discovery } from "./discovery"
|
||||
import CUSTOMIZE_OPENCODE_SKILL_BODY from "./prompt/customize-opencode.md" with { type: "text" }
|
||||
import { isRecord } from "@/util/record"
|
||||
|
||||
const log = Log.create({ service: "skill" })
|
||||
const CLAUDE_EXTERNAL_DIR = ".claude"
|
||||
@@ -41,23 +41,33 @@ export const Info = Schema.Struct({
|
||||
})
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export const InvalidError = NamedError.create(
|
||||
"SkillInvalidError",
|
||||
z.object({
|
||||
path: z.string(),
|
||||
message: z.string().optional(),
|
||||
issues: z.custom<z.core.$ZodIssue[]>().optional(),
|
||||
const Issue = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
message: Schema.String,
|
||||
path: Schema.Array(Schema.String),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Unknown)],
|
||||
)
|
||||
|
||||
export const NameMismatchError = NamedError.create(
|
||||
"SkillNameMismatchError",
|
||||
z.object({
|
||||
path: z.string(),
|
||||
expected: z.string(),
|
||||
actual: z.string(),
|
||||
}),
|
||||
)
|
||||
function isSkillFrontmatter(data: unknown): data is { name: string; description?: string } {
|
||||
return (
|
||||
isRecord(data) &&
|
||||
typeof data.name === "string" &&
|
||||
(data.description === undefined || typeof data.description === "string")
|
||||
)
|
||||
}
|
||||
|
||||
export const InvalidError = NamedError.create("SkillInvalidError", {
|
||||
path: Schema.String,
|
||||
message: Schema.optional(Schema.String),
|
||||
issues: Schema.optional(Schema.Array(Issue)),
|
||||
})
|
||||
|
||||
export const NameMismatchError = NamedError.create("SkillNameMismatchError", {
|
||||
path: Schema.String,
|
||||
expected: Schema.String,
|
||||
actual: Schema.String,
|
||||
})
|
||||
|
||||
type State = {
|
||||
skills: Record<string, Info>
|
||||
@@ -101,21 +111,20 @@ const add = Effect.fnUntraced(function* (state: State, match: string, bus: Bus.I
|
||||
|
||||
if (!md) return
|
||||
|
||||
const parsed = z.object({ name: z.string(), description: z.string().optional() }).safeParse(md.data)
|
||||
if (!parsed.success) return
|
||||
if (!isSkillFrontmatter(md.data)) return
|
||||
|
||||
if (state.skills[parsed.data.name]) {
|
||||
if (state.skills[md.data.name]) {
|
||||
log.warn("duplicate skill name", {
|
||||
name: parsed.data.name,
|
||||
existing: state.skills[parsed.data.name].location,
|
||||
name: md.data.name,
|
||||
existing: state.skills[md.data.name].location,
|
||||
duplicate: match,
|
||||
})
|
||||
}
|
||||
|
||||
state.dirs.add(path.dirname(match))
|
||||
state.skills[parsed.data.name] = {
|
||||
name: parsed.data.name,
|
||||
description: parsed.data.description,
|
||||
state.skills[md.data.name] = {
|
||||
name: md.data.name,
|
||||
description: md.data.description,
|
||||
location: match,
|
||||
content: md.content,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { lazy } from "../util/lazy"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import z from "zod"
|
||||
import path from "path"
|
||||
import { readFileSync, readdirSync, existsSync } from "fs"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
@@ -15,15 +14,13 @@ import { InstallationChannel } from "@opencode-ai/core/installation/version"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { iife } from "@/util/iife"
|
||||
import { init } from "#db"
|
||||
import { Schema } from "effect"
|
||||
|
||||
declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number; name: string }[] | undefined
|
||||
|
||||
export const NotFoundError = NamedError.create(
|
||||
"NotFoundError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const NotFoundError = NamedError.create("NotFoundError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
const log = Log.create({ service: "db" })
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as Log from "@opencode-ai/core/util/log"
|
||||
import path from "path"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import z from "zod"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Effect, Exit, Layer, Option, RcMap, Schema, Context, TxReentrantLock } from "effect"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
@@ -16,12 +15,9 @@ type Migration = (
|
||||
git: Git.Interface,
|
||||
) => Effect.Effect<void, AppFileSystem.Error>
|
||||
|
||||
export const NotFoundError = NamedError.create(
|
||||
"NotFoundError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const NotFoundError = NamedError.create("NotFoundError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export type Error = AppFileSystem.Error | InstanceType<typeof NotFoundError>
|
||||
|
||||
|
||||
@@ -1,51 +1,9 @@
|
||||
import { Schema } from "effect"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
|
||||
/**
|
||||
* 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` (Effect 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) {
|
||||
const dataSchema = Schema.Struct(fields)
|
||||
// Wire shape matches the original NamedError output so the SDK stays stable.
|
||||
const effectSchema = Schema.Struct({
|
||||
name: Schema.Literal(tag),
|
||||
data: dataSchema,
|
||||
}).annotate({ identifier: tag })
|
||||
|
||||
type Data = Schema.Schema.Type<typeof dataSchema>
|
||||
|
||||
class NamedSchemaError extends Error {
|
||||
static readonly Schema = effectSchema
|
||||
static readonly EffectSchema = effectSchema
|
||||
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
|
||||
return NamedError.create(tag, fields)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import z from "zod"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { InstanceLayer } from "@/project/instance-layer"
|
||||
@@ -65,54 +64,33 @@ export const ResetInput = Schema.Struct({
|
||||
}).annotate({ identifier: "WorktreeResetInput" })
|
||||
export type ResetInput = Schema.Schema.Type<typeof ResetInput>
|
||||
|
||||
export const NotGitError = NamedError.create(
|
||||
"WorktreeNotGitError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const NotGitError = NamedError.create("WorktreeNotGitError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export const NameGenerationFailedError = NamedError.create(
|
||||
"WorktreeNameGenerationFailedError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const NameGenerationFailedError = NamedError.create("WorktreeNameGenerationFailedError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export const CreateFailedError = NamedError.create(
|
||||
"WorktreeCreateFailedError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const CreateFailedError = NamedError.create("WorktreeCreateFailedError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export const StartCommandFailedError = NamedError.create(
|
||||
"WorktreeStartCommandFailedError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const StartCommandFailedError = NamedError.create("WorktreeStartCommandFailedError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export const RemoveFailedError = NamedError.create(
|
||||
"WorktreeRemoveFailedError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const RemoveFailedError = NamedError.create("WorktreeRemoveFailedError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export const ResetFailedError = NamedError.create(
|
||||
"WorktreeResetFailedError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const ResetFailedError = NamedError.create("WorktreeResetFailedError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
export const ListFailedError = NamedError.create(
|
||||
"WorktreeListFailedError",
|
||||
z.object({
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
export const ListFailedError = NamedError.create("WorktreeListFailedError", {
|
||||
message: Schema.String,
|
||||
})
|
||||
|
||||
function slugify(input: string) {
|
||||
return input
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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"
|
||||
|
||||
describe("util.error", () => {
|
||||
test("formats native Error instances", () => {
|
||||
@@ -48,4 +52,18 @@ describe("util.error", () => {
|
||||
expect(data.message).toBe("ResolveMessage: Cannot resolve module")
|
||||
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" })
|
||||
|
||||
expect(error).toBeInstanceOf(NamedError)
|
||||
expect(error.toObject()).toEqual({ name: "ExampleError", data: { message: "boom" } })
|
||||
})
|
||||
|
||||
test("void named errors accept JSON without data", () => {
|
||||
const serialized = JSON.parse(JSON.stringify(new UI.CancelledError(undefined).toObject()))
|
||||
|
||||
expect(Schema.decodeUnknownOption(UI.CancelledError.Schema)(serialized)._tag).toBe("Some")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user