@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,6 +1,11 @@
1
- import { anthropicFamilyRejected, claimAnthropicFamilyRevalidation } from "../../oauth/anthropic-model-quota";
2
- import { anthropicRatePauseUntil } from "../../oauth/anthropic-rate-limit-policy";
3
- import { bindAnthropicRefusalCredential } from "../../oauth/anthropic-account-refusal";
1
+ import { SendBudgetExhaustedError } from "../../lib/upstream-retry";
2
+ import { rebindPhysicalSend } from "../../lib/request-execution-budget";
3
+ import { anthropicModelQuotaFor } from "../../oauth/anthropic-model-quota";
4
+ import { anthropicRatePolicyFor } from "../../oauth/anthropic-rate-limit-policy";
5
+ import { configuredAnthropicInstance } from "../../providers/anthropic-instance";
6
+ import { resolveAnthropicMessagesUrl } from "../../adapters/anthropic";
7
+ import { bindAnthropicRefusalCredentialForSend } from "../../oauth/anthropic-account-refusal";
8
+ import { captureAnthropicPhysicalSendOwnership, anthropicPhysicalSendOwnershipIsCurrent, type AnthropicPhysicalSendOwnership } from "../../oauth/anthropic-send-ownership";
4
9
  import type { ResponsesRequestContext, ResponsesAdmissionState } from "./core-options";
5
10
  import type { PreparedResponsesRequest } from "./request-prepare";
6
11
  import type { OAuthAccessSnapshot } from "../../oauth";
@@ -16,20 +21,12 @@ import type { ProviderAdapter, AdapterRequest } from "../../adapters/base";
16
21
  import { releaseProviderRequestSlot, waitForProviderRequestSlot, type ProviderRequestSlot } from "../../providers/request-pacing";
17
22
  import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig, OcxUsage } from "../../types";
18
23
  import type { AnthropicAccountSelectionReason } from "../../oauth/anthropic-routing";
19
- import { resolveAnthropicModelRoute, routeCandidates, type AnthropicRouteDecision } from "../../oauth/anthropic-model-routes";
24
+ import { resolveAnthropicModelRouteForInstance, routeCandidates, type AnthropicRouteDecision } from "../../oauth/anthropic-model-routes";
20
25
  import {
21
- resolveAnthropicDispatchAccountId,
22
- isAnthropicAccountPoolEnabled,
23
- getAnthropicPoolAccessSnapshot,
24
- getAnthropicAccountHealthSnapshot,
26
+ anthropicRoutingFor,
25
27
  AnthropicAccountCooldownError,
26
- getEligibleAnthropicAccounts,
27
- commitAnthropicSelectionRouting,
28
28
  formatAnthropicProviderForLog,
29
29
  anthropicSessionKeyFromParts,
30
- resolveAnthropicAccountForSession,
31
- getAnthropicPoolRetryAfterSeconds,
32
- hasAnthropicFailoverQuorum,
33
30
  } from "../../oauth/anthropic-routing";
34
31
  import {
35
32
  OAuthAccountPausedError,
@@ -65,7 +62,7 @@ import { resolveAdapter, resolveWireProtocolOverride } from "../adapter-resolve"
65
62
  import { providerFetch, sendWithConnectionPolicy } from "./fetch-helpers";
66
63
  import type { ProviderFetchOptions } from "./fetch-helpers";
67
64
  import { captureConfigGeneration } from "../../lib/state-store-sweeper";
68
- import { recordAnthropicAccountQuotaFromHeaders, hasPassiveAccountQuota } from "../../providers/quota";
65
+ import { recordAnthropicAccountQuotaFromHeadersForInstance, hasPassiveAccountQuota } from "../../providers/quota";
69
66
  import { checkOutboundBodySize, describeOutboundBodyRefusal } from "./outbound-body-guard";
70
67
  import { formatErrorResponse } from "../../bridge";
71
68
  import { bindRouteReasoningReplayScope } from "./core-replay";
@@ -105,6 +102,39 @@ export async function prepareResponsesTransport(
105
102
  ) {
106
103
  const { config, logCtx, options, req } = requestContext;
107
104
  const { route, parsed, inboundWire, translatorBudget } = requestState;
105
+ const anthropicInstance = route.provider.authMode === "oauth"
106
+ ? configuredAnthropicInstance(config, route.providerName) : undefined;
107
+ // Refuse custom/orphan B OAuth before any generic resolver can observe its auth row.
108
+ if (route.providerName === "anthropic2" && route.provider.authMode === "oauth" && !anthropicInstance) {
109
+ return formatErrorResponse(401, "authentication_error", "Anthropic Pool 2 requires an enabled builtin OAuth provider");
110
+ }
111
+ const anthropicRouting = anthropicInstance ? anthropicRoutingFor(anthropicInstance) : undefined;
112
+ const anthropicQuota = anthropicInstance ? anthropicModelQuotaFor(anthropicInstance) : undefined;
113
+ const anthropicRatePolicy = anthropicInstance ? anthropicRatePolicyFor(anthropicInstance) : undefined;
114
+ const authorizedAnthropicTarget = anthropicInstance === "anthropic2"
115
+ ? resolveAnthropicMessagesUrl(route.provider) : undefined;
116
+ const anthropicAdmissionIsCurrent = (): boolean => {
117
+ if (!anthropicInstance) return true;
118
+ if (configuredAnthropicInstance(config, route.providerName) !== anthropicInstance || route.provider.authMode !== "oauth") return false;
119
+ if (anthropicInstance !== "anthropic2") return true;
120
+ try {
121
+ return resolveAnthropicMessagesUrl(config.providers[anthropicInstance]!) === authorizedAnthropicTarget
122
+ && resolveAnthropicMessagesUrl(route.provider) === authorizedAnthropicTarget;
123
+ } catch { return false; }
124
+ };
125
+ if (!anthropicAdmissionIsCurrent()) {
126
+ return formatErrorResponse(401, "authentication_error", "Anthropic Pool 2 provider target changed; retry the request");
127
+ }
128
+ const requireAnthropicAdmission = (): void => {
129
+ if (!anthropicAdmissionIsCurrent()) throw new OAuthLoginRequiredError(anthropicInstance!);
130
+ };
131
+ const currentAnthropicRouteDecision = (): AnthropicRouteDecision | null => {
132
+ requireAnthropicAdmission();
133
+ if (!anthropicInstance || !anthropicRouting?.isAnthropicAccountPoolEnabled(config)) return null;
134
+ const result = resolveAnthropicModelRouteForInstance(anthropicInstance, config, route.modelId);
135
+ if (result.error) throw new Error("Invalid Anthropic model route configuration");
136
+ return result.decision;
137
+ };
108
138
  const rawKiroCap = route.providerName === "kiro" && route.provider.authMode === "oauth"
109
139
  ? config.providers.kiro?.oauthAccountFailover?.maxConcurrentPerAccount : undefined;
110
140
  const kiroCap = typeof rawKiroCap === "number" && Number.isInteger(rawKiroCap)
@@ -165,31 +195,40 @@ export async function prepareResponsesTransport(
165
195
  ): Promise<OAuthAccessSnapshot | null> => {
166
196
  const maxSelectionAttempts = 3;
167
197
  for (let attempt = 0; attempt < maxSelectionAttempts; attempt++) {
198
+ requireAnthropicAdmission();
199
+ if (candidate.provider !== route.providerName) return null;
200
+ if (anthropicInstance) anthropicRouteDecision = currentAnthropicRouteDecision();
168
201
  if (!oauthSelection) return null;
169
- const proactiveEnabled = route.providerName === "anthropic"
170
- ? isAnthropicAccountPoolEnabled(config)
202
+ const proactiveEnabled = anthropicInstance !== undefined
203
+ ? anthropicRouting!.isAnthropicAccountPoolEnabled(config)
171
204
  : (config.providers[route.providerName]?.oauthAccountFailover?.enabled
172
205
  ?? config.oauthAccountFailover?.enabled) === true;
173
206
  if (proactive && candidate.accountId !== oauthSelection.accountId && !proactiveEnabled) {
174
207
  oauthSelection = captureOAuthAccountSelection(route.providerName);
175
208
  if (!oauthSelection) return null;
176
- candidate = route.providerName === "anthropic"
177
- ? await getAnthropicPoolAccessSnapshot(oauthSelection.accountId)
209
+ candidate = anthropicInstance !== undefined
210
+ ? await anthropicRouting!.getAnthropicPoolAccessSnapshot(oauthSelection.accountId)
178
211
  : await getValidAccessSnapshotForAccount(route.providerName, oauthSelection.accountId, { requireUsableAccount: true });
179
212
  }
180
- if (anthropicRouteDecision && !routeCandidates(getEligibleAnthropicAccounts(Date.now(), route.modelId), anthropicRouteDecision).includes(candidate.accountId)) return null;
213
+ requireAnthropicAdmission();
214
+ if (candidate.provider !== route.providerName) return null;
215
+ if (anthropicInstance) anthropicRouteDecision = currentAnthropicRouteDecision();
216
+ if (anthropicRouteDecision && !routeCandidates(anthropicRouting!.getEligibleAnthropicAccounts(Date.now(), route.modelId), anthropicRouteDecision).includes(candidate.accountId)) return null;
181
217
  const committed = await commitOAuthAccountSelection(route.providerName, candidate.accountId, {
182
218
  expectedSelection: oauthSelection,
183
219
  expectedCredentialGeneration: candidate.generation,
184
220
  requireUsableAccount: true,
185
221
  });
222
+ requireAnthropicAdmission();
186
223
  if (committed) {
187
- if (route.providerName === "anthropic" && !commitAnthropicSelectionRouting(
224
+ if (anthropicInstance) anthropicRouteDecision = currentAnthropicRouteDecision();
225
+ if (anthropicInstance !== undefined && !anthropicRouting!.commitAnthropicSelectionRouting(
188
226
  candidate.accountId, oauthSelection, committed,
189
227
  { config, sessionKey: anthropicSessionKey, reason: anthropicReason, expectedCredentialGeneration: candidate.generation, routeDecision: anthropicRouteDecision, model: route.modelId },
190
228
  )) return null;
191
229
  oauthSelection = committed;
192
230
  servingOAuthSnapshot = candidate;
231
+ if (anthropicInstance) replayOAuthCredentialSnapshot = { accountId: candidate.accountId, generation: candidate.generation };
193
232
  forgetGenericFailoverRoster(route.providerName);
194
233
  return candidate;
195
234
  }
@@ -199,13 +238,13 @@ export async function prepareResponsesTransport(
199
238
  if (!oauthSelection) return null;
200
239
  // A revision also changes on per-account policy edits. Re-evaluate the selector
201
240
  // after credential waits even without a model route, rather than reusing stale active.
202
- const revisedAnthropic = route.providerName === "anthropic"
203
- ? resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision, route.modelId) : null;
241
+ const revisedAnthropic = anthropicInstance !== undefined
242
+ ? anthropicRouting!.resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision, route.modelId) : null;
204
243
  const revisedAnthropicId = revisedAnthropic?.accountId;
205
- if (route.providerName === "anthropic" && !revisedAnthropicId) return null;
244
+ if (anthropicInstance !== undefined && !revisedAnthropicId) return null;
206
245
  if (revisedAnthropic) anthropicReason = revisedAnthropic.reason;
207
- candidate = route.providerName === "anthropic"
208
- ? await getAnthropicPoolAccessSnapshot(revisedAnthropicId ?? oauthSelection.accountId)
246
+ candidate = anthropicInstance !== undefined
247
+ ? await anthropicRouting!.getAnthropicPoolAccessSnapshot(revisedAnthropicId ?? oauthSelection.accountId)
209
248
  : await getValidAccessSnapshotForAccount(route.providerName, oauthSelection.accountId, { requireUsableAccount: true });
210
249
  if (route.provider.googleMode === "cloud-code-assist" && !candidate.projectId) return null;
211
250
  }
@@ -268,6 +307,8 @@ export async function prepareResponsesTransport(
268
307
  snapshot: OAuthAccessSnapshot,
269
308
  retryParsed: OcxParsedRequest = parsed,
270
309
  ): Promise<OAuthAccessSnapshot | null> => {
310
+ requireAnthropicAdmission();
311
+ if (snapshot.provider !== route.providerName) return null;
271
312
  if (route.provider.googleMode === "cloud-code-assist" && !snapshot.projectId) return null;
272
313
  const signal = options.abortSignal ?? req.signal;
273
314
  let speculative = kiroLoadEnabled && options.accountLoad?.lease?.accountId !== snapshot.accountId
@@ -319,9 +360,9 @@ export async function prepareResponsesTransport(
319
360
  // served it. All three rotation sites funnel through here, so this is the only re-stamp
320
361
  // needed -- and putting it anywhere else would let one of the three drift.
321
362
  stampOAuthAccountLabel(logCtx, route.providerName, route.provider, snapshot.accountId);
322
- if (route.providerName === "anthropic") {
363
+ if (anthropicInstance !== undefined) {
323
364
  anthropicPoolAccountId = snapshot.accountId;
324
- logCtx.provider = formatAnthropicProviderForLog("anthropic", snapshot.accountId, config);
365
+ logCtx.provider = formatAnthropicProviderForLog(anthropicInstance!, snapshot.accountId, config);
325
366
  } else {
326
367
  genericFailoverAccountId = snapshot.accountId;
327
368
  }
@@ -354,14 +395,20 @@ export async function prepareResponsesTransport(
354
395
  }
355
396
  };
356
397
  const selectionIsCurrent = (binding: DispatchBinding | undefined): boolean => {
398
+ if (!anthropicAdmissionIsCurrent()) return false;
357
399
  if (route.provider.authMode === "forward") return true;
358
400
  if (!binding) return false;
359
401
  if (binding.kind === "api-key") return providerApiKeySelectionIsCurrent(config, route.providerName, binding.provider);
402
+ if (binding.snapshot.provider !== route.providerName) return false;
403
+ if (anthropicInstance) {
404
+ anthropicRouteDecision = currentAnthropicRouteDecision();
405
+ if (anthropicRouteDecision && !routeCandidates(anthropicRouting!.getEligibleAnthropicAccounts(Date.now(), route.modelId), anthropicRouteDecision).includes(binding.snapshot.accountId)) return false;
406
+ }
360
407
  const selected = captureOAuthAccountSelection(route.providerName);
361
408
  const row = getAccountCredentialWithStatus(route.providerName, binding.snapshot.accountId);
362
409
  return selected?.accountId === binding.selection.accountId && selected?.revision === binding.selection.revision
363
410
  && !!row && !row.paused && !row.needsReauth && row.credential.expires > Date.now()
364
- && (route.providerName !== "anthropic" || !getAnthropicAccountHealthSnapshot(binding.snapshot.accountId) && !anthropicRatePauseUntil(binding.snapshot.accountId) && !anthropicFamilyRejected(binding.snapshot.accountId, route.modelId))
411
+ && (anthropicInstance === undefined || !anthropicRouting!.getAnthropicAccountHealthSnapshot(binding.snapshot.accountId) && !anthropicRatePolicy!.anthropicRatePauseUntil(binding.snapshot.accountId) && !anthropicQuota!.anthropicFamilyRejected(binding.snapshot.accountId, route.modelId))
365
412
  && credentialGeneration(row.credential) === binding.snapshot.generation;
366
413
  };
367
414
  const resolveSelectionAdapter = (provider: OcxProviderConfig, retention = config.cacheRetention): ProviderAdapter => {
@@ -422,17 +469,18 @@ export async function prepareResponsesTransport(
422
469
  return resolved;
423
470
  };
424
471
  const refreshDispatchAdapter = async (requestParsed: OcxParsedRequest): Promise<ProviderAdapter> => {
472
+ requireAnthropicAdmission();
425
473
  if (route.provider.authMode === "oauth") {
426
474
  // Pacing may outlive admission. Preserve local pause/cooldown reasons even when
427
475
  // a strict route rejects the old proposal before another bearer can be resolved.
428
476
  let candidate = servingOAuthSnapshot;
429
- if (route.providerName === "anthropic") {
477
+ if (anthropicInstance !== undefined) {
430
478
  oauthSelection = captureOAuthAccountSelection(route.providerName);
431
- const accountId = await resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
432
- candidate = await getAnthropicPoolAccessSnapshot(accountId);
479
+ const accountId = await anthropicRouting!.resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
480
+ candidate = await anthropicRouting!.getAnthropicPoolAccessSnapshot(accountId);
433
481
  }
434
482
  if (!candidate || !await applyFailoverSnapshot(candidate, requestParsed)) {
435
- if (route.providerName === "anthropic") await resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
483
+ if (anthropicInstance !== undefined) await anthropicRouting!.resolveAnthropicDispatchAccountId(config, anthropicSessionKey, anthropicRouteDecision, route.modelId);
436
484
  throw new Error("OAuth account selection changed before dispatch");
437
485
  }
438
486
  } else {
@@ -459,6 +507,9 @@ export async function prepareResponsesTransport(
459
507
  selectedAdapter: ProviderAdapter,
460
508
  ...[requestParsed, incoming, emit]: Parameters<NonNullable<ProviderAdapter["runTurn"]>>
461
509
  ): Promise<void> => {
510
+ const producer = options.sendBudget && "beginSpendProducer" in options.sendBudget
511
+ ? (options.sendBudget as import("../../lib/request-execution-budget").RequestExecutionBudget).beginSpendProducer?.() : undefined;
512
+ try {
462
513
  for (let attempt = 0; attempt < 3; attempt++) {
463
514
  if (!selectionIsCurrent(adapterBindings.get(selectedAdapter))) selectedAdapter = await refreshRunTurnAdapter(requestParsed);
464
515
  const binding = adapterBindings.get(selectedAdapter);
@@ -499,6 +550,7 @@ export async function prepareResponsesTransport(
499
550
  selectedAdapter = await refreshRunTurnAdapter(requestParsed);
500
551
  }
501
552
  throw new Error("Account selection changed repeatedly before turn dispatch");
553
+ } finally { producer?.close(); }
502
554
  };
503
555
  const oauthDispatch = (wireRequest: AdapterRequest, requestParsed = parsed): ProviderFetchOptions["dispatchOverride"] => {
504
556
  if (route.provider.authMode === "forward") return undefined;
@@ -509,8 +561,12 @@ export async function prepareResponsesTransport(
509
561
  if (selectionIsCurrent(requestBindings.get(wireRequest))) {
510
562
  const fetchImpl = (route.provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch ?? execute;
511
563
  const binding = requestBindings.get(wireRequest);
512
- const snapshot = route.providerName === "anthropic" && anthropicPoolAccountId && binding?.kind === "oauth"
564
+ const snapshot = anthropicInstance !== undefined && binding?.kind === "oauth"
513
565
  ? binding.snapshot : undefined;
566
+ const target = destination instanceof Request ? destination.url : String(destination);
567
+ if (authorizedAnthropicTarget && target !== authorizedAnthropicTarget) {
568
+ throw new OAuthLoginRequiredError(anthropicInstance!);
569
+ }
514
570
  const writerGeneration = snapshot ? captureConfigGeneration() : 0;
515
571
  const sentHeaders = snapshot ? new Headers(dispatchInit.headers) : undefined;
516
572
  const ownsBearer = snapshot !== undefined
@@ -523,10 +579,23 @@ export async function prepareResponsesTransport(
523
579
  // against the destination it is actually going to rather than the one this dispatch
524
580
  // started with. Account reselection can move the upstream host, which would otherwise
525
581
  // apply a host-scoped decision to a different host.
526
- const releaseFamily = snapshot ? claimAnthropicFamilyRevalidation(snapshot.accountId, route.modelId) : () => {};
582
+ const releaseFamily = snapshot ? anthropicQuota!.claimAnthropicFamilyRevalidation(snapshot.accountId, route.modelId) : () => {};
527
583
  if (!releaseFamily) throw new AnthropicAccountCooldownError(1);
528
584
  let response: Response;
585
+ let physicalOwner: AnthropicPhysicalSendOwnership | null = null;
529
586
  try {
587
+ requireAnthropicAdmission();
588
+ if (!rebindPhysicalSend(options.sendBudget, { poolId: logCtx.spendPoolId ?? route.providerName,
589
+ identityId: logCtx.accountLogLabel })) throw new SendBudgetExhaustedError();
590
+ if (snapshot) {
591
+ // Reserve the incarnation at this synchronous send boundary, never on return.
592
+ physicalOwner = captureAnthropicPhysicalSendOwnership(snapshot);
593
+ if (!physicalOwner || !anthropicPhysicalSendOwnershipIsCurrent(physicalOwner)) {
594
+ throw new OAuthLoginRequiredError(anthropicInstance!);
595
+ }
596
+ sentOAuthSnapshot = snapshot;
597
+ passiveQuotaWriterGeneration = writerGeneration;
598
+ }
530
599
  commitKeyAttemptSend();
531
600
  response = await sendWithConnectionPolicy(
532
601
  fetchImpl,
@@ -538,12 +607,15 @@ export async function prepareResponsesTransport(
538
607
  // Observe each physical response before retries replace it. The binding belongs to
539
608
  // this dispatch, so a manual switch cannot file A's headers against B. Header
540
609
  // overrides and credential replacement make ownership unprovable: skip those writes.
541
- if (ownsBearer && snapshot) {
610
+ if (ownsBearer && snapshot && physicalOwner) {
542
611
  try {
543
- const current = getAccountCredentialWithStatus("anthropic", snapshot.accountId);
544
- if (current && !current.needsReauth && credentialGeneration(current.credential) === snapshot.generation) {
545
- bindAnthropicRefusalCredential(response, snapshot);
546
- recordAnthropicAccountQuotaFromHeaders(snapshot.accountId, response.headers, writerGeneration, response.status, route.modelId);
612
+ const current = getAccountCredentialWithStatus(anthropicInstance!, snapshot.accountId);
613
+ if (anthropicPhysicalSendOwnershipIsCurrent(physicalOwner)
614
+ && snapshot.provider === anthropicInstance && current && !current.needsReauth
615
+ && current.credential.access === snapshot.accessToken
616
+ && credentialGeneration(current.credential) === snapshot.generation) {
617
+ bindAnthropicRefusalCredentialForSend(response, physicalOwner);
618
+ recordAnthropicAccountQuotaFromHeadersForInstance(anthropicInstance!, snapshot.accountId, response.headers, writerGeneration, response.status, route.modelId);
547
619
  }
548
620
  } catch { /* best-effort observation cannot fail the response */ }
549
621
  }
@@ -583,7 +655,7 @@ export async function prepareResponsesTransport(
583
655
  throw new Error("OAuth account selection changed repeatedly before dispatch");
584
656
  };
585
657
  };
586
- const anthropicSessionKey = route.providerName === "anthropic" && route.provider.authMode === "oauth"
658
+ const anthropicSessionKey = anthropicInstance !== undefined && route.provider.authMode === "oauth"
587
659
  ? anthropicSessionKeyFromParts({
588
660
  sessionIdHeader: sessionIdHeaderFromRequest(req.headers),
589
661
  threadIdHeader: req.headers.get("thread-id"),
@@ -594,17 +666,17 @@ export async function prepareResponsesTransport(
594
666
  : null;
595
667
  if (route.provider.authMode === "oauth") {
596
668
  try {
597
- if (route.providerName === "anthropic" && isAnthropicAccountPoolEnabled(config)) {
598
- const routeResult = resolveAnthropicModelRoute(config, route.modelId);
669
+ if (anthropicInstance !== undefined && anthropicRouting!.isAnthropicAccountPoolEnabled(config)) {
670
+ const routeResult = resolveAnthropicModelRouteForInstance(anthropicInstance, config, route.modelId);
599
671
  if (routeResult.error) return formatErrorResponse(400, "invalid_request_error", `Invalid Anthropic model routes: ${routeResult.error}`);
600
672
  anthropicRouteDecision = routeResult.decision;
601
- const selection = resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision, route.modelId);
673
+ const selection = anthropicRouting!.resolveAnthropicAccountForSession(anthropicSessionKey, config, Date.now(), anthropicRouteDecision, route.modelId);
602
674
  if (!selection.accountId) {
603
675
  if (selection.reason === "paused") return formatErrorResponse(403, "permission_error", "Anthropic OAuth accounts are paused. Resume an account in account settings and retry.");
604
676
  // Route names may resemble account IDs; log only the matched rule position.
605
- if (anthropicRouteDecision) console.warn(`[anthropic-pool] route:#${anthropicRouteDecision.position} ${selection.reason}; answering locally`);
677
+ if (anthropicRouteDecision) console.warn(`[${anthropicInstance}-pool] route:#${anthropicRouteDecision.position} ${selection.reason}; answering locally`);
606
678
  if (selection.reason === "all-cooled") {
607
- const retryAfterSec = getAnthropicPoolRetryAfterSeconds(Date.now(), anthropicRouteDecision, route.modelId);
679
+ const retryAfterSec = anthropicRouting!.getAnthropicPoolRetryAfterSeconds(Date.now(), anthropicRouteDecision, route.modelId);
608
680
  return formatErrorResponse(
609
681
  429,
610
682
  "rate_limit_error",
@@ -614,12 +686,12 @@ export async function prepareResponsesTransport(
614
686
  }
615
687
  return formatErrorResponse(401, "authentication_error", anthropicRouteDecision ? "No eligible Anthropic OAuth account for this model route" : "No eligible Anthropic OAuth account available");
616
688
  }
617
- const admitted = await commitResolvedOAuthSelection(await getAnthropicPoolAccessSnapshot(selection.accountId), true, selection.reason);
689
+ const admitted = await commitResolvedOAuthSelection(await anthropicRouting!.getAnthropicPoolAccessSnapshot(selection.accountId), true, selection.reason);
618
690
  if (!admitted) return formatErrorResponse(409, "conflict_error", "OAuth account selection changed; retry the request");
619
691
  anthropicPoolAccountId = admitted.accountId;
620
- if (anthropicRouteDecision) console.info(`[anthropic-pool] route:#${anthropicRouteDecision.position} ${selection.reason}`);
692
+ if (anthropicRouteDecision) console.info(`[${anthropicInstance}-pool] route:#${anthropicRouteDecision.position} ${selection.reason}`);
621
693
  route.provider = { ...route.provider, apiKey: admitted.accessToken };
622
- logCtx.provider = formatAnthropicProviderForLog("anthropic", admitted.accountId, config);
694
+ logCtx.provider = formatAnthropicProviderForLog(anthropicInstance!, admitted.accountId, config);
623
695
  } else {
624
696
  // Prefer the account with known headroom BEFORE the first attempt. Rotation alone
625
697
  // only reacts to a 429, so a turn could open on an account a previous probe already
@@ -664,7 +736,13 @@ export async function prepareResponsesTransport(
664
736
  const failedId = route.providerName === "kiro" ? oauthSelection?.accountId : undefined;
665
737
  const failedRow = failedId ? getAccountCredentialWithStatus("kiro", failedId) : null;
666
738
  const failedGeneration = failedRow ? credentialGeneration(failedRow.credential) : undefined;
667
- try { resolved = await getValidAccessTokenSnapshot(route.providerName); }
739
+ try {
740
+ if (anthropicInstance === "anthropic2") {
741
+ const accountId = oauthSelection?.accountId;
742
+ if (!accountId) throw new OAuthLoginRequiredError(anthropicInstance);
743
+ resolved = await anthropicRouting!.getAnthropicPoolAccessSnapshot(accountId);
744
+ } else resolved = await getValidAccessTokenSnapshot(route.providerName);
745
+ }
668
746
  catch (error) {
669
747
  if (route.providerName !== "kiro" || !(error instanceof OAuthLoginRequiredError)
670
748
  || !failedId || !failedGeneration) throw error;
@@ -733,7 +811,7 @@ export async function prepareResponsesTransport(
733
811
  // dropped whenever the pool flag is off, and a later 429 has no account to cool. Reactive
734
812
  // failover needs only the id: no affinity bind, no promotion, no quota-ranked pick. Those
735
813
  // are proactive and stay behind anthropicAccountPool.enabled.
736
- if (route.providerName === "anthropic" && hasAnthropicFailoverQuorum()) {
814
+ if (anthropicInstance !== undefined && anthropicRouting!.hasAnthropicFailoverQuorum()) {
737
815
  anthropicPoolAccountId = resolved.accountId;
738
816
  }
739
817
  // Captured beside the account it fences, so the two can never disagree.
@@ -903,7 +981,9 @@ export async function prepareResponsesTransport(
903
981
  set replayOAuthCredentialSnapshot(value: Pick<OAuthAccessSnapshot, "accountId" | "generation"> | undefined) {
904
982
  replayOAuthCredentialSnapshot = value;
905
983
  },
906
- anthropicRouteDecision,
984
+ get anthropicRouteDecision(): AnthropicRouteDecision | null { return anthropicRouteDecision; },
985
+ get anthropicInstance() { return anthropicInstance; },
986
+ currentAnthropicRouteDecision,
907
987
  get anthropicPoolAccountId(): string | null {
908
988
  return anthropicPoolAccountId;
909
989
  },
@@ -981,7 +1061,7 @@ export async function prepareResponsesTransport(
981
1061
  adapterBindings,
982
1062
  commitResolvedOAuthSelection,
983
1063
  refreshResolvedOAuthSelection,
984
- passiveQuotaWriterGeneration,
1064
+ get passiveQuotaWriterGeneration(): number { return passiveQuotaWriterGeneration; },
985
1065
  applyFailoverSnapshot,
986
1066
  selectionIsCurrent,
987
1067
  resolveSelectionAdapter,
@@ -10,6 +10,7 @@ import { createAdapterEventQueue, preflightAdapterEvents, type AdapterEventPrefl
10
10
  import {
11
11
  bindRouteReasoningReplayScope,
12
12
  adapterNeedsForcedContinuation,
13
+ adapterNeedsToolCallContinuation,
13
14
  adapterResponseReachedServingTerminal,
14
15
  } from "./core-replay";
15
16
  import { noteAttemptRecoveryWithheld, sealRequestAttemptIdentity, recordAttemptCredentialSource } from "../request-log";
@@ -51,6 +52,7 @@ import { planWebSearch } from "../../web-search";
51
52
  import { runTurnWebSearchInitialParsed, runTurnWebSearchLoop } from "../../web-search/run-turn-loop";
52
53
  import { WEB_SEARCH_TOOL_NAME } from "../../web-search/synthetic-tool";
53
54
  import { orderDevinMessagesOutput } from "../../claude/devin-output-order";
55
+ import { unboundPoolSpendRefusalMessage } from "../workflow-refusal";
54
56
 
55
57
  // LOCAL PATCH (runturn-websearch): top-level fields route binding or the
56
58
  // adapter itself may write during a turn. Iteration-local `turnParsed` objects
@@ -330,7 +332,7 @@ export async function executeResponsesRunTurn(
330
332
  status: 429,
331
333
  errorType: "rate_limit_error",
332
334
  code: SEND_BUDGET_EXHAUSTED_CODE,
333
- message: err.message,
335
+ message: unboundPoolSpendRefusalMessage(logCtx) ?? err.message,
334
336
  }
335
337
  : {
336
338
  type: "error",
@@ -790,7 +792,10 @@ export async function executeResponsesRunTurn(
790
792
  parsed._rawBody,
791
793
  response,
792
794
  continuationStateForResponse(providerState),
793
- responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
795
+ {
796
+ ...responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
797
+ retainForToolContinuation: adapterNeedsToolCallContinuation(transportState.adapter.name),
798
+ },
794
799
  );
795
800
  }
796
801
  notifyResponseComplete(response);
@@ -935,7 +940,10 @@ export async function executeResponsesRunTurn(
935
940
  parsed._rawBody,
936
941
  json,
937
942
  continuationStateForResponse(providerState),
938
- responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
943
+ {
944
+ ...responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
945
+ retainForToolContinuation: adapterNeedsToolCallContinuation(transportState.adapter.name),
946
+ },
939
947
  );
940
948
  }
941
949
  // #1926 gap 2: the buffered path queued its signature persists inside