summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2026-01-26 16:12:41 -0500
committerAiden Cline <[email protected]>2026-01-26 16:12:41 -0500
commit8b5dde5536b5725e68eb43933d23dae22c298b35 (patch)
treed86284497e6afaa22f01f07816093a0cd625c372
parent6a62b44593b7be108af2245730175868ec967c1e (diff)
downloadopencode-8b5dde5536b5725e68eb43933d23dae22c298b35.tar.gz
opencode-8b5dde5536b5725e68eb43933d23dae22c298b35.zip
tweak: retry logic to catch certain provider problems
-rw-r--r--packages/opencode/src/session/retry.ts49
-rw-r--r--packages/opencode/test/session/retry.test.ts27
2 files changed, 56 insertions, 20 deletions
diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts
index dd0fe2380..8d3f72844 100644
--- a/packages/opencode/src/session/retry.ts
+++ b/packages/opencode/src/session/retry.ts
@@ -1,5 +1,6 @@
import type { NamedError } from "@opencode-ai/util/error"
import { MessageV2 } from "./message-v2"
+import { iife } from "@/util/iife"
export namespace SessionRetry {
export const RETRY_INITIAL_DELAY = 2000
@@ -63,28 +64,36 @@ export namespace SessionRetry {
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
}
- if (typeof error.data?.message === "string") {
+ const json = iife(() => {
try {
- const json = JSON.parse(error.data.message)
- if (json.type === "error" && json.error?.type === "too_many_requests") {
- return "Too Many Requests"
+ if (typeof error.data?.message === "string") {
+ const parsed = JSON.parse(error.data.message)
+ return parsed
}
- if (json.code.includes("exhausted") || json.code.includes("unavailable")) {
- return "Provider is overloaded"
- }
- if (json.type === "error" && json.error?.code?.includes("rate_limit")) {
- return "Rate Limited"
- }
- if (
- json.error?.message?.includes("no_kv_space") ||
- (json.type === "error" && json.error?.type === "server_error") ||
- !!json.error
- ) {
- return "Provider Server Error"
- }
- } catch {}
- }
- return undefined
+ return JSON.parse(error.data.message)
+ } catch {
+ return undefined
+ }
+ })
+ if (!json || typeof json !== "object") return undefined
+ const code = typeof json.code === "string" ? json.code : ""
+
+ if (json.type === "error" && json.error?.type === "too_many_requests") {
+ return "Too Many Requests"
+ }
+ if (code.includes("exhausted") || code.includes("unavailable")) {
+ return "Provider is overloaded"
+ }
+ if (json.type === "error" && json.error?.code?.includes("rate_limit")) {
+ return "Rate Limited"
+ }
+ if (
+ json.error?.message?.includes("no_kv_space") ||
+ (json.type === "error" && json.error?.type === "server_error") ||
+ !!json.error
+ ) {
+ return "Provider Server Error"
+ }
}
}
diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts
index 22ffb0cb1..6866b03a1 100644
--- a/packages/opencode/test/session/retry.test.ts
+++ b/packages/opencode/test/session/retry.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test"
+import type { NamedError } from "@opencode-ai/util/error"
import { APICallError } from "ai"
import { SessionRetry } from "../../src/session/retry"
import { MessageV2 } from "../../src/session/message-v2"
@@ -11,6 +12,10 @@ function apiError(headers?: Record<string, string>): MessageV2.APIError {
}).toObject() as MessageV2.APIError
}
+function wrap(message: unknown): ReturnType<NamedError["toObject"]> {
+ return { data: { message } } as ReturnType<NamedError["toObject"]>
+}
+
describe("session.retry.delay", () => {
test("caps delay at 30 seconds when headers missing", () => {
const error = apiError()
@@ -81,6 +86,28 @@ describe("session.retry.delay", () => {
})
})
+describe("session.retry.retryable", () => {
+ test("maps too_many_requests json messages", () => {
+ const error = wrap(JSON.stringify({ type: "error", error: { type: "too_many_requests" } }))
+ expect(SessionRetry.retryable(error)).toBe("Too Many Requests")
+ })
+
+ test("maps overloaded provider codes", () => {
+ const error = wrap(JSON.stringify({ code: "resource_exhausted" }))
+ expect(SessionRetry.retryable(error)).toBe("Provider is overloaded")
+ })
+
+ test("handles json messages without code", () => {
+ const error = wrap(JSON.stringify({ error: { message: "no_kv_space" } }))
+ expect(SessionRetry.retryable(error)).toBe("Provider Server Error")
+ })
+
+ test("returns undefined for non-json message", () => {
+ const error = wrap("not-json")
+ expect(SessionRetry.retryable(error)).toBeUndefined()
+ })
+})
+
describe("session.message-v2.fromError", () => {
test.concurrent(
"converts ECONNRESET socket errors to retryable APIError",