Skip to content

Commit 9a2e8d8

Browse files
simurg79Bertan Ari
andauthored
fix(vscode-lm): reliable auto context condensing (#710)
* fix(vscode-lm): reliable auto context condensing Port of simurg79/Roo-Code#11 into Zoo-Code. * test(vscode-lm): cover condense-window edge branches for codecov/patch Add targeted tests for the previously-uncovered ported branches: the availableInputTokens<=0 fallback to 100% in willManageContext/manageContext, getCondenseContextWindow() guard fallbacks, and the vscode-lm UI family-miss window resolution. Raises patch coverage to satisfy the codecov/patch 80% gate. * chore(vscode-lm): address review — drop changeset, fix condense-window guard test - Remove .changeset/vscode-lm-condense-fix.md (changesets are maintainer-managed per AGENTS.md; CodeRabbit flagged). - Fix getCondenseContextWindow() non-positive-guard test so the selector family (claude-opus-4.8) drives the lookup and the zeroed static row actually exercises the maxInputTokens > 0 guard before falling back. * fix(vscode-lm): scope available-input condense denominator to vscode-lm; address review Address review feedback from edelauna on #710: - Scope the available-input-space condense percent denominator to vscode-lm only (via the getCondenseContextWindow seam); all other providers keep dividing by the full context window. The maxTokens:-1 reserve guard remains global. - Correct the misleading useSelectedModel comment: the gate's primary window is getCondenseContextWindow() (static maxInputTokens), not getModel().info.contextWindow. - Strengthen the listed-family test with a claude-opus-4.8 case (contextWindow != maxInputTokens) to catch a field swap. * docs(vscode-lm): tighten auto-condense comments Simplify comments added in PR #710 to be brief and rationale-focused; no logic, assertions, or test values changed. * test(vscode-lm): harden condense reserve-guard coverage Address review: make the negative-reserve percent test discriminate the maxTokens>0 guard (-200000), and add a manageContext truncation-path test so both copies of the -1 reserve guard are covered. Test-only; no product code changed. * test(task): fix Task.dispose teardown-race flake Mock utils/storage and OutputInterceptor in Task.dispose.test.ts so dispose()'s unawaited cleanup chain cannot emit a late console.error during Vitest worker teardown (EnvironmentTeardownError: Closing rpc while onUserConsoleLog was pending). Test-only; no product code changed. * 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. --------- Co-authored-by: Bertan Ari <bertanari@microsoft.com>
1 parent 67df9f9 commit 9a2e8d8

13 files changed

Lines changed: 800 additions & 121 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, it, expect } from "vitest"
2+
import { vscodeLlmModels, vscodeLlmDefaultModelId } from "../providers/vscode-llm.js"
3+
4+
describe("vscodeLlmModels", () => {
5+
it("exposes the opus-4.8 row with its measured maxInputTokens and contextWindow", () => {
6+
// claude-opus-4.8 intentionally diverges: maxInputTokens (197897) is the enforced ceiling the
7+
// UI reads, contextWindow (679560) the advertised window. Assert the on-disk literals as a tripwire.
8+
expect(vscodeLlmModels).toHaveProperty("claude-opus-4.8")
9+
expect(vscodeLlmModels["claude-opus-4.8"].contextWindow).toBe(679560)
10+
expect(vscodeLlmModels["claude-opus-4.8"].maxInputTokens).toBe(197897)
11+
})
12+
it("preserves the real window for models captured with a smaller maxInputTokens", () => {
13+
expect(vscodeLlmModels["gpt-4o-mini"].maxInputTokens).toBe(12078)
14+
expect(vscodeLlmModels["gpt-4o-mini"].contextWindow).toBe(12078)
15+
expect(vscodeLlmModels["gemini-2.5-pro"].contextWindow).toBe(108594)
16+
expect(vscodeLlmModels["gemini-2.5-pro"].maxInputTokens).toBe(108594)
17+
})
18+
it("keeps both window fields populated and positive for every row", () => {
19+
for (const [family, model] of Object.entries(vscodeLlmModels)) {
20+
expect(model.contextWindow, `${family}: contextWindow must be a positive integer`).toBeGreaterThan(0)
21+
expect(model.maxInputTokens, `${family}: maxInputTokens must be a positive integer`).toBeGreaterThan(0)
22+
}
23+
})
24+
it("excludes fabricated/internal/alias families and the dropped legacy rows", () => {
25+
expect(vscodeLlmModels).not.toHaveProperty("claude-opus-4.7-high")
26+
expect(vscodeLlmModels).not.toHaveProperty("claude-3.5-sonnet")
27+
expect(vscodeLlmModels).not.toHaveProperty("claude-4-sonnet")
28+
})
29+
it("defaults to a model id that exists in the table", () => {
30+
expect(vscodeLlmDefaultModelId).toBe("claude-sonnet-4.5")
31+
expect(vscodeLlmModels).toHaveProperty(vscodeLlmDefaultModelId)
32+
})
33+
})

packages/types/src/providers/vscode-llm.ts

Lines changed: 125 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,189 +2,215 @@ import type { ModelInfo } from "../model.js"
22

33
export type VscodeLlmModelId = keyof typeof vscodeLlmModels
44

5-
export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-3.5-sonnet"
5+
export const vscodeLlmDefaultModelId: VscodeLlmModelId = "claude-sonnet-4.5"
66

7-
// https://docs.cline.bot/provider-config/vscode-language-model-api
7+
// Curated VS Code LM (GitHub Copilot) model catalog.
8+
// The API exposes only `maxInputTokens`; the UI and condense gate read that. `contextWindow` is
9+
// the advertised window, kept for rows where it diverges from the ceiling (e.g. claude-opus-4.8).
810
export const vscodeLlmModels = {
9-
"gpt-3.5-turbo": {
10-
contextWindow: 12114,
11-
supportsImages: false,
11+
"claude-opus-4.8": {
12+
contextWindow: 679560,
13+
supportsImages: true,
1214
supportsPromptCache: false,
1315
inputPrice: 0,
1416
outputPrice: 0,
15-
family: "gpt-3.5-turbo",
16-
version: "gpt-3.5-turbo-0613",
17-
name: "GPT 3.5 Turbo",
17+
family: "claude-opus-4.8",
18+
version: "claude-opus-4.8",
19+
name: "Claude Opus 4.8",
1820
supportsToolCalling: true,
19-
maxInputTokens: 12114,
21+
maxInputTokens: 197897,
2022
},
21-
"gpt-4o-mini": {
22-
contextWindow: 12115,
23-
supportsImages: false,
23+
"claude-opus-4.7": {
24+
contextWindow: 197897,
25+
supportsImages: true,
2426
supportsPromptCache: false,
2527
inputPrice: 0,
2628
outputPrice: 0,
27-
family: "gpt-4o-mini",
28-
version: "gpt-4o-mini-2024-07-18",
29-
name: "GPT-4o mini",
29+
family: "claude-opus-4.7",
30+
version: "claude-opus-4.7",
31+
name: "Claude Opus 4.7",
3032
supportsToolCalling: true,
31-
maxInputTokens: 12115,
33+
maxInputTokens: 197897,
3234
},
33-
"gpt-4": {
34-
contextWindow: 28501,
35-
supportsImages: false,
35+
"claude-opus-4.6": {
36+
contextWindow: 197897,
37+
supportsImages: true,
3638
supportsPromptCache: false,
3739
inputPrice: 0,
3840
outputPrice: 0,
39-
family: "gpt-4",
40-
version: "gpt-4-0613",
41-
name: "GPT 4",
41+
family: "claude-opus-4.6",
42+
version: "claude-opus-4.6",
43+
name: "Claude Opus 4.6",
4244
supportsToolCalling: true,
43-
maxInputTokens: 28501,
45+
maxInputTokens: 197897,
4446
},
45-
"gpt-4-0125-preview": {
46-
contextWindow: 63826,
47-
supportsImages: false,
47+
"claude-opus-4.5": {
48+
contextWindow: 167790,
49+
supportsImages: true,
4850
supportsPromptCache: false,
4951
inputPrice: 0,
5052
outputPrice: 0,
51-
family: "gpt-4-turbo",
52-
version: "gpt-4-0125-preview",
53-
name: "GPT 4 Turbo",
53+
family: "claude-opus-4.5",
54+
version: "claude-opus-4.5",
55+
name: "Claude Opus 4.5",
5456
supportsToolCalling: true,
55-
maxInputTokens: 63826,
57+
maxInputTokens: 167790,
5658
},
57-
"gpt-4o": {
58-
contextWindow: 63827,
59+
"claude-sonnet-4.6": {
60+
contextWindow: 197896,
5961
supportsImages: true,
6062
supportsPromptCache: false,
6163
inputPrice: 0,
6264
outputPrice: 0,
63-
family: "gpt-4o",
64-
version: "gpt-4o-2024-11-20",
65-
name: "GPT-4o",
65+
family: "claude-sonnet-4.6",
66+
version: "claude-sonnet-4.6",
67+
name: "Claude Sonnet 4.6",
6668
supportsToolCalling: true,
67-
maxInputTokens: 63827,
69+
maxInputTokens: 197896,
6870
},
69-
o1: {
70-
contextWindow: 19827,
71-
supportsImages: false,
71+
"claude-sonnet-4.5": {
72+
contextWindow: 167790,
73+
supportsImages: true,
7274
supportsPromptCache: false,
7375
inputPrice: 0,
7476
outputPrice: 0,
75-
family: "o1-ga",
76-
version: "o1-2024-12-17",
77-
name: "o1 (Preview)",
77+
family: "claude-sonnet-4.5",
78+
version: "claude-sonnet-4.5",
79+
name: "Claude Sonnet 4.5",
7880
supportsToolCalling: true,
79-
maxInputTokens: 19827,
81+
maxInputTokens: 167790,
8082
},
81-
"o3-mini": {
82-
contextWindow: 63827,
83-
supportsImages: false,
83+
"claude-haiku-4.5": {
84+
contextWindow: 135790,
85+
supportsImages: true,
8486
supportsPromptCache: false,
8587
inputPrice: 0,
8688
outputPrice: 0,
87-
family: "o3-mini",
88-
version: "o3-mini-2025-01-31",
89-
name: "o3-mini",
89+
family: "claude-haiku-4.5",
90+
version: "claude-haiku-4.5",
91+
name: "Claude Haiku 4.5",
9092
supportsToolCalling: true,
91-
maxInputTokens: 63827,
93+
maxInputTokens: 135790,
9294
},
93-
"claude-3.5-sonnet": {
94-
contextWindow: 81638,
95+
"gpt-5.5": {
96+
contextWindow: 268426,
9597
supportsImages: true,
9698
supportsPromptCache: false,
9799
inputPrice: 0,
98100
outputPrice: 0,
99-
family: "claude-3.5-sonnet",
100-
version: "claude-3.5-sonnet",
101-
name: "Claude 3.5 Sonnet",
101+
family: "gpt-5.5",
102+
version: "gpt-5.5",
103+
name: "GPT-5.5",
102104
supportsToolCalling: true,
103-
maxInputTokens: 81638,
105+
maxInputTokens: 268426,
104106
},
105-
"claude-4-sonnet": {
106-
contextWindow: 128000,
107+
"gpt-5.4": {
108+
contextWindow: 268424,
107109
supportsImages: true,
108110
supportsPromptCache: false,
109111
inputPrice: 0,
110112
outputPrice: 0,
111-
family: "claude-sonnet-4",
112-
version: "claude-sonnet-4",
113-
name: "Claude Sonnet 4",
113+
family: "gpt-5.4",
114+
version: "gpt-5.4",
115+
name: "GPT-5.4",
114116
supportsToolCalling: true,
115-
maxInputTokens: 111836,
117+
maxInputTokens: 268424,
116118
},
117-
"gemini-2.0-flash-001": {
118-
contextWindow: 127827,
119+
"gpt-5.4-mini": {
120+
contextWindow: 271790,
119121
supportsImages: true,
120122
supportsPromptCache: false,
121123
inputPrice: 0,
122124
outputPrice: 0,
123-
family: "gemini-2.0-flash",
124-
version: "gemini-2.0-flash-001",
125-
name: "Gemini 2.0 Flash",
126-
supportsToolCalling: false,
127-
maxInputTokens: 127827,
125+
family: "gpt-5.4-mini",
126+
version: "gpt-5.4-mini",
127+
name: "GPT-5.4 mini",
128+
supportsToolCalling: true,
129+
maxInputTokens: 271790,
128130
},
129-
"gemini-2.5-pro": {
130-
contextWindow: 128000,
131+
"gpt-5.3-codex": {
132+
contextWindow: 271790,
131133
supportsImages: true,
132134
supportsPromptCache: false,
133135
inputPrice: 0,
134136
outputPrice: 0,
135-
family: "gemini-2.5-pro",
136-
version: "gemini-2.5-pro-preview-03-25",
137-
name: "Gemini 2.5 Pro (Preview)",
137+
family: "gpt-5.3-codex",
138+
version: "gpt-5.3-codex",
139+
name: "GPT-5.3-Codex",
138140
supportsToolCalling: true,
139-
maxInputTokens: 108637,
141+
maxInputTokens: 271790,
140142
},
141-
"o4-mini": {
142-
contextWindow: 128000,
143+
"gpt-5-mini": {
144+
contextWindow: 127790,
145+
supportsImages: true,
146+
supportsPromptCache: false,
147+
inputPrice: 0,
148+
outputPrice: 0,
149+
family: "gpt-5-mini",
150+
version: "gpt-5-mini",
151+
name: "GPT-5 mini",
152+
supportsToolCalling: true,
153+
maxInputTokens: 127790,
154+
},
155+
"gpt-4o-mini": {
156+
contextWindow: 12078,
143157
supportsImages: false,
144158
supportsPromptCache: false,
145159
inputPrice: 0,
146160
outputPrice: 0,
147-
family: "o4-mini",
148-
version: "o4-mini-2025-04-16",
149-
name: "o4-mini (Preview)",
161+
family: "gpt-4o-mini",
162+
version: "gpt-4o-mini-2024-07-18",
163+
name: "GPT-4o mini",
150164
supportsToolCalling: true,
151-
maxInputTokens: 111452,
165+
maxInputTokens: 12078,
152166
},
153-
"gpt-4.1": {
154-
contextWindow: 128000,
167+
"gemini-3.1-pro-preview": {
168+
contextWindow: 197897,
155169
supportsImages: true,
156170
supportsPromptCache: false,
157171
inputPrice: 0,
158172
outputPrice: 0,
159-
family: "gpt-4.1",
160-
version: "gpt-4.1-2025-04-14",
161-
name: "GPT-4.1 (Preview)",
173+
family: "gemini-3.1-pro-preview",
174+
version: "gemini-3.1-pro-preview",
175+
name: "Gemini 3.1 Pro (Preview)",
162176
supportsToolCalling: true,
163-
maxInputTokens: 111452,
177+
maxInputTokens: 197897,
164178
},
165-
"gpt-5-mini": {
166-
contextWindow: 128000,
179+
"gemini-3.5-flash": {
180+
contextWindow: 197895,
167181
supportsImages: true,
168182
supportsPromptCache: false,
169183
inputPrice: 0,
170184
outputPrice: 0,
171-
family: "gpt-5-mini",
172-
version: "gpt-5-mini",
173-
name: "GPT-5 mini (Preview)",
185+
family: "gemini-3.5-flash",
186+
version: "gemini-3.5-flash",
187+
name: "Gemini 3.5 Flash",
188+
supportsToolCalling: true,
189+
maxInputTokens: 197895,
190+
},
191+
"gemini-3-flash": {
192+
contextWindow: 108594,
193+
supportsImages: true,
194+
supportsPromptCache: false,
195+
inputPrice: 0,
196+
outputPrice: 0,
197+
family: "gemini-3-flash",
198+
version: "gemini-3-flash-preview",
199+
name: "Gemini 3 Flash (Preview)",
174200
supportsToolCalling: true,
175-
maxInputTokens: 108637,
201+
maxInputTokens: 108594,
176202
},
177-
"gpt-5": {
178-
contextWindow: 128000,
203+
"gemini-2.5-pro": {
204+
contextWindow: 108594,
179205
supportsImages: true,
180206
supportsPromptCache: false,
181207
inputPrice: 0,
182208
outputPrice: 0,
183-
family: "gpt-5",
184-
version: "gpt-5",
185-
name: "GPT-5 (Preview)",
209+
family: "gemini-2.5-pro",
210+
version: "gemini-2.5-pro",
211+
name: "Gemini 2.5 Pro",
186212
supportsToolCalling: true,
187-
maxInputTokens: 108637,
213+
maxInputTokens: 108594,
188214
},
189215
} as const satisfies Record<
190216
string,

src/api/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ export interface ApiHandler {
107107

108108
getModel(): { id: string; info: ModelInfo }
109109

110+
/**
111+
* Optional context window for context-management / auto-condense when it must differ from
112+
* getModel().info.contextWindow. Only VS Code LM overrides it (static `maxInputTokens` vs its
113+
* inflated live window); others leave it undefined and callers fall back.
114+
*/
115+
getCondenseContextWindow?(): number
116+
110117
/**
111118
* Counts tokens for content blocks
112119
* All providers extend BaseProvider which provides a default tiktoken implementation,

0 commit comments

Comments
 (0)