2026-04-25 14:59:17 +00:00
|
|
|
import { NamedError } from "@opencode-ai/core/util/error"
|
2026-03-27 14:00:26 +00:00
|
|
|
import { errorFormat } from "@/util/error"
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
interface ErrorLike {
|
|
|
|
|
name?: string
|
|
|
|
|
_tag?: string
|
|
|
|
|
message?: string
|
|
|
|
|
data?: Record<string, any>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isTaggedError(error: unknown, tag: string): boolean {
|
|
|
|
|
return (
|
|
|
|
|
typeof error === "object" && error !== null && "_tag" in error && (error as Record<string, unknown>)._tag === tag
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-06-20 04:57:28 +00:00
|
|
|
|
|
|
|
|
export function FormatError(input: unknown) {
|
2026-05-02 16:08:04 +00:00
|
|
|
// CliError: domain failure surfaced from an effectCmd handler via fail("...")
|
|
|
|
|
if (isTaggedError(input, "CliError")) {
|
|
|
|
|
const data = input as ErrorLike & { exitCode?: number }
|
|
|
|
|
if (data.exitCode != null) process.exitCode = data.exitCode
|
|
|
|
|
return data.message ?? ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 06:03:03 +00:00
|
|
|
// MCPFailed: { name: string }
|
|
|
|
|
if (NamedError.hasName(input, "MCPFailed")) {
|
|
|
|
|
return `MCP server "${(input as ErrorLike).data?.name}" failed. Note, opencode does not support MCP authentication yet.`
|
2026-04-07 18:31:53 +00:00
|
|
|
}
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
// AccountServiceError, AccountTransportError: TaggedErrorClass
|
|
|
|
|
if (isTaggedError(input, "AccountServiceError") || isTaggedError(input, "AccountTransportError")) {
|
|
|
|
|
return (input as ErrorLike).message ?? ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ProviderModelNotFoundError: { providerID: string, modelID: string, suggestions?: string[] }
|
|
|
|
|
if (NamedError.hasName(input, "ProviderModelNotFoundError")) {
|
|
|
|
|
const data = (input as ErrorLike).data
|
2026-04-16 23:52:04 +00:00
|
|
|
const suggestions: string[] = Array.isArray(data?.suggestions) ? data.suggestions : []
|
2025-11-21 08:01:19 +00:00
|
|
|
return [
|
2026-04-16 06:03:03 +00:00
|
|
|
`Model not found: ${data?.providerID}/${data?.modelID}`,
|
2026-04-16 23:52:04 +00:00
|
|
|
...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
|
2025-11-21 08:01:19 +00:00
|
|
|
`Try: \`opencode models\` to list available models`,
|
|
|
|
|
`Or check your config (opencode.json) provider/model names`,
|
|
|
|
|
].join("\n")
|
|
|
|
|
}
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
// ProviderInitError: { providerID: string }
|
|
|
|
|
if (NamedError.hasName(input, "ProviderInitError")) {
|
|
|
|
|
return `Failed to initialize provider "${(input as ErrorLike).data?.providerID}". Check credentials and configuration.`
|
2025-11-21 08:01:19 +00:00
|
|
|
}
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
// ConfigJsonError: { path: string, message?: string }
|
|
|
|
|
if (NamedError.hasName(input, "ConfigJsonError")) {
|
|
|
|
|
const data = (input as ErrorLike).data
|
|
|
|
|
return `Config file at ${data?.path} is not valid JSON(C)` + (data?.message ? `: ${data.message}` : "")
|
2025-07-31 11:25:26 +00:00
|
|
|
}
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
// ConfigDirectoryTypoError: { dir: string, path: string, suggestion: string }
|
|
|
|
|
if (NamedError.hasName(input, "ConfigDirectoryTypoError")) {
|
|
|
|
|
const data = (input as ErrorLike).data
|
|
|
|
|
return `Directory "${data?.dir}" in ${data?.path} is not valid. Rename the directory to "${data?.suggestion}" or remove it. This is a common typo.`
|
2025-10-17 19:34:37 +00:00
|
|
|
}
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
// ConfigFrontmatterError: { message: string }
|
|
|
|
|
if (NamedError.hasName(input, "ConfigFrontmatterError")) {
|
|
|
|
|
return (input as ErrorLike).data?.message ?? ""
|
2025-10-30 15:56:30 +00:00
|
|
|
}
|
2026-04-16 06:03:03 +00:00
|
|
|
|
|
|
|
|
// ConfigInvalidError: { path?: string, message?: string, issues?: Array<{ message: string, path: string[] }> }
|
|
|
|
|
if (NamedError.hasName(input, "ConfigInvalidError")) {
|
|
|
|
|
const data = (input as ErrorLike).data
|
|
|
|
|
const path = data?.path
|
|
|
|
|
const message = data?.message
|
2026-04-16 23:52:04 +00:00
|
|
|
const issues: Array<{ message: string; path: string[] }> = Array.isArray(data?.issues) ? data.issues : []
|
2025-06-20 04:57:28 +00:00
|
|
|
return [
|
2026-04-16 06:03:03 +00:00
|
|
|
`Configuration is invalid${path && path !== "config" ? ` at ${path}` : ""}` + (message ? `: ${message}` : ""),
|
2026-04-16 23:52:04 +00:00
|
|
|
...issues.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")),
|
2025-06-20 04:57:28 +00:00
|
|
|
].join("\n")
|
2026-04-16 06:03:03 +00:00
|
|
|
}
|
2025-06-25 02:09:43 +00:00
|
|
|
|
2026-04-16 06:03:03 +00:00
|
|
|
// UICancelledError: void (no data)
|
|
|
|
|
if (NamedError.hasName(input, "UICancelledError")) {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
2025-06-20 04:57:28 +00:00
|
|
|
}
|
2025-11-26 16:49:55 +00:00
|
|
|
|
|
|
|
|
export function FormatUnknownError(input: unknown): string {
|
2026-03-27 14:00:26 +00:00
|
|
|
return errorFormat(input)
|
2025-11-26 16:49:55 +00:00
|
|
|
}
|