@bitkyc08/opencodex 2.8.0 → 2.9.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 (175) hide show
  1. package/README.md +24 -0
  2. package/bin/ocx.mjs +32 -4
  3. package/gui/dist/assets/index-CHwf3tTD.css +1 -0
  4. package/gui/dist/assets/index-u5eFOv2y.js +67 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/src/adapters/anthropic-image-normalize.ts +114 -20
  8. package/src/adapters/anthropic.ts +126 -10
  9. package/src/adapters/azure.ts +3 -3
  10. package/src/adapters/base.ts +7 -3
  11. package/src/adapters/cursor/discovery.ts +14 -4
  12. package/src/adapters/cursor/effort-map.ts +18 -6
  13. package/src/adapters/cursor/framing.ts +102 -27
  14. package/src/adapters/cursor/kv-store.ts +30 -3
  15. package/src/adapters/cursor/live-models.ts +22 -2
  16. package/src/adapters/cursor/live-transport.ts +245 -49
  17. package/src/adapters/cursor/mcp-manager.ts +105 -8
  18. package/src/adapters/cursor/native-exec-mcp.ts +5 -3
  19. package/src/adapters/cursor/native-exec-shell.ts +296 -14
  20. package/src/adapters/cursor/native-exec.ts +381 -33
  21. package/src/adapters/cursor/protobuf-events.ts +28 -1
  22. package/src/adapters/cursor/protobuf-request.ts +71 -39
  23. package/src/adapters/cursor/request-builder.ts +2 -2
  24. package/src/adapters/cursor/transport.ts +2 -0
  25. package/src/adapters/cursor.ts +13 -2
  26. package/src/adapters/google-antigravity-replay.ts +184 -17
  27. package/src/adapters/google.ts +58 -8
  28. package/src/adapters/kiro-thinking.ts +23 -9
  29. package/src/adapters/kiro-tools.ts +49 -18
  30. package/src/adapters/kiro.ts +377 -133
  31. package/src/adapters/mimo-free.ts +36 -4
  32. package/src/adapters/openai-chat.ts +143 -17
  33. package/src/adapters/openai-responses.ts +130 -14
  34. package/src/adapters/run-turn-queue.ts +7 -1
  35. package/src/bridge.ts +466 -69
  36. package/src/chat/outbound.ts +144 -38
  37. package/src/claude/inbound-debug.ts +53 -8
  38. package/src/claude/outbound.ts +224 -38
  39. package/src/cli/agent-driven.ts +34 -1
  40. package/src/cli/catalog-prewarm.ts +5 -2
  41. package/src/cli/claude-desktop.ts +2 -2
  42. package/src/cli/doctor.ts +12 -0
  43. package/src/cli/export-command.ts +187 -0
  44. package/src/cli/help.ts +11 -0
  45. package/src/cli/index.ts +13 -3
  46. package/src/cli/init.ts +129 -102
  47. package/src/cli/opencode.ts +36 -151
  48. package/src/cli/star-prompt.ts +13 -4
  49. package/src/cli/status-oauth.ts +12 -2
  50. package/src/clients/config-export.ts +377 -0
  51. package/src/codex/account-runtime-state.ts +19 -1
  52. package/src/codex/account-store.ts +162 -82
  53. package/src/codex/auth-api.ts +467 -159
  54. package/src/codex/auth-context.ts +15 -2
  55. package/src/codex/catalog/aggregation.ts +15 -0
  56. package/src/codex/catalog/effort.ts +16 -6
  57. package/src/codex/catalog/metadata.ts +6 -0
  58. package/src/codex/catalog/parsing.ts +3 -1
  59. package/src/codex/catalog/provider-fetch.ts +29 -0
  60. package/src/codex/catalog/sync.ts +64 -7
  61. package/src/codex/catalog.ts +2 -2
  62. package/src/codex/inject.ts +5 -5
  63. package/src/codex/main-account-cache.ts +8 -1
  64. package/src/codex/model-cache.ts +81 -2
  65. package/src/codex/pool-rotation.ts +39 -0
  66. package/src/codex/project-config-warnings.ts +12 -1
  67. package/src/codex/quota.ts +35 -3
  68. package/src/codex/routing.ts +46 -1
  69. package/src/codex/shim.ts +10 -4
  70. package/src/codex/subagent-model-fallback.ts +12 -0
  71. package/src/codex/websocket-registry.ts +27 -0
  72. package/src/combos/failover.ts +31 -1
  73. package/src/combos/request.ts +9 -0
  74. package/src/combos/resolve.ts +60 -4
  75. package/src/combos/types.ts +12 -0
  76. package/src/config.ts +510 -55
  77. package/src/github/star-state.ts +13 -1
  78. package/src/images/fulfill.ts +39 -1
  79. package/src/images/loop.ts +52 -12
  80. package/src/lib/admission.ts +83 -0
  81. package/src/lib/app-owned-memory-stores.ts +173 -0
  82. package/src/lib/app-owned-memory.ts +265 -0
  83. package/src/lib/bun-stream-caps.ts +31 -7
  84. package/src/lib/config-ownership.ts +33 -0
  85. package/src/lib/crash-guard.ts +65 -5
  86. package/src/lib/debug-log-buffer.ts +47 -6
  87. package/src/lib/destination-policy.ts +12 -1
  88. package/src/lib/errors.ts +3 -0
  89. package/src/lib/gcp-adc.ts +40 -2
  90. package/src/lib/injection-debug-log.ts +26 -2
  91. package/src/lib/provider-outbound.ts +3 -0
  92. package/src/lib/sidecar-tracker.ts +5 -2
  93. package/src/lib/sse-decoder.ts +257 -37
  94. package/src/lib/state-store-registrations.ts +109 -0
  95. package/src/lib/state-store-sweeper.ts +184 -0
  96. package/src/lib/translator-budget.ts +356 -0
  97. package/src/lib/windows-secret-acl.ts +33 -12
  98. package/src/lib/winsw.ts +14 -1
  99. package/src/oauth/anthropic-routing.ts +31 -7
  100. package/src/oauth/google-antigravity.ts +2 -1
  101. package/src/oauth/health.ts +30 -12
  102. package/src/oauth/index.ts +127 -23
  103. package/src/oauth/kiro-credentials.ts +72 -1
  104. package/src/oauth/kiro.ts +23 -4
  105. package/src/oauth/store.ts +165 -18
  106. package/src/oauth/token-guardian.ts +43 -4
  107. package/src/oauth/types.ts +2 -1
  108. package/src/providers/base-url-choices.ts +10 -0
  109. package/src/providers/derive.ts +12 -0
  110. package/src/providers/free-directory.ts +4 -1
  111. package/src/providers/key-failover.ts +12 -0
  112. package/src/providers/openai-sidecar.ts +4 -1
  113. package/src/providers/quota.ts +68 -7
  114. package/src/providers/registry.ts +279 -3
  115. package/src/responses/parser.ts +5 -1
  116. package/src/responses/spill-store.ts +394 -0
  117. package/src/responses/state.ts +520 -102
  118. package/src/router.ts +18 -1
  119. package/src/server/adapter-resolve.ts +20 -3
  120. package/src/server/auth-cors.ts +121 -28
  121. package/src/server/chat-completions.ts +57 -12
  122. package/src/server/claude-messages.ts +85 -13
  123. package/src/server/index.ts +242 -100
  124. package/src/server/lifecycle.ts +155 -25
  125. package/src/server/management/agent-settings-routes.ts +79 -36
  126. package/src/server/management/api-key-usage.ts +167 -0
  127. package/src/server/management/body.ts +35 -0
  128. package/src/server/management/combo-routes.ts +5 -1
  129. package/src/server/management/config-routes.ts +42 -12
  130. package/src/server/management/logs-usage-routes.ts +41 -21
  131. package/src/server/management/model-routes.ts +188 -54
  132. package/src/server/management/oauth-account-routes.ts +115 -26
  133. package/src/server/management/provider-routes.ts +56 -6
  134. package/src/server/management/shared.ts +16 -3
  135. package/src/server/management/sidebar-routes.ts +50 -1
  136. package/src/server/management/system-restart.ts +13 -6
  137. package/src/server/management/system-routes.ts +15 -3
  138. package/src/server/management/usage-summary-cache.ts +86 -0
  139. package/src/server/management-api.ts +39 -5
  140. package/src/server/management-auth.ts +65 -14
  141. package/src/server/port-reclaim.ts +58 -12
  142. package/src/server/ports.ts +2 -0
  143. package/src/server/proxy-liveness.ts +60 -14
  144. package/src/server/relay-eager.ts +20 -4
  145. package/src/server/relay.ts +548 -154
  146. package/src/server/request-decompress.ts +51 -4
  147. package/src/server/request-log.ts +134 -15
  148. package/src/server/responses/collaboration.ts +15 -4
  149. package/src/server/responses/compact.ts +3 -0
  150. package/src/server/responses/core.ts +241 -66
  151. package/src/server/responses-image-gen-repair.ts +19 -5
  152. package/src/server/responses-item-id-repair.ts +23 -5
  153. package/src/server/sse-payload-rewrite.ts +71 -12
  154. package/src/server/startup-health-cache.ts +14 -1
  155. package/src/server/system-env.ts +8 -1
  156. package/src/server/windows-tcp-drop.ts +15 -5
  157. package/src/server/ws-bridge.ts +25 -0
  158. package/src/service.ts +179 -13
  159. package/src/storage/policy-job.ts +93 -23
  160. package/src/storage/policy-worker.ts +6 -0
  161. package/src/storage/restore-job.ts +62 -16
  162. package/src/storage/restore-worker.ts +6 -0
  163. package/src/storage/storage-mutation-coordinator.ts +36 -6
  164. package/src/storage/worker-lifecycle.ts +181 -47
  165. package/src/tray/windows.ts +97 -25
  166. package/src/types.ts +39 -6
  167. package/src/update/index.ts +24 -5
  168. package/src/update/job.ts +598 -73
  169. package/src/usage/log.ts +115 -17
  170. package/src/usage/summary.ts +67 -2
  171. package/src/vision/index.ts +112 -22
  172. package/src/web-search/loop.ts +38 -6
  173. package/src/web-search/progress-stream.ts +14 -3
  174. package/gui/dist/assets/index-BDjpkcRN.js +0 -67
  175. package/gui/dist/assets/index-BHsKRFh9.css +0 -1
@@ -6,7 +6,13 @@ import { namespacedToolName } from "../../types";
6
6
  import type { CursorRunRequest } from "./types";
7
7
  import { isCursorExternalWireModel } from "./discovery";
8
8
  import { debugProviderDiagnostic } from "../../lib/debug";
9
- import { storeCursorBlob } from "./native-exec";
9
+ import {
10
+ createCursorBlobRequestScope,
11
+ releaseCursorBlobRequestScope,
12
+ sealCursorBlobRequestScope,
13
+ storeCursorBlob,
14
+ type CursorBlobRequestScopeToken,
15
+ } from "./native-exec";
10
16
  import { estimateTokens } from "../../lib/token-estimate";
11
17
  import {
12
18
  AgentClientMessageSchema,
@@ -82,8 +88,8 @@ function jsonBlob(value: unknown): { data: Uint8Array; serialized: string } {
82
88
  return { data: encoder.encode(serialized), serialized };
83
89
  }
84
90
 
85
- type StoredRootBlob = {
86
- id: Uint8Array;
91
+ type RootBlobCandidate = {
92
+ data: Uint8Array;
87
93
  byteLength: number;
88
94
  /**
89
95
  * The exact JSON handed to storeCursorBlob(). Retained so a token estimate can read
@@ -97,14 +103,14 @@ type StoredRootBlob = {
97
103
  text?: string;
98
104
  };
99
105
 
100
- function storedRootBlob(
106
+ function rootBlobCandidate(
101
107
  value: unknown,
102
- role: StoredRootBlob["role"],
108
+ role: RootBlobCandidate["role"],
103
109
  opts?: { messageIndex?: number; text?: string },
104
- ): StoredRootBlob {
110
+ ): RootBlobCandidate {
105
111
  const { data, serialized } = jsonBlob(value);
106
112
  return {
107
- id: storeCursorBlob(data),
113
+ data,
108
114
  byteLength: data.byteLength,
109
115
  serialized,
110
116
  role,
@@ -113,7 +119,7 @@ function storedRootBlob(
113
119
  };
114
120
  }
115
121
 
116
- function truncateToolResultBlob(entry: StoredRootBlob, maxBytes: number): StoredRootBlob | null {
122
+ function truncateToolResultBlob(entry: RootBlobCandidate, maxBytes: number): RootBlobCandidate | null {
117
123
  if (entry.byteLength <= maxBytes) return entry;
118
124
  if (entry.role !== "toolResult" || entry.text === undefined) return null;
119
125
  const marker = "\n…[truncated for Cursor external replay budget]";
@@ -124,7 +130,7 @@ function truncateToolResultBlob(entry: StoredRootBlob, maxBytes: number): Stored
124
130
  let end = keepBytes;
125
131
  while (end > 0 && end < encoded.byteLength && (encoded[end]! & 0xc0) === 0x80) end -= 1;
126
132
  const truncated = `${decoder.decode(encoded.subarray(0, end))}${marker}`;
127
- const result = storedRootBlob(
133
+ const result = rootBlobCandidate(
128
134
  { role: "user", content: [{ type: "text", text: truncated }] },
129
135
  "toolResult",
130
136
  { messageIndex: entry.messageIndex, text: truncated },
@@ -133,7 +139,7 @@ function truncateToolResultBlob(entry: StoredRootBlob, maxBytes: number): Stored
133
139
  if (end === 0) break;
134
140
  keepBytes = Math.max(0, end - (result.byteLength - maxBytes) - 16);
135
141
  }
136
- const markerOnly = storedRootBlob(
142
+ const markerOnly = rootBlobCandidate(
137
143
  { role: "user", content: [{ type: "text", text: marker.trimStart() }] },
138
144
  "toolResult",
139
145
  { messageIndex: entry.messageIndex, text: marker.trimStart() },
@@ -141,7 +147,7 @@ function truncateToolResultBlob(entry: StoredRootBlob, maxBytes: number): Stored
141
147
  return markerOnly.byteLength <= maxBytes ? markerOnly : null;
142
148
  }
143
149
 
144
- function systemPromptBlobs(request: CursorRunRequest): StoredRootBlob[] {
150
+ function systemPromptBlobs(request: CursorRunRequest): RootBlobCandidate[] {
145
151
  const prompts = request.system.length > 0 ? [...request.system] : ["You are a helpful assistant."];
146
152
  if (cursorRequestHasShellAlias(request.tools)) prompts.push(CURSOR_SHELL_ALIAS_SYSTEM_NOTE);
147
153
  const cursorToolGuidance = buildCursorToolGuidanceSystemNote(
@@ -149,7 +155,7 @@ function systemPromptBlobs(request: CursorRunRequest): StoredRootBlob[] {
149
155
  request.toolChoice,
150
156
  );
151
157
  if (cursorToolGuidance) prompts.push(cursorToolGuidance);
152
- return prompts.map(content => storedRootBlob({ role: "system", content }, "system"));
158
+ return prompts.map(content => rootBlobCandidate({ role: "system", content }, "system"));
153
159
  }
154
160
 
155
161
  function assistantRootText(
@@ -169,7 +175,7 @@ function assistantRootText(
169
175
  // because it travels in the action. Tool results are rendered as user-role text with a marker, and
170
176
  // each entry is a SHA-256 blob ID (Cursor fetches the bytes back via getBlobArgs). Mirrors the
171
177
  // danger-pi reference buildRootPromptMessagesJson.
172
- function rootPromptMessages(request: CursorRunRequest): {
178
+ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobRequestScopeToken): {
173
179
  ids: Uint8Array[];
174
180
  byteLength: number;
175
181
  historyMessageStart: number;
@@ -181,7 +187,7 @@ function rootPromptMessages(request: CursorRunRequest): {
181
187
  const messages = request.rawMessages;
182
188
  if (!messages?.length) {
183
189
  return {
184
- ids: entries.map(entry => entry.id),
190
+ ids: entries.map(entry => storeCursorBlob(entry.data, requestScope)),
185
191
  byteLength: entries.reduce((sum, entry) => sum + entry.byteLength, 0),
186
192
  historyMessageStart: 0,
187
193
  serialized: entries.map(entry => entry.serialized),
@@ -202,7 +208,7 @@ function rootPromptMessages(request: CursorRunRequest): {
202
208
  // A bare string survives blob hydration but external workers reject the completed replay
203
209
  // before tokenization (`usedTokens: 0`, then invalid_argument).
204
210
  if (text.length > 0) {
205
- entries.push(storedRootBlob({
211
+ entries.push(rootBlobCandidate({
206
212
  role: "user",
207
213
  content: [{ type: "text", text }],
208
214
  }, "user", { messageIndex: i }));
@@ -212,7 +218,7 @@ function rootPromptMessages(request: CursorRunRequest): {
212
218
  // Native Composer state can preserve it through ThinkingMessage/history structures.
213
219
  const text = assistantRootText(message, !externalModel).trim();
214
220
  if (text.length > 0) {
215
- entries.push(storedRootBlob(
221
+ entries.push(rootBlobCandidate(
216
222
  { role: "assistant", content: [{ type: "text", text }] },
217
223
  "assistant",
218
224
  { messageIndex: i },
@@ -222,7 +228,7 @@ function rootPromptMessages(request: CursorRunRequest): {
222
228
  } else if (message.role === "toolResult") {
223
229
  const prefix = message.isError ? "[Tool Error]" : "[Tool Result]";
224
230
  const text = `${prefix}\n${toolResultToText(message)}`;
225
- entries.push(storedRootBlob(
231
+ entries.push(rootBlobCandidate(
226
232
  { role: "user", content: [{ type: "text", text }] },
227
233
  "toolResult",
228
234
  { messageIndex: i, text },
@@ -247,7 +253,7 @@ function rootPromptMessages(request: CursorRunRequest): {
247
253
  const active = history
248
254
  .slice(activeStart)
249
255
  .map(entry => truncateToolResultBlob(entry, historyBudget))
250
- .filter((entry): entry is StoredRootBlob => entry !== null);
256
+ .filter((entry): entry is RootBlobCandidate => entry !== null);
251
257
  let activeBytes = active.reduce((sum, entry) => sum + entry.byteLength, 0);
252
258
  while (active.length > 1 && activeBytes > historyBudget) {
253
259
  const dropped = active.shift();
@@ -265,7 +271,7 @@ function rootPromptMessages(request: CursorRunRequest): {
265
271
  }
266
272
 
267
273
  const prior = history.slice(0, activeStart);
268
- const keptPrior: StoredRootBlob[] = [];
274
+ const keptPrior: RootBlobCandidate[] = [];
269
275
  let priorBytes = 0;
270
276
  // Take complete turns from the end: a turn starts at a user/developer root entry.
271
277
  let i = prior.length - 1;
@@ -298,7 +304,7 @@ function rootPromptMessages(request: CursorRunRequest): {
298
304
  }
299
305
 
300
306
  return {
301
- ids: selected.map(entry => entry.id),
307
+ ids: selected.map(entry => storeCursorBlob(entry.data, requestScope)),
302
308
  byteLength: selected.reduce((sum, entry) => sum + entry.byteLength, 0),
303
309
  historyMessageStart,
304
310
  serialized: selected.map(entry => entry.serialized),
@@ -345,7 +351,11 @@ function argBytes(value: unknown): Uint8Array {
345
351
  }
346
352
  }
347
353
 
348
- function toolCallStep(part: Extract<OcxAssistantContentPart, { type: "toolCall" }>, result?: OcxToolResultMessage): Uint8Array {
354
+ function toolCallStep(
355
+ part: Extract<OcxAssistantContentPart, { type: "toolCall" }>,
356
+ requestScope: CursorBlobRequestScopeToken,
357
+ result?: OcxToolResultMessage,
358
+ ): Uint8Array {
349
359
  const args: Record<string, Uint8Array> = {};
350
360
  for (const [key, value] of Object.entries(part.arguments ?? {})) args[key] = argBytes(value);
351
361
  const toolName = namespacedToolName(part.namespace, part.name);
@@ -368,7 +378,7 @@ function toolCallStep(part: Extract<OcxAssistantContentPart, { type: "toolCall"
368
378
  },
369
379
  }),
370
380
  },
371
- })));
381
+ })), requestScope);
372
382
  }
373
383
 
374
384
  function toolResultPart(message: OcxToolResultMessage) {
@@ -385,15 +395,15 @@ function toolResultPart(message: OcxToolResultMessage) {
385
395
  });
386
396
  }
387
397
 
388
- function assistantStep(part: OcxAssistantContentPart): Uint8Array | undefined {
389
- if (part.type === "toolCall") return toolCallStep(part);
398
+ function assistantStep(part: OcxAssistantContentPart, requestScope: CursorBlobRequestScopeToken): Uint8Array | undefined {
399
+ if (part.type === "toolCall") return toolCallStep(part, requestScope);
390
400
  if (part.type === "thinking") {
391
401
  return storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
392
402
  message: {
393
403
  case: "thinkingMessage",
394
404
  value: create(ThinkingMessageSchema, { text: part.thinking }),
395
405
  },
396
- })));
406
+ })), requestScope);
397
407
  }
398
408
  if (part.text.length === 0) return undefined;
399
409
  return storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
@@ -401,7 +411,7 @@ function assistantStep(part: OcxAssistantContentPart): Uint8Array | undefined {
401
411
  case: "assistantMessage",
402
412
  value: create(AssistantMessageSchema, { text: part.text }),
403
413
  },
404
- })));
414
+ })), requestScope);
405
415
  }
406
416
 
407
417
  function lastActionIndex(messages: readonly OcxMessage[] | undefined): number {
@@ -414,7 +424,11 @@ function lastActionIndex(messages: readonly OcxMessage[] | undefined): number {
414
424
  return -1;
415
425
  }
416
426
 
417
- function conversationTurns(request: CursorRunRequest, historyMessageStart = 0): Uint8Array[] {
427
+ function conversationTurns(
428
+ request: CursorRunRequest,
429
+ requestScope: CursorBlobRequestScopeToken,
430
+ historyMessageStart = 0,
431
+ ): Uint8Array[] {
418
432
  const messages = request.rawMessages;
419
433
  if (!messages?.length) return [];
420
434
  const end = lastActionIndex(messages);
@@ -426,13 +440,13 @@ function conversationTurns(request: CursorRunRequest, historyMessageStart = 0):
426
440
  const pendingToolCalls = new Map<string, Extract<OcxAssistantContentPart, { type: "toolCall" }>>();
427
441
  const flush = () => {
428
442
  if (!current) return;
429
- for (const part of pendingToolCalls.values()) current.steps.push(toolCallStep(part));
443
+ for (const part of pendingToolCalls.values()) current.steps.push(toolCallStep(part, requestScope));
430
444
  turns.push(storeCursorBlob(toBinary(ConversationTurnStructureSchema, create(ConversationTurnStructureSchema, {
431
445
  turn: {
432
446
  case: "agentConversationTurn",
433
447
  value: create(AgentConversationTurnStructureSchema, current),
434
448
  },
435
- }))));
449
+ })), requestScope));
436
450
  current = undefined;
437
451
  pendingToolCalls.clear();
438
452
  };
@@ -451,7 +465,7 @@ function conversationTurns(request: CursorRunRequest, historyMessageStart = 0):
451
465
  case: "assistantMessage",
452
466
  value: create(AssistantMessageSchema, { text: part.text }),
453
467
  },
454
- }))));
468
+ })), requestScope));
455
469
  }
456
470
  continue;
457
471
  }
@@ -459,7 +473,7 @@ function conversationTurns(request: CursorRunRequest, historyMessageStart = 0):
459
473
  pendingToolCalls.set(part.id, part);
460
474
  continue;
461
475
  }
462
- const step = assistantStep(part);
476
+ const step = assistantStep(part, requestScope);
463
477
  if (step) current.steps.push(step);
464
478
  }
465
479
  continue;
@@ -473,12 +487,12 @@ function conversationTurns(request: CursorRunRequest, historyMessageStart = 0):
473
487
  case: "assistantMessage",
474
488
  value: create(AssistantMessageSchema, { text: `${prefix}\n${contentToText(message.content)}` }),
475
489
  },
476
- }))));
490
+ })), requestScope));
477
491
  continue;
478
492
  }
479
493
  const priorCall = pendingToolCalls.get(message.toolCallId);
480
494
  if (priorCall) {
481
- current.steps.push(toolCallStep(priorCall, message));
495
+ current.steps.push(toolCallStep(priorCall, requestScope, message));
482
496
  pendingToolCalls.delete(message.toolCallId);
483
497
  } else {
484
498
  current.steps.push(storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
@@ -486,7 +500,7 @@ function conversationTurns(request: CursorRunRequest, historyMessageStart = 0):
486
500
  case: "assistantMessage",
487
501
  value: create(AssistantMessageSchema, { text: toolResultToText(message) }),
488
502
  },
489
- }))));
503
+ })), requestScope));
490
504
  }
491
505
  continue;
492
506
  }
@@ -495,7 +509,7 @@ function conversationTurns(request: CursorRunRequest, historyMessageStart = 0):
495
509
  userMessage: storeCursorBlob(toBinary(UserMessageSchema, create(UserMessageSchema, {
496
510
  text: contentText(message),
497
511
  messageId: crypto.randomUUID(),
498
- }))),
512
+ })), requestScope),
499
513
  steps: [],
500
514
  };
501
515
  }
@@ -537,6 +551,7 @@ function modelVisibleToolText(definition: McpToolDefinition): string {
537
551
 
538
552
  export interface PreparedCursorRunRequest {
539
553
  bytes: Uint8Array;
554
+ blobRequestScope: CursorBlobRequestScopeToken;
540
555
  /** Only present when the caller asked for it; see prepareCursorRunRequest(). */
541
556
  estimatedInputTokens?: number;
542
557
  }
@@ -552,8 +567,9 @@ export interface PreparedCursorRunRequest {
552
567
  * it honest: history the pruner dropped and tools the filter removed are already
553
568
  * gone by this point.
554
569
  */
555
- export function prepareCursorRunRequest(
570
+ function buildPreparedCursorRunRequest(
556
571
  request: CursorRunRequest,
572
+ requestScope: CursorBlobRequestScopeToken,
557
573
  options?: { estimateInputTokens?: boolean },
558
574
  ): PreparedCursorRunRequest {
559
575
  const rawText = activePromptText(request);
@@ -585,9 +601,9 @@ export function prepareCursorRunRequest(
585
601
  }),
586
602
  },
587
603
  });
588
- const rootPromptMessagesState = rootPromptMessages(request);
604
+ const rootPromptMessagesState = rootPromptMessages(request, requestScope);
589
605
  const rootPromptMessageIds = rootPromptMessagesState.ids;
590
- const turnIds = conversationTurns(request, rootPromptMessagesState.historyMessageStart);
606
+ const turnIds = conversationTurns(request, requestScope, rootPromptMessagesState.historyMessageStart);
591
607
  // Hoisted out of the mcp_tools spread below so the estimate can read the same
592
608
  // filtered definitions the wire carries. Both helpers are pure.
593
609
  const visibleTools = cursorToolsForActivePrompt(request.tools, rawText, request.toolChoice);
@@ -661,7 +677,7 @@ export function prepareCursorRunRequest(
661
677
  message: { case: "runRequest", value: runRequest },
662
678
  });
663
679
  const bytes = toBinary(AgentClientMessageSchema, message);
664
- if (!options?.estimateInputTokens) return { bytes };
680
+ if (!options?.estimateInputTokens) return { bytes, blobRequestScope: requestScope };
665
681
 
666
682
  // Same instances that produced `bytes`, so the estimate cannot count history or
667
683
  // tools the payload dropped — the defect that blocked PR #376.
@@ -672,10 +688,26 @@ export function prepareCursorRunRequest(
672
688
  ];
673
689
  return {
674
690
  bytes,
691
+ blobRequestScope: requestScope,
675
692
  estimatedInputTokens: estimateTokens(modelVisibleParts.join("\n"), request.modelId),
676
693
  };
677
694
  }
678
695
 
696
+ export function prepareCursorRunRequest(
697
+ request: CursorRunRequest,
698
+ options?: { estimateInputTokens?: boolean },
699
+ ): PreparedCursorRunRequest {
700
+ const requestScope = createCursorBlobRequestScope();
701
+ try {
702
+ const prepared = buildPreparedCursorRunRequest(request, requestScope, options);
703
+ sealCursorBlobRequestScope(requestScope);
704
+ return prepared;
705
+ } catch (error) {
706
+ releaseCursorBlobRequestScope(requestScope);
707
+ throw error;
708
+ }
709
+ }
710
+
679
711
  /** Back-compat wrapper: callers that only need the wire bytes. */
680
712
  export function encodeCursorRunRequest(request: CursorRunRequest): Uint8Array {
681
713
  return prepareCursorRunRequest(request).bytes;
@@ -10,7 +10,7 @@ import type {
10
10
  import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
11
11
  import type { CursorRequestMessage, CursorRunRequest } from "./types";
12
12
  import { cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
13
- import { cursorEffortSuffix } from "./effort-map";
13
+ import { cursorEffortSuffix, cursorWireModelIdWithEffort } from "./effort-map";
14
14
  import {
15
15
  cursorMcpToolEncodedSize,
16
16
  cursorMcpToolsEncodedSize,
@@ -127,7 +127,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): { modelId:
127
127
  const selection = cursorWireModelSelection(modelId);
128
128
  const id = selection.modelId;
129
129
  const suffix = cursorEffortSuffix(id, reasoning);
130
- return { ...selection, modelId: suffix ? `${id}-${suffix}` : id };
130
+ return { ...selection, modelId: suffix ? cursorWireModelIdWithEffort(id, suffix) : id };
131
131
  }
132
132
 
133
133
  function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
@@ -1,5 +1,6 @@
1
1
  import type { OcxProviderConfig } from "../../types";
2
2
  import type { CursorClientMessage, CursorRunRequest, CursorServerMessage } from "./types";
3
+ import type { TranslatorBudget } from "../../lib/translator-budget";
3
4
 
4
5
  export interface CursorTransport {
5
6
  run(request: CursorRunRequest, signal?: AbortSignal): AsyncIterable<CursorServerMessage>;
@@ -15,6 +16,7 @@ export interface CursorTransport {
15
16
 
16
17
  export interface CursorTransportFactoryInput {
17
18
  provider: OcxProviderConfig;
19
+ translatorBudget: TranslatorBudget;
18
20
  headers?: Headers;
19
21
  /** Pre-first-frame deadline (dial + first server frame). Defaults to 30s when omitted. */
20
22
  firstFrameTimeoutMs?: number;
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { AdapterEvent, OcxProviderConfig } from "../types";
3
3
  import type { ProviderAdapter } from "./base";
4
+ import { isTranslatorBudgetExceededError } from "../lib/translator-budget";
4
5
  import { cursorExecDeniedMessage, cursorRequestDeclaresFullAccess } from "./cursor/exec-policy";
5
6
  import { isCursorBenignCancelError, isCursorInvalidArgumentError, safeCursorErrorMessage } from "./cursor/cursor-errors";
6
7
  import { isCursorExternalWireModel } from "./cursor/discovery";
@@ -78,7 +79,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
78
79
  }
79
80
  try {
80
81
  const makeTransport = deps.createTransport ?? createLiveCursorTransport;
81
- const kv = deps.kv ?? createCursorKvStore();
82
+ const kv = deps.kv ?? createCursorKvStore({}, incoming.translatorBudget);
82
83
  const rekeyContextUsage = deps.rekeyContextUsage ?? rekeyCursorContextUsage;
83
84
  // Namespace thread→conversation derivation by the authenticated Cursor credential so
84
85
  // shared-proxy tenants with different Cursor accounts cannot collide on a parent thread id.
@@ -118,6 +119,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
118
119
  {
119
120
  provider,
120
121
  headers: incoming.headers,
122
+ translatorBudget: incoming.translatorBudget,
121
123
  requestDeclaresFullAccess: cursorRequestDeclaresFullAccess(activeRequest),
122
124
  },
123
125
  activeRequest,
@@ -178,7 +180,16 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
178
180
  } catch (err) {
179
181
  if (isCursorBenignCancelError(err)) return;
180
182
  const partialUsage = (err as { partialUsage?: import("../types").OcxUsage }).partialUsage;
181
- emit({ type: "error", message: safeCursorTransportError(err), ...(partialUsage ? { usage: partialUsage } : {}) });
183
+ emit({
184
+ type: "error",
185
+ message: isTranslatorBudgetExceededError(err)
186
+ ? "upstream translation buffer exceeded the safe limit"
187
+ : safeCursorTransportError(err),
188
+ ...(isTranslatorBudgetExceededError(err)
189
+ ? { status: 502, errorType: "upstream_error", code: "translation_buffer_limit" }
190
+ : {}),
191
+ ...(partialUsage ? { usage: partialUsage } : {}),
192
+ });
182
193
  }
183
194
  },
184
195
  };