fix(context): qualified key beats bare min when provider is explicit; bare wins for inferred provider

This commit is contained in:
Cursor Agent 2026-03-12 09:22:12 +00:00
parent 821f8234af
commit f8cf752c59
2 changed files with 66 additions and 32 deletions

View File

@ -195,30 +195,33 @@ describe("lookupContextTokens", () => {
expect(result).toBe(200_000);
});
it("resolveContextTokensForModel: bare key wins over qualified; OpenRouter raw entry not shadowed by Google config", async () => {
// applyConfiguredContextWindows writes "gemini-2.5-pro" → 2M (bare key, Google config).
// Discovery writes "google/gemini-2.5-pro" → 999k (OpenRouter raw qualified entry).
// Lookup order: bare first → finds 2M immediately, never reaching the
// OpenRouter raw "google/gemini-2.5-pro" qualified key.
mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }], {
google: {
models: [{ id: "gemini-2.5-pro", contextWindow: 2_000_000 }],
it("resolveContextTokensForModel: config direct scan prevents OpenRouter qualified key collision for Google provider", async () => {
// When provider is explicitly "google" and cfg has a Google contextWindow
// override, the config direct scan returns it before any cache lookup —
// so the OpenRouter raw "google/gemini-2.5-pro" qualified entry is never hit.
// Real callers (status.summary.ts) always pass cfg when provider is explicit.
mockDiscoveryDeps([{ id: "google/gemini-2.5-pro", contextWindow: 999_000 }]);
const cfg = {
models: {
providers: {
google: { models: [{ id: "gemini-2.5-pro", contextWindow: 2_000_000 }] },
},
},
});
};
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
// Google provider: bare key "gemini-2.5-pro" → 2M (config-written) is found
// first; OpenRouter's "google/gemini-2.5-pro" qualified entry is never hit.
// Google with explicit cfg: config direct scan wins before any cache lookup.
const googleResult = resolveContextTokensForModel({
cfg: cfg as never,
provider: "google",
model: "gemini-2.5-pro",
});
expect(googleResult).toBe(2_000_000);
// OpenRouter provider with slash model id: bare lookup "google/gemini-2.5-pro"
// → 999k (OpenRouter raw discovery entry, still intact).
// OpenRouter provider with slash model id: bare lookup finds the raw entry.
const openrouterResult = resolveContextTokensForModel({
provider: "openrouter",
model: "google/gemini-2.5-pro",
@ -297,4 +300,27 @@ describe("lookupContextTokens", () => {
});
expect(explicitResult).toBe(2_000_000);
});
it("resolveContextTokensForModel: qualified key beats bare min when provider is explicit (original #35976 fix)", async () => {
// Regression: when both "gemini-3.1-pro-preview" (bare, min=128k) AND
// "google-gemini-cli/gemini-3.1-pro-preview" (qualified, 1M) are in cache,
// an explicit-provider call must return the provider-specific qualified value,
// not the collided bare minimum.
mockDiscoveryDeps([
{ id: "github-copilot/gemini-3.1-pro-preview", contextWindow: 128_000 },
{ id: "gemini-3.1-pro-preview", contextWindow: 128_000 },
{ id: "google-gemini-cli/gemini-3.1-pro-preview", contextWindow: 1_048_576 },
]);
const { resolveContextTokensForModel } = await import("./context.js");
await new Promise((r) => setTimeout(r, 0));
// Qualified "google-gemini-cli/gemini-3.1-pro-preview" → 1M wins over
// bare "gemini-3.1-pro-preview" → 128k (cross-provider minimum).
const result = resolveContextTokensForModel({
provider: "google-gemini-cli",
model: "gemini-3.1-pro-preview",
});
expect(result).toBe(1_048_576);
});
});

View File

@ -345,30 +345,38 @@ export function resolveContextTokensForModel(params: {
}
}
// Try bare key first. Bare keys contain the most-recently-written value
// (config overrides via applyConfiguredContextWindows run last) and are
// always correct for models registered with bare IDs.
// When provider is explicitly given and the model ID is bare (no slash),
// try the provider-qualified cache key BEFORE the bare key. Discovery
// entries are stored under qualified IDs (e.g. "google-gemini-cli/
// gemini-3.1-pro-preview → 1M"), while the bare key may hold a cross-
// provider minimum (128k). Returning the qualified entry gives the correct
// provider-specific window for /status and session context-token persistence.
//
// Guard: only when params.provider is explicit (not inferred from a slash in
// the model string). For model-only callers (e.g. status.ts log-usage
// fallback with model="google/gemini-2.5-pro"), the inferred provider would
// construct "google/gemini-2.5-pro" as the qualified key which accidentally
// matches OpenRouter's raw discovery entry — the bare lookup is correct there.
if (params.provider && ref && !ref.model.includes("/")) {
const qualifiedResult = lookupContextTokens(
`${normalizeProviderId(ref.provider)}/${ref.model}`,
);
if (qualifiedResult !== undefined) {
return qualifiedResult;
}
}
// Bare key fallback. For model-only calls with slash-containing IDs
// (e.g. "google/gemini-2.5-pro") this IS the raw discovery cache key.
const bareResult = lookupContextTokens(params.model);
if (bareResult !== undefined) {
return bareResult;
}
// Bare key miss. As a last resort, try the provider-qualified key so that
// discovery entries stored under qualified IDs are reachable. The pi-ai
// registry can return entries like "google-gemini-cli/gemini-3.1-pro-preview"
// for the google-gemini-cli provider; without this fallback, resolving
// { provider: "google-gemini-cli", model: "gemini-3.1-pro-preview" } with
// no bare-key hit would always miss.
//
// Collision risk: the same keyspace holds raw slash-containing model IDs
// (e.g. OpenRouter's "google/gemini-2.5-pro"). This fallback is only reached
// when the bare key misses, which means native discovery for the requested
// provider produced no bare entry. In that situation the only available
// context-window data is what another provider stored under the same
// qualified key — in practice identical (OpenRouter mirrors native limits) —
// so returning it is strictly better than falling back to DEFAULT_CONTEXT_TOKENS.
// If native discovery IS running, it writes a bare key, and we never reach here.
if (ref && !ref.model.includes("/")) {
// When provider is implicit, try qualified as a last resort so inferred
// provider/model pairs (e.g. model="google-gemini-cli/gemini-3.1-pro")
// still find discovery entries stored under that qualified ID.
if (!params.provider && ref && !ref.model.includes("/")) {
const qualifiedResult = lookupContextTokens(
`${normalizeProviderId(ref.provider)}/${ref.model}`,
);