Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 31 additions & 22 deletions packages/core/src/util/error.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
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 (
typeof error === "object" && error !== null && "name" in error && (error as Record<string, unknown>).name === name
)
}

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() {
Expand All @@ -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,
})
}
30 changes: 22 additions & 8 deletions packages/enterprise/src/routes/share/[shareID].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/cli/ui.ts
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -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",
Expand Down
14 changes: 5 additions & 9 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
30 changes: 16 additions & 14 deletions packages/opencode/src/config/error.ts
Original file line number Diff line number Diff line change
@@ -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),
})
13 changes: 5 additions & 8 deletions packages/opencode/src/config/markdown.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"
11 changes: 7 additions & 4 deletions packages/opencode/src/config/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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,
},
],
})
}
Expand All @@ -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 },
)
Expand Down
12 changes: 4 additions & 8 deletions packages/opencode/src/ide/index.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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") {
Expand Down
18 changes: 11 additions & 7 deletions packages/opencode/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
10 changes: 3 additions & 7 deletions packages/opencode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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(
Expand Down
9 changes: 3 additions & 6 deletions packages/opencode/src/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 1 addition & 3 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading