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
11 changes: 10 additions & 1 deletion packages/llm/src/provider-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ const patterns = [
/token limit exceeded/i,
]

const exclusions = [/^(throttling error|service unavailable):/i, /rate limit/i, /too many requests/i]
// Throttling messages can mention tokens (e.g. Bedrock's ThrottlingException
// "Too many tokens, please wait before trying again.") without any prefix, so
// exclude throttle vocabulary and transient retry language anywhere in the message.
const exclusions = [
/^service unavailable:/i,
/throttl/i,
/rate limit/i,
/too many requests/i,
/please (wait|try again)/i,
]

export const isContextOverflow = (message: string) =>
!exclusions.some((pattern) => pattern.test(message)) &&
Expand Down
5 changes: 5 additions & 0 deletions packages/llm/test/provider-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ describe("provider error classification", () => {
test("does not classify rate limits as context overflow", () => {
const messages = [
"Throttling error: Too many tokens, please wait before trying again.",
// Bedrock ThrottlingException via @ai-sdk/amazon-bedrock (no prefix)
"Too many tokens, please wait before trying again.",
"ThrottlingException: Too many tokens, please wait before trying again.",
"undefined: Too many tokens, please wait before trying again.",
"Too many tokens, please try again later.",
"Rate limit exceeded, please retry after 30 seconds.",
"Too many requests. Please slow down.",
]
Expand Down
7 changes: 6 additions & 1 deletion packages/opencode/src/provider/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ export type ParsedAPICallError =
export function parseAPICallError(input: { providerID: ProviderV2.ID; error: APICallError }): ParsedAPICallError {
const m = message(input.providerID, input.error)
const body = json(input.error.responseBody)
if (isContextOverflow(m) || input.error.statusCode === 413 || body?.error?.code === "context_length_exceeded") {
// 429 is throttling, never a context overflow, even when the message mentions
// tokens (e.g. Bedrock's "Too many tokens, please wait before trying again.")
const overflow =
input.error.statusCode !== 429 &&
(isContextOverflow(m) || input.error.statusCode === 413 || body?.error?.code === "context_length_exceeded")
if (overflow) {
return {
type: "context_overflow",
message: m,
Expand Down
18 changes: 18 additions & 0 deletions packages/opencode/test/session/retry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,24 @@ describe("session.message-v2.fromError", () => {
expect(result.data.isRetryable).toBe(true)
})

test("classifies Bedrock throttling as retryable APIError, not context overflow", () => {
const error = new APICallError({
message: "Too many tokens, please wait before trying again.",
url: "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-sonnet/converse-stream",
requestBodyValues: {},
statusCode: 429,
responseHeaders: { "content-type": "application/json" },
responseBody: '{"message":"Too many tokens, please wait before trying again."}',
isRetryable: true,
})
const result = MessageV2.fromError(error, { providerID: ProviderV2.ID.make("amazon-bedrock") })
expect(SessionV1.ContextOverflowError.isInstance(result)).toBe(false)
if (!SessionV1.APIError.isInstance(result)) throw new Error("expected APIError")
expect(result.data.isRetryable).toBe(true)
expect(result.data.statusCode).toBe(429)
expect(SessionRetry.retryable(result, retryProvider)).toBeDefined()
})

test("converts OpenAI server_error stream chunks to retryable APIError", () => {
const result = MessageV2.fromError(
{
Expand Down
Loading