Skip to content

fix(router-provider): fetch model metadata before context management decisions - #1053

Open
JamesRobert20 wants to merge 2 commits into
mainfrom
fix/router-provider-context-window
Open

fix(router-provider): fetch model metadata before context management decisions#1053
JamesRobert20 wants to merge 2 commits into
mainfrom
fix/router-provider-context-window

Conversation

@JamesRobert20

@JamesRobert20 JamesRobert20 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Router providers that are auth-scoped (zoo-gateway, kimi-code) skip the model cache entirely. When a fresh handler is constructed (on provider switch or profile change), getModel() falls back to hardcoded default model info because the real model list has not been fetched from the API yet.

Context management in Task.ts calls getModel() before createMessage() (where fetchModel() normally runs). This means condensing and truncation decisions use the default context window (200k for zoo-gateway) instead of the actual model's context window.

If the user selects a model with a larger context window through the gateway (e.g. Opus 4.8 at 1M tokens), the system incorrectly calculates context usage against 200k, triggering premature condensation.

Fix

  • Add ensureModelFetched() to RouterProvider that calls fetchModel() once when the instance model map is empty. Subsequent calls are no-ops.
  • Add ensureModelFetched as an optional method on the ApiHandler interface so Task.ts can call it.
  • Call await this.api.ensureModelFetched?.() in Task.ts before both context management paths (attemptApiRequest and handleContextWindowExceededError).

Static providers (Anthropic, Gemini, Bedrock, etc.) are unaffected since they don't implement the method. Non-auth-scoped router providers (OpenRouter, Vercel AI Gateway) already have cache hits via getModelsFromCache() so the fetch is skipped after the first call.

Summary by CodeRabbit

  • New Features

    • Model information is now fetched automatically when needed, providing more accurate context window and pricing details.
    • Concurrent model-data requests are consolidated to avoid unnecessary duplicate fetches.
  • Bug Fixes

    • Improved handling of context limits and token management by using current model metadata instead of default values.

…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>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

ApiHandler now optionally supports lazy model fetching. RouterProvider fetches and caches remote model metadata while deduplicating concurrent requests. Task request and context-window flows await model availability before using model-derived values, with Zoo Gateway tests covering the new behavior.

Changes

Model Metadata Fetching

Layer / File(s) Summary
Provider model fetching
src/api/index.ts, src/api/providers/router-provider.ts, src/api/providers/__tests__/zoo-gateway.spec.ts
The API contract and RouterProvider add lazy model fetching with concurrent-call deduplication; Zoo Gateway tests cover cache behavior and fetched context windows.
Task model-dependent flows
src/core/task/Task.ts
Task request setup and context-window handling await model fetching before accessing model-derived values.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Task
  participant ApiHandler
  participant RouterProvider
  Task->>ApiHandler: ensureModelFetched()
  ApiHandler->>RouterProvider: ensureModelFetched()
  RouterProvider->>RouterProvider: fetchModel() when models are empty
  RouterProvider-->>Task: model metadata available
Loading

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: edelauna, taltas, navedmerchant

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the problem and fix, but it omits required sections like the linked issue and test procedure. Add the required template sections, especially Closes: #..., a Test Procedure, and the pre-submission checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: fetching model metadata before context decisions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/router-provider-context-window

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/task/Task.ts 66.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/router-provider.ts`:
- Around line 64-68: Make ensureModelFetched() single-flight by storing and
reusing an in-flight fetchModel() promise, clearing it after completion while
preserving the empty-model check. Update ZooGatewayHandler.createMessage() and
completePrompt() to call ensureModelFetched() followed by getModel(), replacing
their unconditional fetchModel() calls so auth-scoped providers avoid duplicate
metadata requests.

In `@src/core/task/Task.ts`:
- Around line 4046-4047: Move the optional ensureModelFetched call before the
cachedStreamingModel snapshot and before getSystemPrompt-dependent request
setup. Reuse the initialized model returned by this.api.getModel() for both
cachedStreamingModel and modelInfo, ensuring fresh router providers have
metadata available even when contextTokens is falsy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc932c48-2b90-434b-b92a-9aef4da2165e

📥 Commits

Reviewing files that changed from the base of the PR and between dcaa3cb and 101b490.

📒 Files selected for processing (4)
  • src/api/index.ts
  • src/api/providers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/router-provider.ts
  • src/core/task/Task.ts

Comment on lines +64 to +68
async ensureModelFetched(): Promise<void> {
if (Object.keys(this.models).length === 0) {
await this.fetchModel()
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make model fetching single-flight and reusable.

Concurrent callers can all observe an empty models map before fetchModel() completes, causing duplicate remote requests. Also, ZooGatewayHandler.createMessage() and completePrompt() still call fetchModel() unconditionally; for auth-scoped providers, getModels() skips caching, so the same request can fetch metadata twice.

Use an in-flight promise and update request paths to call ensureModelFetched() followed by getModel().

Suggested fix
+private modelFetchPromise?: Promise<void>
+
 async ensureModelFetched(): Promise<void> {
 	if (Object.keys(this.models).length === 0) {
-		await this.fetchModel()
+		const fetchPromise = (this.modelFetchPromise ??= this.fetchModel().then(() => undefined))
+		try {
+			await fetchPromise
+		} finally {
+			if (this.modelFetchPromise === fetchPromise) {
+				this.modelFetchPromise = undefined
+			}
+		}
 	}
 }

Then replace direct request-time fetchModel() calls with:

-await this.fetchModel()
+await this.ensureModelFetched()
+const { id, info } = this.getModel()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async ensureModelFetched(): Promise<void> {
if (Object.keys(this.models).length === 0) {
await this.fetchModel()
}
}
private modelFetchPromise?: Promise<void>
async ensureModelFetched(): Promise<void> {
if (Object.keys(this.models).length === 0) {
const fetchPromise = (this.modelFetchPromise ??= this.fetchModel().then(() => undefined))
try {
await fetchPromise
} finally {
if (this.modelFetchPromise === fetchPromise) {
this.modelFetchPromise = undefined
}
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/router-provider.ts` around lines 64 - 68, Make
ensureModelFetched() single-flight by storing and reusing an in-flight
fetchModel() promise, clearing it after completion while preserving the
empty-model check. Update ZooGatewayHandler.createMessage() and completePrompt()
to call ensureModelFetched() followed by getModel(), replacing their
unconditional fetchModel() calls so auth-scoped providers avoid duplicate
metadata requests.

Comment thread src/core/task/Task.ts
…ll site

Make ensureModelFetched single-flight so concurrent callers share a
single in-flight fetch instead of firing duplicates. Move the call site
before the cachedStreamingModel snapshot so the model info is accurate
from the start of the streaming session, not just for context management.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/core/task/Task.ts (1)

4048-4049: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move model fetching before getSystemPrompt() and remove the contextTokens guard.

This is the same still-valid issue from the previous review. Direct attemptApiRequest() callers compute getSystemPrompt() before fetching metadata, and requests with falsy contextTokens skip fetching entirely, leaving router-provider defaults in model-dependent logic. Fetch at the start of attemptApiRequest() before getSystemPrompt().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/Task.ts` around lines 4048 - 4049, Update attemptApiRequest to
call ensureModelFetched at its start, before getSystemPrompt is computed, and
remove the contextTokens guard that currently allows fetching to be skipped.
Preserve the existing model metadata usage through getModel().info after the
unconditional fetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/core/task/Task.ts`:
- Around line 4048-4049: Update attemptApiRequest to call ensureModelFetched at
its start, before getSystemPrompt is computed, and remove the contextTokens
guard that currently allows fetching to be skipped. Preserve the existing model
metadata usage through getModel().info after the unconditional fetch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d25401b-9da0-459f-9f39-79608468aa3a

📥 Commits

Reviewing files that changed from the base of the PR and between 101b490 and 47e0732.

📒 Files selected for processing (3)
  • src/api/providers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/router-provider.ts
  • src/core/task/Task.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/api/providers/tests/zoo-gateway.spec.ts
  • src/api/providers/router-provider.ts

@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 29, 2026
Comment thread src/core/task/Task.ts

await this.diffViewProvider.reset()

await this.api.ensureModelFetched?.()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The litellm/deepseek/moonshot fetchers re-throw, so a failure here would reject into the catch at L3743, which return trues and ends the task silently — the comment there assumes only attemptApiRequest can throw. Pre-change, the same failure surfaced via the createMessage retry path. Would catching and logging here be safer, letting getModel() fall back to defaults?


private modelFetchPromise?: Promise<void>

async ensureModelFetched(): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For auth-scoped providers, does the first request now fetch the model list twice — once here and again unconditionally in createMessage (zoo-gateway.ts L184)? getModels bypasses both caches for auth-scoped providers (modelCache.ts L265), so the second call is another network round trip. Worth making fetchModel() single-flight (or short-circuiting when this.models is populated) so both callers share one fetch?

})
})

describe("ensureModelFetched", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the rejection path covered anywhere? If getModels rejects once and the finally cleanup in router-provider.ts (L71-74) ever regresses, the stored rejected promise would poison every subsequent call. A reject-then-resolve test here would lock that in.

@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-author PR is waiting for the author to address requested changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants