Skip to content

Commit 6deba15

Browse files
committed
fix: prevent grey screen when VS Code LM API hits context window limits
- Added error recovery mechanism in vscode-lm.ts to detect and handle context window errors - Implemented client reset on context window errors to recover from stuck states - Enhanced handleContextWindowExceededError in Task.ts with try-catch and fallback truncation - Added non-blocking error handling to prevent UI freeze during context operations - Improved error handling in ClineProvider for context condensing operations - Made handleContextWindowExceededError public to allow recovery from ClineProvider Fixes #8594
1 parent 3a47c55 commit 6deba15

File tree

3 files changed

+248
-82
lines changed

3 files changed

+248
-82
lines changed

src/api/providers/vscode-lm.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
4444
private client: vscode.LanguageModelChat | null
4545
private disposable: vscode.Disposable | null
4646
private currentRequestCancellation: vscode.CancellationTokenSource | null
47+
private isRecovering: boolean = false
4748

4849
constructor(options: ApiHandlerOptions) {
4950
super()
@@ -330,6 +331,63 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
330331
return content
331332
}
332333

334+
/**
335+
* Check if an error is a context window error
336+
*/
337+
private isContextWindowError(error: unknown): boolean {
338+
if (!error) return false
339+
340+
const errorMessage = error instanceof Error ? error.message : String(error)
341+
const lowerMessage = errorMessage.toLowerCase()
342+
343+
// Check for common context window error patterns
344+
return (
345+
(lowerMessage.includes("context") &&
346+
(lowerMessage.includes("window") ||
347+
lowerMessage.includes("length") ||
348+
lowerMessage.includes("limit"))) ||
349+
(lowerMessage.includes("token") && lowerMessage.includes("limit")) ||
350+
(lowerMessage.includes("maximum") && lowerMessage.includes("tokens")) ||
351+
lowerMessage.includes("too many tokens") ||
352+
lowerMessage.includes("exceeds")
353+
)
354+
}
355+
356+
/**
357+
* Handle context window errors with recovery mechanism
358+
*/
359+
private async handleContextWindowError(error: unknown): Promise<void> {
360+
if (this.isRecovering) {
361+
console.warn("Roo Code <Language Model API>: Already recovering from context window error")
362+
return
363+
}
364+
365+
this.isRecovering = true
366+
367+
try {
368+
console.warn("Roo Code <Language Model API>: Context window error detected, attempting recovery")
369+
370+
// Clean up current state
371+
this.ensureCleanState()
372+
373+
// Reset the client to force re-initialization
374+
this.client = null
375+
376+
// Wait a bit before retrying
377+
await new Promise((resolve) => setTimeout(resolve, 1000))
378+
379+
// Re-initialize the client
380+
await this.initializeClient()
381+
382+
console.log("Roo Code <Language Model API>: Recovery from context window error successful")
383+
} catch (recoveryError) {
384+
console.error("Roo Code <Language Model API>: Failed to recover from context window error:", recoveryError)
385+
throw recoveryError
386+
} finally {
387+
this.isRecovering = false
388+
}
389+
}
390+
333391
override async *createMessage(
334392
systemPrompt: string,
335393
messages: Anthropic.Messages.MessageParam[],
@@ -452,6 +510,21 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
452510
} catch (error: unknown) {
453511
this.ensureCleanState()
454512

513+
// Check if this is a context window error
514+
if (this.isContextWindowError(error)) {
515+
// Handle context window error with recovery
516+
await this.handleContextWindowError(error)
517+
518+
// Create a specific error for context window issues
519+
const contextError = new Error(
520+
"Context window exceeded. The conversation is too long for the current model. " +
521+
"Please try condensing the context or starting a new conversation.",
522+
)
523+
// Add a flag to indicate this is a context window error
524+
;(contextError as any).isContextWindowError = true
525+
throw contextError
526+
}
527+
455528
if (error instanceof vscode.CancellationError) {
456529
throw new Error("Roo Code <Language Model API>: Request cancelled by user")
457530
}
@@ -552,6 +625,12 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
552625
}
553626
return result
554627
} catch (error) {
628+
// Check if this is a context window error
629+
if (this.isContextWindowError(error)) {
630+
await this.handleContextWindowError(error)
631+
throw new Error("Context window exceeded. Please reduce the prompt size and try again.")
632+
}
633+
555634
if (error instanceof Error) {
556635
throw new Error(`VSCode LM completion error: ${error.message}`)
557636
}

src/core/task/Task.ts

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,7 +2456,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
24562456
)
24572457
}
24582458

2459-
private async handleContextWindowExceededError(): Promise<void> {
2459+
public async handleContextWindowExceededError(): Promise<void> {
24602460
const state = await this.providerRef.deref()?.getState()
24612461
const { profileThresholds = {} } = state ?? {}
24622462

@@ -2481,38 +2481,62 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
24812481
`Forcing truncation to ${FORCED_CONTEXT_REDUCTION_PERCENT}% of current context.`,
24822482
)
24832483

2484-
// Force aggressive truncation by keeping only 75% of the conversation history
2485-
const truncateResult = await truncateConversationIfNeeded({
2486-
messages: this.apiConversationHistory,
2487-
totalTokens: contextTokens || 0,
2488-
maxTokens,
2489-
contextWindow,
2490-
apiHandler: this.api,
2491-
autoCondenseContext: true,
2492-
autoCondenseContextPercent: FORCED_CONTEXT_REDUCTION_PERCENT,
2493-
systemPrompt: await this.getSystemPrompt(),
2494-
taskId: this.taskId,
2495-
profileThresholds,
2496-
currentProfileId,
2497-
})
2484+
try {
2485+
// Force aggressive truncation by keeping only 75% of the conversation history
2486+
const truncateResult = await truncateConversationIfNeeded({
2487+
messages: this.apiConversationHistory,
2488+
totalTokens: contextTokens || 0,
2489+
maxTokens,
2490+
contextWindow,
2491+
apiHandler: this.api,
2492+
autoCondenseContext: true,
2493+
autoCondenseContextPercent: FORCED_CONTEXT_REDUCTION_PERCENT,
2494+
systemPrompt: await this.getSystemPrompt(),
2495+
taskId: this.taskId,
2496+
profileThresholds,
2497+
currentProfileId,
2498+
})
24982499

2499-
if (truncateResult.messages !== this.apiConversationHistory) {
2500-
await this.overwriteApiConversationHistory(truncateResult.messages)
2501-
}
2500+
if (truncateResult.messages !== this.apiConversationHistory) {
2501+
await this.overwriteApiConversationHistory(truncateResult.messages)
2502+
}
25022503

2503-
if (truncateResult.summary) {
2504-
const { summary, cost, prevContextTokens, newContextTokens = 0 } = truncateResult
2505-
const contextCondense: ContextCondense = { summary, cost, newContextTokens, prevContextTokens }
2506-
await this.say(
2507-
"condense_context",
2508-
undefined /* text */,
2509-
undefined /* images */,
2510-
false /* partial */,
2511-
undefined /* checkpoint */,
2512-
undefined /* progressStatus */,
2513-
{ isNonInteractive: true } /* options */,
2514-
contextCondense,
2515-
)
2504+
if (truncateResult.summary) {
2505+
const { summary, cost, prevContextTokens, newContextTokens = 0 } = truncateResult
2506+
const contextCondense: ContextCondense = { summary, cost, newContextTokens, prevContextTokens }
2507+
await this.say(
2508+
"condense_context",
2509+
undefined /* text */,
2510+
undefined /* images */,
2511+
false /* partial */,
2512+
undefined /* checkpoint */,
2513+
undefined /* progressStatus */,
2514+
{ isNonInteractive: true } /* options */,
2515+
contextCondense,
2516+
)
2517+
}
2518+
} catch (error) {
2519+
// If truncation fails, log the error but don't throw to prevent UI from freezing
2520+
console.error(`[Task#${this.taskId}] Failed to handle context window error:`, error)
2521+
2522+
// Try a more aggressive truncation as a last resort
2523+
if (this.apiConversationHistory.length > 2) {
2524+
const fallbackMessages = [
2525+
this.apiConversationHistory[0], // Keep first message
2526+
...this.apiConversationHistory.slice(-2), // Keep last 2 messages
2527+
]
2528+
await this.overwriteApiConversationHistory(fallbackMessages)
2529+
2530+
await this.say(
2531+
"error",
2532+
"Context window exceeded. Conversation history has been significantly reduced to continue.",
2533+
undefined,
2534+
false,
2535+
undefined,
2536+
undefined,
2537+
{ isNonInteractive: true },
2538+
)
2539+
}
25162540
}
25172541
}
25182542

@@ -2715,7 +2739,23 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27152739
`Retry attempt ${retryAttempt + 1}/${MAX_CONTEXT_WINDOW_RETRIES}. ` +
27162740
`Attempting automatic truncation...`,
27172741
)
2742+
2743+
// Notify UI that we're handling a context window error to prevent grey screen
2744+
await this.say(
2745+
"text",
2746+
"⚠️ Context window limit reached. Automatically reducing conversation size to continue...",
2747+
undefined,
2748+
false,
2749+
undefined,
2750+
undefined,
2751+
{ isNonInteractive: true },
2752+
)
2753+
27182754
await this.handleContextWindowExceededError()
2755+
2756+
// Give UI time to update before retrying
2757+
await delay(500)
2758+
27192759
// Retry the request after handling the context window error
27202760
yield* this.attemptApiRequest(retryAttempt + 1)
27212761
return

0 commit comments

Comments
 (0)