summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-11-25 15:44:24 -0500
committerDax Raad <[email protected]>2025-11-25 15:47:34 -0500
commit9eabbe2e76d3c837cc55ed14b6fdba80ebe602aa (patch)
treeae946f27107133174a5c7912ff43a572ef73889a /packages
parent5f35c579e2ec53b472749f77e7754e5cb86597e4 (diff)
downloadopencode-9eabbe2e76d3c837cc55ed14b6fdba80ebe602aa.tar.gz
opencode-9eabbe2e76d3c837cc55ed14b6fdba80ebe602aa.zip
retry anthropic overloaded errors
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/session/processor.ts4
-rw-r--r--packages/opencode/src/session/retry.ts46
-rw-r--r--packages/opencode/test/session/retry.test.ts18
3 files changed, 35 insertions, 33 deletions
diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts
index 6d1125c66..5b45dc14d 100644
--- a/packages/opencode/src/session/processor.ts
+++ b/packages/opencode/src/session/processor.ts
@@ -333,9 +333,9 @@ export namespace SessionProcessor {
error: e,
})
const error = MessageV2.fromError(e, { providerID: input.providerID })
- if (error?.name === "APIError" && error.data.isRetryable) {
+ if ((error?.name === "APIError" && error.data.isRetryable) || error.data.message.includes("Overloaded")) {
attempt++
- const delay = SessionRetry.delay(error, attempt)
+ const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
SessionStatus.set(input.sessionID, {
type: "retry",
attempt,
diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts
index 75472b568..4ad81ea08 100644
--- a/packages/opencode/src/session/retry.ts
+++ b/packages/opencode/src/session/retry.ts
@@ -19,32 +19,34 @@ export namespace SessionRetry {
})
}
- export function delay(error: MessageV2.APIError, attempt: number) {
- const headers = error.data.responseHeaders
- if (headers) {
- const retryAfterMs = headers["retry-after-ms"]
- if (retryAfterMs) {
- const parsedMs = Number.parseFloat(retryAfterMs)
- if (!Number.isNaN(parsedMs)) {
- return parsedMs
+ export function delay(attempt: number, error?: MessageV2.APIError) {
+ if (error) {
+ const headers = error.data.responseHeaders
+ if (headers) {
+ const retryAfterMs = headers["retry-after-ms"]
+ if (retryAfterMs) {
+ const parsedMs = Number.parseFloat(retryAfterMs)
+ if (!Number.isNaN(parsedMs)) {
+ return parsedMs
+ }
}
- }
- const retryAfter = headers["retry-after"]
- if (retryAfter) {
- const parsedSeconds = Number.parseFloat(retryAfter)
- if (!Number.isNaN(parsedSeconds)) {
- // convert seconds to milliseconds
- return Math.ceil(parsedSeconds * 1000)
- }
- // Try parsing as HTTP date format
- const parsed = Date.parse(retryAfter) - Date.now()
- if (!Number.isNaN(parsed) && parsed > 0) {
- return Math.ceil(parsed)
+ const retryAfter = headers["retry-after"]
+ if (retryAfter) {
+ const parsedSeconds = Number.parseFloat(retryAfter)
+ if (!Number.isNaN(parsedSeconds)) {
+ // convert seconds to milliseconds
+ return Math.ceil(parsedSeconds * 1000)
+ }
+ // Try parsing as HTTP date format
+ const parsed = Date.parse(retryAfter) - Date.now()
+ if (!Number.isNaN(parsed) && parsed > 0) {
+ return Math.ceil(parsed)
+ }
}
- }
- return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
+ return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
+ }
}
return Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)
diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts
index dc7470f0a..b685eae95 100644
--- a/packages/opencode/test/session/retry.test.ts
+++ b/packages/opencode/test/session/retry.test.ts
@@ -13,49 +13,49 @@ function apiError(headers?: Record<string, string>): MessageV2.APIError {
describe("session.retry.delay", () => {
test("caps delay at 30 seconds when headers missing", () => {
const error = apiError()
- const delays = Array.from({ length: 10 }, (_, index) => SessionRetry.delay(error, index + 1))
+ const delays = Array.from({ length: 10 }, (_, index) => SessionRetry.delay(index + 1, error))
expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 30000, 30000, 30000, 30000, 30000, 30000])
})
test("prefers retry-after-ms when shorter than exponential", () => {
const error = apiError({ "retry-after-ms": "1500" })
- expect(SessionRetry.delay(error, 4)).toBe(1500)
+ expect(SessionRetry.delay(4, error)).toBe(1500)
})
test("uses retry-after seconds when reasonable", () => {
const error = apiError({ "retry-after": "30" })
- expect(SessionRetry.delay(error, 3)).toBe(30000)
+ expect(SessionRetry.delay(3, error)).toBe(30000)
})
test("accepts http-date retry-after values", () => {
const date = new Date(Date.now() + 20000).toUTCString()
const error = apiError({ "retry-after": date })
- const d = SessionRetry.delay(error, 1)
+ const d = SessionRetry.delay(1, error)
expect(d).toBeGreaterThanOrEqual(19000)
expect(d).toBeLessThanOrEqual(20000)
})
test("ignores invalid retry hints", () => {
const error = apiError({ "retry-after": "not-a-number" })
- expect(SessionRetry.delay(error, 1)).toBe(2000)
+ expect(SessionRetry.delay(1, error)).toBe(2000)
})
test("ignores malformed date retry hints", () => {
const error = apiError({ "retry-after": "Invalid Date String" })
- expect(SessionRetry.delay(error, 1)).toBe(2000)
+ expect(SessionRetry.delay(1, error)).toBe(2000)
})
test("ignores past date retry hints", () => {
const pastDate = new Date(Date.now() - 5000).toUTCString()
const error = apiError({ "retry-after": pastDate })
- expect(SessionRetry.delay(error, 1)).toBe(2000)
+ expect(SessionRetry.delay(1, error)).toBe(2000)
})
test("uses retry-after values even when exceeding 10 minutes with headers", () => {
const error = apiError({ "retry-after": "50" })
- expect(SessionRetry.delay(error, 1)).toBe(50000)
+ expect(SessionRetry.delay(1, error)).toBe(50000)
const longError = apiError({ "retry-after-ms": "700000" })
- expect(SessionRetry.delay(longError, 1)).toBe(700000)
+ expect(SessionRetry.delay(1, longError)).toBe(700000)
})
})