Skip to content

Commit 7447ab3

Browse files
author
Bertan Ari
committed
refactor(vscode-lm): default-row condense fallback + shared contextPercent helper
getCondenseContextWindow() resolves the default vscode-lm row for an unknown/absent family (catalog drift) instead of the inflated live window; only a non-positive static maxInputTokens still falls back to it. Extract the duplicated contextPercent math shared by willManageContext and manageContext into computeContextPercent so the two stay in lockstep. Addresses PR review feedback.
1 parent 211fe55 commit 7447ab3

3 files changed

Lines changed: 56 additions & 34 deletions

File tree

src/api/providers/__tests__/vscode-lm.spec.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import * as vscode from "vscode"
6363
import { VsCodeLmHandler } from "../vscode-lm"
6464
import type { ApiHandlerOptions } from "../../../shared/api"
6565
import type { Anthropic } from "@anthropic-ai/sdk"
66-
import { openAiModelInfoSaneDefaults, vscodeLlmModels } from "@roo-code/types"
66+
import { openAiModelInfoSaneDefaults, vscodeLlmDefaultModelId, vscodeLlmModels } from "@roo-code/types"
6767

6868
const mockLanguageModelChat = {
6969
id: "test-model",
@@ -483,20 +483,21 @@ describe("VsCodeLmHandler", () => {
483483
opusHandler.dispose()
484484
})
485485

486-
it("falls back to the live model context window for families not in the static table", () => {
487-
// Not a curated row, so the gate uses the live runtime window.
486+
it("falls back to the default-row maxInputTokens for an unknown family (catalog drift)", () => {
487+
// `test-family` isn't a curated row (e.g. a selector left over from a dropped model), so the
488+
// gate resolves the default row instead of the inflated live window.
488489
handler["client"] = mockLanguageModelChat as unknown as vscode.LanguageModelChat
489-
expect(handler.getCondenseContextWindow()).toBe(handler.getModel().info.contextWindow)
490-
expect(handler.getCondenseContextWindow()).toBe(mockLanguageModelChat.maxInputTokens)
490+
expect(handler.getCondenseContextWindow()).toBe(vscodeLlmModels[vscodeLlmDefaultModelId].maxInputTokens)
491491
})
492492

493-
it("falls back to the live window when no family is resolvable (no client, no selector family)", () => {
494-
// No client and no selector family means `family` is undefined, so the gate skips the
495-
// static lookup and uses getModel().info.contextWindow.
493+
it("falls back to the default-row maxInputTokens when no family is resolvable (no client, no selector family)", () => {
494+
// No client and no selector family means `family` is undefined, so the gate uses the default
495+
// row's maxInputTokens rather than the live getModel().info.contextWindow.
496496
const noFamilyHandler = new VsCodeLmHandler({ vsCodeLmModelSelector: { vendor: "copilot" } })
497497
noFamilyHandler["client"] = null
498-
expect(noFamilyHandler.getCondenseContextWindow()).toBe(noFamilyHandler.getModel().info.contextWindow)
499-
expect(noFamilyHandler.getCondenseContextWindow()).toBe(openAiModelInfoSaneDefaults.contextWindow)
498+
expect(noFamilyHandler.getCondenseContextWindow()).toBe(
499+
vscodeLlmModels[vscodeLlmDefaultModelId].maxInputTokens,
500+
)
500501
noFamilyHandler.dispose()
501502
})
502503

src/api/providers/vscode-lm.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
22
import * as vscode from "vscode"
33
import OpenAI from "openai"
44

5-
import { type ModelInfo, openAiModelInfoSaneDefaults, vscodeLlmModels } from "@roo-code/types"
5+
import { type ModelInfo, openAiModelInfoSaneDefaults, vscodeLlmDefaultModelId, vscodeLlmModels } from "@roo-code/types"
66

77
import type { ApiHandlerOptions } from "../../shared/api"
88
import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "../../shared/vsCodeSelectorUtils"
@@ -565,12 +565,15 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
565565
/**
566566
* Context window for auto-condense. The API's advertised `client.maxInputTokens` is far larger
567567
* than usable, so relying on it stops auto-condense from firing; measure against the curated
568-
* static table's `maxInputTokens` instead (the same value the bar uses). Fall back to the live
569-
* window when the model isn't in the table.
568+
* static table's `maxInputTokens` instead (the same value the bar uses). An unknown family (e.g.
569+
* a selector left over from a model dropped from the catalog) resolves to the default row rather
570+
* than the inflated live window; only a non-positive static `maxInputTokens` falls back to it.
570571
*/
571572
getCondenseContextWindow(): number {
572573
const family = this.client?.family ?? this.options.vsCodeLmModelSelector?.family
573-
const staticModel = family ? vscodeLlmModels[family as keyof typeof vscodeLlmModels] : undefined
574+
const staticModel = family
575+
? (vscodeLlmModels[family as keyof typeof vscodeLlmModels] ?? vscodeLlmModels[vscodeLlmDefaultModelId])
576+
: vscodeLlmModels[vscodeLlmDefaultModelId]
574577

575578
if (staticModel && typeof staticModel.maxInputTokens === "number" && staticModel.maxInputTokens > 0) {
576579
return staticModel.maxInputTokens

src/core/context-management/index.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,32 @@ export async function estimateTokenCount(
4040
return apiHandler.countTokens(content)
4141
}
4242

43+
/**
44+
* Computes the percentage of the context budget consumed by the prior context.
45+
*
46+
* Default: divide by the full context window. Opt-in (vscode-lm) divides by available input
47+
* (window minus reserved output); an unknown/unlimited reserve (maxTokens -1) falls back to the
48+
* full window. Shared by `willManageContext` and `manageContext` so the two stay in lockstep.
49+
*/
50+
function computeContextPercent({
51+
prevContextTokens,
52+
contextWindow,
53+
maxTokens,
54+
useAvailableInputForContextPercent,
55+
}: {
56+
prevContextTokens: number
57+
contextWindow: number
58+
maxTokens?: number | null
59+
useAvailableInputForContextPercent?: boolean
60+
}): number {
61+
if (!useAvailableInputForContextPercent) {
62+
return (100 * prevContextTokens) / contextWindow
63+
}
64+
const reservedForOutput = maxTokens && maxTokens > 0 ? maxTokens : 0
65+
const availableInputTokens = contextWindow - reservedForOutput
66+
return availableInputTokens > 0 ? (100 * prevContextTokens) / availableInputTokens : 100
67+
}
68+
4369
/**
4470
* Result of truncation operation, includes the truncation ID for UI events.
4571
*/
@@ -200,16 +226,12 @@ export function willManageContext({
200226
// Invalid values fall back to global setting (effectiveThreshold already set)
201227
}
202228

203-
// Default: divide by the full context window. Opt-in (vscode-lm) divides by available input
204-
// (window minus reserved output); an unknown/unlimited reserve (-1) falls back to the full window.
205-
let contextPercent: number
206-
if (useAvailableInputForContextPercent) {
207-
const reservedForOutput = maxTokens && maxTokens > 0 ? maxTokens : 0
208-
const availableInputTokens = contextWindow - reservedForOutput
209-
contextPercent = availableInputTokens > 0 ? (100 * prevContextTokens) / availableInputTokens : 100
210-
} else {
211-
contextPercent = (100 * prevContextTokens) / contextWindow
212-
}
229+
const contextPercent = computeContextPercent({
230+
prevContextTokens,
231+
contextWindow,
232+
maxTokens,
233+
useAvailableInputForContextPercent,
234+
})
213235
return contextPercent >= effectiveThreshold || prevContextTokens > allowedTokens
214236
}
215237

@@ -328,16 +350,12 @@ export async function manageContext({
328350
// If no specific threshold is found for the profile, fall back to global setting
329351

330352
if (autoCondenseContext) {
331-
// Default: divide by the full context window. Opt-in (vscode-lm) divides by available input
332-
// (window minus reserved output); an unknown/unlimited reserve (-1) falls back to the full window.
333-
let contextPercent: number
334-
if (useAvailableInputForContextPercent) {
335-
const reservedForOutput = maxTokens && maxTokens > 0 ? maxTokens : 0
336-
const availableInputTokens = contextWindow - reservedForOutput
337-
contextPercent = availableInputTokens > 0 ? (100 * prevContextTokens) / availableInputTokens : 100
338-
} else {
339-
contextPercent = (100 * prevContextTokens) / contextWindow
340-
}
353+
const contextPercent = computeContextPercent({
354+
prevContextTokens,
355+
contextWindow,
356+
maxTokens,
357+
useAvailableInputForContextPercent,
358+
})
341359
if (contextPercent >= effectiveThreshold || prevContextTokens > allowedTokens) {
342360
// Attempt to intelligently condense the context
343361
const result = await summarizeConversation({

0 commit comments

Comments
 (0)