diff --git a/src/adapters/cursor/discovery.ts b/src/adapters/cursor/discovery.ts index 4d4e7d964e7..119827dc6ab 100644 --- a/src/adapters/cursor/discovery.ts +++ b/src/adapters/cursor/discovery.ts @@ -236,10 +236,15 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM { id: "claude-4.6-opus", contextWindow: CONTEXT_200K, supportsReasoningEffort: true }, { id: "claude-4.6-sonnet", contextWindow: CONTEXT_200K, supportsReasoningEffort: true }, { id: "claude-opus-4-7", contextWindow: CONTEXT_200K, supportsReasoningEffort: true }, - // opus-4-7-fast: effort-suffix tiers unverified -> no tier picker; sent bare like live-only ids. - { id: "claude-opus-4-7-fast", contextWindow: CONTEXT_200K }, + // Opus Fast families: live GetUsableModels (260822) lists ONLY effort-suffixed wire ids + // ({base-without-fast}-{effort}-fast; the bare id returns not_found), so every entry + // carries a tier picker. Live-verified: claude-opus-4-8-high-fast completed a turn. + // Tiers per the 260822 dump (devlog 260822_senpi_cursor_transfer/300). + { 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 }, + { id: "claude-opus-5-fast", contextWindow: CONTEXT_200K, supportsReasoningEffort: true }, { id: "claude-fable-5", contextWindow: CONTEXT_200K, supportsReasoningEffort: true }, { id: "composer-1", contextWindow: CONTEXT_200K }, diff --git a/src/adapters/cursor/effort-map.ts b/src/adapters/cursor/effort-map.ts index 346ee4ef2ce..979c34e7c5e 100644 --- a/src/adapters/cursor/effort-map.ts +++ b/src/adapters/cursor/effort-map.ts @@ -24,8 +24,14 @@ const CURSOR_MODEL_EFFORT_TIERS: Record = { // against Anthropic's effort ladder docs and Cursor's live model lineup. "claude-fable-5": ["low", "medium", "high", "xhigh", "max"], "claude-opus-4-7": ["low", "medium", "high", "xhigh", "max"], + // Opus Fast tiers from the 260822 GetUsableModels dump (devlog .../300): the wire + // exposes {base-without-fast}-{effort}-fast only; suffix derivation at the bottom of + // this file produces those ids. opus-5-fast has no xhigh/max (non-thinking) yet. + "claude-opus-4-7-fast": ["low", "medium", "high", "xhigh", "max"], "claude-opus-4-8": ["low", "medium", "high", "xhigh", "max"], + "claude-opus-4-8-fast": ["low", "medium", "high", "xhigh", "max"], "claude-opus-5": ["low", "medium", "high", "xhigh", "max"], + "claude-opus-5-fast": ["low", "medium", "high"], "claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"], "glm-5.2": ["high", "max"], // 260814 preemptive: glm-5.3 seeded ahead of Cursor's lineup update. Unlike 5.2, Z.AI folds diff --git a/tests/cursor-static-catalog.test.ts b/tests/cursor-static-catalog.test.ts index fa8be304f3d..1775989bf54 100644 --- a/tests/cursor-static-catalog.test.ts +++ b/tests/cursor-static-catalog.test.ts @@ -120,3 +120,34 @@ describe("Cursor static Codex catalog", () => { } }); }); + +describe("Opus Fast catalog families (devlog 300, live-verified 260822)", () => { + test("all three -fast families are present with tier pickers", async () => { + const { CURSOR_STATIC_MODELS } = await import("../src/adapters/cursor/discovery"); + for (const id of ["claude-opus-4-7-fast", "claude-opus-4-8-fast", "claude-opus-5-fast"]) { + const entry = CURSOR_STATIC_MODELS.find(model => model.id === id); + expect(entry, `${id} missing from static catalog`).toBeDefined(); + expect(entry?.supportsReasoningEffort, `${id} must carry a tier picker — the bare wire id is not_found`).toBe(true); + } + }); + + test("tier ladders match the 260822 GetUsableModels dump", async () => { + const { cursorModelEffortLadder } = await import("../src/adapters/cursor/effort-map"); + expect(cursorModelEffortLadder("claude-opus-4-7-fast")).toEqual(["low", "medium", "high", "xhigh", "max"]); + expect(cursorModelEffortLadder("claude-opus-4-8-fast")).toEqual(["low", "medium", "high", "xhigh", "max"]); + expect(cursorModelEffortLadder("claude-opus-5-fast")).toEqual(["low", "medium", "high"]); + }); + + test("wire-id derivation produces the live-verified suffixed forms and never a bare -fast id", async () => { + const { cursorWireModelIdWithEffort, cursorEffortSuffix } = await import("../src/adapters/cursor/effort-map"); + expect(cursorWireModelIdWithEffort("claude-opus-4-8-fast", "high")).toBe("claude-opus-4-8-high-fast"); + expect(cursorWireModelIdWithEffort("claude-opus-5-fast", "medium")).toBe("claude-opus-5-medium-fast"); + expect(cursorWireModelIdWithEffort("claude-opus-4-7-fast", "max")).toBe("claude-opus-4-7-max-fast"); + // No-effort requests must still resolve to a suffix (bare id is not_found on the wire). + for (const id of ["claude-opus-4-7-fast", "claude-opus-4-8-fast", "claude-opus-5-fast"]) { + expect(cursorEffortSuffix(id, undefined), `${id} must never send bare`).toBeTruthy(); + } + // Out-of-ladder effort clamps within the family ladder (opus-5-fast has no xhigh). + expect(cursorEffortSuffix("claude-opus-5-fast", "xhigh")).toBe("high"); + }); +});