Skip to content

Commit 101b490

Browse files
fix(router-provider): fetch model metadata before context management decisions
Router providers (zoo-gateway, kimi-code) that are auth-scoped skip the model cache entirely. On a fresh handler instance getModel() falls back to hardcoded defaults (e.g. 200k context window) because the real model list has not been fetched yet. Context management runs before createMessage() which is where fetchModel() normally happens, so condensing/truncation decisions use the wrong context window. Add ensureModelFetched() to RouterProvider that fetches once when the instance model map is empty. Call it in Task before context management so getModel() returns accurate metadata from the API. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent dcaa3cb commit 101b490

4 files changed

Lines changed: 62 additions & 0 deletions

File tree

src/api/index.ts

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

126126
getModel(): { id: string; info: ModelInfo }
127127

128+
/**
129+
* Ensures model metadata has been fetched from the remote API so that getModel()
130+
* returns accurate info (context window, pricing, etc.) instead of hardcoded defaults.
131+
* Only router providers that discover models over the network implement this.
132+
*/
133+
ensureModelFetched?(): Promise<void>
134+
128135
/**
129136
* Optional context window for context-management / auto-condense when it must differ from
130137
* getModel().info.contextWindow. Only VS Code LM overrides it (static `maxInputTokens` vs its

src/api/providers/__tests__/zoo-gateway.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,51 @@ describe("ZooGatewayHandler", () => {
635635
)
636636
})
637637
})
638+
639+
describe("ensureModelFetched", () => {
640+
it("fetches models when instance models are empty", async () => {
641+
const handler = new ZooGatewayHandler(mockOptions)
642+
const { getModels } = await import("../fetchers/modelCache")
643+
644+
expect(handler.getModel().info.contextWindow).toBe(200000)
645+
646+
await handler.ensureModelFetched()
647+
648+
expect(getModels).toHaveBeenCalled()
649+
})
650+
651+
it("skips the fetch when models are already populated", async () => {
652+
const handler = new ZooGatewayHandler(mockOptions)
653+
const { getModels } = await import("../fetchers/modelCache")
654+
655+
await handler.ensureModelFetched()
656+
vitest.mocked(getModels).mockClear()
657+
658+
await handler.ensureModelFetched()
659+
expect(getModels).not.toHaveBeenCalled()
660+
})
661+
662+
it("makes getModel return the fetched context window instead of the default", async () => {
663+
const { getModels } = await import("../fetchers/modelCache")
664+
vitest.mocked(getModels).mockResolvedValueOnce({
665+
"google/gemini-2.5-pro": {
666+
maxTokens: 65536,
667+
contextWindow: 1048576,
668+
supportsImages: true,
669+
supportsPromptCache: false,
670+
},
671+
})
672+
673+
const handler = new ZooGatewayHandler({
674+
...mockOptions,
675+
zooGatewayModelId: "google/gemini-2.5-pro",
676+
})
677+
678+
expect(handler.getModel().info.contextWindow).toBe(200000)
679+
680+
await handler.ensureModelFetched()
681+
682+
expect(handler.getModel().info.contextWindow).toBe(1048576)
683+
})
684+
})
638685
})

src/api/providers/router-provider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export abstract class RouterProvider extends BaseProvider {
6161
return this.getModel()
6262
}
6363

64+
async ensureModelFetched(): Promise<void> {
65+
if (Object.keys(this.models).length === 0) {
66+
await this.fetchModel()
67+
}
68+
}
69+
6470
override getModel(): { id: string; info: ModelInfo } {
6571
// Use `||` (not `??`) so an empty-string modelId also falls back to the default,
6672
// guaranteeing a non-empty id rather than forwarding "" to the API as an invalid

src/core/task/Task.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,6 +3842,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
38423842
const { profileThresholds = {}, mode, apiConfiguration } = state ?? {}
38433843

38443844
const { contextTokens } = this.getTokenUsage()
3845+
await this.api.ensureModelFetched?.()
38453846
const modelInfo = this.api.getModel().info
38463847

38473848
const maxTokens = getModelMaxOutputTokens({
@@ -4042,6 +4043,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
40424043
const { contextTokens } = this.getTokenUsage()
40434044

40444045
if (contextTokens) {
4046+
await this.api.ensureModelFetched?.()
40454047
const modelInfo = this.api.getModel().info
40464048

40474049
const maxTokens = getModelMaxOutputTokens({

0 commit comments

Comments
 (0)