diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 96d150793d..fc9f37a2e0 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -10,6 +10,8 @@ ### Fixed +- Fixed Cursor Claude Fable 5.1 variants remaining as ten separate models with a 200k fallback window: they now group into plain and thinking identities with the full effort ladder and a 1M catalog window, while server-observed context limits still take precedence ([#1613](https://github.com/code-yeongyu/senpi/pull/1613) by [@thisisjun786](https://github.com/thisisjun786)). + ### Removed ## [2026.9.12-2] - 2026-09-12 diff --git a/packages/ai/src/changes.md b/packages/ai/src/changes.md index 25846f2ad5..6c912aa56c 100644 --- a/packages/ai/src/changes.md +++ b/packages/ai/src/changes.md @@ -1,3 +1,23 @@ +## Cursor claude-fable-5-1 capability entry and variant aliases (2026-09-11) + +### What changed + +- `packages/ai/src/cursor/model-capabilities.ts`: `CURSOR_MODEL_CAPABILITIES` gains `claude-fable-5-1` with the same contract as `claude-fable-5` (1M window, 1M maxWindow, `300k` default context, `1m` request context, `low..max` effort ladder). +- `packages/ai/src/cursor/cursor-variant-aliases.json`: ten new aliases (`claude-fable-5-1-{low,medium,high,xhigh,max}` and `claude-fable-5-1-thinking-{low,medium,high,xhigh,max}`) resolving to the `claude-fable-5-1` / `claude-fable-5-1-thinking` identities; `source` records the 2026-09-11 capture. +- `packages/ai/test/fixtures/cursor-usable-models-claude-fable-5-1-20260911.json`: the ten raw `GetUsableModels` entries as Cursor served them on 2026-09-11, every display name carrying the "1M" label. + +### Why + +- Cursor started serving `claude-fable-5-1-*` after the 2026-08-18 capture the table and alias file were built from. Without a capability entry, `normalizeCursorCatalog` kept the ten variants ungrouped and stamped each with the 200000 unknown-family fallback, discarding the 1M window discovery had already recovered from the display name, so compaction fired at 200k on a 1M model; without an alias, `regroupStoredCursorModels` could never fold an existing store into the grouped shape, and the ten variants stayed ten separate 200k entries in the picker instead of one plain and one thinking identity with an effort ladder. + +### Why an extension could not handle it + +- The capability table and alias map are the core provider data both Cursor transports and the store migration read; there is no extension hook between discovery and the persisted catalog. + +### Expected merge conflict zones + +- `model-capabilities.ts` family table, `cursor-variant-aliases.json` Claude block, the expected-window map in `test/cursor-model-capabilities.test.ts`. + ## Cursor context ceilings come from the server, not the capability table (2026-09-12) ### What changed diff --git a/packages/ai/src/cursor/cursor-variant-aliases.json b/packages/ai/src/cursor/cursor-variant-aliases.json index 5012f4dc47..296e2ba213 100644 --- a/packages/ai/src/cursor/cursor-variant-aliases.json +++ b/packages/ai/src/cursor/cursor-variant-aliases.json @@ -1,6 +1,6 @@ { "$schema": "cursor-variant-aliases/v1", - "source": "union of GetUsableModels live capture (204 ids) and cursor-agent CLI listing capture (203 ids), 2026-08-18", + "source": "union of GetUsableModels live capture (204 ids) and cursor-agent CLI listing capture (203 ids), 2026-08-18; claude-fable-5-1 variants from GetUsableModels live capture (223 ids), 2026-09-11", "aliases": { "claude-4-sonnet": { "targetId": "claude-4-sonnet", @@ -130,6 +130,66 @@ "legacyVariantId": "claude-fable-5-thinking-xhigh", "encoding": "legacy-variant" }, + "claude-fable-5-1-high": { + "targetId": "claude-fable-5-1", + "level": "high", + "legacyVariantId": "claude-fable-5-1-high", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-low": { + "targetId": "claude-fable-5-1", + "level": "low", + "legacyVariantId": "claude-fable-5-1-low", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-max": { + "targetId": "claude-fable-5-1", + "level": "max", + "legacyVariantId": "claude-fable-5-1-max", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-medium": { + "targetId": "claude-fable-5-1", + "level": "medium", + "legacyVariantId": "claude-fable-5-1-medium", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-xhigh": { + "targetId": "claude-fable-5-1", + "level": "xhigh", + "legacyVariantId": "claude-fable-5-1-xhigh", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-thinking-high": { + "targetId": "claude-fable-5-1-thinking", + "level": "high", + "legacyVariantId": "claude-fable-5-1-thinking-high", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-thinking-low": { + "targetId": "claude-fable-5-1-thinking", + "level": "low", + "legacyVariantId": "claude-fable-5-1-thinking-low", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-thinking-max": { + "targetId": "claude-fable-5-1-thinking", + "level": "max", + "legacyVariantId": "claude-fable-5-1-thinking-max", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-thinking-medium": { + "targetId": "claude-fable-5-1-thinking", + "level": "medium", + "legacyVariantId": "claude-fable-5-1-thinking-medium", + "encoding": "legacy-variant" + }, + "claude-fable-5-1-thinking-xhigh": { + "targetId": "claude-fable-5-1-thinking", + "level": "xhigh", + "legacyVariantId": "claude-fable-5-1-thinking-xhigh", + "encoding": "legacy-variant" + }, "claude-opus-4-7-high": { "targetId": "claude-opus-4-7", "level": "high", diff --git a/packages/ai/src/cursor/model-capabilities.ts b/packages/ai/src/cursor/model-capabilities.ts index e3631f74cb..057e3922c4 100644 --- a/packages/ai/src/cursor/model-capabilities.ts +++ b/packages/ai/src/cursor/model-capabilities.ts @@ -77,9 +77,15 @@ function gpt( * local-ignore/qa-evidence/20260818-cursor-reasoning-levels/available-models-catalog.json). * `GetUsableModels` carries no window or parameter field, so this table is the * authoritative source; see .omo/plans/cursor-reasoning-levels.md §3.1/§6. + * + * `claude-fable-5-1` comes from the 2026-09-11 `GetUsableModels` capture + * (test/fixtures/cursor-usable-models-claude-fable-5-1-20260911.json): the same + * ten `-{level}` / `-thinking-{level}` variant ids as `claude-fable-5`, every + * display name carrying the "1M" label, so it takes the same 1M contract. */ export const CURSOR_MODEL_CAPABILITIES: Record = { "claude-fable-5": claude(1000000, 1000000, ["low", "medium", "high", "xhigh", "max"], "300k"), + "claude-fable-5-1": claude(1000000, 1000000, ["low", "medium", "high", "xhigh", "max"], "300k"), "claude-sonnet-5": claude(1000000, 1000000, ["low", "medium", "high", "xhigh", "max"], "300k"), "claude-opus-4-7": claude(1000000, 1000000, ["low", "medium", "high", "xhigh", "max"], "300k"), "claude-opus-4-8": claude(1000000, 1000000, ["low", "medium", "high", "xhigh", "max"], "300k"), diff --git a/packages/ai/test/cursor-model-capabilities.test.ts b/packages/ai/test/cursor-model-capabilities.test.ts index 344fb67fba..36d669ff1f 100644 --- a/packages/ai/test/cursor-model-capabilities.test.ts +++ b/packages/ai/test/cursor-model-capabilities.test.ts @@ -6,6 +6,7 @@ import { parseCursorVariantId, } from "../src/cursor/model-capabilities.ts"; import fixture from "./fixtures/cursor-usable-models-20260818.json" with { type: "json" }; +import fable51Fixture from "./fixtures/cursor-usable-models-claude-fable-5-1-20260911.json" with { type: "json" }; /** * C1 — capability table and context windows. @@ -35,6 +36,7 @@ describe("cursor model capabilities", () => { "claude-4.6-sonnet": 1000000, "claude-4.6-opus": 1000000, "claude-fable-5": 1000000, + "claude-fable-5-1": 1000000, "claude-sonnet-5": 1000000, "claude-opus-4-7": 1000000, "claude-opus-4-8": 1000000, @@ -65,6 +67,8 @@ describe("cursor model capabilities", () => { it("resolves a variant id to its base capability with the same window", () => { expect(getCursorBaseIdForVariant("kimi-k3-max")).toBe("kimi-k3"); expect(getCursorBaseIdForVariant("claude-fable-5-thinking-xhigh")).toBe("claude-fable-5"); + expect(getCursorBaseIdForVariant("claude-fable-5-1-thinking-xhigh")).toBe("claude-fable-5-1"); + expect(getCursorBaseIdForVariant("claude-fable-5-1-max")).toBe("claude-fable-5-1"); expect(getCursorBaseIdForVariant("claude-opus-4-7-thinking-high-fast")).toBe("claude-opus-4-7"); expect(getCursorCapabilityForBase(getCursorBaseIdForVariant("kimi-k3-max")!)?.window).toBe(1048576); }); @@ -82,6 +86,19 @@ describe("cursor model capabilities", () => { expect([...uncovered].sort()).toEqual(["default"]); }); + it("gives claude-fable-5-1 the same 1M contract as claude-fable-5", () => { + const fable = getCursorCapabilityForBase("claude-fable-5"); + const fable51 = getCursorCapabilityForBase("claude-fable-5-1"); + expect(fable51).toEqual(fable); + expect(fable51?.window).toBe(1000000); + expect(fable51?.requestContext).toBe("1m"); + for (const entry of fable51Fixture) { + const parsed = parseCursorVariantId(entry.id); + expect(parsed.baseId, entry.id).toBe("claude-fable-5-1"); + expect(getCursorBaseIdForVariant(entry.id), entry.id).toBe("claude-fable-5-1"); + } + }); + it("marks evidence provenance for every entry", () => { for (const [id, cap] of Object.entries(CURSOR_MODEL_CAPABILITIES)) { expect(["available-models", "cli-live", "suffix-only"], `${id} evidence`).toContain(cap.evidence); diff --git a/packages/ai/test/cursor-model-grouping.test.ts b/packages/ai/test/cursor-model-grouping.test.ts index 287e053776..0f061783ab 100644 --- a/packages/ai/test/cursor-model-grouping.test.ts +++ b/packages/ai/test/cursor-model-grouping.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { normalizeCursorCatalog, parseCursorVariantId } from "../src/cursor/catalog-grouping.ts"; import { getCursorVariantAlias } from "../src/cursor/model-capabilities.ts"; import fixture from "./fixtures/cursor-usable-models-20260818.json" with { type: "json" }; +import fable51Fixture from "./fixtures/cursor-usable-models-claude-fable-5-1-20260911.json" with { type: "json" }; const ALL_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; @@ -114,6 +115,43 @@ describe("normalizeCursorCatalog (golden 204-id live fixture)", () => { }); }); +describe("normalizeCursorCatalog (claude-fable-5-1 live capture, 2026-09-11)", () => { + function normalizedFable51() { + return normalizeCursorCatalog( + fable51Fixture.map((entry) => ({ + id: entry.id, + name: entry.name, + input: entry.input as ("text" | "image")[], + cursorMaxMode: entry.cursorMaxMode, + })), + ); + } + + it("collapses the ten raw variants into a plain and a thinking identity", () => { + const out = normalizedFable51(); + expect(out.map((entry) => entry.id).sort()).toEqual(["claude-fable-5-1", "claude-fable-5-1-thinking"]); + const plain = out.find((entry) => entry.id === "claude-fable-5-1"); + const thinking = out.find((entry) => entry.id === "claude-fable-5-1-thinking"); + expect(plain?.thinkingMode).toBe(false); + expect(thinking?.thinkingMode).toBe(true); + for (const identity of [plain, thinking]) { + expect(identity?.reasoning).toBe(true); + expect(identity?.capabilityId).toBe("claude-fable-5-1"); + for (const level of ["low", "medium", "high", "xhigh", "max"] as const) { + expect(identity?.thinkingLevelMap?.[level], `${identity?.id} ${level}`).toBe(level); + } + } + }); + + it("advertises the 1M window instead of the 200k unknown-family fallback", () => { + for (const entry of normalizedFable51()) { + expect(entry.window, entry.id).toBe(1000000); + expect(entry.maxWindow, entry.id).toBe(1000000); + expect(entry.name, entry.id).toContain("1M"); + } + }); +}); + describe("parseCursorVariantId", () => { it("parses every fixture id and round-trips via originalId", () => { for (const entry of fixture) { diff --git a/packages/ai/test/fixtures/cursor-usable-models-claude-fable-5-1-20260911.json b/packages/ai/test/fixtures/cursor-usable-models-claude-fable-5-1-20260911.json new file mode 100644 index 0000000000..3ad7a815b9 --- /dev/null +++ b/packages/ai/test/fixtures/cursor-usable-models-claude-fable-5-1-20260911.json @@ -0,0 +1,112 @@ +[ + { + "id": "claude-fable-5-1-high", + "name": "Claude Fable 5.1 1M (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-low", + "name": "Claude Fable 5.1 1M Low (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-max", + "name": "Claude Fable 5.1 1M Max (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-medium", + "name": "Claude Fable 5.1 1M Medium (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-thinking-high", + "name": "Claude Fable 5.1 1M Thinking (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-thinking-low", + "name": "Claude Fable 5.1 1M Low Thinking (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-thinking-max", + "name": "Claude Fable 5.1 1M Max Thinking (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-thinking-medium", + "name": "Claude Fable 5.1 1M Medium Thinking (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-thinking-xhigh", + "name": "Claude Fable 5.1 1M Extra High Thinking (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + }, + { + "id": "claude-fable-5-1-xhigh", + "name": "Claude Fable 5.1 1M Extra High (NO ZDR)", + "reasoning": false, + "contextWindow": 1000000, + "cursorMaxMode": false, + "input": [ + "text", + "image" + ] + } +] diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a0282bb6d1..fec80778c0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -16,6 +16,7 @@ ### Fixed - Fixed native prompt-preset matching for Devin SWE-2 effort variants so `swe-2-high`, `swe-2-max`, `swe-2-low`, and `swe-2-high-lite` resolve to the Kimi K3 preset; the rejected bare `swe-2` id remains unmatched. +- Fixed Cursor Claude Fable 5.1 variants remaining as ten separate models with a 200k fallback window: they now group into plain and thinking identities with the full effort ladder and a 1M catalog window, while server-observed context limits still take precedence ([#1613](https://github.com/code-yeongyu/senpi/pull/1613) by [@thisisjun786](https://github.com/thisisjun786)). ### Removed