summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 17:29:52 -0600
committerAdam <[email protected]>2025-12-29 17:29:56 -0600
commit2dec956a17e26aad323835133933bf230e6c8d69 (patch)
tree01b2e89fd72a60411760ee61e86eb835d316e11c /packages
parentef8388f0ee1ac760661524be42d57d79d2465ed9 (diff)
downloadopencode-2dec956a17e26aad323835133933bf230e6c8d69.tar.gz
opencode-2dec956a17e26aad323835133933bf230e6c8d69.zip
fix(desktop): better error messages
Diffstat (limited to 'packages')
-rw-r--r--packages/app/src/pages/error.tsx101
-rw-r--r--packages/desktop/vite.config.ts4
2 files changed, 86 insertions, 19 deletions
diff --git a/packages/app/src/pages/error.tsx b/packages/app/src/pages/error.tsx
index 37bd5ccd3..330334fe0 100644
--- a/packages/app/src/pages/error.tsx
+++ b/packages/app/src/pages/error.tsx
@@ -20,11 +20,51 @@ function isInitError(error: unknown): error is InitError {
)
}
+function safeJson(value: unknown): string {
+ const seen = new WeakSet<object>()
+ const json = JSON.stringify(
+ value,
+ (_key, val) => {
+ if (typeof val === "bigint") return val.toString()
+ if (typeof val === "object" && val) {
+ if (seen.has(val)) return "[Circular]"
+ seen.add(val)
+ }
+ return val
+ },
+ 2,
+ )
+ return json ?? String(value)
+}
+
function formatInitError(error: InitError): string {
const data = error.data
switch (error.name) {
case "MCPFailed":
return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
+ case "ProviderAuthError": {
+ const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
+ const message = typeof data.message === "string" ? data.message : safeJson(data.message)
+ return `Provider authentication failed (${providerID}): ${message}`
+ }
+ case "APIError": {
+ const message = typeof data.message === "string" ? data.message : "API error"
+ const lines: string[] = [message]
+
+ if (typeof data.statusCode === "number") {
+ lines.push(`Status: ${data.statusCode}`)
+ }
+
+ if (typeof data.isRetryable === "boolean") {
+ lines.push(`Retryable: ${data.isRetryable}`)
+ }
+
+ if (typeof data.responseBody === "string" && data.responseBody) {
+ lines.push(`Response body:\n${data.responseBody}`)
+ }
+
+ return lines.join("\n")
+ }
case "ProviderModelNotFoundError": {
const { providerID, modelID, suggestions } = data as {
providerID: string
@@ -37,10 +77,14 @@ function formatInitError(error: InitError): string {
`Check your config (opencode.json) provider/model names`,
].join("\n")
}
- case "ProviderInitError":
- return `Failed to initialize provider "${data.providerID}". Check credentials and configuration.`
- case "ConfigJsonError":
- return `Config file at ${data.path} is not valid JSON(C)` + (data.message ? `: ${data.message}` : "")
+ case "ProviderInitError": {
+ const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
+ return `Failed to initialize provider "${providerID}". Check credentials and configuration.`
+ }
+ case "ConfigJsonError": {
+ const message = typeof data.message === "string" ? data.message : ""
+ return `Config file at ${data.path} is not valid JSON(C)` + (message ? `: ${message}` : "")
+ }
case "ConfigDirectoryTypoError":
return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
case "ConfigFrontmatterError":
@@ -51,14 +95,14 @@ function formatInitError(error: InitError): string {
(issue: { message: string; path: string[] }) => "↳ " + issue.message + " " + issue.path.join("."),
)
: []
- return [`Config file at ${data.path} is invalid` + (data.message ? `: ${data.message}` : ""), ...issues].join(
- "\n",
- )
+ const message = typeof data.message === "string" ? data.message : ""
+ return [`Config file at ${data.path} is invalid` + (message ? `: ${message}` : ""), ...issues].join("\n")
}
case "UnknownError":
- return String(data.message)
+ return typeof data.message === "string" ? data.message : safeJson(data)
default:
- return data.message ? String(data.message) : JSON.stringify(data, null, 2)
+ if (typeof data.message === "string") return data.message
+ return safeJson(data)
}
}
@@ -69,7 +113,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
const message = formatInitError(error)
if (depth > 0 && parentMessage === message) return ""
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
- return indent + message
+ return indent + `${error.name}\n${message}`
}
if (error instanceof Error) {
@@ -77,15 +121,34 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
const parts: string[] = []
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
- if (!isDuplicate) {
- // Stack already includes error name and message, so prefer it
- parts.push(indent + (error.stack ?? `${error.name}: ${error.message}`))
- } else if (error.stack) {
- // Duplicate message - only show the stack trace lines (skip message)
- const trace = error.stack.split("\n").slice(1).join("\n").trim()
- if (trace) {
- parts.push(trace)
+ const header = `${error.name}${error.message ? `: ${error.message}` : ""}`
+ const stack = error.stack?.trim()
+
+ if (stack) {
+ const startsWithHeader = stack.startsWith(header)
+
+ if (isDuplicate && startsWithHeader) {
+ const trace = stack.split("\n").slice(1).join("\n").trim()
+ if (trace) {
+ parts.push(indent + trace)
+ }
+ }
+
+ if (isDuplicate && !startsWithHeader) {
+ parts.push(indent + stack)
}
+
+ if (!isDuplicate && startsWithHeader) {
+ parts.push(indent + stack)
+ }
+
+ if (!isDuplicate && !startsWithHeader) {
+ parts.push(indent + `${header}\n${stack}`)
+ }
+ }
+
+ if (!stack && !isDuplicate) {
+ parts.push(indent + header)
}
if (error.cause) {
@@ -105,7 +168,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
}
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
- return indent + JSON.stringify(error, null, 2)
+ return indent + safeJson(error)
}
function formatError(error: unknown): string {
diff --git a/packages/desktop/vite.config.ts b/packages/desktop/vite.config.ts
index 6d4f62dc2..9d17fa9da 100644
--- a/packages/desktop/vite.config.ts
+++ b/packages/desktop/vite.config.ts
@@ -10,6 +10,10 @@ export default defineConfig({
//
// 1. prevent Vite from obscuring rust errors
clearScreen: false,
+ esbuild: {
+ // Improves production stack traces (less "kQ@..." noise)
+ keepNames: true,
+ },
build: {
sourcemap: true,
},