Skip to content

Commit a5b143b

Browse files
daniel-lxsroomote
andauthored
feat: add OpenAI context window error handling (#6967)
* feat: add OpenAI context window error handling - Add comprehensive context window error detection for OpenAI, OpenRouter, Anthropic, and Cerebras - Implement automatic retry with aggressive context truncation (25% reduction) - Use proper profile settings for condensing operations - Add robust error handling with try-catch blocks Based on PR #5479 from cline/cline repository * fix: address PR review comments - Improved type safety by using Record<string, any> instead of direct any casts - Enhanced Anthropic error detection with message pattern matching - Added comprehensive unit tests for context-error-handling module - Added named constant FORCED_CONTEXT_REDUCTION_PERCENT - Added MAX_CONTEXT_WINDOW_RETRIES limit to prevent infinite loops - Added logging for context window exceeded errors - Extracted getCurrentProfileId helper method to reduce duplication - All tests passing (3438 tests) * fix: address PR review comments for context window error handling - Improve Anthropic error detection with more specific patterns and error codes - Add comprehensive unit tests for context-error-handling module - Add logging for context window errors with detailed information - Fix comment for FORCED_CONTEXT_REDUCTION_PERCENT constant - Fix TypeScript error for untyped error parameter - Maintain existing getCurrentProfileId helper method --------- Co-authored-by: Roo Code <[email protected]>
1 parent 007b58d commit a5b143b

File tree

3 files changed

+529
-4
lines changed

3 files changed

+529
-4
lines changed
Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
import { describe, it, expect, vi } from "vitest"
2+
import { APIError } from "openai"
3+
import { checkContextWindowExceededError } from "../context-error-handling"
4+
5+
describe("checkContextWindowExceededError", () => {
6+
describe("OpenAI errors", () => {
7+
it("should detect OpenAI context window error with APIError instance", () => {
8+
const error = Object.create(APIError.prototype)
9+
Object.assign(error, {
10+
status: 400,
11+
code: "400",
12+
message: "This model's maximum context length is 4096 tokens",
13+
error: {
14+
message: "This model's maximum context length is 4096 tokens",
15+
type: "invalid_request_error",
16+
param: null,
17+
code: "context_length_exceeded",
18+
},
19+
})
20+
21+
expect(checkContextWindowExceededError(error)).toBe(true)
22+
})
23+
24+
it("should detect OpenAI LengthFinishReasonError", () => {
25+
const error = {
26+
name: "LengthFinishReasonError",
27+
message: "The response was cut off due to length",
28+
}
29+
30+
expect(checkContextWindowExceededError(error)).toBe(true)
31+
})
32+
33+
it("should not detect non-context OpenAI errors", () => {
34+
const error = Object.create(APIError.prototype)
35+
Object.assign(error, {
36+
status: 400,
37+
code: "400",
38+
message: "Invalid API key",
39+
error: {
40+
message: "Invalid API key",
41+
type: "invalid_request_error",
42+
param: null,
43+
code: "invalid_api_key",
44+
},
45+
})
46+
47+
expect(checkContextWindowExceededError(error)).toBe(false)
48+
})
49+
})
50+
51+
describe("OpenRouter errors", () => {
52+
it("should detect OpenRouter context window error with status 400", () => {
53+
const error = {
54+
status: 400,
55+
message: "Request exceeds maximum context length of 8192 tokens",
56+
}
57+
58+
expect(checkContextWindowExceededError(error)).toBe(true)
59+
})
60+
61+
it("should detect OpenRouter error with nested error structure", () => {
62+
const error = {
63+
error: {
64+
status: 400,
65+
message: "Input tokens exceed model limit",
66+
},
67+
}
68+
69+
expect(checkContextWindowExceededError(error)).toBe(true)
70+
})
71+
72+
it("should detect OpenRouter error with response status", () => {
73+
const error = {
74+
response: {
75+
status: 400,
76+
},
77+
message: "Too many tokens in the request",
78+
}
79+
80+
expect(checkContextWindowExceededError(error)).toBe(true)
81+
})
82+
83+
it("should detect various context error patterns", () => {
84+
const patterns = [
85+
"context length exceeded",
86+
"maximum context window",
87+
"input tokens exceed limit",
88+
"too many tokens",
89+
]
90+
91+
patterns.forEach((pattern) => {
92+
const error = {
93+
status: 400,
94+
message: pattern,
95+
}
96+
expect(checkContextWindowExceededError(error)).toBe(true)
97+
})
98+
})
99+
100+
it("should not detect non-context 400 errors", () => {
101+
const error = {
102+
status: 400,
103+
message: "Invalid request format",
104+
}
105+
106+
expect(checkContextWindowExceededError(error)).toBe(false)
107+
})
108+
109+
it("should not detect errors with different status codes", () => {
110+
const error = {
111+
status: 500,
112+
message: "context length exceeded",
113+
}
114+
115+
expect(checkContextWindowExceededError(error)).toBe(false)
116+
})
117+
})
118+
119+
describe("Anthropic errors", () => {
120+
it("should detect Anthropic context window error", () => {
121+
const error = {
122+
error: {
123+
error: {
124+
type: "invalid_request_error",
125+
message: "prompt is too long: 150000 tokens > 100000 maximum",
126+
},
127+
},
128+
}
129+
130+
expect(checkContextWindowExceededError(error)).toBe(true)
131+
})
132+
133+
it("should detect Anthropic error with context_length_exceeded code", () => {
134+
const error = {
135+
error: {
136+
error: {
137+
type: "invalid_request_error",
138+
code: "context_length_exceeded",
139+
message: "The request exceeds the maximum context window",
140+
},
141+
},
142+
}
143+
144+
expect(checkContextWindowExceededError(error)).toBe(true)
145+
})
146+
147+
it("should detect various Anthropic context error patterns", () => {
148+
const patterns = [
149+
"prompt is too long",
150+
"maximum 200000 tokens",
151+
"context is too long",
152+
"exceeds the context window",
153+
"token limit exceeded",
154+
]
155+
156+
patterns.forEach((pattern) => {
157+
const error = {
158+
error: {
159+
error: {
160+
type: "invalid_request_error",
161+
message: pattern,
162+
},
163+
},
164+
}
165+
expect(checkContextWindowExceededError(error)).toBe(true)
166+
})
167+
})
168+
169+
it("should not detect non-context Anthropic errors", () => {
170+
const error = {
171+
error: {
172+
error: {
173+
type: "invalid_request_error",
174+
message: "Invalid model specified",
175+
},
176+
},
177+
}
178+
179+
expect(checkContextWindowExceededError(error)).toBe(false)
180+
})
181+
182+
it("should not detect errors with different error types", () => {
183+
const error = {
184+
error: {
185+
error: {
186+
type: "authentication_error",
187+
message: "prompt is too long",
188+
},
189+
},
190+
}
191+
192+
expect(checkContextWindowExceededError(error)).toBe(false)
193+
})
194+
})
195+
196+
describe("Cerebras errors", () => {
197+
it("should detect Cerebras context window error", () => {
198+
const error = {
199+
status: 400,
200+
message: "Please reduce the length of the messages or completion",
201+
}
202+
203+
expect(checkContextWindowExceededError(error)).toBe(true)
204+
})
205+
206+
it("should detect Cerebras error with nested structure", () => {
207+
const error = {
208+
error: {
209+
status: 400,
210+
message: "Please reduce the length of the messages or completion",
211+
},
212+
}
213+
214+
expect(checkContextWindowExceededError(error)).toBe(true)
215+
})
216+
217+
it("should not detect non-context Cerebras errors", () => {
218+
const error = {
219+
status: 400,
220+
message: "Invalid request parameters",
221+
}
222+
223+
expect(checkContextWindowExceededError(error)).toBe(false)
224+
})
225+
})
226+
227+
describe("Edge cases", () => {
228+
it("should handle null input", () => {
229+
expect(checkContextWindowExceededError(null)).toBe(false)
230+
})
231+
232+
it("should handle undefined input", () => {
233+
expect(checkContextWindowExceededError(undefined)).toBe(false)
234+
})
235+
236+
it("should handle empty object", () => {
237+
expect(checkContextWindowExceededError({})).toBe(false)
238+
})
239+
240+
it("should handle string input", () => {
241+
expect(checkContextWindowExceededError("error")).toBe(false)
242+
})
243+
244+
it("should handle number input", () => {
245+
expect(checkContextWindowExceededError(123)).toBe(false)
246+
})
247+
248+
it("should handle array input", () => {
249+
expect(checkContextWindowExceededError([])).toBe(false)
250+
})
251+
252+
it("should handle errors with circular references", () => {
253+
const error: any = { status: 400, message: "context length exceeded" }
254+
error.self = error // Create circular reference
255+
256+
expect(checkContextWindowExceededError(error)).toBe(true)
257+
})
258+
259+
it("should handle errors with deeply nested undefined values", () => {
260+
const error = {
261+
error: {
262+
error: {
263+
type: undefined,
264+
message: undefined,
265+
},
266+
},
267+
}
268+
269+
expect(checkContextWindowExceededError(error)).toBe(false)
270+
})
271+
272+
it("should handle errors that throw during property access", () => {
273+
const error = {
274+
get status() {
275+
throw new Error("Property access error")
276+
},
277+
message: "context length exceeded",
278+
}
279+
280+
expect(checkContextWindowExceededError(error)).toBe(false)
281+
})
282+
283+
it("should handle mixed provider error structures", () => {
284+
// Error that could match multiple providers
285+
const error = {
286+
status: 400,
287+
code: "400",
288+
message: "context length exceeded",
289+
error: {
290+
error: {
291+
type: "invalid_request_error",
292+
message: "prompt is too long",
293+
},
294+
},
295+
}
296+
297+
expect(checkContextWindowExceededError(error)).toBe(true)
298+
})
299+
})
300+
301+
describe("Multiple provider detection", () => {
302+
it("should detect error if any provider check returns true", () => {
303+
// This error should be detected by OpenRouter check
304+
const error1 = {
305+
status: 400,
306+
message: "context window exceeded",
307+
}
308+
expect(checkContextWindowExceededError(error1)).toBe(true)
309+
310+
// This error should be detected by Anthropic check
311+
const error2 = {
312+
error: {
313+
error: {
314+
type: "invalid_request_error",
315+
message: "prompt is too long",
316+
},
317+
},
318+
}
319+
expect(checkContextWindowExceededError(error2)).toBe(true)
320+
321+
// This error should be detected by Cerebras check
322+
const error3 = {
323+
status: 400,
324+
message: "Please reduce the length of the messages or completion",
325+
}
326+
expect(checkContextWindowExceededError(error3)).toBe(true)
327+
})
328+
})
329+
})

0 commit comments

Comments
 (0)