Skip to content
Open
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
16 changes: 16 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "OpenCode Dev",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",

"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "24"
}
},

"postCreateCommand": "curl -fsSL https://bun.sh/install | bash && ~/.bun/bin/bun install",

"remoteEnv": {
"PATH": "${containerEnv:PATH}:/home/vscode/.bun/bin"
}
}
72 changes: 45 additions & 27 deletions packages/opencode/src/provider/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,60 @@ function isOpenAiErrorRetryable(e: APICallError) {

// Providers not reliably handled in this function:
// - z.ai: can accept overflow silently (needs token-count/context-window checks)
function emptyMessage(e: APICallError) {
if (e.message !== "") return undefined
if (e.responseBody) return e.responseBody
if (e.statusCode) {
const err = STATUS_CODES[e.statusCode]
if (err) return err
}
return "Unknown error"
}

function responseBodyMessage(e: APICallError, msg: string) {
if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) return undefined

try {
const body = JSON.parse(e.responseBody)
const errMsg = body.message || body.error || body.error?.message
if (errMsg && typeof errMsg === "string") {
return `${msg}: ${errMsg}`
}
} catch {}

return undefined
}

function gatewayMessage(e: APICallError, msg: string) {
if (!e.responseBody) return undefined
if (!/^\s*<!doctype|^\s*<html/i.test(e.responseBody)) return undefined

if (e.statusCode === 401) {
return "Unauthorized: request was blocked by a gateway or proxy. Your authentication token may be missing or expired — try running `opencode auth login <your provider URL>` to re-authenticate."
}
if (e.statusCode === 403) {
return "Forbidden: request was blocked by a gateway or proxy. You may not have permission to access this resource — check your account and provider settings."
}

return msg
}

function message(providerID: ProviderV2.ID, e: APICallError) {
return iife(() => {
const msg = e.message
if (msg === "") {
if (e.responseBody) return e.responseBody
if (e.statusCode) {
const err = STATUS_CODES[e.statusCode]
if (err) return err
}
return "Unknown error"
}

const fallback = emptyMessage(e)
if (fallback) return fallback

if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) {
return msg
}

try {
const body = JSON.parse(e.responseBody)
// try to extract common error message fields
const errMsg = body.message || body.error || body.error?.message
if (errMsg && typeof errMsg === "string") {
return `${msg}: ${errMsg}`
}
} catch {}
const bodyMessage = responseBodyMessage(e, msg)
if (bodyMessage) return bodyMessage

// If responseBody is HTML (e.g. from a gateway or proxy error page),
// provide a human-readable message instead of dumping raw markup
if (/^\s*<!doctype|^\s*<html/i.test(e.responseBody)) {
if (e.statusCode === 401) {
return "Unauthorized: request was blocked by a gateway or proxy. Your authentication token may be missing or expired — try running `opencode auth login <your provider URL>` to re-authenticate."
}
if (e.statusCode === 403) {
return "Forbidden: request was blocked by a gateway or proxy. You may not have permission to access this resource — check your account and provider settings."
}
return msg
}
const htmlMessage = gatewayMessage(e, msg)
if (htmlMessage) return htmlMessage

return `${msg}: ${e.responseBody}`
}).trim()
Expand Down
Loading