feat: improve context overflow error detection for third-party proxies

- Add checkIsLiteLLMContextWindowError() for LiteLLM proxy errors
- Add checkIsGenericContextWindowError() as catch-all for various error formats
- Add checkMessageForContextOverflow() helper for pattern matching
- Support Chinese, Japanese, and Korean error messages
- Handle string errors, nested error structures, and cause chains
- Add comprehensive tests for new error detection functions

Addresses issue #10246 where third-party API proxies return plain text
error messages that were not being detected as context overflow errors.
This commit is contained in:
Roo Code 2025-12-24 10:58:48 +00:00
parent 1e71015e54
commit e0d0d565de
2 changed files with 394 additions and 7 deletions

View file

@ -106,13 +106,15 @@ describe("checkContextWindowExceededError", () => {
expect(checkContextWindowExceededError(error)).toBe(false)
})
it("should not detect errors with different status codes", () => {
it("should detect errors with different status codes via generic check", () => {
// Note: The generic check now catches context overflow messages regardless of status code
// This is intentional to handle third-party proxies that may return different status codes
const error = {
status: 500,
message: "context length exceeded",
}
expect(checkContextWindowExceededError(error)).toBe(false)
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
@ -179,7 +181,9 @@ describe("checkContextWindowExceededError", () => {
expect(checkContextWindowExceededError(error)).toBe(false)
})
it("should not detect errors with different error types", () => {
it("should detect errors with different error types via generic check", () => {
// Note: The generic check now catches context overflow messages regardless of error type
// This is intentional to handle third-party proxies that may return different error structures
const error = {
error: {
error: {
@ -189,7 +193,7 @@ describe("checkContextWindowExceededError", () => {
},
}
expect(checkContextWindowExceededError(error)).toBe(false)
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
@ -269,7 +273,9 @@ describe("checkContextWindowExceededError", () => {
expect(checkContextWindowExceededError(error)).toBe(false)
})
it("should handle errors that throw during property access", () => {
it("should handle errors that throw during property access via generic check", () => {
// Note: The generic check now catches context overflow messages even when some properties throw
// This is intentional to handle edge cases where error objects have problematic getters
const error = {
get status() {
throw new Error("Property access error")
@ -277,7 +283,7 @@ describe("checkContextWindowExceededError", () => {
message: "context length exceeded",
}
expect(checkContextWindowExceededError(error)).toBe(false)
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should handle mixed provider error structures", () => {
@ -326,4 +332,244 @@ describe("checkContextWindowExceededError", () => {
expect(checkContextWindowExceededError(error3)).toBe(true)
})
})
describe("LiteLLM errors", () => {
it("should detect LiteLLM context window error with standard message", () => {
const error = {
message: "context length exceeded for this model",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect LiteLLM error with nested error structure", () => {
const error = {
error: {
message: "maximum context length is 4096 tokens",
},
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect LiteLLM error with deeply nested structure", () => {
const error = {
error: {
error: {
message: "input is too long for this model",
},
},
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect LiteLLM error with detail field", () => {
const error = {
detail: "request is too large, please reduce input size",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect LiteLLM error with context_length_exceeded type", () => {
const error = {
error: {
type: "context_length_exceeded",
message: "The request exceeds the maximum context",
},
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect LiteLLM error with context_length_exceeded code", () => {
const error = {
error: {
code: "context_length_exceeded",
message: "Error processing request",
},
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect various LiteLLM context error patterns", () => {
const patterns = [
"context length exceeded",
"maximum token limit reached",
"too many tokens in input",
"input is too long",
"exceeds max context size",
"request is too large",
"prompt is too long",
]
patterns.forEach((pattern) => {
const error = {
message: pattern,
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
it("should detect Chinese context error messages", () => {
const patterns = ["输入超长了", "超出上下文限制", "请求太长了", "上下文超出限制"]
patterns.forEach((pattern) => {
const error = {
message: pattern,
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
it("should not detect non-context LiteLLM errors", () => {
const error = {
message: "Invalid API key provided",
}
expect(checkContextWindowExceededError(error)).toBe(false)
})
})
describe("Generic context window errors", () => {
it("should detect string error with context overflow message", () => {
const error = "context length exceeded for this request"
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect error with body field containing context message", () => {
const error = {
body: "The input exceeds the maximum token limit",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect error with text field containing context message", () => {
const error = {
text: "Please reduce the length of your input",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect error with data field containing context message", () => {
const error = {
data: "token count exceeded the maximum allowed",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect error with stringified error field", () => {
const error = {
error: "context window overflow detected",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect error with cause chain", () => {
const error = {
message: "Request failed",
cause: {
message: "context length exceeded",
},
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should detect various generic context error patterns", () => {
const patterns = [
"context length exceeded",
"context window overflow",
"maximum token count reached",
"too many tokens",
"input is too long",
"exceeds the max length",
"request is too large",
"prompt is too long",
"token limit exceeded",
"reduce the length of your input",
]
patterns.forEach((pattern) => {
const error = {
message: pattern,
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
it("should detect Chinese context error messages in generic check", () => {
const patterns = ["输入超长", "超出长度限制", "上下文太长", "令牌超出限制"]
patterns.forEach((pattern) => {
const error = {
message: pattern,
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
it("should detect Japanese context error messages", () => {
const patterns = ["コンテキストが長すぎます", "入力が超過しました", "リクエストが制限を超えました"]
patterns.forEach((pattern) => {
const error = {
message: pattern,
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
it("should detect Korean context error messages", () => {
// These patterns match the regex: /(?:컨텍스트|입력|요청).*(?:너무\s*길|초과|제한)/
const patterns = ["컨텍스트 너무 길다", "입력 초과", "요청 제한"]
patterns.forEach((pattern) => {
const error = {
message: pattern,
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
it("should not detect non-context generic errors", () => {
const error = {
message: "Network connection failed",
}
expect(checkContextWindowExceededError(error)).toBe(false)
})
it("should not detect string errors without context keywords", () => {
const error = "Invalid authentication credentials"
expect(checkContextWindowExceededError(error)).toBe(false)
})
})
describe("Edge cases for new functions", () => {
it("should handle string input with context overflow message", () => {
expect(checkContextWindowExceededError("context length exceeded")).toBe(true)
})
it("should handle string input without context overflow message", () => {
expect(checkContextWindowExceededError("some other error")).toBe(false)
})
it("should handle deeply nested cause chain", () => {
const error = {
message: "Outer error",
cause: {
message: "Middle error",
cause: {
message: "context length exceeded",
},
},
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
it("should handle error with multiple message sources", () => {
const error = {
message: "Generic error",
error: {
message: "context window exceeded",
},
detail: "Some detail",
}
expect(checkContextWindowExceededError(error)).toBe(true)
})
})
})

View file

@ -5,7 +5,9 @@ export function checkContextWindowExceededError(error: unknown): boolean {
checkIsOpenAIContextWindowError(error) ||
checkIsOpenRouterContextWindowError(error) ||
checkIsAnthropicContextWindowError(error) ||
checkIsCerebrasContextWindowError(error)
checkIsCerebrasContextWindowError(error) ||
checkIsLiteLLMContextWindowError(error) ||
checkIsGenericContextWindowError(error)
)
}
@ -112,3 +114,142 @@ function checkIsCerebrasContextWindowError(response: unknown): boolean {
return false
}
}
/**
* Check for LiteLLM and third-party proxy context window errors.
* LiteLLM is commonly used as a proxy for various LLM providers and may return
* different error formats than the original providers.
*/
function checkIsLiteLLMContextWindowError(error: unknown): boolean {
try {
if (!error || typeof error !== "object") {
return false
}
const err = error as Record<string, any>
// LiteLLM may wrap errors in different structures
const message: string = String(
err.message || err.error?.message || err.error?.error?.message || err.detail || "",
)
const status = err.status ?? err.code ?? err.error?.status ?? err.response?.status ?? err.statusCode
// LiteLLM-specific error patterns for context window exceeded
const LITELLM_CONTEXT_PATTERNS = [
/\bcontext\s*(?:length|window)\s*(?:exceeded|too\s*long|limit)/i,
/\bmax(?:imum)?\s*(?:context|token)\s*(?:length|limit|size)/i,
/\btoo\s*(?:many|long)\s*(?:tokens?|input)/i,
/\binput\s*(?:is\s*)?too\s*long/i,
/\bexceeds?\s*(?:the\s*)?(?:max(?:imum)?|context)\s*(?:token|length|limit)/i,
/\brequest\s*(?:is\s*)?too\s*large/i,
/\bprompt\s*(?:is\s*)?too\s*long/i,
// Chinese error messages (common in Chinese LLM proxies)
/超长|超出.*(?:长度|限制|上下文)/,
/(?:上下文|输入|请求).*(?:太长|过长|超出)/,
] as const
// Check if message matches any context window pattern
if (LITELLM_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))) {
return true
}
// Also check for status 400 with context-related keywords
if (String(status) === "400" && LITELLM_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))) {
return true
}
// Check for LiteLLM-specific error structure
if (err.error?.type === "context_length_exceeded" || err.error?.code === "context_length_exceeded") {
return true
}
return false
} catch {
return false
}
}
/**
* Check for generic context window errors that may come from various sources.
* This is a catch-all for error messages that indicate context overflow but
* don't match provider-specific patterns.
*
* This handles cases where:
* - Third-party proxies return plain text errors
* - Error messages are wrapped in unexpected structures
* - JSON parsing fails and the raw error text contains context overflow info
*/
function checkIsGenericContextWindowError(error: unknown): boolean {
try {
// Handle string errors (e.g., from JSON parse failures)
if (typeof error === "string") {
return checkMessageForContextOverflow(error)
}
if (!error || typeof error !== "object") {
return false
}
const err = error as Record<string, any>
// Collect all possible message sources
const messageSources = [
err.message,
err.error?.message,
err.error?.error?.message,
err.detail,
err.body,
err.text,
err.data,
// Handle cases where the error might be a stringified JSON
typeof err.error === "string" ? err.error : null,
]
// Check each message source for context overflow patterns
for (const source of messageSources) {
if (source && typeof source === "string" && checkMessageForContextOverflow(source)) {
return true
}
}
// Check for cause chain (some errors wrap the original error)
if (err.cause && checkIsGenericContextWindowError(err.cause)) {
return true
}
return false
} catch {
return false
}
}
/**
* Helper function to check if a message string indicates context overflow.
* Used by multiple error checking functions.
*/
function checkMessageForContextOverflow(message: string): boolean {
// Comprehensive patterns for context window/length exceeded errors
const GENERIC_CONTEXT_PATTERNS = [
// English patterns
/\bcontext\s*(?:length|window|size)\s*(?:exceeded|too\s*long|limit|overflow)/i,
/\bmax(?:imum)?\s*(?:context|token|input)\s*(?:length|limit|size|count)/i,
/\btoo\s*(?:many|long)\s*(?:tokens?|input|characters?)/i,
/\binput\s*(?:is\s*)?too\s*(?:long|large)/i,
/\bexceeds?\s*(?:the\s*)?(?:max(?:imum)?|context|token)\s*(?:length|limit|size)/i,
/\brequest\s*(?:is\s*)?too\s*(?:large|long)/i,
/\bprompt\s*(?:is\s*)?too\s*(?:long|large)/i,
/\btoken\s*(?:count|limit)\s*exceeded/i,
/\b(?:input|prompt|request)\s*(?:length|size)\s*(?:exceeded|too\s*(?:long|large))/i,
/\breduce\s*(?:the\s*)?(?:length|size|tokens?)/i,
// Chinese patterns (common in Chinese LLM services)
/超长|超出.*(?:长度|限制|上下文|令牌)/,
/(?:上下文|输入|请求|提示).*(?:太长|过长|超出|超过)/,
/(?:长度|大小|令牌).*(?:超出|超过|限制)/,
// Japanese patterns
/(?:コンテキスト|入力|リクエスト).*(?:長すぎ|超過|制限)/,
// Korean patterns
/(?:컨텍스트|입력|요청).*(?:너무\s*길|초과|제한)/,
] as const
return GENERIC_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))
}