@iislee/opencodex 2.31.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +99 -70
  3. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  4. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -1
  7. package/src/adapters/anthropic.ts +104 -15
  8. package/src/adapters/base.ts +29 -1
  9. package/src/adapters/command-code.ts +42 -6
  10. package/src/adapters/cursor/call-id.ts +44 -0
  11. package/src/adapters/cursor/catalog.ts +541 -0
  12. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  13. package/src/adapters/cursor/cursor-errors.ts +15 -0
  14. package/src/adapters/cursor/discovery.ts +69 -18
  15. package/src/adapters/cursor/effort-map.ts +79 -1
  16. package/src/adapters/cursor/envelope-echo.ts +290 -0
  17. package/src/adapters/cursor/live-models.ts +7 -2
  18. package/src/adapters/cursor/live-transport.ts +14 -0
  19. package/src/adapters/cursor/message-mapper.ts +4 -1
  20. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  21. package/src/adapters/cursor/native-exec-fs.ts +2 -2
  22. package/src/adapters/cursor/native-exec-network.ts +1 -1
  23. package/src/adapters/cursor/native-exec-shell.ts +5 -2
  24. package/src/adapters/cursor/native-exec.ts +20 -0
  25. package/src/adapters/cursor/protobuf-request.ts +68 -14
  26. package/src/adapters/cursor/request-builder.ts +40 -10
  27. package/src/adapters/cursor/tool-definitions.ts +14 -4
  28. package/src/adapters/cursor/tool-result-normalize.ts +28 -5
  29. package/src/adapters/cursor/types.ts +20 -0
  30. package/src/adapters/cursor.ts +209 -38
  31. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  32. package/src/adapters/google-antigravity-replay.ts +173 -25
  33. package/src/adapters/google-antigravity-wire.ts +5 -0
  34. package/src/adapters/google-errors.ts +44 -12
  35. package/src/adapters/google.ts +136 -24
  36. package/src/adapters/image.ts +1 -1
  37. package/src/adapters/kiro-constants.ts +27 -0
  38. package/src/adapters/kiro-tools.ts +55 -16
  39. package/src/adapters/kiro.ts +182 -23
  40. package/src/adapters/openai-chat.ts +300 -242
  41. package/src/adapters/openai-responses.ts +353 -27
  42. package/src/adapters/run-turn-queue.ts +36 -1
  43. package/src/adapters/tool-catalog-nudge.ts +4 -3
  44. package/src/adapters/xai-tool-schema.ts +436 -0
  45. package/src/adapters/xai-web-search.ts +17 -16
  46. package/src/bridge.ts +90 -37
  47. package/src/chat/inbound.ts +14 -1
  48. package/src/chat/outbound.ts +15 -7
  49. package/src/claude/agents-inject.ts +8 -1
  50. package/src/claude/context-windows.ts +16 -9
  51. package/src/claude/outbound.ts +24 -11
  52. package/src/cli/access.ts +46 -3
  53. package/src/cli/account-api.ts +116 -19
  54. package/src/cli/account-extended.ts +262 -29
  55. package/src/cli/account-main.ts +12 -12
  56. package/src/cli/account.ts +69 -15
  57. package/src/cli/agent.ts +8 -1
  58. package/src/cli/alias.ts +66 -0
  59. package/src/cli/capabilities-command.ts +94 -0
  60. package/src/cli/capabilities.ts +496 -0
  61. package/src/cli/claude-desktop.ts +31 -11
  62. package/src/cli/claude.ts +26 -1
  63. package/src/cli/dispatch.ts +256 -28
  64. package/src/cli/doctor.ts +126 -14
  65. package/src/cli/help.ts +17 -6
  66. package/src/cli/index.ts +39 -9
  67. package/src/cli/init.ts +1 -0
  68. package/src/cli/inspect.ts +230 -0
  69. package/src/cli/models-runtime.ts +95 -0
  70. package/src/cli/models.ts +26 -10
  71. package/src/cli/observe.ts +31 -8
  72. package/src/cli/provider-runtime.ts +16 -2
  73. package/src/cli/provider.ts +2 -1
  74. package/src/cli/registry.ts +49 -10
  75. package/src/cli/runtime-api.ts +51 -7
  76. package/src/cli/status.ts +41 -1
  77. package/src/cli/storage.ts +234 -0
  78. package/src/cli/system-command.ts +16 -0
  79. package/src/cli/system-restart-client.ts +1 -1
  80. package/src/cli/usage-report.ts +184 -0
  81. package/src/cli/v2.ts +34 -10
  82. package/src/cli/version-skew.ts +46 -0
  83. package/src/clients/config-export.ts +85 -3
  84. package/src/clients/effective-status.ts +30 -2
  85. package/src/clients/probes/cline.ts +81 -0
  86. package/src/codex/account-label.ts +21 -0
  87. package/src/codex/account-pause.ts +2 -1
  88. package/src/codex/account-priority.ts +3 -2
  89. package/src/codex/app-server-processes.ts +83 -7
  90. package/src/codex/auth-api.ts +50 -10
  91. package/src/codex/auth-context.ts +21 -18
  92. package/src/codex/autostart-health.ts +16 -0
  93. package/src/codex/catalog/aggregation.ts +18 -0
  94. package/src/codex/catalog/effort.ts +18 -3
  95. package/src/codex/catalog/metadata.ts +27 -1
  96. package/src/codex/catalog/native-models.ts +5 -2
  97. package/src/codex/catalog/parsing.ts +72 -27
  98. package/src/codex/catalog/provider-fetch.ts +187 -31
  99. package/src/codex/catalog/sync.ts +131 -5
  100. package/src/codex/catalog.ts +1 -1
  101. package/src/codex/codex-write-lock.ts +3 -1
  102. package/src/codex/convergence-types.ts +1 -1
  103. package/src/codex/convergence.ts +27 -2
  104. package/src/codex/desired-state.ts +2 -2
  105. package/src/codex/desktop-app-restart.ts +355 -0
  106. package/src/codex/history-job.ts +32 -3
  107. package/src/codex/history-manifest.ts +112 -0
  108. package/src/codex/history-migration-guardian.ts +5 -5
  109. package/src/codex/history-provider.ts +825 -247
  110. package/src/codex/history-worker.ts +8 -5
  111. package/src/codex/inject-coordination.ts +83 -0
  112. package/src/codex/inject.ts +63 -22
  113. package/src/codex/injected-marker.ts +1 -1
  114. package/src/codex/internal/history-writer.ts +4 -3
  115. package/src/codex/log-guard/inspect.ts +22 -4
  116. package/src/codex/model-entitlements.ts +9 -2
  117. package/src/codex/native-profile-startup.ts +157 -27
  118. package/src/codex/native-residue.ts +26 -33
  119. package/src/codex/prompt-layers.ts +371 -25
  120. package/src/codex/prompt-text-probe.ts +238 -0
  121. package/src/codex/quota.ts +91 -10
  122. package/src/codex/routing.ts +9 -0
  123. package/src/codex/shim.ts +56 -3
  124. package/src/codex/subagent-model-fallback.ts +104 -25
  125. package/src/codex/transition-state.ts +116 -8
  126. package/src/combos/failover.ts +27 -0
  127. package/src/combos/types.ts +10 -0
  128. package/src/compatibility/index.ts +26 -0
  129. package/src/compatibility/manifest.ts +253 -0
  130. package/src/compatibility/openai-responses.ts +121 -0
  131. package/src/config/atomic-write.ts +219 -0
  132. package/src/config/paths.ts +40 -0
  133. package/src/config/process-state.ts +309 -0
  134. package/src/config/provider-validation.ts +177 -0
  135. package/src/config/rebase-provenance.ts +68 -0
  136. package/src/config.ts +206 -824
  137. package/src/generated/compatibility-version.json +373 -213
  138. package/src/images/loop.ts +5 -4
  139. package/src/images/plan.ts +5 -4
  140. package/src/integrations/ownership-policy.ts +160 -0
  141. package/src/integrations/ownership.ts +46 -2
  142. package/src/integrations/registry.ts +7 -0
  143. package/src/integrations/state.ts +79 -7
  144. package/src/integrations/writer.ts +26 -2
  145. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  146. package/src/lab/fabric/producer-child.ts +1 -1
  147. package/src/lib/admin-secrets.ts +24 -0
  148. package/src/lib/bounded-body.ts +14 -2
  149. package/src/lib/config-ownership.ts +20 -0
  150. package/src/lib/errors.ts +36 -3
  151. package/src/lib/package-tree-integrity.ts +101 -0
  152. package/src/lib/process-control.ts +2 -1
  153. package/src/lib/service-secrets.ts +15 -0
  154. package/src/lib/tool-argument-integers.ts +56 -5
  155. package/src/oauth/callback-server.ts +22 -2
  156. package/src/oauth/cline.ts +203 -0
  157. package/src/oauth/generic-account-failover.ts +231 -0
  158. package/src/oauth/health.ts +1 -1
  159. package/src/oauth/index.ts +65 -6
  160. package/src/oauth/key-providers.ts +10 -1
  161. package/src/oauth/kimi.ts +9 -1
  162. package/src/oauth/kiro.ts +45 -0
  163. package/src/oauth/local-token-detect.ts +2 -0
  164. package/src/oauth/open-browser-choice.ts +26 -0
  165. package/src/oauth/store.ts +14 -5
  166. package/src/oauth/types.ts +15 -0
  167. package/src/providers/auto-compact-budget.ts +65 -0
  168. package/src/providers/command-code-efforts.ts +38 -6
  169. package/src/providers/context-cap.ts +4 -3
  170. package/src/providers/default-aliases.ts +65 -0
  171. package/src/providers/derive.ts +28 -0
  172. package/src/providers/fastwire.ts +7 -1
  173. package/src/providers/label.ts +34 -1
  174. package/src/providers/model-presets.ts +119 -0
  175. package/src/providers/new-model-policy.ts +146 -0
  176. package/src/providers/provider-id-rewrite.ts +2 -1
  177. package/src/providers/quota.ts +65 -7
  178. package/src/providers/registry.ts +184 -69
  179. package/src/providers/slug-codec.ts +52 -0
  180. package/src/providers/xai-transport.ts +21 -0
  181. package/src/reasoning-effort.ts +19 -2
  182. package/src/responses/apply-patch-envelope.ts +63 -0
  183. package/src/responses/code-mode-helper-compat.ts +50 -0
  184. package/src/responses/custom-tool-compat.ts +157 -39
  185. package/src/responses/namespace-tool-compat.ts +84 -4
  186. package/src/responses/parser.ts +7 -2
  187. package/src/responses/reasoning-replay-cache.ts +81 -3
  188. package/src/responses/schema.ts +5 -1
  189. package/src/responses/state.ts +94 -4
  190. package/src/responses/thought-signature-replay.ts +17 -0
  191. package/src/responses/turn-termination.ts +107 -0
  192. package/src/router.ts +40 -2
  193. package/src/routing/analytics.ts +1 -0
  194. package/src/routing/quota.ts +10 -0
  195. package/src/server/auth-cors.ts +27 -7
  196. package/src/server/chat-completions.ts +24 -16
  197. package/src/server/chat-native-sse.ts +3 -3
  198. package/src/server/chat-native.ts +30 -11
  199. package/src/server/claude-messages.ts +1 -1
  200. package/src/server/index.ts +179 -32
  201. package/src/server/lifecycle.ts +52 -1
  202. package/src/server/local-management-read-client.ts +1 -1
  203. package/src/server/local-provider-reload-client.ts +1 -1
  204. package/src/server/management/agent-settings-routes.ts +45 -23
  205. package/src/server/management/codex-prompt-routes.ts +570 -0
  206. package/src/server/management/combo-routes.ts +2 -1
  207. package/src/server/management/config-routes.ts +59 -12
  208. package/src/server/management/context.ts +10 -1
  209. package/src/server/management/logs-usage-routes.ts +30 -24
  210. package/src/server/management/model-routes.ts +274 -4
  211. package/src/server/management/model-rows.ts +4 -0
  212. package/src/server/management/native-integration-routes.ts +2 -1
  213. package/src/server/management/oauth-account-routes.ts +10 -4
  214. package/src/server/management/provider-capability-config.ts +1 -1
  215. package/src/server/management/provider-routes.ts +81 -11
  216. package/src/server/management/route-registry.ts +332 -0
  217. package/src/server/management/routing-profile-routes.ts +2 -2
  218. package/src/server/management/system-restart.ts +1 -1
  219. package/src/server/management-api.ts +18 -16
  220. package/src/server/port-reclaim.ts +1 -1
  221. package/src/server/proxy-liveness.ts +29 -5
  222. package/src/server/relay-eager.ts +147 -20
  223. package/src/server/relay.ts +251 -19
  224. package/src/server/request-log-conversation.ts +51 -0
  225. package/src/server/request-log.ts +70 -16
  226. package/src/server/responses/codex-auth-error.ts +55 -0
  227. package/src/server/responses/collaboration.ts +37 -3
  228. package/src/server/responses/combo-stream-preflight.ts +178 -0
  229. package/src/server/responses/compact.ts +6 -21
  230. package/src/server/responses/core.ts +810 -208
  231. package/src/server/responses/empty-completion-guard.ts +63 -6
  232. package/src/server/responses/fetch-helpers.ts +17 -102
  233. package/src/server/responses/input-admission.ts +3 -1
  234. package/src/server/responses/passthrough-error.ts +33 -9
  235. package/src/server/responses/responses-field-backfill.ts +105 -13
  236. package/src/server/responses/ws-upstream.ts +75 -2
  237. package/src/server/responses-custom-tool-repair.ts +87 -6
  238. package/src/server/responses-terminal-repair.ts +25 -4
  239. package/src/server/responses-undeclared-tool-guard.ts +241 -18
  240. package/src/server/sse-frame-buffer.ts +31 -4
  241. package/src/server/ws-bridge.ts +14 -2
  242. package/src/service.ts +42 -4
  243. package/src/storage/policy-job.ts +14 -4
  244. package/src/storage/policy.ts +88 -23
  245. package/src/types/config.ts +66 -0
  246. package/src/types/provider.ts +69 -0
  247. package/src/types/request.ts +17 -4
  248. package/src/types/tools.ts +116 -11
  249. package/src/types.ts +3 -1
  250. package/src/update/index.ts +5 -4
  251. package/src/update/job.ts +3 -1
  252. package/src/update/transactional-install.mjs +8 -1
  253. package/src/usage/expected-prices.ts +43 -24
  254. package/src/usage/log.ts +63 -12
  255. package/src/usage/summary.ts +211 -8
  256. package/src/vision/describe.ts +18 -13
  257. package/src/web-search/exa-executor.ts +40 -9
  258. package/src/web-search/executor.ts +10 -3
  259. package/src/web-search/loop.ts +5 -4
  260. package/gui/dist/assets/index-BF38heuV.js +0 -104
  261. package/gui/dist/assets/index-DMiI18Kv.css +0 -1
@@ -0,0 +1,541 @@
1
+ /**
2
+ * Cursor umbrella catalog — the single source of truth for cursor model
3
+ * identities (devlog 260828_cursor_umbrella_catalog).
4
+ *
5
+ * Design (003_design.md, audited): one capability record per BASE model.
6
+ * Thinking / fast / thinking-fast are DIMENSIONS of the base, each with its
7
+ * own effort ladder and wire order, because the live wire really does differ
8
+ * per variant (claude-opus-5-fast stops at high while its thinking-fast runs
9
+ * to max). The umbrella picker row defaults to the thinking variant when one
10
+ * exists; every legacy variant id keeps resolving through the alias grammar.
11
+ *
12
+ * Max Mode is evidence-gated and separate from context-window size: prior
13
+ * live probes (devlog 260822_senpi_cursor_transfer/210+310) found maxMode
14
+ * only on specific variants, so `maxModeVerified` marks bases with proven
15
+ * support (kimi-k3, user-verified) and live `maxModeModels` extends it.
16
+ */
17
+
18
+ export type CursorVariantKind = "regular" | "thinking" | "fast" | "thinkingFast";
19
+
20
+ export type CursorThinkingOrder = "thinking-then-effort" | "effort-then-thinking" | "bare";
21
+
22
+ export interface CursorVariantSpec {
23
+ /** Ascending canonical effort rungs the wire lists for this variant; empty = bare id. */
24
+ readonly levels: readonly string[];
25
+ /** Where the thinking marker sits relative to the effort rung (thinking variants only). */
26
+ readonly order?: CursorThinkingOrder;
27
+ /** Variant-specific quarantine (base-wide quarantine would erase healthy siblings). */
28
+ readonly quarantined?: boolean;
29
+ }
30
+
31
+ export interface CursorCapability {
32
+ readonly variants: Partial<Record<CursorVariantKind, CursorVariantSpec>>;
33
+ /** Which variant the umbrella picker row selects (thinking merges into the base). */
34
+ readonly defaultVariant: CursorVariantKind;
35
+ /** Context-window metadata (display/routing only — never implies maxMode). */
36
+ readonly window: number;
37
+ /** Max Mode proven on the wire for this base (static evidence; live maxModeModels unions in). */
38
+ readonly maxModeVerified?: boolean;
39
+ /** Wire prefix required by AgentService/Run for the regular variant (grok families). */
40
+ readonly wirePrefix?: "cursor-";
41
+ }
42
+
43
+ const K = 1_000;
44
+ const CONTEXT_200K = 200 * K;
45
+ const CONTEXT_256K = 256 * K;
46
+ const CONTEXT_272K = 272 * K;
47
+ const CONTEXT_500K = 500 * K;
48
+ const CONTEXT_1M = 1_000 * K;
49
+
50
+ const FULL = ["low", "medium", "high", "xhigh", "max"] as const;
51
+ const T = "thinking-then-effort" as const;
52
+ const E = "effort-then-thinking" as const;
53
+
54
+ /**
55
+ * One entry per base model. Ladders mirror the live GetUsableModels roster the
56
+ * retired effort-map recorded (260813-260825 captures); windows follow the
57
+ * per-family table verified against senpi's AvailableModels capture
58
+ * (001_reference_analysis.md).
59
+ */
60
+ export const CURSOR_CAPABILITIES: Record<string, CursorCapability> = {
61
+ "claude-4.5-opus": {
62
+ window: CONTEXT_200K,
63
+ defaultVariant: "thinking",
64
+ variants: {
65
+ regular: { levels: ["high"] },
66
+ thinking: { levels: ["high"], order: E },
67
+ },
68
+ },
69
+ "claude-4.6-opus": {
70
+ window: CONTEXT_1M,
71
+ defaultVariant: "thinking",
72
+ variants: {
73
+ regular: { levels: ["high", "max"] },
74
+ thinking: { levels: ["high", "max"], order: E },
75
+ },
76
+ },
77
+ "claude-4.6-sonnet": {
78
+ window: CONTEXT_1M,
79
+ defaultVariant: "thinking",
80
+ variants: {
81
+ regular: { levels: ["medium"] },
82
+ thinking: { levels: ["medium"], order: E },
83
+ },
84
+ },
85
+ "claude-4.5-sonnet": {
86
+ window: CONTEXT_200K,
87
+ defaultVariant: "thinking",
88
+ variants: {
89
+ regular: { levels: [] },
90
+ thinking: { levels: [], order: "bare" },
91
+ },
92
+ },
93
+ "claude-4-sonnet": {
94
+ window: CONTEXT_200K,
95
+ defaultVariant: "thinking",
96
+ variants: {
97
+ regular: { levels: [] },
98
+ thinking: { levels: [], order: "bare" },
99
+ },
100
+ },
101
+ "claude-fable-5": {
102
+ window: CONTEXT_1M,
103
+ defaultVariant: "thinking",
104
+ variants: {
105
+ regular: { levels: FULL },
106
+ thinking: { levels: FULL, order: T },
107
+ },
108
+ },
109
+ "claude-sonnet-5": {
110
+ window: CONTEXT_1M,
111
+ defaultVariant: "thinking",
112
+ variants: {
113
+ regular: { levels: FULL },
114
+ thinking: { levels: FULL, order: T },
115
+ },
116
+ },
117
+ "claude-opus-4-7": {
118
+ window: CONTEXT_1M,
119
+ defaultVariant: "thinking",
120
+ variants: {
121
+ regular: { levels: FULL },
122
+ thinking: { levels: FULL, order: T },
123
+ fast: { levels: FULL },
124
+ thinkingFast: { levels: FULL, order: T },
125
+ },
126
+ },
127
+ "claude-opus-4-8": {
128
+ window: CONTEXT_1M,
129
+ defaultVariant: "thinking",
130
+ variants: {
131
+ regular: { levels: FULL },
132
+ thinking: { levels: FULL, order: T },
133
+ fast: { levels: FULL },
134
+ thinkingFast: { levels: FULL, order: T },
135
+ },
136
+ },
137
+ "claude-opus-5": {
138
+ window: CONTEXT_1M,
139
+ defaultVariant: "thinking",
140
+ variants: {
141
+ // Regular stays quarantined (devlog 260826: dead-model quarantine) while
142
+ // the thinking/fast siblings remain live — quarantine is per-variant.
143
+ regular: { levels: FULL, quarantined: true },
144
+ thinking: { levels: FULL, order: T },
145
+ fast: { levels: ["low", "medium", "high"] },
146
+ thinkingFast: { levels: FULL, order: T },
147
+ },
148
+ },
149
+ "glm-5.2": {
150
+ window: CONTEXT_1M,
151
+ defaultVariant: "regular",
152
+ variants: { regular: { levels: ["high", "max"] } },
153
+ },
154
+ "glm-5.3": {
155
+ window: CONTEXT_1M,
156
+ defaultVariant: "regular",
157
+ variants: { regular: { levels: ["low", "high", "max"] } },
158
+ },
159
+ "gemini-3.6-flash": {
160
+ window: CONTEXT_1M,
161
+ defaultVariant: "regular",
162
+ variants: { regular: { levels: ["minimal", "low", "medium", "high"] } },
163
+ },
164
+ "gemini-3.7-flash": {
165
+ window: CONTEXT_1M,
166
+ defaultVariant: "regular",
167
+ variants: { regular: { levels: ["low", "medium", "high"] } },
168
+ },
169
+ "kimi-k3": {
170
+ window: CONTEXT_1M,
171
+ defaultVariant: "regular",
172
+ maxModeVerified: true,
173
+ variants: { regular: { levels: ["low", "high", "max"] } },
174
+ },
175
+ "grok-4.5": {
176
+ window: CONTEXT_500K,
177
+ defaultVariant: "regular",
178
+ wirePrefix: "cursor-",
179
+ variants: {
180
+ regular: { levels: ["low", "medium", "high"] },
181
+ fast: { levels: ["low", "medium", "high"] },
182
+ },
183
+ },
184
+ "grok-4.6": {
185
+ window: CONTEXT_500K,
186
+ defaultVariant: "regular",
187
+ wirePrefix: "cursor-",
188
+ variants: {
189
+ regular: { levels: ["low", "medium", "high", "xhigh"] },
190
+ fast: { levels: ["low", "medium", "high", "xhigh"] },
191
+ },
192
+ },
193
+ "gpt-5.1": {
194
+ window: CONTEXT_272K,
195
+ defaultVariant: "regular",
196
+ variants: { regular: { levels: ["low", "high"] } },
197
+ },
198
+ "gpt-5.1-codex-max": {
199
+ window: CONTEXT_272K,
200
+ defaultVariant: "regular",
201
+ variants: { regular: { levels: ["low", "medium", "high", "xhigh"] } },
202
+ },
203
+ "gpt-5.1-codex-mini": {
204
+ window: CONTEXT_272K,
205
+ defaultVariant: "regular",
206
+ variants: { regular: { levels: ["low", "high"] } },
207
+ },
208
+ "gpt-5.2": {
209
+ window: CONTEXT_272K,
210
+ defaultVariant: "regular",
211
+ variants: { regular: { levels: ["low", "high", "xhigh"] } },
212
+ },
213
+ "gpt-5.2-codex": {
214
+ window: CONTEXT_272K,
215
+ defaultVariant: "regular",
216
+ variants: { regular: { levels: ["low", "high", "xhigh"] } },
217
+ },
218
+ "gpt-5.3-codex": {
219
+ window: CONTEXT_272K,
220
+ defaultVariant: "regular",
221
+ variants: { regular: { levels: ["low", "high", "xhigh"] } },
222
+ },
223
+ "gpt-5.4": {
224
+ window: CONTEXT_272K,
225
+ defaultVariant: "regular",
226
+ variants: { regular: { levels: ["low", "medium", "high", "xhigh"] } },
227
+ },
228
+ "gpt-5.4-mini": {
229
+ window: CONTEXT_272K,
230
+ defaultVariant: "regular",
231
+ variants: { regular: { levels: ["low", "medium", "high", "xhigh"] } },
232
+ },
233
+ "gpt-5.4-nano": {
234
+ window: CONTEXT_272K,
235
+ defaultVariant: "regular",
236
+ variants: { regular: { levels: ["low", "medium", "high", "xhigh"] } },
237
+ },
238
+ "gpt-5.5": {
239
+ window: CONTEXT_272K,
240
+ defaultVariant: "regular",
241
+ variants: { regular: { levels: ["low", "medium", "high"] } },
242
+ },
243
+ "gpt-5.5-extra": {
244
+ window: CONTEXT_272K,
245
+ defaultVariant: "regular",
246
+ variants: { regular: { levels: ["high"] } },
247
+ },
248
+ "gpt-5.6-sol": {
249
+ window: CONTEXT_1M,
250
+ defaultVariant: "regular",
251
+ variants: { regular: { levels: FULL } },
252
+ },
253
+ "gpt-5.6-terra": {
254
+ window: CONTEXT_1M,
255
+ defaultVariant: "regular",
256
+ variants: { regular: { levels: FULL } },
257
+ },
258
+ "gpt-5.6-luna": {
259
+ window: CONTEXT_1M,
260
+ defaultVariant: "regular",
261
+ variants: { regular: { levels: FULL } },
262
+ },
263
+ };
264
+
265
+ const LEVEL_TOKENS = ["extra-high", "minimal", "low", "medium", "high", "xhigh", "max", "none"] as const;
266
+
267
+ export interface ParsedCursorVariantId {
268
+ readonly baseId: string;
269
+ readonly kind: CursorVariantKind;
270
+ readonly level?: string;
271
+ /** True for synthetic big-context marker ids (`<base>-1m`). */
272
+ readonly ultra: boolean;
273
+ /** True when the id resolved through the capability table (else passthrough). */
274
+ readonly known: boolean;
275
+ }
276
+
277
+ function stripLevelSuffix(id: string): { stem: string; level?: string } {
278
+ // Prefer the parse whose stem is a KNOWN capability, and among known stems
279
+ // the most specific (longest) one: "gpt-5.5-extra-high" must parse as
280
+ // gpt-5.5-extra + high (its real single-rung wire id), not gpt-5.5 +
281
+ // extra-high (A-gate blocker 2 family).
282
+ let fallback: { stem: string; level?: string } | undefined;
283
+ let best: { stem: string; level?: string } | undefined;
284
+ for (const token of LEVEL_TOKENS) {
285
+ if (!id.endsWith(`-${token}`)) continue;
286
+ const candidate = { stem: id.slice(0, -(token.length + 1)), level: token };
287
+ fallback ??= candidate;
288
+ if (CURSOR_CAPABILITIES[candidate.stem] && (best === undefined || candidate.stem.length > best.stem.length)) {
289
+ best = candidate;
290
+ }
291
+ }
292
+ return best ?? fallback ?? { stem: id };
293
+ }
294
+
295
+ /**
296
+ * Parse any cursor-facing id (picker slug tail, legacy variant id, or wire id)
297
+ * into its base + dimensions. Precedence is exact-identity-first so ids like
298
+ * `gpt-5.1-codex-max` and `gpt-5.5-extra` — whose tails collide with effort
299
+ * tokens — never mis-parse (A-gate round-1 blocker 2).
300
+ */
301
+ /**
302
+ * Real wire ids that merely END in "-1m" — they are distinct catalog rows the
303
+ * wire serves verbatim, never the synthetic ultra marker (A-gate blocker 2:
304
+ * a real legacy wire identity must not parse as `<base>-1m`).
305
+ */
306
+ const REAL_1M_WIRE_IDS: ReadonlySet<string> = new Set(["claude-4-sonnet-1m"]);
307
+
308
+ export function parseCursorVariantId(rawId: string): ParsedCursorVariantId {
309
+ const id = rawId.trim();
310
+ // 1. Exact base identity.
311
+ if (CURSOR_CAPABILITIES[id]) {
312
+ return { baseId: id, kind: defaultKindFor(id), ultra: false, known: true };
313
+ }
314
+ if (REAL_1M_WIRE_IDS.has(id)) {
315
+ return { baseId: id, kind: "regular", ultra: false, known: false };
316
+ }
317
+ // 2. cursor- wire prefix (regular grok wire forms).
318
+ if (id.startsWith("cursor-")) {
319
+ const inner = parseCursorVariantId(id.slice("cursor-".length));
320
+ if (inner.known) return inner;
321
+ }
322
+ // 3. Synthetic big-context marker.
323
+ if (id.endsWith("-1m")) {
324
+ const baseId = id.slice(0, -"-1m".length);
325
+ if (CURSOR_CAPABILITIES[baseId]) {
326
+ return { baseId, kind: "regular", ultra: true, known: true };
327
+ }
328
+ }
329
+ // 4. Suffix grammar: strip -fast, then thinking/effort markers.
330
+ let stem = id;
331
+ let fast = false;
332
+ if (stem.endsWith("-fast")) {
333
+ fast = true;
334
+ stem = stem.slice(0, -"-fast".length);
335
+ }
336
+ let thinking = false;
337
+ let level: string | undefined;
338
+ const thinkingLevel = /^(.*)-thinking-([a-z-]+)$/.exec(stem);
339
+ if (thinkingLevel && CURSOR_CAPABILITIES[thinkingLevel[1]!] && (LEVEL_TOKENS as readonly string[]).includes(thinkingLevel[2]!)) {
340
+ return finishParse(thinkingLevel[1]!, true, fast, thinkingLevel[2]!);
341
+ }
342
+ const levelThinking = stem.endsWith("-thinking") ? stripLevelSuffix(stem.slice(0, -"-thinking".length)) : undefined;
343
+ if (levelThinking && CURSOR_CAPABILITIES[levelThinking.stem]) {
344
+ return finishParse(levelThinking.stem, true, fast, levelThinking.level);
345
+ }
346
+ if (stem.endsWith("-thinking") && CURSOR_CAPABILITIES[stem.slice(0, -"-thinking".length)]) {
347
+ return finishParse(stem.slice(0, -"-thinking".length), true, fast, undefined);
348
+ }
349
+ const plain = stripLevelSuffix(stem);
350
+ if (plain.level !== undefined && CURSOR_CAPABILITIES[plain.stem]) {
351
+ return finishParse(plain.stem, false, fast, plain.level);
352
+ }
353
+ if (fast && CURSOR_CAPABILITIES[stem]) {
354
+ return finishParse(stem, false, true, undefined);
355
+ }
356
+ void thinking;
357
+ void level;
358
+ // Unknown: passthrough (adapter sends the id unchanged).
359
+ return { baseId: id, kind: "regular", ultra: false, known: false };
360
+ }
361
+
362
+ function finishParse(baseId: string, thinking: boolean, fast: boolean, level: string | undefined): ParsedCursorVariantId {
363
+ const kind: CursorVariantKind = thinking ? (fast ? "thinkingFast" : "thinking") : fast ? "fast" : "regular";
364
+ return { baseId, kind, ...(level !== undefined ? { level } : {}), ultra: false, known: true };
365
+ }
366
+
367
+ function defaultKindFor(baseId: string): CursorVariantKind {
368
+ return CURSOR_CAPABILITIES[baseId]?.defaultVariant ?? "regular";
369
+ }
370
+
371
+ function normalizeRequestedEffort(reasoning: string | undefined): string | undefined {
372
+ const normalized = reasoning?.toLowerCase();
373
+ return normalized === "ultra" ? "max" : normalized;
374
+ }
375
+
376
+ function codexEffortRank(reasoning: string | undefined): "low" | "medium" | "high" {
377
+ switch (normalizeRequestedEffort(reasoning) ?? "") {
378
+ case "none":
379
+ case "minimal":
380
+ case "low":
381
+ return "low";
382
+ case "medium":
383
+ return "medium";
384
+ case "high":
385
+ case "max":
386
+ case "xhigh":
387
+ return "high";
388
+ default:
389
+ return "high";
390
+ }
391
+ }
392
+
393
+ /** Pick this variant's effort rung for a Codex reasoning label: literal-first, else rank clamp. */
394
+ export function cursorVariantEffort(spec: CursorVariantSpec, reasoning: string | undefined): string | undefined {
395
+ if (spec.levels.length === 0) return undefined;
396
+ const requested = normalizeRequestedEffort(reasoning);
397
+ if (requested && spec.levels.includes(requested)) return requested;
398
+ switch (codexEffortRank(reasoning)) {
399
+ case "low":
400
+ return spec.levels[0];
401
+ case "high":
402
+ return spec.levels[spec.levels.length - 1];
403
+ case "medium":
404
+ return spec.levels[Math.floor((spec.levels.length - 1) / 2)];
405
+ }
406
+ }
407
+
408
+ export interface CursorResolvedSelection {
409
+ /** Flattened wire id for AgentService/Run (with any required cursor- prefix). */
410
+ readonly wireId: string;
411
+ /** Canonical prefix-free id for discovery/catalog comparison. */
412
+ readonly canonicalId: string;
413
+ /** True when the request should raise the Max Mode wire flag (evidence-gated). */
414
+ readonly maxMode: boolean;
415
+ readonly known: boolean;
416
+ }
417
+
418
+ /**
419
+ * Compose a variant's flattened wire id, reproducing the legacy effort-map
420
+ * order rules exactly (thinking-then-effort / effort-then-thinking / bare;
421
+ * fast marker terminal; wrong order is ERROR_BAD_MODEL_NAME on the wire).
422
+ */
423
+ function composeWireId(baseId: string, kind: CursorVariantKind, effort: string | undefined): string {
424
+ const capability = CURSOR_CAPABILITIES[baseId];
425
+ const spec = capability?.variants[kind];
426
+ if (!capability || !spec) return baseId;
427
+ const thinking = kind === "thinking" || kind === "thinkingFast";
428
+ const fast = kind === "fast" || kind === "thinkingFast";
429
+ if (thinking) {
430
+ const order = spec.order ?? "thinking-then-effort";
431
+ if (order === "bare" || effort === undefined) return `${baseId}-thinking`;
432
+ if (order === "effort-then-thinking") return `${baseId}-${effort}-thinking`;
433
+ return fast ? `${baseId}-thinking-${effort}-fast` : `${baseId}-thinking-${effort}`;
434
+ }
435
+ if (effort === undefined) return fast ? `${baseId}-fast` : baseId;
436
+ return fast ? `${baseId}-${effort}-fast` : `${baseId}-${effort}`;
437
+ }
438
+
439
+ /**
440
+ * Resolve any picked cursor id + Codex reasoning effort to the wire identity.
441
+ * Legacy slugs (thinking/fast/-1m variants) keep resolving forever — picker
442
+ * rows shrink, routability does not (alias-retention contract, 003).
443
+ *
444
+ * `liveMaxModeIds` optionally extends the static maxMode evidence with the
445
+ * bases the live GetUsableModels roster flags (union semantics).
446
+ */
447
+ export function resolveCursorSelection(
448
+ pickedId: string,
449
+ reasoning: string | undefined,
450
+ liveMaxModeIds?: ReadonlySet<string>,
451
+ ): CursorResolvedSelection {
452
+ const parsed = parseCursorVariantId(pickedId);
453
+ if (!parsed.known) {
454
+ return { wireId: pickedId, canonicalId: pickedId, maxMode: false, known: false };
455
+ }
456
+ const capability = CURSOR_CAPABILITIES[parsed.baseId]!;
457
+ const spec = capability.variants[parsed.kind] ?? capability.variants.regular;
458
+ if (!spec) {
459
+ return { wireId: parsed.baseId, canonicalId: parsed.baseId, maxMode: false, known: true };
460
+ }
461
+ const requested = parsed.level ?? reasoning;
462
+ const effort = cursorVariantEffort(spec, requested);
463
+ const canonicalId = composeWireId(parsed.baseId, parsed.kind, effort);
464
+ const wireId = capability.wirePrefix && parsed.kind === "regular"
465
+ ? `${capability.wirePrefix}${canonicalId}`
466
+ : canonicalId;
467
+ const ultraRequested = parsed.ultra || reasoning?.toLowerCase() === "ultra";
468
+ const evidence = liveMaxModeIds ?? liveCursorMaxModeBases;
469
+ const maxModeArmed = capability.maxModeVerified === true || evidence.has(parsed.baseId);
470
+ return { wireId, canonicalId, maxMode: ultraRequested && maxModeArmed, known: true };
471
+ }
472
+
473
+ /**
474
+ * Live Max-Mode evidence (GetUsableModels maxModeModels). Provider discovery
475
+ * records the BASES the live roster flags; the resolver unions this with the
476
+ * static `maxModeVerified` gate so ultra generalizes automatically as evidence
477
+ * arrives — never from window size (devlog 260828 blocker-4 fold).
478
+ */
479
+ let liveCursorMaxModeBases: ReadonlySet<string> = new Set();
480
+
481
+ export function recordLiveCursorMaxModeModels(liveIds: readonly string[]): void {
482
+ const bases = new Set<string>();
483
+ for (const id of liveIds) {
484
+ const parsed = parseCursorVariantId(id);
485
+ if (parsed.known) bases.add(parsed.baseId);
486
+ }
487
+ liveCursorMaxModeBases = bases;
488
+ }
489
+
490
+ export function liveCursorMaxModeBasesForTests(): ReadonlySet<string> {
491
+ return liveCursorMaxModeBases;
492
+ }
493
+
494
+ export interface CursorUmbrellaRow {
495
+ readonly id: string;
496
+ readonly efforts: readonly string[];
497
+ readonly window: number;
498
+ /** Max Mode evidence present: the ultra rung maps to maxMode on the wire. */
499
+ readonly maxModeVerified: boolean;
500
+ }
501
+
502
+ /**
503
+ * Grok Fast keeps the parameterized wire shape (base id + effort/fast
504
+ * parameters) rather than a flattened -fast id — current Cursor clients send
505
+ * it that way and the flat form is rejected. Returns undefined for every
506
+ * other id.
507
+ */
508
+ export function cursorGrokFastSelection(
509
+ pickedId: string,
510
+ reasoning: string | undefined,
511
+ ): { wireBaseId: string; effort: string } | undefined {
512
+ const parsed = parseCursorVariantId(pickedId);
513
+ if (!parsed.known || parsed.kind !== "fast") return undefined;
514
+ const capability = CURSOR_CAPABILITIES[parsed.baseId];
515
+ if (capability?.wirePrefix !== "cursor-") return undefined;
516
+ const spec = capability.variants.fast;
517
+ if (!spec) return undefined;
518
+ const effort = cursorVariantEffort(spec, parsed.level ?? reasoning);
519
+ if (effort === undefined) return undefined;
520
+ return { wireBaseId: parsed.baseId, effort };
521
+ }
522
+
523
+ /**
524
+ * The umbrella picker rows: one per base whose default variant is selectable.
525
+ * Thinking merges into the base row; fast/thinking-fast/legacy slugs stay
526
+ * routable as aliases but add no rows. Router ids stay in discovery.
527
+ */
528
+ export function cursorUmbrellaRows(): CursorUmbrellaRow[] {
529
+ const rows: CursorUmbrellaRow[] = [];
530
+ for (const [baseId, capability] of Object.entries(CURSOR_CAPABILITIES)) {
531
+ const spec = capability.variants[capability.defaultVariant];
532
+ if (!spec || spec.quarantined) continue;
533
+ rows.push({
534
+ id: baseId,
535
+ efforts: spec.levels,
536
+ window: capability.window,
537
+ maxModeVerified: capability.maxModeVerified === true,
538
+ });
539
+ }
540
+ return rows;
541
+ }
@@ -236,6 +236,7 @@ export function commitCursorCheckpoint(input: {
236
236
  }
237
237
 
238
238
  export function getCursorCheckpointForPrefix(input: {
239
+ conversationId: string;
239
240
  prefixDigest: string;
240
241
  systemDigest: string;
241
242
  coveredMessageCount: number;
@@ -244,17 +245,21 @@ export function getCursorCheckpointForPrefix(input: {
244
245
  }): CursorCheckpointSnapshot | undefined {
245
246
  prune();
246
247
  const refs = store.prefixIndex.get(input.prefixDigest);
247
- if (!refs || refs.size !== 1) return undefined;
248
- const [ref] = refs;
249
- if (!ref) return undefined;
250
- const snapshot = getCursorCheckpoint(ref);
251
- if (!snapshot) return undefined;
248
+ if (!refs) return undefined;
252
249
  const identityScope = input.identityScope?.trim() || "local";
253
- if (snapshot.systemDigest !== input.systemDigest) return undefined;
254
- if (snapshot.coveredMessageCount !== input.coveredMessageCount) return undefined;
255
- if (snapshot.identityScope !== identityScope) return undefined;
256
- if (snapshot.modelId !== input.modelId) return undefined;
257
- return snapshot;
250
+ let foundRef: string | undefined;
251
+ for (const ref of refs) {
252
+ const snapshot = store.snapshots.get(ref);
253
+ if (!snapshot) continue;
254
+ if (snapshot.conversationId !== input.conversationId) continue;
255
+ if (snapshot.systemDigest !== input.systemDigest) continue;
256
+ if (snapshot.coveredMessageCount !== input.coveredMessageCount) continue;
257
+ if (snapshot.identityScope !== identityScope) continue;
258
+ if (snapshot.modelId !== input.modelId) continue;
259
+ if (foundRef) return undefined;
260
+ foundRef = ref;
261
+ }
262
+ return getCursorCheckpoint(foundRef);
258
263
  }
259
264
 
260
265
  export function getLatestCursorCheckpoint(
@@ -241,6 +241,21 @@ export function classifyCursorError(message: string, sizeContext?: CursorSizeCon
241
241
  lower.includes("access denied")
242
242
  ) return "Cursor authentication failed";
243
243
 
244
+ // gRPC FAILED_PRECONDITION is deterministic and non-retryable (unlike UNAVAILABLE):
245
+ // the backend rejected the call because the account/plan state does not allow it —
246
+ // seen live when a plan-gated model (e.g. claude-fable-5) runs on a plan without it.
247
+ // Leaving it as "Cursor upstream error" (502) made clients retry it as overload.
248
+ //
249
+ // This MUST precede the overload keywords. The explicit gRPC status code is a
250
+ // structured signal from the backend; the keywords are inference over free text. A
251
+ // plan-gated rejection routinely reads "failed_precondition: model unavailable for
252
+ // this plan", which matched "unavailable" first and came back as a retryable
253
+ // overload — the exact misclassification this branch was added to stop. Checking the
254
+ // code first lets the deterministic signal win over the words around it.
255
+ if (lower.includes("failed_precondition") || lower.includes("failed precondition")) {
256
+ return "Cursor invalid request";
257
+ }
258
+
244
259
  if (
245
260
  lower.includes("unavailable") ||
246
261
  lower.includes("overloaded") ||