@bitkyc08/opencodex 2.79.0 → 2.81.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 (245) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/App-7yhoys8a.js +54 -0
  3. package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
  4. package/gui/dist/assets/index-RsoaAxXY.js +94 -0
  5. package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
  6. package/gui/dist/index.html +1 -1
  7. package/gui/dist/provider-icons/claude-green.svg +1 -0
  8. package/package.json +3 -2
  9. package/src/adapters/anthropic-model-contract.ts +8 -2
  10. package/src/adapters/anthropic.ts +7 -2
  11. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  12. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  13. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  14. package/src/adapters/coding-agent/protocol.ts +122 -17
  15. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  16. package/src/adapters/coding-agent/turn.ts +101 -44
  17. package/src/adapters/cursor/catalog.ts +8 -0
  18. package/src/adapters/cursor/effort-map.ts +2 -0
  19. package/src/adapters/cursor/live-transport.ts +5 -3
  20. package/src/adapters/cursor/native-exec-fs.ts +12 -9
  21. package/src/adapters/cursor/native-exec.ts +13 -5
  22. package/src/adapters/cursor/protobuf-request.ts +13 -7
  23. package/src/adapters/cursor/request-builder.ts +23 -10
  24. package/src/adapters/cursor/tool-definitions.ts +9 -6
  25. package/src/adapters/cursor/tool-guidance.ts +29 -8
  26. package/src/adapters/cursor/tool-naming.ts +40 -7
  27. package/src/adapters/cursor/tool-wording.ts +46 -0
  28. package/src/adapters/cursor.ts +3 -0
  29. package/src/adapters/devin/live-models.ts +2 -0
  30. package/src/adapters/devin.ts +25 -11
  31. package/src/adapters/kiro-retry.ts +9 -4
  32. package/src/adapters/openai-chat/messages.ts +7 -4
  33. package/src/adapters/physical-send.ts +8 -5
  34. package/src/adapters/qoder/adapter.ts +41 -6
  35. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  36. package/src/adapters/zed.ts +16 -1
  37. package/src/claude/inbound-cache-stabilize.ts +4 -9
  38. package/src/claude/inbound.ts +10 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-listener.ts +71 -4
  41. package/src/cli/account-anthropic-threshold.ts +14 -5
  42. package/src/cli/account-api.ts +3 -1
  43. package/src/cli/account-extended.ts +13 -9
  44. package/src/cli/account-policy-dto.ts +1 -1
  45. package/src/cli/account-policy.ts +13 -4
  46. package/src/cli/account.ts +3 -3
  47. package/src/cli/capabilities-accounts.ts +2 -2
  48. package/src/cli/capabilities-agents-routing.ts +24 -0
  49. package/src/cli/capabilities-integrations.ts +16 -0
  50. package/src/cli/codex-shim-autorestore.ts +3 -0
  51. package/src/cli/dispatch.ts +10 -1
  52. package/src/cli/help.ts +4 -1
  53. package/src/cli/index.ts +11 -5
  54. package/src/cli/integration-plan-dto.ts +1 -0
  55. package/src/cli/integration-preview.ts +8 -1
  56. package/src/cli/integrations.ts +32 -3
  57. package/src/cli/message-args.ts +34 -0
  58. package/src/cli/message-command.ts +43 -0
  59. package/src/cli/message-runtime.ts +6 -0
  60. package/src/cli/observe.ts +3 -1
  61. package/src/cli/provider.ts +29 -6
  62. package/src/cli/registry.ts +24 -2
  63. package/src/clients/config-export/commandcode.ts +131 -0
  64. package/src/clients/config-export/contracts.ts +12 -0
  65. package/src/clients/config-export.ts +64 -1
  66. package/src/codex/auth-api/account-list.ts +11 -3
  67. package/src/codex/auth-api/main-account-probe.ts +25 -8
  68. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  69. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  70. package/src/codex/auth-api/routes.ts +1 -0
  71. package/src/codex/autostart-health.ts +3 -3
  72. package/src/codex/catalog/auto-review.ts +13 -6
  73. package/src/codex/catalog/build-entries.ts +15 -7
  74. package/src/codex/catalog/control-plane.ts +45 -0
  75. package/src/codex/catalog/effort.ts +11 -1
  76. package/src/codex/catalog/gather-capture.ts +5 -3
  77. package/src/codex/catalog/parsing.ts +2 -1
  78. package/src/codex/catalog/provider-models.ts +15 -9
  79. package/src/codex/control-plane-models.ts +6 -0
  80. package/src/codex/journal.ts +36 -5
  81. package/src/codex/shim-overlay.ts +9 -1
  82. package/src/combos/jev-decision-contract.ts +25 -2
  83. package/src/combos/jev.ts +6 -3
  84. package/src/combos/types.ts +1 -1
  85. package/src/config/diagnostics.ts +27 -16
  86. package/src/config/load-degrade.ts +34 -0
  87. package/src/config/schema/anthropic-account-pool.ts +143 -0
  88. package/src/config/schema/config-schema.ts +7 -1
  89. package/src/config/schema/leaf-validators.ts +4 -0
  90. package/src/config.ts +2 -0
  91. package/src/generated/compatibility-version.json +347 -199
  92. package/src/generated/model-metadata.ts +5 -4
  93. package/src/images/loop.ts +2 -1
  94. package/src/integrations/catalog-refresh.ts +1 -1
  95. package/src/integrations/mutation-plan.ts +7 -0
  96. package/src/integrations/registry.ts +8 -0
  97. package/src/integrations/state.ts +9 -2
  98. package/src/integrations/writer.ts +8 -2
  99. package/src/lib/lab-live-route-production.ts +9 -1
  100. package/src/lib/request-execution-budget.ts +236 -58
  101. package/src/lib/spend-pool-alias-validation.ts +79 -0
  102. package/src/lib/spend-pool-continuity.ts +34 -0
  103. package/src/lib/spend-reservation-ledger.ts +412 -50
  104. package/src/lib/state-store-registrations.ts +20 -3
  105. package/src/lib/upstream-retry.ts +17 -5
  106. package/src/lib/workflow-budget.ts +53 -11
  107. package/src/messaging/budget.ts +45 -0
  108. package/src/messaging/discovery.ts +57 -0
  109. package/src/messaging/envelope.ts +49 -0
  110. package/src/messaging/input.ts +37 -0
  111. package/src/messaging/rpc.ts +196 -0
  112. package/src/messaging/send.ts +58 -0
  113. package/src/messaging/socket.ts +72 -0
  114. package/src/messaging/types.ts +29 -0
  115. package/src/oauth/anthropic-account-refusal.ts +51 -17
  116. package/src/oauth/anthropic-account-threshold.ts +8 -2
  117. package/src/oauth/anthropic-continuity.ts +15 -1
  118. package/src/oauth/anthropic-model-quota.ts +120 -48
  119. package/src/oauth/anthropic-model-routes.ts +11 -3
  120. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  121. package/src/oauth/anthropic-pool-config.ts +30 -0
  122. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  123. package/src/oauth/anthropic-routing.ts +1202 -866
  124. package/src/oauth/anthropic-send-ownership.ts +41 -0
  125. package/src/oauth/anthropic.ts +4 -1
  126. package/src/oauth/generic-account-failover.ts +3 -1
  127. package/src/oauth/health.ts +8 -6
  128. package/src/oauth/index.ts +90 -173
  129. package/src/oauth/kiro.ts +22 -9
  130. package/src/oauth/login-cli.ts +2 -0
  131. package/src/oauth/model-discovery-auth.ts +43 -0
  132. package/src/oauth/pool-kernel.ts +11 -5
  133. package/src/oauth/pool-settings-capability.ts +5 -3
  134. package/src/oauth/store-anthropic-instance.ts +87 -0
  135. package/src/oauth/store.ts +67 -9
  136. package/src/oauth/token-guardian.ts +2 -0
  137. package/src/oauth/types.ts +2 -0
  138. package/src/protocols/plan-snapshot.ts +5 -0
  139. package/src/protocols/settings.ts +20 -5
  140. package/src/providers/anthropic-instance-id.ts +47 -0
  141. package/src/providers/anthropic-instance.ts +48 -0
  142. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  143. package/src/providers/derive.ts +2 -0
  144. package/src/providers/kiro-models.ts +3 -0
  145. package/src/providers/label.ts +1 -1
  146. package/src/providers/model-rename-fields.ts +2 -0
  147. package/src/providers/quota/account-cache.ts +37 -11
  148. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  149. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  150. package/src/providers/quota/report-cache.ts +24 -1
  151. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  152. package/src/providers/quota.ts +22 -32
  153. package/src/providers/registry/entries-core.ts +35 -30
  154. package/src/providers/registry/model-ids.ts +1 -0
  155. package/src/providers/registry/model-seeds.ts +7 -4
  156. package/src/providers/registry/types.ts +3 -0
  157. package/src/providers/registry.ts +3 -3
  158. package/src/responses/spill-store.ts +5 -1
  159. package/src/responses/state/metrics.ts +87 -0
  160. package/src/responses/state/snapshot-codec.ts +6 -0
  161. package/src/responses/state/spill-queue.ts +1 -0
  162. package/src/responses/state/unforced-store-false.ts +55 -0
  163. package/src/responses/state.ts +50 -66
  164. package/src/router.ts +28 -4
  165. package/src/routing/compatibility/assemble.ts +1 -1
  166. package/src/routing/quota.ts +7 -3
  167. package/src/server/auth-cors.ts +5 -0
  168. package/src/server/chat-completions.ts +6 -1
  169. package/src/server/chat-native.ts +45 -25
  170. package/src/server/claude-messages.ts +16 -2
  171. package/src/server/gui-static.ts +38 -4
  172. package/src/server/index/spend-ledger-lifecycle.ts +10 -4
  173. package/src/server/index.ts +16 -16
  174. package/src/server/inference/context.ts +1 -1
  175. package/src/server/lifecycle.ts +1 -1
  176. package/src/server/management/agent-settings-routes.ts +30 -6
  177. package/src/server/management/anthropic-account-threshold.ts +12 -9
  178. package/src/server/management/anthropic-pool-settings.ts +72 -0
  179. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  180. package/src/server/management/config-routes.ts +27 -7
  181. package/src/server/management/decision-routes.ts +2 -1
  182. package/src/server/management/integration-routes.ts +25 -2
  183. package/src/server/management/oauth-account-routes.ts +79 -98
  184. package/src/server/management/provider-instance-ownership.ts +44 -0
  185. package/src/server/management/provider-routes.ts +21 -28
  186. package/src/server/management/route-registry.ts +3 -1
  187. package/src/server/management/routing-profile-routes.ts +4 -4
  188. package/src/server/management/system-restart.ts +44 -14
  189. package/src/server/management/system-routes.ts +24 -2
  190. package/src/server/management/vision-sidecar-options.ts +9 -6
  191. package/src/server/management/web-search-sidecar-options.ts +51 -2
  192. package/src/server/management-auth.ts +6 -0
  193. package/src/server/messages-native-eligibility.ts +3 -2
  194. package/src/server/messages-native-oauth.ts +86 -41
  195. package/src/server/messages-native-scope.ts +4 -1
  196. package/src/server/messages-native-selector.ts +39 -0
  197. package/src/server/messages-native.ts +84 -25
  198. package/src/server/request-log.ts +5 -0
  199. package/src/server/responses/adapter-continuation.ts +29 -17
  200. package/src/server/responses/adapter-dispatch.ts +47 -17
  201. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  202. package/src/server/responses/combo-requested-effort.ts +14 -0
  203. package/src/server/responses/compact.ts +25 -8
  204. package/src/server/responses/compaction-recovery.ts +1 -1
  205. package/src/server/responses/core-combo-native.ts +4 -1
  206. package/src/server/responses/core-combo.ts +21 -18
  207. package/src/server/responses/core-normalize.ts +1 -0
  208. package/src/server/responses/core-options.ts +3 -1
  209. package/src/server/responses/core-replay.ts +5 -0
  210. package/src/server/responses/passthrough-delivery.ts +7 -3
  211. package/src/server/responses/passthrough-dispatch.ts +21 -8
  212. package/src/server/responses/request-prepare.ts +37 -5
  213. package/src/server/responses/request-send-budget.ts +74 -37
  214. package/src/server/responses/request-spend.ts +325 -33
  215. package/src/server/responses/request-transport.ts +132 -52
  216. package/src/server/responses/run-turn-execution.ts +11 -3
  217. package/src/server/responses/sidecar-execution.ts +96 -14
  218. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  219. package/src/server/workflow-refusal.ts +30 -0
  220. package/src/service/desktop-startup.ts +100 -3
  221. package/src/service/ownership-mutation-lease.d.mts +1 -1
  222. package/src/service/ownership-mutation-lease.mjs +5 -7
  223. package/src/sidecar/anthropic-binding.ts +79 -0
  224. package/src/sidecar/auth.ts +76 -2
  225. package/src/types/anthropic-account-pool.ts +33 -0
  226. package/src/types/config.ts +12 -28
  227. package/src/types/provider.ts +5 -0
  228. package/src/types/tools.ts +7 -0
  229. package/src/types.ts +4 -0
  230. package/src/usage/cost.ts +3 -2
  231. package/src/usage/expected-prices.ts +66 -14
  232. package/src/vision/anthropic-describe.ts +16 -3
  233. package/src/vision/index.ts +3 -0
  234. package/src/vision/plan.ts +19 -6
  235. package/src/web-search/alpha-search.ts +7 -1
  236. package/src/web-search/anthropic-executor.ts +16 -4
  237. package/src/web-search/header-deadline.ts +67 -0
  238. package/src/web-search/index.ts +7 -3
  239. package/src/web-search/loop.ts +32 -10
  240. package/src/web-search/passthrough-bridge.ts +4 -1
  241. package/src/web-search/sidecar-providers.ts +4 -3
  242. package/gui/dist/assets/App-BOUa-ek3.js +0 -54
  243. package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
  244. package/gui/dist/assets/index-DwHNDF4s.js +0 -94
  245. package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import { classifyAnthropic429 } from "../../oauth/anthropic-rate-limit-policy";
2
- import { rotateAnthropicAccountOnResponse } from "../../oauth/anthropic-account-refusal";
2
+ import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
3
3
  import { authorizeResendForRecovery } from "../../lib/request-resend-gate";
4
4
  import { ambiguousResendAllowanceFor, selfContainedResponsesBody } from "./reset-replay";
5
5
  import { transientSendCapFor } from "./request-send-budget";
@@ -65,7 +65,7 @@ import { bindRouteReasoningReplayScope } from "./core-replay";
65
65
  import {
66
66
  AnthropicAccountCooldownError,
67
67
  ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
68
- getAnthropicPoolAccessSnapshot,
68
+ anthropicRoutingFor,
69
69
  formatAnthropicProviderForLog,
70
70
  } from "../../oauth/anthropic-routing";
71
71
  import {
@@ -106,6 +106,7 @@ import { isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
106
106
  import { mapCodexAuthContextErrorToResponse } from "./codex-auth-error";
107
107
  import { chargeWorkflowSends } from "../../lib/workflow-budget";
108
108
  import { isAntigravityValidationRefusal } from "./antigravity-validation-refusal";
109
+ import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
109
110
 
110
111
  /** One responsibility of the Responses request pipeline; state owners are explicit. */
111
112
  export async function prepareAdapterExchange(
@@ -139,6 +140,8 @@ export async function prepareAdapterExchange(
139
140
  | "replayOAuthCredentialSnapshot"
140
141
  | "invalidateSameTargetRequest"
141
142
  | "resolveSelectionAdapter"
143
+ | "anthropicInstance"
144
+ | "currentAnthropicRouteDecision"
142
145
  | "anthropicRouteDecision"
143
146
  | "anthropicPoolAccountId"
144
147
  | "anthropicPoolFailovers"
@@ -157,7 +160,8 @@ export async function prepareAdapterExchange(
157
160
  | "noteAdapterPhysicalSend"
158
161
  | "noteAdapterRecoveryWithheld"
159
162
  | "remainingTransientSendBudget"
160
- | "noteTransientSends"
163
+ | "adapterSendBudget"
164
+ | "transientSendReporter"
161
165
  | "recoverySendAllowance"
162
166
  | "recoveryClassFor"
163
167
  | "sendBudgetExhausted"
@@ -175,6 +179,8 @@ export async function prepareAdapterExchange(
175
179
  refreshResolvedOAuthSelection,
176
180
  invalidateSameTargetRequest,
177
181
  resolveSelectionAdapter,
182
+ anthropicInstance,
183
+ currentAnthropicRouteDecision,
178
184
  anthropicSessionKey,
179
185
  commitResolvedOAuthSelection,
180
186
  applyFailoverSnapshot,
@@ -194,7 +200,7 @@ export async function prepareAdapterExchange(
194
200
  noteAdapterPhysicalSend,
195
201
  noteAdapterRecoveryWithheld,
196
202
  remainingTransientSendBudget,
197
- noteTransientSends,
203
+ transientSendReporter,
198
204
  recoverySendAllowance,
199
205
  recoveryClassFor,
200
206
  sendBudgetExhausted,
@@ -333,6 +339,8 @@ export async function prepareAdapterExchange(
333
339
  try {
334
340
  if (transportState.activeAdapter.fetchResponse) {
335
341
  transportState.noteRoutedAttemptSend(inputTokenEstimate);
342
+ const producer = adapterDispatchBudget?.beginSpendProducer?.();
343
+ try {
336
344
  upstreamResponse = await withProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal, pacingSlot =>
337
345
  transportState.activeAdapter.fetchResponse!(builtInitialRequest, {
338
346
  kiroPreferAccountFailover: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro"),
@@ -352,6 +360,7 @@ export async function prepareAdapterExchange(
352
360
  ? createCodexAuthDispatchGuard(admissionState.authCtx, options.codexAuthPolicy ?? config, route.modelId, options.admission, options.visionDescribeTerminal === true) : undefined,
353
361
  }),
354
362
  }));
363
+ } finally { producer?.close(); }
355
364
  } else {
356
365
  // #1851 scope guard: transient-5xx retry on this generic adapter path is opt-in for
357
366
  // direct Google AI Studio only (Vertex/Antigravity use fetchResponse above). Other
@@ -361,13 +370,14 @@ export async function prepareAdapterExchange(
361
370
  // legacy direct-Google exception is preserved exactly; every other adapter still keeps
362
371
  // reset-only semantics so combo failover hops on the first 5xx.
363
372
  const transientPolicy = transientRetryPolicyFor(route.provider);
373
+ const resetPolicy = resetReplayPolicyFor(route.provider);
364
374
  const compactPrepaid = options.compactionRecoveryAttempted ? sendBudgetState.pendingHopPermit : undefined;
365
375
  if (compactPrepaid) sendBudgetState.pendingHopPermit = undefined;
366
376
  let compactPrepaidUsed = false;
367
377
  // Combo and emergency compaction admission already book this target's first send.
368
378
  // Its configured initial ceiling is target-local; the shared remainder below still
369
379
  // accounts for earlier targets without deducting their sends from this target twice.
370
- const initialSendCap = transientPolicy || resetReplayPolicyFor(route.provider)
380
+ const initialSendCap = transientPolicy || resetPolicy
371
381
  ? transientSendCapFor(transientPolicy?.attempts,
372
382
  (options.comboAttempt || compactPrepaid) ? 0 : sendBudgetState.sendsUsed)
373
383
  : 1;
@@ -376,6 +386,11 @@ export async function prepareAdapterExchange(
376
386
  : fetchWithResetRetry;
377
387
  upstreamResponse = await fetchWithRetryPolicy(
378
388
  recovery => {
389
+ // Unconfigured generic sends may omit the counting reporter. Capture policy
390
+ // independently so a later recovery still belongs to this request's start.
391
+ sendBudgetState.adapterSendBudget?.startRequest?.({
392
+ poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
393
+ });
379
394
  if (compactPrepaid && !compactPrepaidUsed) {
380
395
  if (!compactPrepaid.use()) throw new SendBudgetExhaustedError(safeHostLabel(builtInitialRequest.url));
381
396
  compactPrepaidUsed = true;
@@ -398,12 +413,18 @@ export async function prepareAdapterExchange(
398
413
  abortSignal: upstream.signal,
399
414
  label: safeHostLabel(builtInitialRequest.url),
400
415
  claimAmbiguousResend: claimPreHeaderResend,
401
- ...(transientPolicy || resetReplayPolicyFor(route.provider) || compactPrepaid
416
+ ...(sendBudgetState.adapterSendBudget?.spendEnforced || options.comboDispatchPermit || transientPolicy || resetPolicy || compactPrepaid
402
417
  ? {
403
418
  // A pending compaction permit already booked this leg's first physical send.
404
- attempts: Math.min(initialSendCap,
405
- remainingTransientSendBudget(initialSendCap) + (compactPrepaid ? 1 : 0)),
406
- onSendsConsumed: noteTransientSends,
419
+ ...(transientPolicy || resetPolicy || compactPrepaid
420
+ ? {
421
+ attempts: Math.min(initialSendCap,
422
+ remainingTransientSendBudget(initialSendCap) + (compactPrepaid ? 1 : 0)),
423
+ }
424
+ : {}),
425
+ // Keep the exact combo or compaction receipt on the helper callback. The reporter
426
+ // also settles the request and workflow counters for reset-only retries.
427
+ onSendsConsumed: transientSendReporter(compactPrepaid ?? options.comboDispatchPermit),
407
428
  }
408
429
  : {}),
409
430
  },
@@ -437,7 +458,8 @@ export async function prepareAdapterExchange(
437
458
  // blaming the provider makes the caller send the whole turn again -- the amplification this
438
459
  // budget exists to stop. The passthrough path has answered 429 here since #4546.
439
460
  if (err instanceof SendBudgetExhaustedError) {
440
- return formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, err.message);
461
+ return unboundPoolSpendRefusalResponse(logCtx)
462
+ ?? formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, err.message);
441
463
  }
442
464
  const msg = describeUpstreamConnectFailure(err, connectMs);
443
465
  return formatErrorResponse(502, "upstream_error", msg);
@@ -541,6 +563,8 @@ export async function prepareAdapterExchange(
541
563
  try {
542
564
  if (transportState.activeAdapter.fetchResponse) {
543
565
  transportState.noteRoutedAttemptSend(retryEstimate, recovery);
566
+ const producer = adapterDispatchBudget?.beginSpendProducer?.();
567
+ try {
544
568
  return await withProviderRequestSlot(route.providerName, route.provider, route.modelId, upstream.signal, pacingSlot => {
545
569
  // The dispatch boundary is HERE, not before the pacing wait: that wait can reject for
546
570
  // an abort, a saturated queue, an expired slot or a removed provider, and none of
@@ -570,6 +594,7 @@ export async function prepareAdapterExchange(
570
594
  }),
571
595
  });
572
596
  });
597
+ } finally { producer?.close(); }
573
598
  }
574
599
  // #2643 review: this leg used to call fetchWithHeaderTimeout directly, so an
575
600
  // opted-in provider's transient-5xx policy applied to the initial send and to
@@ -581,7 +606,7 @@ export async function prepareAdapterExchange(
581
606
  const refetchWithPolicy = (route.provider.adapter === "google" || refetchTransientPolicy)
582
607
  ? fetchWithTransientRetry
583
608
  : fetchWithResetRetry;
584
- const helperCountsSends = refetchTransientPolicy !== null || resetReplayPolicyFor(route.provider) !== null;
609
+ const helperCountsSends = sendBudgetState.adapterSendBudget?.spendEnforced === true || refetchTransientPolicy !== null || resetReplayPolicyFor(route.provider) !== null;
585
610
  const prepaid = sendBudgetState.pendingHopPermit;
586
611
  const configuredTotal = refetchTransientPolicy?.attempts;
587
612
  const refetchCap = transientSendCapFor(configuredTotal,
@@ -601,6 +626,9 @@ export async function prepareAdapterExchange(
601
626
  try {
602
627
  return await refetchWithPolicy(
603
628
  recoveryKind => {
629
+ sendBudgetState.adapterSendBudget?.startRequest?.({
630
+ poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
631
+ });
604
632
  if (refetchAllowance?.permit && !refetchAllowance.permit.use()) {
605
633
  throw new SendBudgetExhaustedError(safeHostLabel(retryRequest.url));
606
634
  }
@@ -629,7 +657,7 @@ export async function prepareAdapterExchange(
629
657
  ...(refetchAllowance
630
658
  ? {
631
659
  attempts: refetchAllowance.attempts,
632
- onSendsConsumed: noteTransientSends,
660
+ onSendsConsumed: transientSendReporter(refetchAllowance.permit),
633
661
  }
634
662
  : {}),
635
663
  },
@@ -673,7 +701,8 @@ export async function prepareAdapterExchange(
673
701
  // Same rule on the recovery leg: the ladder refused to send again, so the answer names
674
702
  // this proxy rather than the provider it never reached.
675
703
  if (err instanceof SendBudgetExhaustedError) {
676
- return { failed: formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, err.message) };
704
+ return { failed: unboundPoolSpendRefusalResponse(logCtx)
705
+ ?? formatErrorResponse(429, SEND_BUDGET_EXHAUSTED_CODE, err.message) };
677
706
  }
678
707
  const msg = describeUpstreamConnectFailure(err, connectMs);
679
708
  return { failed: formatErrorResponse(502, "upstream_error", msg) };
@@ -1048,23 +1077,24 @@ export async function prepareAdapterExchange(
1048
1077
  // before output, within the shared request and account rotation limits.
1049
1078
  while (
1050
1079
  (upstreamResponse.status === 429 || upstreamResponse.status === 403)
1080
+ && anthropicInstance
1051
1081
  && transportState.anthropicPoolAccountId
1052
1082
  ) {
1053
- const nextAccountId = await rotateAnthropicAccountOnResponse(upstreamResponse, {
1083
+ const nextAccountId = await rotateAnthropicAccountOnResponseForInstance(anthropicInstance, upstreamResponse, {
1054
1084
  config, accountId: transportState.anthropicPoolAccountId, sessionKey: anthropicSessionKey,
1055
- model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, signal: upstream.signal,
1085
+ model: route.modelId, requestKey: transportState, decision: transportState.anthropicRouteDecision, currentDecision: currentAnthropicRouteDecision, signal: upstream.signal,
1056
1086
  canRetry: !sendBudgetExhausted() && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
1057
1087
  });
1058
1088
  if (!nextAccountId) break;
1059
1089
  try {
1060
- const admitted = await commitResolvedOAuthSelection(await getAnthropicPoolAccessSnapshot(nextAccountId));
1090
+ const admitted = await commitResolvedOAuthSelection(await anthropicRoutingFor(anthropicInstance).getAnthropicPoolAccessSnapshot(nextAccountId));
1061
1091
  if (!admitted) throw new Error("OAuth selection changed during recovery");
1062
1092
  try { void upstreamResponse.body?.cancel().catch(() => {}); } catch { /* already consumed/closed */ }
1063
1093
  transportState.anthropicPoolAccountId = admitted.accountId;
1064
1094
  transportState.anthropicPoolFailovers += 1;
1065
1095
  route.provider = { ...route.provider, apiKey: admitted.accessToken };
1066
1096
  invalidateSameTargetRequest();
1067
- logCtx.provider = formatAnthropicProviderForLog("anthropic", admitted.accountId, config);
1097
+ logCtx.provider = formatAnthropicProviderForLog(anthropicInstance, admitted.accountId, config);
1068
1098
  transportState.activeAdapter = resolveSelectionAdapter(
1069
1099
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
1070
1100
  config.cacheRetention,
@@ -1,19 +1,33 @@
1
- import { ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX } from "../../adapters/google-errors";
1
+ import { ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX, safeAntigravityHttpErrorMessage } from "../../adapters/google-errors";
2
2
  import { readBoundedResponseBody } from "../../lib/bounded-body";
3
3
 
4
+ /** Match only the normalized Antigravity validation-refusal marker. */
5
+ export function hasAntigravityValidationRefusalMarker(text: string): boolean {
6
+ return text.startsWith(`${ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX}: `);
7
+ }
8
+
4
9
  /** Inspect only the Google adapter's normalized, already bounded 403 response. */
5
10
  export async function isAntigravityValidationRefusal(response: Response, signal?: AbortSignal): Promise<boolean> {
11
+ return inspectValidationRefusal(response, signal, false);
12
+ }
13
+
14
+ /** Sidecar dispatch retains the raw Google envelope; normalize only a complete bounded body. */
15
+ export async function isAntigravityRawValidationRefusal(response: Response, signal?: AbortSignal): Promise<boolean> {
16
+ return inspectValidationRefusal(response, signal, true);
17
+ }
18
+
19
+ async function inspectValidationRefusal(response: Response, signal: AbortSignal | undefined, raw: boolean): Promise<boolean> {
6
20
  if (response.status !== 403 || signal?.aborted) return false;
7
21
  try {
8
22
  const body = await readBoundedResponseBody(response.clone(), {
9
- maxBytes: 1024,
10
- totalTimeoutMs: 1000,
11
- firstByteTimeoutMs: 1000,
12
- inactivityTimeoutMs: 1000,
23
+ maxBytes: raw ? 4096 : 1024,
24
+ totalTimeoutMs: raw ? 2000 : 1000,
25
+ firstByteTimeoutMs: raw ? 2000 : 1000,
26
+ inactivityTimeoutMs: raw ? 2000 : 1000,
13
27
  signal,
14
28
  });
15
29
  return !signal?.aborted && body.displaySafe && !body.truncated && !body.timedOut && !body.oversized
16
- && body.text.startsWith(`${ANTIGRAVITY_VALIDATION_REQUIRED_PREFIX}: `);
30
+ && hasAntigravityValidationRefusalMarker(raw ? safeAntigravityHttpErrorMessage(403, body.text) : body.text);
17
31
  } catch {
18
32
  return false;
19
33
  }
@@ -0,0 +1,14 @@
1
+ /** Restore the caller label while retaining JEV's forced effort and later child transitions. */
2
+ export function comboRequestedEffortLabel(
3
+ originalRequestedEffort: string | undefined,
4
+ childRequestedEffort: string | undefined,
5
+ forcedEffort?: string | null,
6
+ ): string | undefined {
7
+ if (originalRequestedEffort === undefined) return childRequestedEffort;
8
+ const transitionIndex = childRequestedEffort?.indexOf("->") ?? -1;
9
+ const laterTransitions = transitionIndex >= 0 ? childRequestedEffort!.slice(transitionIndex) : "";
10
+ const forcedTransition = forcedEffort != null && forcedEffort !== originalRequestedEffort
11
+ ? `->${forcedEffort}`
12
+ : "";
13
+ return `${originalRequestedEffort}${forcedTransition}${laterTransitions}`;
14
+ }
@@ -1,3 +1,6 @@
1
+ import { createPhysicalSendReporter } from "../../lib/request-execution-budget";
2
+ import { createInferenceSendBudget } from "../inference/context";
3
+ import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
1
4
  import { capturePoolQuotaWriter } from "../../codex/account-store";
2
5
  import { previewXaiOauthWireModel } from "./core-normalize";
3
6
  import {
@@ -722,6 +725,7 @@ export async function handleResponsesCompact(
722
725
  logCtx.provider = route.codexAccountNamespace
723
726
  ? `${route.providerName}-${route.codexAccountNamespace}`
724
727
  : route.providerName;
728
+ logCtx.spendPoolId = route.providerName;
725
729
  logCtx.providerAdapter = route.provider.adapter;
726
730
  // #4940, and the same refusal the ordinary Responses path makes in request-prepare.ts. Compact has
727
731
  // to repeat it rather than inherit it: the native branch below dispatches straight to
@@ -804,7 +808,7 @@ export async function handleResponsesCompact(
804
808
  // failure that hands off, continues here. The routed turn used to call handleResponses with
805
809
  // no budget at all, so `handleResponsesInner` minted a fresh four after the native attempt
806
810
  // had already spent some of the first one.
807
- const sendBudget: RequestExecutionBudget = options.sendBudget ?? createRequestExecutionBudget();
811
+ const sendBudget: RequestExecutionBudget = options.sendBudget ?? createInferenceSendBudget(req, logCtx);
808
812
  // A manual override onto another backend must not mint ciphertext the conversation model cannot replay.
809
813
  const manualOverrideCrossesProvider = options.compactionRoutingOverride
810
814
  ? !compactionRoutingKeepsProviderIdentity(config, options.compactionRoutingOverride, route)
@@ -1022,12 +1026,21 @@ export async function handleResponsesCompact(
1022
1026
  // `kind === "pool"` and a main-pool credential left it false -- so 401 then 429 really did
1023
1027
  // reach five. The single sends spend the base allowance first and then the one shared
1024
1028
  // final-recovery reserve, which is the same rule the Responses path follows.
1025
- const sendSingleCompactAttempt = (
1029
+ const sendSingleCompactAttempt = async (
1026
1030
  doFetch: () => Promise<Response>,
1027
1031
  ): Promise<Response> => {
1028
1032
  if (sendBudget.remainingBaseSends(TRANSIENT_RETRY_MAX_ATTEMPTS) > 0) {
1029
- sendBudget.used += 1;
1030
- return doFetch();
1033
+ const report = createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? route.providerName,
1034
+ identityId: logCtx.accountLogLabel }));
1035
+ let started = false;
1036
+ try {
1037
+ if (report.beforeSend?.() === false) throw new SendBudgetExhaustedError(safeHostLabel(compactUrl));
1038
+ started = true;
1039
+ return await doFetch();
1040
+ } finally {
1041
+ try { if (started) report(1); }
1042
+ finally { report.close?.(); }
1043
+ }
1031
1044
  }
1032
1045
  const decision = sendBudget.reserveDispatch({
1033
1046
  sendClass: "auth-recovery",
@@ -1076,7 +1089,8 @@ export async function handleResponsesCompact(
1076
1089
  // Draws the shared remainder instead of a fresh three. Compact is a native endpoint
1077
1090
  // of the same logical turn, so its sends belong to the same cap.
1078
1091
  attempts: sendBudget.remainingBaseSends(TRANSIENT_RETRY_MAX_ATTEMPTS),
1079
- onSendsConsumed: (used: number) => { sendBudget.used += Math.max(0, used); },
1092
+ onSendsConsumed: createPhysicalSendReporter(sendBudget, () => ({ poolId: logCtx.spendPoolId ?? route.providerName,
1093
+ identityId: logCtx.accountLogLabel })),
1080
1094
  });
1081
1095
  };
1082
1096
 
@@ -1084,9 +1098,12 @@ export async function handleResponsesCompact(
1084
1098
  // actually happens, so every recorder call names the context that produced it.
1085
1099
  let outcomeCtx = authCtx;
1086
1100
  const localDispatchRefusal = (error: unknown): Response | undefined => {
1087
- const response = mapCodexAuthContextErrorToResponse(unwrapUpstreamRetryEvidenceError(error), {
1088
- now: Date.now(), accountSelector: route.codexAccountNamespace,
1089
- });
1101
+ const cause = unwrapUpstreamRetryEvidenceError(error);
1102
+ const response = cause instanceof SendBudgetExhaustedError
1103
+ ? unboundPoolSpendRefusalResponse(logCtx) ?? formatErrorResponse(429, "request_send_budget_exhausted", cause.message)
1104
+ : mapCodexAuthContextErrorToResponse(cause, {
1105
+ now: Date.now(), accountSelector: route.codexAccountNamespace,
1106
+ });
1090
1107
  if (response) {
1091
1108
  releaseUpstreamHostAdmission(compactHostAdmissionLease);
1092
1109
  compactHostAdmissionLease = null;
@@ -47,7 +47,7 @@ function settlePhysicalSends(log: RequestLogContext, budget: RequestExecutionBud
47
47
  // An external report may settle a prepaid booking without changing used. Its explicit
48
48
  // receipt outranks the numeric delta, or the same source send would be charged twice.
49
49
  const unreported = Math.max(0, sent - Math.max(reported, Math.max(0, budget.used - beforeUsed)));
50
- if (unreported > 0) budget.used += unreported;
50
+ if (unreported > 0 && !budget.spendEnforced) budget.used += unreported;
51
51
  return sent;
52
52
  }
53
53
 
@@ -18,7 +18,7 @@ import type { OcxConfig } from "../../types";
18
18
  import type { RouteResult } from "../../router";
19
19
  import type { DataPlaneAdmission } from "../auth-cors";
20
20
  import type { AdmissionLease } from "../../lib/admission";
21
- import type { RequestExecutionBudget } from "../../lib/request-execution-budget";
21
+ import type { RequestExecutionBudget, SingleUseDispatchPermit } from "../../lib/request-execution-budget";
22
22
  import type { TransientSendBudget } from "../../lib/upstream-retry";
23
23
  import type { ResponsesTerminalStatus } from "../../bridge";
24
24
  import type { NativeChatFinishLog } from "../chat-native";
@@ -62,6 +62,7 @@ export interface NativeComboChildRun {
62
62
  childLog: RequestLogContext;
63
63
  attemptHandle: InferenceAttempt;
64
64
  sendBudget: RequestExecutionBudget;
65
+ comboDispatchPermit?: SingleUseDispatchPermit;
65
66
  turnAdmissionLease?: AdmissionLease;
66
67
  onFirstOutput: () => void;
67
68
  /** Reports to the combo's child callbacks; it never writes the parent's final row itself. */
@@ -272,6 +273,7 @@ export interface ComboChildCallbacks {
272
273
  export async function dispatchNativeComboChild(input: {
273
274
  source: ComboProtocolSource;
274
275
  plan: NativeComboChildPlan;
276
+ comboDispatchPermit?: SingleUseDispatchPermit;
275
277
  logCtx: RequestLogContext;
276
278
  childLog: RequestLogContext;
277
279
  attempt: PersistedUsageAttempt;
@@ -317,6 +319,7 @@ export async function dispatchNativeComboChild(input: {
317
319
  childLog,
318
320
  attemptHandle,
319
321
  sendBudget: plan.sendBudget,
322
+ comboDispatchPermit: input.comboDispatchPermit,
320
323
  ...(input.turnAdmissionLease ? { turnAdmissionLease: input.turnAdmissionLease } : {}),
321
324
  onFirstOutput: () => {
322
325
  outputSeen = true;
@@ -52,11 +52,10 @@ import { routeConcreteModel, comboRouteDecisionTrace } from "../../router";
52
52
  import { memoryModelRouteReason } from "./memory-models";
53
53
  import { poolAccountProviderLabel } from "../../providers/label";
54
54
  import { getAccountSet } from "../../oauth/store";
55
+ import { configuredAnthropicInstance } from "../../providers/anthropic-instance";
55
56
  import {
57
+ anthropicRoutingFor,
56
58
  formatAnthropicProviderForLog,
57
- getAnthropicAccountHealthSnapshot,
58
- getAnthropicPoolRetryAfterSeconds,
59
- getEligibleAnthropicAccounts,
60
59
  } from "../../oauth/anthropic-routing";
61
60
  import { codexAccountLogLabel } from "../../codex/account-label";
62
61
  import { codexQuotaScopeForModel, getCodexQuotaHealthSnapshot } from "../../codex/routing";
@@ -106,6 +105,7 @@ import { mandatoryResponsesReasoningReplayUnavailable } from "./core-replay";
106
105
  import { settleOperatorReplacement } from "../../lib/upstream-retry";
107
106
  import { createComboProtocolLanes, dispatchNativeComboChild } from "./core-combo-native";
108
107
  import { createJevModelInvoker } from "./jev-model-invoke";
108
+ import { comboRequestedEffortLabel } from "./combo-requested-effort";
109
109
  import { clientWireOf } from "../inference/client-wire";
110
110
 
111
111
  /**
@@ -117,10 +117,11 @@ export const COMBO_TARGET_BASE_SENDS = CODEX_TEXT_GUARDED_BUDGET_POLICY.baseSend
117
117
 
118
118
  function cooledPoolAccountLabel(config: OcxConfig, providerName: string, modelId: string, label: string | undefined): string | undefined {
119
119
  if (!label) return undefined;
120
- if (providerName === "anthropic") {
121
- const matches = getAccountSet("anthropic")?.accounts.filter(account =>
122
- formatAnthropicProviderForLog("anthropic", account.id) === label) ?? [];
123
- return matches.length === 1 && getAnthropicAccountHealthSnapshot(matches[0]!.id) ? label : undefined;
120
+ const instance = configuredAnthropicInstance(config, providerName);
121
+ if (instance && config.providers[providerName]?.authMode === "oauth") {
122
+ const matches = getAccountSet(instance)?.accounts.filter(account =>
123
+ formatAnthropicProviderForLog(instance, account.id) === label) ?? [];
124
+ return matches.length === 1 && anthropicRoutingFor(instance).getAnthropicAccountHealthSnapshot(matches[0]!.id) ? label : undefined;
124
125
  }
125
126
  const provider = config.providers[providerName];
126
127
  if (!provider || !isCanonicalOpenAiForwardProvider(provider)) return undefined;
@@ -151,12 +152,13 @@ export function isAnthropicPoolLocalRefusal(
151
152
  failedAccount: string | undefined,
152
153
  now = Date.now(),
153
154
  ): boolean {
154
- return providerName === "anthropic"
155
+ const instance = configuredAnthropicInstance(config, providerName);
156
+ return instance !== undefined
155
157
  && config.providers[providerName]?.authMode === "oauth"
156
158
  && status === 429
157
159
  && failedAccount === undefined
158
- && getEligibleAnthropicAccounts(now).length === 0
159
- && getAnthropicPoolRetryAfterSeconds(now) !== null;
160
+ && anthropicRoutingFor(instance).getEligibleAnthropicAccounts(now).length === 0
161
+ && anthropicRoutingFor(instance).getAnthropicPoolRetryAfterSeconds(now) !== null;
160
162
  }
161
163
 
162
164
  /**
@@ -656,13 +658,9 @@ export async function executeComboResponses(
656
658
  && isDeclaredReasoningEffort(originalRequestedEffortValue)
657
659
  ? originalRequestedEffortValue
658
660
  : undefined;
659
- const restoreOriginalRequestedEffort = (childLog: RequestLogContext): void => {
661
+ const restoreOriginalRequestedEffort = (childLog: RequestLogContext, forcedEffort?: string | null): void => {
660
662
  if (originalRequestedEffort === undefined) return;
661
- const normalizedRequestedEffort = childLog.requestedEffort;
662
- const transitionIndex = normalizedRequestedEffort?.indexOf("->") ?? -1;
663
- childLog.requestedEffort = transitionIndex >= 0
664
- ? `${originalRequestedEffort}${normalizedRequestedEffort!.slice(transitionIndex)}`
665
- : originalRequestedEffort;
663
+ childLog.requestedEffort = comboRequestedEffortLabel(originalRequestedEffort, childLog.requestedEffort, forcedEffort);
666
664
  recordAttemptRequestedEffort(childLog);
667
665
  };
668
666
 
@@ -716,6 +714,9 @@ export async function executeComboResponses(
716
714
  while (pick) {
717
715
  if (options.abortSignal?.aborted) return clientCancelledResponse();
718
716
  const firstComboTarget = comboTargetsDispatched === 0;
717
+ const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
718
+ // The inherited spend tracker observes this parent log, before the child has its own label.
719
+ logCtx.spendPoolId = targetRoute.providerName;
719
720
  // The first target seeds the ledger's target identity and charges nothing; every later one
720
721
  // is a real transition, refused once the declared hops, the alternate-target ledger or the
721
722
  // request total are spent. `countedExternally` is required: the child charges its own
@@ -747,7 +748,6 @@ export async function executeComboResponses(
747
748
  ...(logCtx.conversationId ? { conversationId: logCtx.conversationId } : {}),
748
749
  ...(logCtx.surface ? { surface: logCtx.surface } : {}),
749
750
  };
750
- const targetRoute = routeConcreteModel(config, `${pick.target.provider}/${pick.target.model}`);
751
751
  const targetReasoningEfforts = supportedLadderFor({
752
752
  provider: targetRoute.provider,
753
753
  modelId: targetRoute.modelId,
@@ -864,6 +864,7 @@ export async function executeComboResponses(
864
864
  response = nativeChild ? await dispatchNativeComboChild({
865
865
  source: options.protocolSource!,
866
866
  plan: nativeChild,
867
+ comboDispatchPermit: hopDecision?.allowed ? hopDecision.permit : undefined,
867
868
  logCtx,
868
869
  childLog,
869
870
  attempt,
@@ -889,6 +890,7 @@ export async function executeComboResponses(
889
890
  // parent arrived with.
890
891
  sendBudget: targetSendBudget,
891
892
  comboAttempt: true,
893
+ comboDispatchPermit: hopDecision?.allowed ? hopDecision.permit : undefined,
892
894
  comboReplaySnapshot,
893
895
  deferCodexResetDerivedCooldown,
894
896
  // Attempt-relative TTFT is recorded HERE (not via childLog.firstOutputMs — a later
@@ -907,7 +909,8 @@ export async function executeComboResponses(
907
909
  onNativePassthroughCancel: callbackGate.onCancel,
908
910
  onResponseComplete: callbackGate.onResponseComplete,
909
911
  });
910
- restoreOriginalRequestedEffort(childLog);
912
+ // Native Chat rebuilds its own body and does not apply the initial JEV effort.
913
+ restoreOriginalRequestedEffort(childLog, nativeChild ? undefined : initialJevDecision?.effort);
911
914
  } catch (error) {
912
915
  callbackGate.discard();
913
916
  if (options.abortSignal?.aborted) {
@@ -210,6 +210,7 @@ export async function applyFinalRouteRequestNormalization(args: {
210
210
  if (preserveAnthropicResponseModel) parsed._responseModelId = responseModelId;
211
211
  logCtx.model = virtualModel?.selectedModelId ?? route.modelId;
212
212
  logCtx.provider = route.providerName;
213
+ logCtx.spendPoolId = route.providerName;
213
214
  logCtx.providerAdapter = route.provider.adapter;
214
215
  logCtx.routeDecision = route.routeDecision;
215
216
  logCtx.policyEligibility = route.policyEligibility;
@@ -71,7 +71,7 @@ export interface HandleResponsesOptions {
71
71
  compactionRecoveryKind?: "compaction-v1" | "compaction-v2";
72
72
  onCompactionRecoveryRoute?: (route: RouteResult) => void;
73
73
  onCompactionRecoveryAdapterEvent?: (event: AdapterEvent) => void;
74
- /** Physical-send reports already delivered to the shared used setter, including booking settlement. */
74
+ /** Physical-send reports already delivered to the shared ledger, including exact booking settlement. */
75
75
  onCompactionRecoverySendsReported?: (count: number) => void;
76
76
  /** Private holder for the Kiro serving-account lease. */
77
77
  accountLoad?: { lease: AccountLease | null; cancelled: boolean };
@@ -158,6 +158,8 @@ export interface HandleResponsesOptions {
158
158
  callerDirectAuth?: CallerDirectAuth | null;
159
159
  /** Internal recursion guard; callers outside this module must not set it. */
160
160
  comboAttempt?: boolean;
161
+ /** Exact externally booked combo hop, used for its child's spend preflight and send reports. */
162
+ comboDispatchPermit?: SingleUseDispatchPermit;
161
163
  /** Internal handoff: this combo was selected by shadow-call interception. */
162
164
  shadowCallIntercepted?: boolean;
163
165
  /** Internal handoff: the memory phase this turn belongs to, so combo children keep its routing. */
@@ -37,6 +37,11 @@ export function adapterNeedsForcedContinuation(name: string): boolean {
37
37
  return name === "kiro" || name === "cursor";
38
38
  }
39
39
 
40
+ /** Adapters opting into restricted store:false replay for pending client function calls. */
41
+ export function adapterNeedsToolCallContinuation(name: string): boolean {
42
+ return name === "qoder";
43
+ }
44
+
40
45
 
41
46
  export type ContinuationOwnerRead =
42
47
  | { kind: "missing" }
@@ -364,6 +364,7 @@ export async function deliverPassthroughResponse(
364
364
  | "subagentFallbackAccountId"
365
365
  | "clientRequestedStream"
366
366
  | "translatorBudget"
367
+ | "inboundWire"
367
368
  >,
368
369
  transportState: Pick<ResponsesTransport, "requestBindings">,
369
370
  sidecarState: Pick<ResponsesSidecarAuth, "openAiSidecar">,
@@ -428,7 +429,8 @@ export async function deliverPassthroughResponse(
428
429
  normalizeFunctionCompletionJson,
429
430
  } = nativeExchange;
430
431
  const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
431
- const { parsed, route, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
432
+ const { parsed, route, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
433
+ const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
432
434
  const { openAiSidecar } = sidecarState;
433
435
  const { requestBindings } = transportState;
434
436
 
@@ -637,6 +639,7 @@ export async function deliverPassthroughResponse(
637
639
  route.provider.webSearchBridge?.backend,
638
640
  config,
639
641
  openAiSidecar,
642
+ route.providerName,
640
643
  );
641
644
  const webSearchBridgePlan = planPassthroughWebSearchBridge(parsed, route.provider, {
642
645
  providerName: route.providerName,
@@ -836,6 +839,7 @@ export async function deliverPassthroughResponse(
836
839
  providerExecutedCallTypes,
837
840
  declaredBareWireToolNames,
838
841
  recoverableBareCustomWireToolNames,
842
+ enforceDeclaredToolNames,
839
843
  )
840
844
  : undefined,
841
845
  grokUpstreamEchoEnabled
@@ -1293,7 +1297,7 @@ export async function deliverPassthroughResponse(
1293
1297
  if (grokUpstreamEchoEnabled) {
1294
1298
  clientJson = stripGrokUpstreamEnvelopeEchoFromResponsesJson(clientJson);
1295
1299
  }
1296
- // #1700: same fail-closed policy as the SSE relay above. Both the plain JSON answer and
1300
+ // #1700: same inbound-wire refusal policy as the SSE relay above. Both the plain JSON answer and
1297
1301
  // the reframed-SSE branch below are built from this body, so one check covers them. This
1298
1302
  // runs BEFORE the continuation cache write below: a refused turn must not become state a
1299
1303
  // later `previous_response_id` replay can expand from.
@@ -1312,7 +1316,7 @@ export async function deliverPassthroughResponse(
1312
1316
  return undefined;
1313
1317
  }
1314
1318
  })();
1315
- if (undeclared !== undefined) {
1319
+ if (enforceDeclaredToolNames && undeclared !== undefined) {
1316
1320
  return formatErrorResponse(502, "upstream_error", undeclaredToolCallMessage(undeclared));
1317
1321
  }
1318
1322
  clientJson = normalizeDefaultNamespaceInJson(