diff --git a/src/adapters/cursor/discovery.ts b/src/adapters/cursor/discovery.ts index 15f529fdb0..61b4ed340c 100644 --- a/src/adapters/cursor/discovery.ts +++ b/src/adapters/cursor/discovery.ts @@ -231,15 +231,18 @@ export function isCursorRouterModelId(modelId: string): boolean { export function filterCursorConfiguredModelsByLiveDiscovery( configured: readonly T[], liveIds: readonly string[], + maxModeLiveIds: readonly string[] = [], ): T[] { - return configured.filter(model => - !CURSOR_KNOWN_UNCALLABLE_MODEL_IDS.has(model.id) - && ( - isCursorRouterModelId(model.id) - // Synthetic ultra rows ride their base model's account availability. - || isCursorModelAvailableForAccount(cursorUltraBaseModelId(model.id) ?? model.id, liveIds) - ), - ); + return configured.filter(model => { + if (CURSOR_KNOWN_UNCALLABLE_MODEL_IDS.has(model.id)) return false; + if (isCursorRouterModelId(model.id)) return true; + const ultraBase = cursorUltraBaseModelId(model.id); + return isCursorModelAvailableForAccount(ultraBase ?? model.id, liveIds) + // Successful discovery is authoritative: synthetic ultra rows additionally require the + // account-specific Max Mode capability. Discovery failures bypass this filter and retain + // the static seed under the caller's existing degraded-catalog policy. + && (ultraBase === undefined || isCursorModelAvailableForAccount(ultraBase, maxModeLiveIds)); + }); } /** diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index 1c8d62ae76..9a0c1717d7 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -1308,7 +1308,11 @@ async function fetchProviderModelsWithAuth( ...(cursorFetch ? { fetch: cursorFetch } : {}), }); if (liveResult.ok) { - const available = filterCursorConfiguredModelsByLiveDiscovery(configured, liveResult.models); + const available = filterCursorConfiguredModelsByLiveDiscovery( + configured, + liveResult.models, + liveResult.maxModeModels ?? [], + ); const result = available.length > 0 ? available : configured; // Cache the discovery-filtered roster without combo retention so a later // gather can re-apply the current capture's retain set on read. diff --git a/tests/cursor-ultra-mode.test.ts b/tests/cursor-ultra-mode.test.ts index a6cbf5bb3a..df6d479a2b 100644 --- a/tests/cursor-ultra-mode.test.ts +++ b/tests/cursor-ultra-mode.test.ts @@ -79,13 +79,20 @@ describe("cursor ultra (-1m / Max Mode) toggle (devlog 260826 070)", () => { expect(decoded.modelDetails?.maxMode ?? false).toBe(false); }); - test("account filter admits the synthetic row through its base availability", () => { + test("account filter admits the synthetic row only with base and Max Mode availability", () => { const configured = [{ id: "kimi-k3-1m" }, { id: "kimi-k3" }]; const live = ["kimi-k3-high", "kimi-k3-max"]; - const filtered = filterCursorConfiguredModelsByLiveDiscovery(configured, live); + const filtered = filterCursorConfiguredModelsByLiveDiscovery(configured, live, ["kimi-k3-max"]); expect(filtered.map(model => model.id)).toEqual(["kimi-k3-1m", "kimi-k3"]); }); + test("account filter hides the synthetic row without discovered Max Mode capability", () => { + const configured = [{ id: "kimi-k3-1m" }, { id: "kimi-k3" }]; + const live = ["kimi-k3-high", "kimi-k3-max"]; + const filtered = filterCursorConfiguredModelsByLiveDiscovery(configured, live); + expect(filtered.map(model => model.id)).toEqual(["kimi-k3"]); + }); + test("ultra id set stays narrow and every entry has a static row", () => { for (const id of CURSOR_ULTRA_1M_MODEL_IDS) { expect(CURSOR_STATIC_MODELS.some(model => model.id === id)).toBe(true);