Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/adapters/cursor/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,23 @@ export function filterCursorConfiguredModelsByLiveDiscovery<T extends { id: stri
liveIds: readonly string[],
): T[] {
return configured.filter(model =>
isCursorRouterModelId(model.id) || isCursorModelAvailableForAccount(model.id, liveIds),
!CURSOR_KNOWN_UNCALLABLE_MODEL_IDS.has(model.id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope the quarantine to canonical Cursor catalogs

For a custom provider using adapter: "cursor", this helper is still called by src/codex/catalog/provider-fetch.ts; if live discovery confirms claude-opus-5 plus any other configured model, the other model keeps available nonempty and this check silently removes the explicitly configured Opus model, even when the custom endpoint can call it. Apply the quarantine only when the models originate from the canonical Cursor registry/transport, rather than to every provider using this adapter.

AGENTS.md reference: src/AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

&& (isCursorRouterModelId(model.id) || isCursorModelAvailableForAccount(model.id, liveIds)),
);
}

/**
* Models GetUsableModels advertises but whose every Run returns not_found (catalog honesty,
* devlog 260826_cursor_responses_gap 060). Live probes 2026-08-26: cursor/claude-opus-5 failed
* 100% ("Cursor Connect error not_found") while its -fast and -thinking siblings — separate
* wire families — succeed. Quarantined here, in the shared filter, so live, cached, stale, and
* static serving paths all agree. Custom user provider overrides are not routed through this
* canonical seed and stay untouched.
*/
export const CURSOR_KNOWN_UNCALLABLE_MODEL_IDS: ReadonlySet<string> = new Set([
"claude-opus-5",
]);

export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorModels([
// Context windows and the model lineup mirror Cursor's public models/pricing docs plus the jawcode
// SOT (../jawcode/packages/ai/src/models.json, `cursor` provider), which mirrors the real
Expand Down Expand Up @@ -245,7 +258,8 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM
{ id: "claude-opus-4-7-fast", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
{ id: "claude-opus-4-8-fast", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
{ id: "claude-opus-4-8", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
{ id: "claude-opus-5", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
// claude-opus-5 (bare) removed from the seed: GetUsableModels lists it but every Run returns
// not_found (quarantined via CURSOR_KNOWN_UNCALLABLE_MODEL_IDS; -fast/-thinking families stay).
{ id: "claude-opus-5-fast", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },
{ id: "claude-fable-5", contextWindow: CONTEXT_200K, supportsReasoningEffort: true },

Expand Down
33 changes: 33 additions & 0 deletions tests/cursor-uncallable-quarantine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, test } from "bun:test";
import {
CURSOR_KNOWN_UNCALLABLE_MODEL_IDS,
CURSOR_STATIC_MODELS,
filterCursorConfiguredModelsByLiveDiscovery,
} from "../src/adapters/cursor/discovery";

describe("cursor uncallable-model quarantine (devlog 260826 060)", () => {
test("static seed no longer carries bare claude-opus-5", () => {
expect(CURSOR_STATIC_MODELS.some(model => model.id === "claude-opus-5")).toBe(false);
});

test("siblings from other wire families survive", () => {
expect(CURSOR_STATIC_MODELS.some(model => model.id === "claude-opus-5-fast")).toBe(true);
});

test("live filter drops quarantined ids even when GetUsableModels lists them", () => {
const configured = [{ id: "claude-opus-5" }, { id: "claude-opus-5-fast" }, { id: "grok-4.6" }];
const live = ["claude-opus-5-high", "claude-opus-5-high-fast", "grok-4.6-high"];
const filtered = filterCursorConfiguredModelsByLiveDiscovery(configured, live);
expect(filtered.map(model => model.id)).toEqual(["claude-opus-5-fast", "grok-4.6"]);
});

test("quarantine applies with an empty live list too (stale/static degradation path)", () => {
const configured = [{ id: "claude-opus-5" }, { id: "auto" }];
const filtered = filterCursorConfiguredModelsByLiveDiscovery(configured, []);
expect(filtered.some(model => model.id === "claude-opus-5")).toBe(false);
});

test("quarantine set stays narrow", () => {
expect([...CURSOR_KNOWN_UNCALLABLE_MODEL_IDS]).toEqual(["claude-opus-5"]);
});
});
Loading