From 191d03bd7b77e470fdc41671c1cd2598c265e4b3 Mon Sep 17 00:00:00 2001 From: Brandon Leung Date: Fri, 24 Jul 2026 13:37:03 -0400 Subject: [PATCH 1/2] fix(agent): treat gateway 413 as prompt-too-long Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RA68K3mgc4P3X9jwPWVaFM --- .../src/adapters/error-classification.test.ts | 46 ++++++++++++++++++- .../src/adapters/error-classification.ts | 15 +++++- .../agent/src/server/agent-server.test.ts | 25 ++++++++-- 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/packages/agent/src/adapters/error-classification.test.ts b/packages/agent/src/adapters/error-classification.test.ts index d15bb4851f..d8178b10f5 100644 --- a/packages/agent/src/adapters/error-classification.test.ts +++ b/packages/agent/src/adapters/error-classification.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { classifyAgentError } from "./error-classification"; +import { + classifyAgentError, + isPromptTooLongError, +} from "./error-classification"; describe("classifyAgentError", () => { it.each([ @@ -22,6 +25,8 @@ describe("classifyAgentError", () => { ["API Error: 429 rate limited", "upstream_provider_failure"], ["API Error: 529 overloaded", "upstream_provider_failure"], ["API Error: 400 invalid request", "agent_error"], + // 413 is a hard client rejection, never a transient upstream failure. + ["API Error: 413 Payload Too Large", "agent_error"], [ "Connection closed mid-response without the API Error prefix", "agent_error", @@ -32,3 +37,42 @@ describe("classifyAgentError", () => { expect(classifyAgentError(message)).toBe(expected); }); }); + +describe("isPromptTooLongError", () => { + it.each([ + [ + 'API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 214431 tokens > 204698 maximum"}}', + true, + ], + [ + 'API Error: 413 {"error":{"message":"litellm.ContextWindowExceededError: The estimated number of input and maximum output tokens (262334) exceeded this model context window limit (262144)","code":"5021"}}', + true, + ], + // The context-window phrasing alone must match, without the 413 prefix. + [ + "litellm.ContextWindowExceededError: The estimated number of input and maximum output tokens (262334) exceeded this model context window limit (262144)", + true, + ], + // The ACP layer wraps adapter failures as "Internal error: "; + // this is the shape the agent-server catch sees. + [ + 'Internal error: API Error: 413 {"error":{"message":"exceeded this model context window limit (262144)"}}', + true, + ], + // Any 413 from the gateway means the request payload is oversized, even + // when the body text varies. + ["API Error: 413 Payload Too Large", true], + // Case-insensitive, matching the sibling matchers in this file. + ["api error: 413 payload too large", true], + ["API Error: 429 rate limited", false], + ["API Error: 400 invalid request", false], + ["some unrelated failure", false], + ] as const)("detects %j as %s", (message, expected) => { + expect(isPromptTooLongError(new Error(message))).toBe(expected); + }); + + it("handles non-Error inputs", () => { + expect(isPromptTooLongError({ message: "prompt is too long" })).toBe(true); + expect(isPromptTooLongError(undefined)).toBe(false); + }); +}); diff --git a/packages/agent/src/adapters/error-classification.ts b/packages/agent/src/adapters/error-classification.ts index 83a88f78fe..15f30a2461 100644 --- a/packages/agent/src/adapters/error-classification.ts +++ b/packages/agent/src/adapters/error-classification.ts @@ -48,7 +48,18 @@ export function classifyAgentError( return "agent_error"; } -/** Hard API rejection: the assembled prompt exceeds the model's context window. */ +/** + * Hard API rejection: the assembled prompt exceeds the model's context + * window. Anthropic phrases this as "prompt is too long"; the LLM gateway + * rejects the same condition with HTTP 413 and "exceeded this model context + * window limit". Deterministic client errors: retrying the same transcript + * can never succeed, so callers must shrink the prompt instead of retrying. + */ export function isPromptTooLongError(error: unknown): boolean { - return /prompt is too long/i.test(getErrorMessage(error)); + const message = getErrorMessage(error); + return ( + /prompt is too long/i.test(message) || + /exceeded this model context window limit/i.test(message) || + /API Error:\s*413\b/i.test(message) + ); } diff --git a/packages/agent/src/server/agent-server.test.ts b/packages/agent/src/server/agent-server.test.ts index ce7534f083..52063198a1 100644 --- a/packages/agent/src/server/agent-server.test.ts +++ b/packages/agent/src/server/agent-server.test.ts @@ -3280,11 +3280,28 @@ describe("AgentServer HTTP Mode", () => { }); it.each([ - { retryOutcome: "succeeds", retryFails: false }, - { retryOutcome: "fails", retryFails: true }, + { + retryOutcome: "succeeds", + retryFails: false, + oversizedError: "Internal error: Prompt is too long", + }, + { + retryOutcome: "fails", + retryFails: true, + oversizedError: "Internal error: Prompt is too long", + }, + // The LLM gateway rejects oversized requests with HTTP 413 rather than + // Anthropic's "prompt is too long" phrasing; the fresh-session retry + // must trigger on that shape as well. + { + retryOutcome: "succeeds after a gateway 413", + retryFails: false, + oversizedError: + 'Internal error: API Error: 413 {"error":{"message":"litellm.ContextWindowExceededError: The estimated number of input and maximum output tokens (262334) exceeded this model context window limit (262144)","code":"5021"}}', + }, ])( "clears resume state when the fresh-session retry $retryOutcome", - async ({ retryFails }) => { + async ({ retryFails, oversizedError }) => { const s = createServer(); await s.start(); @@ -3292,7 +3309,7 @@ describe("AgentServer HTTP Mode", () => { const prompt = vi.fn(async (params: { prompt: ContentBlock[] }) => { prompts.push(params.prompt); if (prompts.length === 1) { - throw new Error("Internal error: Prompt is too long"); + throw new Error(oversizedError); } if (retryFails) { throw new Error("Fresh-session retry failed"); From c4450550dcb8046b128d8ee7454da424680a0a6b Mon Sep 17 00:00:00 2001 From: Brandon Leung Date: Fri, 24 Jul 2026 13:59:27 -0400 Subject: [PATCH 2/2] chore(agent): tighten comments Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01RA68K3mgc4P3X9jwPWVaFM --- .../agent/src/adapters/error-classification.test.ts | 10 ++++------ packages/agent/src/adapters/error-classification.ts | 8 +++----- packages/agent/src/server/agent-server.test.ts | 5 ++--- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/agent/src/adapters/error-classification.test.ts b/packages/agent/src/adapters/error-classification.test.ts index d8178b10f5..af20ac5b3f 100644 --- a/packages/agent/src/adapters/error-classification.test.ts +++ b/packages/agent/src/adapters/error-classification.test.ts @@ -48,21 +48,19 @@ describe("isPromptTooLongError", () => { 'API Error: 413 {"error":{"message":"litellm.ContextWindowExceededError: The estimated number of input and maximum output tokens (262334) exceeded this model context window limit (262144)","code":"5021"}}', true, ], - // The context-window phrasing alone must match, without the 413 prefix. + // Must match without the "API Error: 413" prefix. [ "litellm.ContextWindowExceededError: The estimated number of input and maximum output tokens (262334) exceeded this model context window limit (262144)", true, ], - // The ACP layer wraps adapter failures as "Internal error: "; - // this is the shape the agent-server catch sees. + // The ACP-wrapped shape the agent-server catch actually sees. [ 'Internal error: API Error: 413 {"error":{"message":"exceeded this model context window limit (262144)"}}', true, ], - // Any 413 from the gateway means the request payload is oversized, even - // when the body text varies. + // Any gateway 413 means an oversized payload, whatever the body text. ["API Error: 413 Payload Too Large", true], - // Case-insensitive, matching the sibling matchers in this file. + // Pins the 413 matcher's i flag. ["api error: 413 payload too large", true], ["API Error: 429 rate limited", false], ["API Error: 400 invalid request", false], diff --git a/packages/agent/src/adapters/error-classification.ts b/packages/agent/src/adapters/error-classification.ts index 15f30a2461..56e5268585 100644 --- a/packages/agent/src/adapters/error-classification.ts +++ b/packages/agent/src/adapters/error-classification.ts @@ -49,11 +49,9 @@ export function classifyAgentError( } /** - * Hard API rejection: the assembled prompt exceeds the model's context - * window. Anthropic phrases this as "prompt is too long"; the LLM gateway - * rejects the same condition with HTTP 413 and "exceeded this model context - * window limit". Deterministic client errors: retrying the same transcript - * can never succeed, so callers must shrink the prompt instead of retrying. + * Hard API rejection: the prompt exceeds the model's context window + * (Anthropic phrasing, or the LLM gateway's HTTP 413). Retrying the same + * transcript can never succeed; callers must shrink the prompt. */ export function isPromptTooLongError(error: unknown): boolean { const message = getErrorMessage(error); diff --git a/packages/agent/src/server/agent-server.test.ts b/packages/agent/src/server/agent-server.test.ts index aeac74312c..171e953ba0 100644 --- a/packages/agent/src/server/agent-server.test.ts +++ b/packages/agent/src/server/agent-server.test.ts @@ -3416,9 +3416,8 @@ describe("AgentServer HTTP Mode", () => { retryFails: true, oversizedError: "Internal error: Prompt is too long", }, - // The LLM gateway rejects oversized requests with HTTP 413 rather than - // Anthropic's "prompt is too long" phrasing; the fresh-session retry - // must trigger on that shape as well. + // The LLM gateway phrases oversized rejections as HTTP 413; the + // fresh-session retry must trigger on that shape too. { retryOutcome: "succeeds after a gateway 413", retryFails: false,