/** * Context-overflow error detection helpers. * * Provider error messages vary — Anthropic returns "prompt is too long", * OpenAI returns "context_length_exceeded", Bedrock returns "Input is too * long", Google returns a size-limit phrase. This module centralises the * phrase list so the agent graph's emergency-prune retry can classify * errors consistently instead of duplicating inline substring matches at * each call site. * * The strict check (`isContextOverflowError`) matches only known phrases. * The loose check (`isLikelyContextOverflowError`) also matches a heuristic * regex for providers we haven't explicitly catalogued. Both filter out * false positives (rate-limit, auth, quota, billing) that might otherwise * trigger an unnecessary prune retry. */ const CONTEXT_OVERFLOW_PHRASES = [ 'request_too_large', 'context length exceeded', 'maximum context length', 'prompt is too long', 'exceeds model context window', 'exceeds the model', 'too large for model', 'context_length_exceeded', 'max_tokens', 'token limit', 'input too long', 'input is too long', 'payload too large', 'content_too_large', ] as const; const CONTEXT_OVERFLOW_HINT_RE = /413|too large|too long|context.*exceed|exceed.*context|token.*limit|limit.*token|prompt.*size|size.*limit|maximum.*length|length.*maximum/i; const FALSE_POSITIVE_RE = /rate.?limit|too many requests|quota|billing|auth|permission|forbidden/i; /** * Extracts a human-readable error message from an unknown error value. * Walks common shapes: strings, Error instances, `{ message }`, * `{ error: string }`, `{ error: { message } }`. Falls back to * JSON.stringify or String() so callers never have to null-check. */ export function extractErrorMessage(error: unknown): string { if (error == null) { return ''; } if (typeof error === 'string') { return error; } if (error instanceof Error) { return error.message; } if (typeof error === 'object') { const record = error as Record; if (typeof record.message === 'string') { return record.message; } if (typeof record.error === 'string') { return record.error; } if ( typeof record.error === 'object' && record.error != null && typeof (record.error as Record).message === 'string' ) { return (record.error as Record).message as string; } } try { return JSON.stringify(error); } catch { return String(error); } } /** * Strict check: returns true only for known, unambiguous context-overflow * phrases. Use when the recovery action is expensive (full prune + retry) * and false positives are undesirable. */ export function isContextOverflowError(errorMessage?: string): boolean { if (!errorMessage) { return false; } const lower = errorMessage.toLowerCase(); if (FALSE_POSITIVE_RE.test(lower)) { return false; } return CONTEXT_OVERFLOW_PHRASES.some((phrase) => lower.includes(phrase)); } /** * Loose check: returns true for known phrases OR heuristic regex matches. * Preferred by the graph's emergency-prune retry because the cost of a * false positive is one extra retry with a smaller context, while the * cost of a false negative is an opaque provider failure surfaced to * the user. */ export function isLikelyContextOverflowError(errorMessage?: string): boolean { if (!errorMessage) { return false; } if (isContextOverflowError(errorMessage)) { return true; } const lower = errorMessage.toLowerCase(); if (FALSE_POSITIVE_RE.test(lower)) { return false; } return CONTEXT_OVERFLOW_HINT_RE.test(lower); }