-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(cursor): derive the picker seed from the capability table and label every row #3222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3595d47
cba7d78
3776e2e
419e896
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Cursor unified model identity | ||
|
|
||
| One published row per Cursor base. Thinking, fast, and 1M are dimensions of that row, | ||
| never extra slugs. The Codex Fast toggle drives the fast dimension; a global switch | ||
| exposes `-fast` identities to clients that have no toggle. | ||
|
|
||
| ## Why | ||
|
|
||
| Cursor's own picker already works this way: `Claude Opus 5` is one row whose submenu | ||
| carries Thinking, Fast, Context (300K/1M), and Effort. OpenCodex has the same shape in | ||
| `CURSOR_CAPABILITIES` but never publishes it — `cursorUmbrellaRows()` is called by tests | ||
| only, and the picker is fed by the leftover product seed in `discovery.ts`. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - Every legacy id stays routable. Picker rows shrink; routability does not. | ||
| - Never run the repo-wide suite locally. Focused `bun test` files + `bun run typecheck` | ||
| + `bun run privacy:scan`; exact-head GitHub CI is the authoritative gate. | ||
| - Stacked PR chain against `dev`, parent first. Pushes use `git push --no-verify`. | ||
| - Out of scope: Codex app UI, Cursor transport/native-exec, other providers' fast wires, | ||
| dashboard `/api/models` namespaced ids, Desktop 3P hashed aliases. | ||
|
|
||
| ## Work-phase map (dependency-ordered) | ||
|
|
||
| | WP | Deliverable | Consumes | | ||
| |----|-------------|----------| | ||
| | wp1 | this roadmap (docs only) | — | | ||
| | wp2 / PR1 | seed derives from the capability table; display names; window alignment | wp1 | | ||
| | wp3 / PR2 | `cursor-variant` FastWire; Codex Fast toggle reaches the fast dimension | wp2 (needs a stable base row set) | | ||
| | wp4 / PR3 | `fastMode` lists `-fast` identities outside Codex; request-time promotion | wp3 (needs the resolver's fast upgrade) | | ||
|
|
||
| wp3 depends on wp2 because the Fast toggle is stamped per row: the row set must be the | ||
| capability-derived one before a per-base capability map can be attached to it. wp4 depends | ||
| on wp3 because listing `<id>-fast` is only honest once the request path actually honours it. | ||
|
|
||
| ## Measured current state (2026-09-02, `.tmp/cursor_diff_probe.ts`) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Correct or label the future-dated verification claims. The plan attributes completed measurements and observations to September 2, 2026, although the review date is September 1, 2026. Update these dates to the actual execution date or mark the sections as planned so verifier provenance remains chronologically auditable. Also applies to:
📍 Affects 2 files
🤖 Prompt for AI Agents📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add language tags to all reported fenced blocks.
📍 Affects 2 files
🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| ``` | ||
| SEED_COUNT 54 # CURSOR_STATIC_MODELS | ||
| CAPS_COUNT 34 # CURSOR_CAPABILITIES | ||
| UMBRELLA_ROWS 34 # cursorUmbrellaRows() — none missing from the seed | ||
| ROWS_NOT_IN_SEED [] # capability rows are all seeded | ||
| CAPS_NOT_IN_SEED [] | ||
| SEED_NOT_IN_CAPS (16) # claude-4-sonnet-1m, claude-4.5-haiku, composer-1, composer-2.5, | ||
| # composer-2.5-fast, gemini-2.5-flash, gemini-3-flash, gemini-3-pro, | ||
| # gemini-3-pro-image-preview, gemini-3.1-pro, gemini-3.5-flash, | ||
| # gpt-5-codex, gpt-5-fast, gpt-5-mini, gpt-5.1-codex, kimi-k2.7-code | ||
| WINDOW_MISMATCH # gemini-3.6-flash 1048576/1000000, gemini-3.7-flash 1048576/1000000, | ||
| # gpt-5.5-extra 200000/272000 | ||
| FAST_CAPABLE_BASES # claude-opus-4-7, claude-opus-4-8, claude-opus-5, grok-4.5, grok-4.6 | ||
| ``` | ||
|
|
||
| 54 = 4 routers + 34 capability bases + 16 non-capability product ids. | ||
|
|
||
| ## Verifiers (RUN 2026-09-02 before being written here, PLAN-VERIFIER-REAL-01) | ||
|
|
||
| | Command | Exit | Reads the change target? | | ||
| |---|---|---| | ||
| | `bun test tests/cursor-umbrella-rows.test.ts tests/cursor-catalog.test.ts tests/cursor-static-catalog.test.ts` | **1 — 74 pass / 1 fail** | yes — imports `catalog.ts` + `discovery.ts` directly | | ||
| | `bun test tests/fastwire-policy.test.ts tests/fastwire-observability.test.ts tests/service-tier-capability.test.ts` | 0 — 303 pass | yes — imports `fastwire.ts` / `service-tier.ts` | | ||
| | `bun test tests/claude-model-info.test.ts tests/claude-models-discovery.test.ts` | 0 — 27 pass | yes — imports `claude/model-info.ts` | | ||
| | `bun run typecheck` | pending measurement at wp2 B | yes — `tsc --noEmit` over `src/` and `tests/` | | ||
| | `bun run privacy:scan` | pending measurement at wp2 B | repo-wide credential scan; **does not observe this unit's behavior** | | ||
|
|
||
| `privacy:scan` is a required gate, not a verifier of identity behavior; that acceptance row | ||
| is human review plus the focused tests above. | ||
|
|
||
| **Pre-existing red on this branch point.** The cursor suite fails at HEAD `d975feaa4`, | ||
| before any change in this unit: | ||
|
|
||
| ``` | ||
| (fail) row count shrank from the 69-row legacy seed | ||
| tests/cursor-umbrella-rows.test.ts:40 Expected: 51 Received: 54 | ||
| ``` | ||
|
|
||
| Commit `5fc7d073e` seeded three `claude-fable-5-1` spellings and did not update the | ||
| assertion. wp2 owns the fix (010 §4 rewrites that assertion to the derived composition), | ||
| so wp2's C-phase evidence must show this file green rather than inheriting the failure. | ||
|
|
||
| Environment note: a fresh worktree needs `bun install` first — without it these files fail | ||
| with `Cannot find module 'zod/v4'` / `'@bufbuild/protobuf'`, which is not a code defect. | ||
|
|
||
| ## Terminal outcomes | ||
|
|
||
| DONE = wp1-wp4 closed through D with three stacked PRs at exact-head green CI. | ||
| BLOCKED = CI infrastructure or a live Cursor roster change with evidence. | ||
| NEEDS_HUMAN = a user-visible identity fork beyond the stated intent. | ||
| BUDGET_EXHAUSTED = 6h wall-clock or three failed repair rounds on one WP. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # Current state: how a Cursor row is built and where Fast dies | ||
|
|
||
| Research only. No diffs here. | ||
|
|
||
| ## 1. The picker path never reads the capability table | ||
|
|
||
| `cursorUmbrellaRows()` (`src/adapters/cursor/catalog.ts:554`) is imported by | ||
| `tests/cursor-umbrella-rows.test.ts` and nothing else in `src/`. The published rows come | ||
| from a different list: | ||
|
|
||
| ``` | ||
| CURSOR_STATIC_MODELS (discovery.ts:276) | ||
| -> registry.ts:1110 models: cursorModelIds(CURSOR_STATIC_MODELS) | ||
| -> derive.ts:230 seeded into config.providers.cursor.models | ||
| -> provider-fetch.ts:1394 cursor branch: live GetUsableModels intersection | ||
| -> sync.ts disabledModels removal, deriveEntry writes slug/display_name/... | ||
| ``` | ||
|
|
||
| So the capability table describes dimensions the picker never sees. Collapsing a variant in | ||
| `catalog.ts` changes routing, not listing. | ||
|
|
||
| ## 2. Four inconsistencies, measured | ||
|
|
||
| **Mixed row semantics.** 16 seed ids have no capability record. Some are genuine products | ||
| with no base (`composer-1`, `composer-2.5`, `gemini-3-pro`, `gpt-5-codex`), and three are | ||
| dimensions wearing a row costume: `claude-4-sonnet-1m` (a real wire id, guarded by | ||
| `REAL_1M_WIRE_IDS` at `catalog.ts:333`), `gpt-5-fast`, `composer-2.5-fast`. | ||
|
|
||
| **1M means two things.** `kimi-k3-1m` is synthetic — `CURSOR_ULTRA_1M_MODEL_IDS` | ||
| (`discovery.ts:174`) folds it into `kimi-k3` + Max Mode. `claude-4-sonnet-1m` is a real | ||
| upstream id and stays a second row. Both read as "1M" to a user. | ||
|
|
||
| **Fast means two things.** Opus/Grok fast ids were folded to aliases | ||
| (`tests/cursor-umbrella-rows.test.ts:20-31`); `gpt-5-fast` and `composer-2.5-fast` remain | ||
| rows because they have no capability base. | ||
|
|
||
| **Labels and windows.** `routedDisplayName()` (`sync.ts:272`) returns the slug unchanged for | ||
| every provider except command-code, so Cursor rows read `cursor/kimi-k3`. Three windows | ||
| disagree between seed and capability table (000_plan.md). | ||
|
|
||
| `ProviderRegistryEntry` has `modelContextWindows`, `modelInputModalities`, | ||
| `modelReasoningEfforts` — but **no `modelDisplayNames`** (`registry.ts:265-290`), and | ||
| `ProviderConfigSeed` (`registry.ts:327`) does not list it either. The consumer exists | ||
| (`configuredModelDisplayName`, `provider-fetch.ts:634`) and reads | ||
| `prov.modelDisplayNames`; only the registry->config path is missing. | ||
|
|
||
| ## 3. Where Codex Fast dies for Cursor | ||
|
|
||
| Codex Fast is OpenAI `service_tier`, not a boolean: | ||
|
|
||
| ``` | ||
| app catalog row service_tiers:[{id:"priority",name:"Fast"}] (effort.ts:160) | ||
| -> request service_tier:"priority" (parser.ts:826) | ||
| -> decideTier(policy, config.fastMode, callerTier) (fastwire.ts:392) | ||
| -> applyServiceTierGate deletes the field when kind==="drop" (responses/core.ts:2638) | ||
| ``` | ||
|
|
||
| The drop is structural. `FAST_WIRE_ADAPTERS` (`fastwire.ts:14-18`) maps | ||
| `"service-tier" -> {openai-chat, openai-responses}` and `"anthropic-speed" -> {}`. Cursor is | ||
| in neither, so `resolveFastPolicy` sets `eligibility: "wire-unavailable"`, | ||
| `serviceTierSupportFromPolicy` publishes `supportsServiceTier: false`, and | ||
| `applyCatalogModelMetadata` never stamps the tier. No config value fixes this: forcing | ||
| `supportsServiceTier: true` still fails the wire check, and declaring | ||
| `fastWire.kind: "service-tier"` fails the adapter-set check. | ||
|
|
||
| Meanwhile the fast wire genuinely exists, keyed off the picked id: | ||
|
|
||
| - Grok (`wirePrefix: "cursor-"`): base id + `{id:"effort"},{id:"fast",value:"true"}` | ||
| parameters, via `cursorGrokFastSelection` (`catalog.ts:538`, | ||
| `request-builder.ts:204-213`). | ||
| - Everyone else: flattened wire id `claude-opus-5-thinking-high-fast` via `composeWireId` | ||
| (`catalog.ts:446-466`). | ||
|
|
||
| `normalizeCursorModelId` (`request-builder.ts:189`) receives only `parsed.modelId` and | ||
| `parsed.options.reasoning`. `rg` finds no `serviceTier`/`tierDecision` read anywhere under | ||
| `src/adapters/cursor/`. | ||
|
|
||
| Telemetry is a separate hole: `adapters/registry.ts:156-176` attaches | ||
| `createAdapterTierMetadata(..., null, null)` for every non-OpenAI adapter, so even a | ||
| working Cursor fast request would report an absent wire field. | ||
|
|
||
| Five bases have a fast dimension: `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, | ||
| `grok-4.5`, `grok-4.6`. Stamping a tier on the other 29 would recreate the dead-toggle | ||
| defect `NO_FAST_TIER_NATIVE_SLUGS` (`parsing.ts:297`) exists to prevent. | ||
|
|
||
| ## 4. Listing surfaces outside Codex | ||
|
|
||
| `GET /v1/models` has three branches (`server/index.ts:1316-1560`): | ||
|
|
||
| | Trigger | Id shape | Composed at | | ||
| |---|---|---| | ||
| | `?client_version` | catalog slugs | `buildCatalogEntries` | | ||
| | `anthropic-version` / `?flavor=anthropic` | `claude-ocx-*` or Desktop hashes | `claude/model-info.ts:105` | | ||
| | default | `alias ?? provider/id` | `server/index.ts:1534` | | ||
|
|
||
| `buildAnthropicModelInfos` already publishes a second row for a dimension: `push1mVariant` | ||
| (`model-info.ts:115-128`) appends `<id>[1m]`, and `resolveInboundModel` strips it before | ||
| routing. That is the precedent `-fast` listing should follow. | ||
|
|
||
| `config.fastMode` (`types/config.ts:462`) is tri-state and today only reaches | ||
| `decideTier` plus Codex's injected `[features] fast_mode` (`codex/inject.ts:708`). It | ||
| touches no listing code: `rg fastMode` is empty in `server/index.ts`, | ||
| `claude/model-info.ts`, `management/model-rows.ts`, and `cli/models.ts`. | ||
|
|
||
| Dashboard `/api/models` uses `namespaced` as the disable/export key | ||
| (`catalogModelSlug`, `parsing.ts:703`), so rewriting it would desync `disabledModels`. | ||
| That surface stays untouched. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Define the scope of the one-row rule.
Lines 3-5 prohibit extra fast slugs, but the roadmap also exposes
<id>-fastidentities.devlog/_plan/260902_cursor_unified_identity/001_current_state.md, Lines 96-103, describes-fastas an additional listing row. State that the one-row rule applies to the Cursor picker or base seed, or revise the global listing requirement. Otherwise WP3 and WP4 can implement conflicting identity contracts.Proposed wording
🤖 Prompt for AI Agents