mirror of https://github.com/openclaw/openclaw.git
fix(context): qualified key beats bare min when provider is explicit; bare wins for inferred provider
This commit is contained in:
parent
821f8234af
commit
f8cf752c59
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue