@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
@@ -10,7 +10,12 @@ import {
10
10
  import { parseRequest } from "../../responses/parser";
11
11
  import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
12
12
  import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
13
- import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
13
+ import {
14
+ expandPreviousResponseInput,
15
+ previousResponseProviderState,
16
+ previousResponseReplayFailure,
17
+ rememberResponseState,
18
+ } from "../../responses/state";
14
19
  import { routeModel, type RouteResult } from "../../router";
15
20
  import {
16
21
  advanceComboAfterFailure,
@@ -82,6 +87,7 @@ import {
82
87
  } from "../../codex/routing";
83
88
  import { fetchWithResetRetry, fetchWithTransientRetry, applyUpstreamRecoveryInit } from "../../lib/upstream-retry";
84
89
  import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../auth-cors";
90
+ import { createTranslatorBudget, isTranslatorBudgetExceededError, type TranslatorBudget } from "../../lib/translator-budget";
85
91
  import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar";
86
92
  import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
87
93
  import { slugsEquivalent } from "../../providers/slug-codec";
@@ -89,6 +95,7 @@ import { applyOpenAiVirtualModel, resolveOpenAiCompactModel } from "../../provid
89
95
  import { isUsageDebugEnabled } from "../../usage/debug";
90
96
  import { readJsonRequestBody, DecompressedBodyTooLargeError, UnsupportedContentEncodingError } from "../request-decompress";
91
97
  import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve";
98
+ import type { InboundWire } from "../../providers/registry";
92
99
  import { hasKeyPoolFailover, rotateProviderTransportOn429 } from "../../providers/key-failover";
93
100
  import { shouldAttemptImageTierRetry } from "../image-retry";
94
101
  import { resolveProviderTransport } from "../../providers/xai-transport";
@@ -96,6 +103,7 @@ import type { WsData } from "../ws-bridge";
96
103
  import { registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle";
97
104
  import { redactSecretString } from "../../lib/redact";
98
105
  import { readBoundedResponseBody } from "../../lib/bounded-body";
106
+ import type { AdmissionLease } from "../../lib/admission";
99
107
  import { supportedLadderFor } from "../effort-policy";
100
108
  import { isThreadSpawnRequest } from "../effort-policy";
101
109
  import {
@@ -127,13 +135,16 @@ import {
127
135
  consumeForInspection,
128
136
  consumeForResponseLogMetadata,
129
137
  createSseInspector,
138
+ isEagerRelaySseResponse,
139
+ isNativePassthroughSseResponse,
140
+ markEagerRelaySseResponse,
130
141
  markNativePassthroughSseResponse,
131
142
  relaySseWithFailedTail,
132
143
  relayWithAbort,
133
144
  sanitizePassthroughHeaders,
134
145
  } from "../relay";
135
146
  import { relaySseEagerBounded } from "../relay-eager";
136
- import { decideEagerRelay } from "../../lib/bun-stream-caps";
147
+ import { selectEagerPath } from "../../lib/bun-stream-caps";
137
148
  import { cancelBodyOnAbort } from "../../lib/abort";
138
149
  import {
139
150
  createResponsesItemIdPayloadRewrite,
@@ -168,6 +179,7 @@ export function sidecarOutcomeRecorder(
168
179
  ? outcome => recordCodexUpstreamOutcome(config, authCtx.accountId, outcome, {
169
180
  threadId,
170
181
  probeLeaseId: authCtx.probeLeaseId,
182
+ writerGeneration: authCtx.writerGeneration,
171
183
  })
172
184
  : undefined;
173
185
  }
@@ -248,6 +260,11 @@ interface CodexPoolAccountRetryArgs {
248
260
  abortSignal?: AbortSignal;
249
261
  onCodexAuthContextResolved?: (ctx: CodexAuthContext) => void;
250
262
  deferCodexResetDerivedCooldown?: boolean;
263
+ // Narrowed subset of HandleResponsesOptions: the retry rebuilds the adapter, so it
264
+ // needs the inbound scope or the retry could land on a different wire than the
265
+ // first attempt.
266
+ inboundWire?: InboundWire;
267
+ translatorBudget: TranslatorBudget;
251
268
  };
252
269
  firstAuthCtx: Extract<CodexAuthContext, { kind: "pool" | "main-pool" }>;
253
270
  firstResponse: Response;
@@ -309,6 +326,7 @@ async function retryCodexPoolOnAlternateAccount(
309
326
  req, config, route, parsed, logCtx, options, firstAuthCtx, firstResponse,
310
327
  outcomeStatus, upstream, connectMs, passthroughEstimate, stream,
311
328
  } = args;
329
+ const inboundWire = options.inboundWire ?? "responses";
312
330
  let retryAuthCtx: CodexAuthContext | undefined;
313
331
  try {
314
332
  retryAuthCtx = await resolveCodexAuthContext(
@@ -331,7 +349,11 @@ async function retryCodexPoolOnAlternateAccount(
331
349
  const quotaMeta = codexQuotaOutcomeMeta(firstResponse);
332
350
  if (outcomeStatus === 429 || outcomeStatus === 402) {
333
351
  const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
334
- applyAccountQuotaFromUpstreamHeaders(firstAuthCtx.accountId, firstResponse.headers);
352
+ applyAccountQuotaFromUpstreamHeaders(
353
+ firstAuthCtx.accountId,
354
+ firstResponse.headers,
355
+ firstAuthCtx.writerGeneration,
356
+ );
335
357
  }
336
358
  if (!shouldDeferCodexResetDerivedCooldown(firstResponse, options.deferCodexResetDerivedCooldown)) {
337
359
  recordCodexUpstreamOutcome(config, firstAuthCtx.accountId, outcomeStatus, {
@@ -340,6 +362,7 @@ async function retryCodexPoolOnAlternateAccount(
340
362
  modelId: route.modelId,
341
363
  probeLeaseId: codexProbeLeaseId(firstAuthCtx),
342
364
  probeQuotaScope: codexProbeQuotaScope(firstAuthCtx),
365
+ writerGeneration: firstAuthCtx.writerGeneration,
343
366
  // Retry already advanced the RR ring via excludeAccountId — reuse for promotion.
344
367
  ...(retryAuthCtx.accountId ? { promoteAccountId: retryAuthCtx.accountId } : {}),
345
368
  });
@@ -352,10 +375,13 @@ async function retryCodexPoolOnAlternateAccount(
352
375
  "pool",
353
376
  );
354
377
  const retryAdapter = resolveAdapter(
355
- resolveWireProtocolOverride(route.providerName, route.modelId, retryProvider),
378
+ resolveWireProtocolOverride(route.providerName, route.modelId, retryProvider, inboundWire),
356
379
  config.cacheRetention,
357
380
  );
358
- const request = await retryAdapter.buildRequest(parsed, { headers: retryHeaders });
381
+ const request = await retryAdapter.buildRequest(parsed, {
382
+ headers: retryHeaders,
383
+ translatorBudget: options.translatorBudget,
384
+ });
359
385
  recordAdapterReasoning(logCtx, request);
360
386
 
361
387
  await firstResponse.body?.cancel().catch(() => undefined);
@@ -391,6 +417,8 @@ async function retryCodexPoolOnAlternateAccount(
391
417
  } catch (error) {
392
418
  // Attribute the transport failure to the alternate account (already selected).
393
419
  return { kind: "transport", error, authCtx: retryAuthCtx };
420
+ } finally {
421
+ request.releaseBodyObservation?.();
394
422
  }
395
423
  }
396
424
 
@@ -415,6 +443,7 @@ export function codexForwardTerminalOutcomeRecorder(
415
443
  modelId,
416
444
  probeLeaseId: codexProbeLeaseId(authCtx),
417
445
  probeQuotaScope: codexProbeQuotaScope(authCtx),
446
+ writerGeneration: authCtx.writerGeneration,
418
447
  });
419
448
  return;
420
449
  }
@@ -435,6 +464,7 @@ export function codexForwardTerminalOutcomeRecorder(
435
464
  modelId,
436
465
  probeLeaseId: codexProbeLeaseId(authCtx),
437
466
  probeQuotaScope: codexProbeQuotaScope(authCtx),
467
+ writerGeneration: authCtx.writerGeneration,
438
468
  });
439
469
  };
440
470
  }
@@ -442,6 +472,11 @@ export function codexForwardTerminalOutcomeRecorder(
442
472
 
443
473
 
444
474
  export function decodeRequestErrorResponse(err: unknown, label: string): Response {
475
+ if (isTranslatorBudgetExceededError(err)) {
476
+ return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
477
+ code: "translation_buffer_limit",
478
+ });
479
+ }
445
480
  if (err instanceof UnsupportedContentEncodingError) {
446
481
  return formatErrorResponse(415, "invalid_request_error", err.message);
447
482
  }
@@ -479,6 +514,7 @@ export interface ConsumedComboFailure {
479
514
 
480
515
 
481
516
  export interface HandleResponsesOptions {
517
+ turnAdmissionLease?: AdmissionLease;
482
518
  forceEmptyResponseId?: boolean;
483
519
  abortSignal?: AbortSignal;
484
520
  /** One-shot TTFT callback: first non-empty model output observed (WP4). */
@@ -493,12 +529,22 @@ export interface HandleResponsesOptions {
493
529
  * (system/tools hash), not a per-session id — do not use it for Anthropic pool affinity.
494
530
  */
495
531
  promptCacheKeyIsSharedCohort?: boolean;
532
+ /**
533
+ * Wire protocol the ORIGINAL client spoke. The Chat and Anthropic surfaces translate
534
+ * their body into a Responses shape and replay through this function, so without an
535
+ * explicit value the replay would look like a native Responses request and an
536
+ * inbound-scoped registry wire default would fire for a client that never asked for
537
+ * it. Omitted means a genuine Responses inbound.
538
+ */
539
+ inboundWire?: InboundWire;
496
540
  /** Internal recursion guard; callers outside this module must not set it. */
497
541
  comboAttempt?: boolean;
498
542
  /** Internal combo handoff: allow a later same-provider model after a reset-derived 429/402. */
499
543
  deferCodexResetDerivedCooldown?: boolean;
500
544
  /** 030-owned handoff when a child consumed the original failure under bounds. */
501
545
  onConsumedComboFailure?: (failure: ConsumedComboFailure) => void;
546
+ /** Caller-owned for Chat/Claude replay; omitted only at genuine Responses ingress. */
547
+ translatorBudget?: TranslatorBudget;
502
548
  }
503
549
 
504
550
 
@@ -729,8 +775,9 @@ async function applyFinalRouteRequestNormalization(args: {
729
775
  config: OcxConfig;
730
776
  req: Request;
731
777
  logCtx: RequestLogContext;
778
+ inboundWire: InboundWire;
732
779
  }): Promise<void> {
733
- const { parsed, route, config, req, logCtx } = args;
780
+ const { parsed, route, config, req, logCtx, inboundWire } = args;
734
781
 
735
782
  // Apply the routed model id upstream: routing may strip a "<provider>/" namespace.
736
783
  if (route.modelId !== parsed.modelId) {
@@ -741,7 +788,7 @@ async function applyFinalRouteRequestNormalization(args: {
741
788
  }
742
789
  // Settle the wire once so logging, fast-mode, auth, and sidecars read the adapter
743
790
  // this request will actually use (#404).
744
- route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider);
791
+ route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
745
792
  logCtx.model = route.modelId;
746
793
  logCtx.provider = route.providerName;
747
794
  logCtx.providerAdapter = route.provider.adapter;
@@ -978,7 +1025,7 @@ export async function handleComboResponses(
978
1025
  );
979
1026
  (logCtx.attempts ??= []).push(attempt);
980
1027
  attemptRetained = true;
981
- noteComboSuccess(comboId, combo, pick.target);
1028
+ noteComboSuccess(comboId, combo, pick.target, pick.writerGeneration);
982
1029
  Object.assign(logCtx, childLog, {
983
1030
  requestedModel,
984
1031
  model: requestedModel,
@@ -1051,15 +1098,81 @@ export async function handleComboResponses(
1051
1098
 
1052
1099
 
1053
1100
 
1101
+ function finalizeOwnedTranslatorBudget(response: Response, budget: TranslatorBudget): Response {
1102
+ if (!response.body) {
1103
+ budget.dispose();
1104
+ return response;
1105
+ }
1106
+ const reader = response.body.getReader();
1107
+ let finalized = false;
1108
+ const finalize = () => {
1109
+ if (finalized) return;
1110
+ finalized = true;
1111
+ budget.dispose();
1112
+ };
1113
+ const body = new ReadableStream<Uint8Array>({
1114
+ async pull(controller) {
1115
+ try {
1116
+ const result = await reader.read();
1117
+ if (result.done) {
1118
+ finalize();
1119
+ controller.close();
1120
+ } else {
1121
+ controller.enqueue(result.value);
1122
+ }
1123
+ } catch (error) {
1124
+ finalize();
1125
+ controller.error(error);
1126
+ }
1127
+ },
1128
+ async cancel(reason) {
1129
+ try { await reader.cancel(reason); } finally { finalize(); }
1130
+ },
1131
+ });
1132
+ const finalizedResponse = new Response(body, {
1133
+ status: response.status,
1134
+ statusText: response.statusText,
1135
+ headers: response.headers,
1136
+ });
1137
+ if (isNativePassthroughSseResponse(response)) {
1138
+ markNativePassthroughSseResponse(finalizedResponse);
1139
+ }
1140
+ if (isEagerRelaySseResponse(response)) {
1141
+ markEagerRelaySseResponse(finalizedResponse);
1142
+ }
1143
+ return finalizedResponse;
1144
+ }
1145
+
1054
1146
  export async function handleResponses(
1055
1147
  req: Request,
1056
1148
  config: OcxConfig,
1057
1149
  logCtx: RequestLogContext,
1058
1150
  options: HandleResponsesOptions = {},
1059
1151
  ): Promise<Response> {
1152
+ const ownsBudget = options.translatorBudget === undefined;
1153
+ const translatorBudget = options.translatorBudget ?? createTranslatorBudget();
1154
+ try {
1155
+ const response = await handleResponsesInner(req, config, logCtx, { ...options, translatorBudget });
1156
+ return ownsBudget ? finalizeOwnedTranslatorBudget(response, translatorBudget) : response;
1157
+ } catch (error) {
1158
+ if (ownsBudget) translatorBudget.dispose();
1159
+ throw error;
1160
+ }
1161
+ }
1162
+
1163
+ async function handleResponsesInner(
1164
+ req: Request,
1165
+ config: OcxConfig,
1166
+ logCtx: RequestLogContext,
1167
+ options: HandleResponsesOptions & { translatorBudget: TranslatorBudget },
1168
+ ): Promise<Response> {
1169
+ // The Chat and Anthropic surfaces replay through here with a Responses-shaped body,
1170
+ // so an omitted value means a genuine Responses inbound.
1171
+ const inboundWire = options.inboundWire ?? "responses";
1172
+ const translatorBudget = options.translatorBudget;
1060
1173
  let body: unknown;
1061
1174
  try {
1062
- body = await readJsonRequestBody(req);
1175
+ body = await readJsonRequestBody(req, translatorBudget);
1063
1176
  } catch (err) {
1064
1177
  return decodeRequestErrorResponse(err, "responses");
1065
1178
  }
@@ -1072,6 +1185,13 @@ export async function handleResponses(
1072
1185
  );
1073
1186
  const originalBody = body;
1074
1187
  body = expandPreviousResponseInput(body);
1188
+ if (previousResponseReplayFailure(body)) {
1189
+ return formatErrorResponse(
1190
+ 400,
1191
+ "previous_response_not_found",
1192
+ "Continuation state is unavailable or corrupt; resend the full conversation without previous_response_id.",
1193
+ );
1194
+ }
1075
1195
  const previousResponseInputExpanded = body !== originalBody;
1076
1196
 
1077
1197
  // Spawn-message compatibility (both directions): agent_message task payloads ride in
@@ -1090,14 +1210,21 @@ export async function handleResponses(
1090
1210
  }
1091
1211
 
1092
1212
  let parsed;
1213
+ let toolBridgeMaps: ReturnType<typeof buildToolBridgeMaps>;
1093
1214
  try {
1094
1215
  parsed = parseRequest(body);
1216
+ toolBridgeMaps = buildToolBridgeMaps(parsed, translatorBudget);
1095
1217
  if (previousResponseInputExpanded) parsed._previousResponseInputExpanded = true;
1096
1218
  parsed._providerContinuation = previousResponseProviderState(parsed.previousResponseId);
1097
1219
  parsed._cursorConversationId = parsed._providerContinuation?.cursor?.conversationId;
1098
1220
  const clientThreadId = req.headers.get("x-codex-parent-thread-id")?.trim();
1099
1221
  if (clientThreadId) parsed._clientThreadId = clientThreadId;
1100
1222
  } catch (err) {
1223
+ if (isTranslatorBudgetExceededError(err)) {
1224
+ return formatErrorResponse(413, "request_too_large", "request translation buffer exceeded the safe limit", {
1225
+ code: "translation_buffer_limit",
1226
+ });
1227
+ }
1101
1228
  return formatErrorResponse(400, "invalid_request_error", err instanceof Error ? err.message : String(err));
1102
1229
  }
1103
1230
  // Prefer a pre-populated id (routed Claude) over Responses headers that may be
@@ -1220,7 +1347,7 @@ export async function handleResponses(
1220
1347
  );
1221
1348
  }
1222
1349
 
1223
- await applyFinalRouteRequestNormalization({ parsed, route, config, req, logCtx });
1350
+ await applyFinalRouteRequestNormalization({ parsed, route, config, req, logCtx, inboundWire });
1224
1351
 
1225
1352
  {
1226
1353
  const finalAuth = await resolveResponsesCodexAuth(req, config, route, options);
@@ -1310,7 +1437,7 @@ export async function handleResponses(
1310
1437
  parsed.options.promptCacheKey,
1311
1438
  route.providerName === "github-copilot" ? getOAuthCredentialApiBaseUrl(route.providerName) : undefined,
1312
1439
  );
1313
- const adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider);
1440
+ const adapterProvider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire);
1314
1441
  const adapter = resolveAdapter(adapterProvider, config.cacheRetention);
1315
1442
  logCtx.providerAdapter = adapter.name;
1316
1443
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name);
@@ -1353,11 +1480,18 @@ export async function handleResponses(
1353
1480
  const visionPlan = planVisionSidecar(config, route.provider, route.modelId, parsed, openAiSidecar);
1354
1481
  const recordSidecarOutcome = openAiSidecar?.recordOutcome;
1355
1482
  if (visionPlan) {
1356
- await describeImagesInPlace(parsed, visionPlan, openAiSidecar?.headers ?? selectedForwardHeaders, options.abortSignal, recordSidecarOutcome);
1483
+ await describeImagesInPlace(
1484
+ parsed,
1485
+ visionPlan,
1486
+ openAiSidecar?.headers ?? selectedForwardHeaders,
1487
+ options.abortSignal,
1488
+ recordSidecarOutcome,
1489
+ translatorBudget,
1490
+ );
1357
1491
  } else if (modelInList(route.provider.noVisionModels, route.modelId)) {
1358
1492
  // Sidecar-covered model but NO plan (no forward provider / missing forwarded auth / sidecar
1359
1493
  // disabled): fail closed — never forward raw images to a text-only upstream.
1360
- stripImagesInPlace(parsed);
1494
+ stripImagesInPlace(parsed, translatorBudget);
1361
1495
  }
1362
1496
 
1363
1497
  const recordTerminalOutcomes = options.recordTerminalOutcomes !== false;
@@ -1407,7 +1541,7 @@ export async function handleResponses(
1407
1541
  if ("passthrough" in adapter && adapter.passthrough && !routedCompaction) {
1408
1542
  const imageGenCallAliases = route.provider.authMode === "forward"
1409
1543
  ? new Map<string, { namespace: string; name: string }>()
1410
- : imageGenToolCallAliases(buildToolBridgeMaps(parsed).toolNsMap, parsed._rawBody);
1544
+ : imageGenToolCallAliases(toolBridgeMaps.toolNsMap, parsed._rawBody, translatorBudget);
1411
1545
  // Local continuation cache for the ChatGPT passthrough. Codex WS turns chain with
1412
1546
  // previous_response_id, ocx converts them to internal HTTP requests, and the ChatGPT Codex
1413
1547
  // REST backend rejects the parameter — the adapter strips it in forward mode, so the ONLY
@@ -1429,7 +1563,7 @@ export async function handleResponses(
1429
1563
  + `(model ${parsed.modelId}); forwarding without it — earlier turns may be missing from this request`,
1430
1564
  );
1431
1565
  }
1432
- let request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders });
1566
+ let request = await adapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
1433
1567
  recordAdapterReasoning(logCtx, request);
1434
1568
  const passthroughEstimate = typeof request.usageLog?.inputTokens === "number"
1435
1569
  ? request.usageLog.inputTokens
@@ -1454,6 +1588,7 @@ export async function handleResponses(
1454
1588
  modelId: route.modelId,
1455
1589
  probeLeaseId: codexProbeLeaseId(authCtx),
1456
1590
  probeQuotaScope: codexProbeQuotaScope(authCtx),
1591
+ writerGeneration: authCtx.writerGeneration,
1457
1592
  });
1458
1593
  }
1459
1594
  const msg = outcome === "timeout"
@@ -1478,6 +1613,8 @@ export async function handleResponses(
1478
1613
  );
1479
1614
  } catch (err) {
1480
1615
  return transportFailureResponse(err);
1616
+ } finally {
1617
+ request.releaseBodyObservation?.();
1481
1618
  }
1482
1619
 
1483
1620
  if (usesCodexForwardPoolAuth(authCtx, route.provider)) {
@@ -1550,7 +1687,11 @@ export async function handleResponses(
1550
1687
  // Prefer primary when present, fall back to secondary for compatibility.
1551
1688
  const quotaMeta = codexQuotaOutcomeMeta(upstreamResponse);
1552
1689
  const { applyAccountQuotaFromUpstreamHeaders } = await import("../../codex/auth-api");
1553
- applyAccountQuotaFromUpstreamHeaders(authCtx.accountId, upstreamResponse.headers);
1690
+ applyAccountQuotaFromUpstreamHeaders(
1691
+ authCtx.accountId,
1692
+ upstreamResponse.headers,
1693
+ authCtx.writerGeneration,
1694
+ );
1554
1695
  if (terminalBodyWillRecord) {
1555
1696
  options.setTerminalOutcomeRecorder?.((status, httpStatusOverride) => {
1556
1697
  terminalRecorder(status, httpStatusOverride);
@@ -1582,6 +1723,7 @@ export async function handleResponses(
1582
1723
  modelId: route.modelId,
1583
1724
  probeLeaseId: codexProbeLeaseId(authCtx),
1584
1725
  probeQuotaScope: codexProbeQuotaScope(authCtx),
1726
+ writerGeneration: authCtx.writerGeneration,
1585
1727
  });
1586
1728
  }
1587
1729
  }
@@ -1607,21 +1749,25 @@ export async function handleResponses(
1607
1749
  // async-pull segfault on Windows. Branch[0] goes directly to the Response (Bun
1608
1750
  // native relay, never enters JS Sink.write); branch[1] is consumed in the
1609
1751
  // background for terminal-outcome/quota inspection only.
1610
- // #314 alternative shape: on win32 (no repair) with a runtime carrying the
1611
- // Bun#32111 fix or explicit `streamMode: "eager-relay"` opt-in — the tee
1612
- // is skipped entirely and relaySseEagerBounded provides a single eager
1613
- // bounded reader with inline inspection (see src/server/relay-eager.ts and
1614
- // devlog/_plan/260723_win_mem_safestream/020). Default on the bundled
1615
- // known-bad runtime remains the tee path below.
1752
+ // #314 alternative shape: win32 no-rewrite traffic follows the runtime/config
1753
+ // gate; darwin no-rewrite traffic joins it only for explicit
1754
+ // `streamMode: "eager-relay"` opt-in. Darwin `auto` always stays tee. The
1755
+ // eager shape skips tee and uses one bounded reader with inline inspection
1756
+ // (src/server/relay-eager.ts; policy:
1757
+ // devlog/_plan/260731_macos_rss_retention/100_darwin_eager_optin.md).
1758
+ // The bundled known-bad runtime remains on tee by default on both platforms.
1616
1759
  if (isEventStream && upstreamResponse.body) {
1617
1760
  const repairConfig = route.provider.responsesItemIdRepair;
1618
1761
  const needsClientRewrite = imageGenCallAliases.size > 0 || hasResponsesItemIdRepair(repairConfig);
1619
- const winNoClientRewrite = process.platform === "win32" && !needsClientRewrite;
1620
- const eagerDecision = winNoClientRewrite ? decideEagerRelay(config.streamMode ?? "auto") : null;
1621
- if (eagerDecision?.useEagerRelay) {
1762
+ const eagerPath = selectEagerPath(
1763
+ process.platform,
1764
+ needsClientRewrite,
1765
+ config.streamMode ?? "auto",
1766
+ );
1767
+ if (eagerPath?.useEagerRelay) {
1622
1768
  const turnAc = new AbortController();
1623
1769
  linkAbortSignal(upstream, turnAc.signal);
1624
- registerTurn(turnAc);
1770
+ registerTurn(turnAc, options.turnAdmissionLease);
1625
1771
  const reportNativeTerminal = recordTerminalOutcomes
1626
1772
  ? (status: ResponsesTerminalStatus, httpStatusOverride?: number) => {
1627
1773
  terminalRecorder?.(status, httpStatusOverride);
@@ -1653,6 +1799,7 @@ export async function handleResponses(
1653
1799
  const eagerBody = relaySseEagerBounded(upstreamResponse.body, turnAc, {
1654
1800
  inspectChunk: chunk => inspector.feed(chunk),
1655
1801
  finishInspection: () => inspector.finish(),
1802
+ disposeInspection: () => inspector.dispose(),
1656
1803
  sawTerminal: () => inspector.reported(),
1657
1804
  onSynthetic: kind => {
1658
1805
  if (!reportNativeTerminal) return;
@@ -1668,18 +1815,26 @@ export async function handleResponses(
1668
1815
  onClientCancel: () => options.onNativePassthroughCancel?.(),
1669
1816
  onDone: () => unregisterTurn(turnAc),
1670
1817
  });
1671
- // The eager branch is reachable only through winNoClientRewrite, so it must stay a pure
1672
- // native relay without an image-gen or item-id JS pull wrapper on win32 (Bun#32111).
1818
+ // selectEagerPath admits only no-rewrite traffic, so this stays a pure native relay on
1819
+ // both eligible platforms; win32 must never enter an image/item-id JS pull wrapper (#32111).
1673
1820
  if (!headers.has("content-type")) headers.set("content-type", "text/event-stream");
1674
- return markNativePassthroughSseResponse(new Response(eagerBody, {
1675
- status: upstreamResponse.status,
1676
- headers,
1677
- }));
1821
+ return markEagerRelaySseResponse(
1822
+ markNativePassthroughSseResponse(new Response(eagerBody, {
1823
+ status: upstreamResponse.status,
1824
+ headers,
1825
+ })),
1826
+ );
1678
1827
  }
1679
1828
  const [nativeBody, inspectBody] = upstreamResponse.body.tee();
1680
1829
  const turnAc = new AbortController();
1830
+ const clientGone = new AbortController();
1681
1831
  linkAbortSignal(upstream, turnAc.signal);
1682
- registerTurn(turnAc);
1832
+ registerTurn(turnAc, options.turnAdmissionLease);
1833
+ const inspectionConsumerOptions = {
1834
+ clientGoneSignal: clientGone.signal,
1835
+ drainBounds: { ms: 15_000, bytes: 32 * 1024 * 1024 },
1836
+ upstream,
1837
+ };
1683
1838
  if (recordTerminalOutcomes) {
1684
1839
  // A real terminal was parsed from the (teed) inspection stream — record it as the outcome
1685
1840
  // even if the client has already disconnected: the turn genuinely reached that terminal, so
@@ -1714,6 +1869,7 @@ export async function handleResponses(
1714
1869
  () => options.onNativePassthroughCancel?.(),
1715
1870
  rememberPassthroughResponse,
1716
1871
  options.onFirstOutput,
1872
+ inspectionConsumerOptions,
1717
1873
  );
1718
1874
  } else {
1719
1875
  consumeForResponseLogMetadata(
@@ -1723,6 +1879,7 @@ export async function handleResponses(
1723
1879
  () => unregisterTurn(turnAc),
1724
1880
  rememberPassthroughResponse,
1725
1881
  options.onFirstOutput,
1882
+ inspectionConsumerOptions,
1726
1883
  );
1727
1884
  }
1728
1885
  if (!headers.has("content-type")) headers.set("content-type", "text/event-stream");
@@ -1733,15 +1890,15 @@ export async function handleResponses(
1733
1890
  const payloadRewrites = [
1734
1891
  createImageGenCallRestoreRewrite(imageGenCallAliases),
1735
1892
  hasResponsesItemIdRepair(repairConfig)
1736
- ? createResponsesItemIdPayloadRewrite(repairConfig!)
1893
+ ? createResponsesItemIdPayloadRewrite(repairConfig!, translatorBudget)
1737
1894
  : undefined,
1738
1895
  ].filter((rewrite): rewrite is NonNullable<typeof rewrite> => rewrite !== undefined);
1739
1896
  const rewrittenBody = payloadRewrites.length > 0
1740
- ? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites))
1897
+ ? relaySseWithPayloadRewrite(nativeBody, composeSsePayloadRewrites(...payloadRewrites), translatorBudget)
1741
1898
  : nativeBody;
1742
1899
  const clientBody = process.platform === "win32" && !needsClientRewrite
1743
1900
  ? nativeBody
1744
- : relaySseWithFailedTail(rewrittenBody, upstream);
1901
+ : relaySseWithFailedTail(rewrittenBody, upstream, reason => clientGone.abort(reason));
1745
1902
  return markNativePassthroughSseResponse(new Response(clientBody, {
1746
1903
  status: upstreamResponse.status,
1747
1904
  headers,
@@ -1763,7 +1920,7 @@ export async function handleResponses(
1763
1920
  }
1764
1921
  const body = relayWithAbort(upstreamResponse.body, upstream);
1765
1922
  const turnAc = new AbortController();
1766
- const tracked = body ? trackStreamLifetime(body, turnAc) : null;
1923
+ const tracked = body ? trackStreamLifetime(body, turnAc, undefined, options.turnAdmissionLease) : null;
1767
1924
  return new Response(tracked, {
1768
1925
  status: upstreamResponse.status,
1769
1926
  headers,
@@ -1837,6 +1994,7 @@ export async function handleResponses(
1837
1994
  }
1838
1995
  const imgResponse = await runWithImageBridge({
1839
1996
  parsed, adapter,
1997
+ incomingMeta: { headers: selectedForwardHeaders, abortSignal: options.abortSignal, translatorBudget },
1840
1998
  ...(imgPlan ? { plan: imgPlan } : {}),
1841
1999
  ...(vidPlan ? { videoPlan: vidPlan } : {}),
1842
2000
  forwardHeaders: selectedForwardHeaders,
@@ -1875,7 +2033,7 @@ export async function handleResponses(
1875
2033
  if (!rotated) return null;
1876
2034
  route.provider = rotated;
1877
2035
  return resolveAdapter(
1878
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
2036
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
1879
2037
  config.cacheRetention,
1880
2038
  );
1881
2039
  },
@@ -1891,7 +2049,7 @@ export async function handleResponses(
1891
2049
  });
1892
2050
  if (imgResponse.body) {
1893
2051
  const imgTurnAc = new AbortController();
1894
- return new Response(trackStreamLifetime(imgResponse.body, imgTurnAc), {
2052
+ return new Response(trackStreamLifetime(imgResponse.body, imgTurnAc, undefined, options.turnAdmissionLease), {
1895
2053
  status: imgResponse.status,
1896
2054
  headers: imgResponse.headers,
1897
2055
  });
@@ -1910,6 +2068,7 @@ export async function handleResponses(
1910
2068
  noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens);
1911
2069
  const wsResponse = await runWithWebSearch({
1912
2070
  parsed, adapter,
2071
+ incomingMeta: { headers: selectedForwardHeaders, abortSignal: options.abortSignal, translatorBudget },
1913
2072
  backend: wsPlan.backend,
1914
2073
  forwardProvider: wsPlan.forwardSidecar?.provider,
1915
2074
  anthropicSidecar: wsPlan.anthropicSidecar,
@@ -1942,7 +2101,7 @@ export async function handleResponses(
1942
2101
  if (!rotated) return null;
1943
2102
  route.provider = rotated;
1944
2103
  return resolveAdapter(
1945
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
2104
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
1946
2105
  config.cacheRetention,
1947
2106
  );
1948
2107
  },
@@ -1951,7 +2110,7 @@ export async function handleResponses(
1951
2110
  // in-flight web-search turns instead of skipping them during graceful shutdown.
1952
2111
  if (wsResponse.body) {
1953
2112
  const wsTurnAc = new AbortController();
1954
- return new Response(trackStreamLifetime(wsResponse.body, wsTurnAc), {
2113
+ return new Response(trackStreamLifetime(wsResponse.body, wsTurnAc, undefined, options.turnAdmissionLease), {
1955
2114
  status: wsResponse.status,
1956
2115
  headers: wsResponse.headers,
1957
2116
  });
@@ -1970,7 +2129,7 @@ export async function handleResponses(
1970
2129
  noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens);
1971
2130
  await adapter.runTurn?.(
1972
2131
  parsed,
1973
- { headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal },
2132
+ { headers: selectedForwardHeaders, abortSignal: runTurnAbort.signal, translatorBudget },
1974
2133
  queue.push,
1975
2134
  );
1976
2135
  } catch (err) {
@@ -1988,7 +2147,7 @@ export async function handleResponses(
1988
2147
  }
1989
2148
  };
1990
2149
 
1991
- const { toolNsMap, freeformToolNames, toolSearchToolNames } = buildToolBridgeMaps(parsed);
2150
+ const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
1992
2151
  if (parsed.stream) {
1993
2152
  void runTurn();
1994
2153
  let eventSource: AsyncIterable<AdapterEvent> = queue.stream();
@@ -2009,6 +2168,7 @@ export async function handleResponses(
2009
2168
  queue.close();
2010
2169
  }, 2_000,
2011
2170
  {
2171
+ translatorBudget,
2012
2172
  ...(options.forceEmptyResponseId ? { responseId: "" } : {}),
2013
2173
  stallTimeoutSec: config.stallTimeoutSec,
2014
2174
  hideThinkingSummary: parsed.options.hideThinkingSummary,
@@ -2035,7 +2195,7 @@ export async function handleResponses(
2035
2195
  },
2036
2196
  );
2037
2197
  const bridgeTurnAc = new AbortController();
2038
- const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc);
2198
+ const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, undefined, options.turnAdmissionLease);
2039
2199
  return new Response(trackedSse, {
2040
2200
  headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
2041
2201
  });
@@ -2054,6 +2214,7 @@ export async function handleResponses(
2054
2214
  }
2055
2215
  let providerState: OcxProviderContinuationState | undefined;
2056
2216
  const json = buildResponseJSON(events, parsed.modelId, {
2217
+ translatorBudget,
2057
2218
  hideThinkingSummary: parsed.options.hideThinkingSummary,
2058
2219
  toolNsMap,
2059
2220
  freeformToolNames,
@@ -2084,7 +2245,7 @@ export async function handleResponses(
2084
2245
  const connectMs = config.connectTimeoutMs ?? 200_000;
2085
2246
  let activeAdapter = adapter;
2086
2247
 
2087
- const request = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders });
2248
+ const request = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget });
2088
2249
  recordAdapterReasoning(logCtx, request);
2089
2250
  const inputTokenEstimate = typeof request.usageLog?.inputTokens === "number"
2090
2251
  ? request.usageLog.inputTokens
@@ -2118,6 +2279,8 @@ export async function handleResponses(
2118
2279
  if (options.abortSignal?.aborted) return clientCancelledResponse();
2119
2280
  const msg = describeUpstreamConnectFailure(err, connectMs);
2120
2281
  return formatErrorResponse(502, "upstream_error", msg);
2282
+ } finally {
2283
+ request.releaseBodyObservation?.();
2121
2284
  }
2122
2285
 
2123
2286
  if (!upstreamResponse.ok) {
@@ -2134,6 +2297,7 @@ export async function handleResponses(
2134
2297
  ): Promise<Response | { failed: Response }> => {
2135
2298
  const retryRequest = await activeAdapter.buildRequest(parsed, {
2136
2299
  headers: selectedForwardHeaders,
2300
+ translatorBudget,
2137
2301
  ...(imageTierBias > 0 ? { imageTierBias } : {}),
2138
2302
  });
2139
2303
  recordAdapterReasoning(logCtx, retryRequest);
@@ -2145,11 +2309,15 @@ export async function handleResponses(
2145
2309
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
2146
2310
  noteAttemptSend(logCtx.activeAttempt, retryEstimate, recovery);
2147
2311
  try {
2148
- return activeAdapter.fetchResponse
2149
- ? await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
2150
- : await fetchWithHeaderTimeout(retryRequest.url, {
2312
+ try {
2313
+ return activeAdapter.fetchResponse
2314
+ ? await activeAdapter.fetchResponse(retryRequest, { abortSignal: upstream.signal, timeoutMs: connectMs, stream: parsed.stream })
2315
+ : await fetchWithHeaderTimeout(retryRequest.url, {
2151
2316
  method: retryRequest.method, headers: retryRequest.headers, body: retryRequest.body,
2152
2317
  }, upstream.signal, connectMs, parsed.stream, providerFetch(route.provider));
2318
+ } finally {
2319
+ retryRequest.releaseBodyObservation?.();
2320
+ }
2153
2321
  } catch (err) {
2154
2322
  cleanupUpstreamAbort();
2155
2323
  upstream.abort();
@@ -2188,7 +2356,7 @@ export async function handleResponses(
2188
2356
  );
2189
2357
  route.provider = refreshedProvider;
2190
2358
  activeAdapter = resolveAdapter(
2191
- resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider),
2359
+ resolveWireProtocolOverride(route.providerName, route.modelId, refreshedProvider, inboundWire),
2192
2360
  config.cacheRetention,
2193
2361
  );
2194
2362
  const result = await rebuildAndRefetch("oauth-401");
@@ -2213,7 +2381,7 @@ export async function handleResponses(
2213
2381
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
2214
2382
  route.provider = rotated;
2215
2383
  activeAdapter = resolveAdapter(
2216
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
2384
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2217
2385
  config.cacheRetention,
2218
2386
  );
2219
2387
  const result = await rebuildAndRefetch("key-429");
@@ -2245,7 +2413,7 @@ export async function handleResponses(
2245
2413
  promoteAnthropicActiveAccount(nextAccountId);
2246
2414
  logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
2247
2415
  activeAdapter = resolveAdapter(
2248
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
2416
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2249
2417
  config.cacheRetention,
2250
2418
  );
2251
2419
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
@@ -2320,6 +2488,7 @@ export async function handleResponses(
2320
2488
  try {
2321
2489
  const continuationRequest = await activeAdapter.buildRequest(nextParsed, {
2322
2490
  headers: selectedForwardHeaders,
2491
+ translatorBudget,
2323
2492
  ...(imageTierBias > 0 ? { imageTierBias } : {}),
2324
2493
  });
2325
2494
  recordAdapterReasoning(logCtx, continuationRequest);
@@ -2327,15 +2496,16 @@ export async function handleResponses(
2327
2496
  ? continuationRequest.usageLog.inputTokens
2328
2497
  : undefined;
2329
2498
  if (continuationEstimate !== undefined) logCtx.usageLogInputTokens = continuationEstimate;
2330
- if (activeAdapter.fetchResponse) {
2331
- noteAttemptSend(logCtx.activeAttempt, continuationEstimate);
2332
- response = await activeAdapter.fetchResponse(continuationRequest, {
2499
+ try {
2500
+ if (activeAdapter.fetchResponse) {
2501
+ noteAttemptSend(logCtx.activeAttempt, continuationEstimate);
2502
+ response = await activeAdapter.fetchResponse(continuationRequest, {
2333
2503
  abortSignal: upstream.signal,
2334
2504
  timeoutMs: connectMs,
2335
2505
  stream: nextParsed.stream,
2336
2506
  });
2337
- } else {
2338
- response = await fetchWithResetRetry(
2507
+ } else {
2508
+ response = await fetchWithResetRetry(
2339
2509
  recovery => {
2340
2510
  noteAttemptSend(logCtx.activeAttempt, continuationEstimate, recovery);
2341
2511
  return fetchWithHeaderTimeout(
@@ -2352,7 +2522,10 @@ export async function handleResponses(
2352
2522
  );
2353
2523
  },
2354
2524
  { abortSignal: upstream.signal, label: safeHostLabel(continuationRequest.url) },
2355
- );
2525
+ );
2526
+ }
2527
+ } finally {
2528
+ continuationRequest.releaseBodyObservation?.();
2356
2529
  }
2357
2530
  } catch (error) {
2358
2531
  if (options.abortSignal?.aborted) {
@@ -2374,7 +2547,7 @@ export async function handleResponses(
2374
2547
  try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
2375
2548
  route.provider = rotated;
2376
2549
  activeAdapter = resolveAdapter(
2377
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
2550
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2378
2551
  config.cacheRetention,
2379
2552
  );
2380
2553
  continue;
@@ -2402,7 +2575,7 @@ export async function handleResponses(
2402
2575
  promoteAnthropicActiveAccount(nextAccountId);
2403
2576
  logCtx.provider = formatAnthropicProviderForLog("anthropic", nextAccountId, config);
2404
2577
  activeAdapter = resolveAdapter(
2405
- resolveWireProtocolOverride(route.providerName, route.modelId, route.provider),
2578
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
2406
2579
  config.cacheRetention,
2407
2580
  );
2408
2581
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, activeAdapter.name);
@@ -2442,9 +2615,9 @@ export async function handleResponses(
2442
2615
  const detachContinuationBodyGuard = cancelBodyOnAbort(response.body, upstream.signal);
2443
2616
  try {
2444
2617
  if (nextParsed.stream) {
2445
- yield* activeAdapter.parseStream(response);
2618
+ yield* activeAdapter.parseStream(response, translatorBudget);
2446
2619
  } else if (activeAdapter.parseResponse) {
2447
- yield* await activeAdapter.parseResponse(response);
2620
+ yield* await activeAdapter.parseResponse(response, translatorBudget);
2448
2621
  } else {
2449
2622
  yield { type: "error", message: "Provider continuation does not support response parsing" };
2450
2623
  }
@@ -2461,7 +2634,7 @@ export async function handleResponses(
2461
2634
  };
2462
2635
 
2463
2636
  if (parsed.stream) {
2464
- const initialEventStream = activeAdapter.parseStream(upstreamResponse);
2637
+ const initialEventStream = activeAdapter.parseStream(upstreamResponse, translatorBudget);
2465
2638
  const eventStream = terminalGuardEnabled
2466
2639
  ? guardTerminalEventStream({
2467
2640
  parsed,
@@ -2471,11 +2644,12 @@ export async function handleResponses(
2471
2644
  continuation: fetchTerminalGuardContinuation,
2472
2645
  })
2473
2646
  : initialEventStream;
2474
- const { toolNsMap, freeformToolNames, toolSearchToolNames } = buildToolBridgeMaps(parsed);
2647
+ const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
2475
2648
  const sseStream = bridgeToResponsesSSE(
2476
2649
  eventStream, parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
2477
2650
  () => upstream.abort(), 2_000,
2478
2651
  {
2652
+ translatorBudget,
2479
2653
  ...(options.forceEmptyResponseId ? { responseId: "" } : {}),
2480
2654
  stallTimeoutSec: config.stallTimeoutSec,
2481
2655
  hideThinkingSummary: parsed.options.hideThinkingSummary,
@@ -2504,7 +2678,7 @@ export async function handleResponses(
2504
2678
  },
2505
2679
  );
2506
2680
  const bridgeTurnAc = new AbortController();
2507
- const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, cleanupUpstreamAbort);
2681
+ const trackedSse = trackStreamLifetime(sseStream, bridgeTurnAc, cleanupUpstreamAbort, options.turnAdmissionLease);
2508
2682
  return new Response(trackedSse, {
2509
2683
  headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no" },
2510
2684
  });
@@ -2513,7 +2687,7 @@ export async function handleResponses(
2513
2687
  if (activeAdapter.parseResponse) {
2514
2688
  let events: AdapterEvent[];
2515
2689
  try {
2516
- const initialEvents = await activeAdapter.parseResponse(upstreamResponse);
2690
+ const initialEvents = await activeAdapter.parseResponse(upstreamResponse, translatorBudget);
2517
2691
  if (terminalGuardEnabled) {
2518
2692
  events = [];
2519
2693
  for await (const event of guardTerminalEventStream({
@@ -2529,9 +2703,10 @@ export async function handleResponses(
2529
2703
  } finally {
2530
2704
  cleanupUpstreamAbort();
2531
2705
  }
2532
- const { toolNsMap, freeformToolNames, toolSearchToolNames } = buildToolBridgeMaps(parsed);
2706
+ const { toolNsMap, freeformToolNames, toolSearchToolNames } = toolBridgeMaps;
2533
2707
  let providerState: OcxProviderContinuationState | undefined;
2534
2708
  const json = buildResponseJSON(events, parsed.modelId, {
2709
+ translatorBudget,
2535
2710
  hideThinkingSummary: parsed.options.hideThinkingSummary,
2536
2711
  toolNsMap,
2537
2712
  freeformToolNames,