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
30 changes: 30 additions & 0 deletions src/adapters/cursor/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,36 @@ export const CURSOR_CAPABILITIES: Record<string, CursorCapability> = {
thinking: { levels: FULL, order: T },
},
},
// 260902 preemptive: Claude Fable 5.1 seeded ahead of Cursor's lineup update, mirroring
// claude-fable-5 (same 1M window and full effort ladder). Cursor has spelled Claude ids
// both Anthropic-style (`claude-opus-4-7`, thinking-then-effort) and version-first
// (`claude-4.6-opus`, effort-then-thinking), so all three plausible spellings are seeded;
// the live GetUsableModels filter drops whichever the roster does not expose. Collapse to
// the one real spelling once it is observed.
"claude-fable-5-1": {
window: CONTEXT_1M,
defaultVariant: "thinking",
variants: {
regular: { levels: FULL },
thinking: { levels: FULL, order: T },
},
},
"claude-fable-5.1": {
window: CONTEXT_1M,
defaultVariant: "thinking",
variants: {
regular: { levels: FULL },
thinking: { levels: FULL, order: T },
},
},
"claude-5.1-fable": {
window: CONTEXT_1M,
defaultVariant: "thinking",
variants: {
regular: { levels: FULL },
thinking: { levels: FULL, order: E },
},
},
"claude-sonnet-5": {
window: CONTEXT_1M,
defaultVariant: "thinking",
Expand Down
7 changes: 7 additions & 0 deletions src/adapters/cursor/discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export function inferCursorContextWindow(modelId: string): number {
if (id.includes("1m")) return CONTEXT_1M;
if (id.startsWith("gemini-")) return CONTEXT_1M;
if (id === "glm-5.3" || id === "glm-5.2") return CONTEXT_1M;
// 260902: every Fable is a 1M model; catch live spellings the seed does not carry.
if (id.includes("fable")) return CONTEXT_1M;
if (id.startsWith("gpt-5.6-")) return CONTEXT_1M;
if (id.startsWith("gpt-5") || id === "gpt-5-codex") return CONTEXT_272K;
if (id.startsWith("grok-4.5") || id.startsWith("grok-4.6")) return 500_000;
Expand Down Expand Up @@ -291,6 +293,11 @@ export const CURSOR_STATIC_MODELS: readonly CursorModelInfo[] = normalizeCursorM
// quarantined regular wire id for the bare slug).
{ id: "claude-opus-5", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },
{ id: "claude-fable-5", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },
// 260902 preemptive: Fable 5.1 seeded ahead of Cursor's lineup update (mirrors fable-5) under
// the three spellings Cursor has used for Claude ids; see CURSOR_CAPABILITIES.
{ id: "claude-fable-5-1", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },
{ id: "claude-fable-5.1", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },
{ id: "claude-5.1-fable", contextWindow: CONTEXT_1M, supportsReasoningEffort: true },

{ id: "composer-1", contextWindow: CONTEXT_200K },
{ id: "composer-2.5", contextWindow: CONTEXT_200K },
Expand Down
11 changes: 11 additions & 0 deletions src/adapters/cursor/effort-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
// max is always the top tier (canonical order: low < medium < high < xhigh < max), confirmed
// against Anthropic's effort ladder docs and Cursor's live model lineup.
"claude-fable-5": ["low", "medium", "high", "xhigh", "max"],
// 260902 preemptive: Fable 5.1 seeded ahead of Cursor's lineup update (mirrors fable-5) under
// the three spellings Cursor has used for Claude ids.
"claude-fable-5-1": ["low", "medium", "high", "xhigh", "max"],
"claude-fable-5.1": ["low", "medium", "high", "xhigh", "max"],
"claude-5.1-fable": ["low", "medium", "high", "xhigh", "max"],
Comment on lines +28 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Derive Fable tiers from the canonical catalog

In src/adapters/cursor/effort-map.ts, these three ladders duplicate the same Fable 5.1 facts added to CURSOR_CAPABILITIES, while this legacy table still drives picker metadata, discovery matching, and legacy wire composition. When the speculative spelling or effort ladder is corrected after live observation, updating only the declared single source of truth will leave these paths stale and can advertise efforts that the resolver no longer sends. Derive the tiers and thinking grammar from CURSOR_CAPABILITIES instead of registering the models independently here.

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

Useful? React with 👍 / 👎.

"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
Expand All @@ -49,6 +54,9 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
"claude-opus-4-7-thinking-fast": ["low", "medium", "high", "xhigh", "max"],
"claude-sonnet-5-thinking": ["low", "medium", "high", "xhigh", "max"],
"claude-fable-5-thinking": ["low", "medium", "high", "xhigh", "max"],
"claude-fable-5-1-thinking": ["low", "medium", "high", "xhigh", "max"],
"claude-fable-5.1-thinking": ["low", "medium", "high", "xhigh", "max"],
"claude-5.1-fable-thinking": ["low", "medium", "high", "xhigh", "max"],
"claude-4.6-opus-thinking": ["high", "max"],
"claude-4.5-opus-thinking": ["high"],
"claude-4.6-sonnet-thinking": ["medium"],
Expand Down Expand Up @@ -113,6 +121,9 @@ const CURSOR_THINKING_FAMILIES: Readonly<Record<string, { source: string; order:
"claude-opus-4-7-thinking-fast": { source: "claude-opus-4-7-fast", order: "thinking-then-effort" },
"claude-sonnet-5-thinking": { source: "claude-sonnet-5", order: "thinking-then-effort" },
"claude-fable-5-thinking": { source: "claude-fable-5", order: "thinking-then-effort" },
"claude-fable-5-1-thinking": { source: "claude-fable-5-1", order: "thinking-then-effort" },
"claude-fable-5.1-thinking": { source: "claude-fable-5.1", order: "thinking-then-effort" },
"claude-5.1-fable-thinking": { source: "claude-5.1-fable", order: "effort-then-thinking" },
"claude-4.6-opus-thinking": { source: "claude-4.6-opus", order: "effort-then-thinking" },
"claude-4.5-opus-thinking": { source: "claude-4.5-opus", order: "effort-then-thinking" },
"claude-4.6-sonnet-thinking": { source: "claude-4.6-sonnet", order: "effort-then-thinking" },
Expand Down
7 changes: 7 additions & 0 deletions src/usage/expected-prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
// bundle collapses anthropic-apikey onto anthropic).
{ provider: "anthropic", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input`, verifiedAt: "2026-09-02", status: "verified" },
{ provider: "anthropic-apikey", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input`, verifiedAt: "2026-09-02", status: "verified" },
// Cursor seeds Fable 5.1 preemptively under three spellings (adapters/cursor/catalog.ts);
// the model-level vendor fallback only searches jawcode metadata, which has no Fable 5.1
// row yet, so each Cursor spelling needs its own overlay. Vendor list price, like the
// cursor/claude-opus-5 row.
{ provider: "cursor", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input; vendor list price applied to the Cursor surface`, verifiedAt: "2026-09-02", status: "verified-derived" },
{ provider: "cursor", modelId: "claude-fable-5.1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input; vendor list price applied to the Cursor surface`, verifiedAt: "2026-09-02", status: "verified-derived" },
{ provider: "cursor", modelId: "claude-5.1-fable", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input; vendor list price applied to the Cursor surface`, verifiedAt: "2026-09-02", status: "verified-derived" },
// claude-opus-5 is exposed by three providers but absent from the jawcode bundle, so
// cost resolution returned null and the Logs `~$` column rendered an em dash. The
// model-level vendor fallback only searches jawcode metadata, never overlays, so one
Expand Down
8 changes: 8 additions & 0 deletions tests/cursor-discovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ describe("Cursor discovery metadata", () => {
expect(ids).toContain("glm-5.2");
expect(ids).toContain("kimi-k2.7-code");
expect(ids).toContain("kimi-k3");
// 260902 preemptive seed: Fable 5.1 registered ahead of Cursor's lineup update, at 1M,
// under the three spellings Cursor has used for Claude ids.
for (const spelling of ["claude-fable-5-1", "claude-fable-5.1", "claude-5.1-fable"]) {
expect(ids).toContain(spelling);
expect(cursorModelContextWindows(CURSOR_STATIC_MODELS)[spelling]).toBe(1_000_000);
}
// Any live Fable spelling the seed does not carry still infers a 1M window.
expect(inferCursorContextWindow("claude-fable-6")).toBe(1_000_000);
// Umbrella merge (devlog 260828): fast duplicate rows folded into bases.
expect(ids).not.toContain("claude-opus-4-7-fast");
// 260709 refresh: stale ids dropped from the static seed (cursor.com docs); gpt-5.5-extra
Expand Down
4 changes: 4 additions & 0 deletions tests/cursor-effort-suffix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ describe("#2569 Cursor explicit-thinking variants", () => {
["claude-opus-4-8-thinking-fast", "xhigh", "claude-opus-4-8-thinking-xhigh-fast"],
["claude-sonnet-5-thinking", "medium", "claude-sonnet-5-thinking-medium"],
["claude-fable-5-thinking", "xhigh", "claude-fable-5-thinking-xhigh"],
["claude-fable-5-1-thinking", "xhigh", "claude-fable-5-1-thinking-xhigh"],
["claude-fable-5.1-thinking", "xhigh", "claude-fable-5.1-thinking-xhigh"],
// Version-first spelling follows the 4.x families: marker at the END.
["claude-5.1-fable-thinking", "max", "claude-5.1-fable-max-thinking"],
// The marker moves to the END for these families.
["claude-4.6-opus-thinking", "max", "claude-4.6-opus-max-thinking"],
["claude-4.5-opus-thinking", "high", "claude-4.5-opus-high-thinking"],
Expand Down
12 changes: 10 additions & 2 deletions tests/usage-cost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ describe("resolveMatchedPrice", () => {
expect(price?.sourceRef).toContain("0.025x");
}
expect(resolveMatchedPrice("anthropic-pb51d9b", "claude-fable-5-1")?.cost4).toEqual(COST4);
// Cursor seeds the id preemptively under three spellings and jawcode has no row, so
// each carries its own (derived) overlay rather than falling through to null.
for (const spelling of ["claude-fable-5-1", "claude-fable-5.1", "claude-5.1-fable"]) {
expect(resolveMatchedPrice("cursor", spelling), spelling).toMatchObject({ cost4: COST4, source: "expected", status: "verified-derived" });
}
// The cheaper cache-hit rate must not leak onto Fable 5, which stays at 0.1x.
expect(resolveMatchedPrice("anthropic", "claude-fable-5")?.cost4.cacheRead).toBe(1);
});
Expand Down Expand Up @@ -292,13 +297,16 @@ describe("resolveMatchedPrice", () => {
expect(resolveMatchedPrice("openrouter", "anthropic-claude-3.5-sonnet")).toBeNull();
});

test("16. shipped overlay membership: 58 keys, including Fable 5.1, Opus 5 and compatibility prices", () => {
expect(EXPECTED_PRICE_OVERLAYS.length).toBe(58);
test("16. shipped overlay membership: 61 keys, including Fable 5.1, Opus 5 and compatibility prices", () => {
expect(EXPECTED_PRICE_OVERLAYS.length).toBe(61);
expect(EXPECTED_PRICE_OVERLAYS.some(row => row.status === "unverified")).toBe(false);
const keys = new Set(EXPECTED_PRICE_OVERLAYS.map(row => `${row.provider}/${row.modelId}`));
for (const expected of [
"anthropic/claude-fable-5-1",
"anthropic-apikey/claude-fable-5-1",
"cursor/claude-fable-5-1",
"cursor/claude-fable-5.1",
"cursor/claude-5.1-fable",
"anthropic/claude-opus-5",
"cursor/claude-opus-5",
"kiro/claude-opus-5",
Expand Down
Loading