@bitkyc08/opencodex 2.86.0 → 2.87.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 (378) hide show
  1. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  2. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  3. package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-DCd-A1az.js} +1 -1
  4. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  5. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  6. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic/beta-allowlist.ts +9 -8
  10. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  11. package/src/adapters/anthropic/passthrough.ts +7 -23
  12. package/src/adapters/anthropic-progress.ts +84 -0
  13. package/src/adapters/anthropic.ts +108 -21
  14. package/src/adapters/base.ts +2 -0
  15. package/src/adapters/claude-cli/adapter.ts +111 -23
  16. package/src/adapters/claude-cli/profiles.ts +2 -1
  17. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  18. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  19. package/src/adapters/coding-agent/protocol.ts +1 -1
  20. package/src/adapters/coding-agent/turn.ts +18 -2
  21. package/src/adapters/dsh-account.ts +97 -0
  22. package/src/adapters/google.ts +35 -8
  23. package/src/adapters/openai-chat/messages.ts +4 -3
  24. package/src/adapters/openai-chat/passthrough.ts +8 -2
  25. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  26. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  27. package/src/adapters/openai-responses/passthrough.ts +57 -125
  28. package/src/adapters/openai-responses/request-strips.ts +25 -3
  29. package/src/adapters/openai-responses/transport.ts +81 -0
  30. package/src/adapters/qoder/adapter.ts +4 -2
  31. package/src/adapters/registry.ts +5 -0
  32. package/src/adapters/zed.ts +26 -1
  33. package/src/advisor/activity.ts +55 -0
  34. package/src/advisor/budget.ts +62 -0
  35. package/src/advisor/consult.ts +4 -1
  36. package/src/advisor/context.ts +16 -4
  37. package/src/advisor/evidence-tool.ts +21 -0
  38. package/src/advisor/evidence.ts +117 -0
  39. package/src/advisor/runtime.ts +79 -31
  40. package/src/advisor/settings.ts +11 -2
  41. package/src/bridge/errors.ts +19 -1
  42. package/src/bridge/internal.ts +3 -1
  43. package/src/bridge/response-json.ts +18 -3
  44. package/src/bridge/sse.ts +58 -8
  45. package/src/bridge/terminal-refusal.ts +59 -0
  46. package/src/claude/desktop-3p-library.ts +32 -7
  47. package/src/claude/desktop-3p.ts +55 -13
  48. package/src/claude/desktop-first-party.ts +1 -1
  49. package/src/claude/desktop-picker-platform.ts +4 -0
  50. package/src/claude/desktop-picker.ts +15 -8
  51. package/src/claude/desktop-system-proxy.ts +50 -4
  52. package/src/claude/inbound-content-options.ts +11 -0
  53. package/src/claude/inbound.ts +33 -0
  54. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  55. package/src/claude/intercept/picker-runtime.ts +6 -5
  56. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  57. package/src/claude/intercept/picker-trust.ts +19 -0
  58. package/src/claude/intercept/port-policy.ts +30 -0
  59. package/src/claude/intercept/runtime.ts +97 -94
  60. package/src/claude/outbound.ts +38 -13
  61. package/src/cli/account-api.ts +1 -0
  62. package/src/cli/account-policy-dto.ts +6 -0
  63. package/src/cli/account-policy.ts +34 -5
  64. package/src/cli/account-preservation.ts +44 -0
  65. package/src/cli/account.ts +8 -2
  66. package/src/cli/advisor.ts +17 -3
  67. package/src/cli/capabilities-accounts.ts +20 -1
  68. package/src/cli/capabilities-agents-routing.ts +5 -2
  69. package/src/cli/capabilities-base.ts +38 -10
  70. package/src/cli/capabilities-observe-system.ts +63 -3
  71. package/src/cli/capabilities-provider-models.ts +11 -0
  72. package/src/cli/codex-cli-update.ts +151 -41
  73. package/src/cli/codex-provider.ts +44 -0
  74. package/src/cli/combo-input.ts +10 -1
  75. package/src/cli/combo-result.ts +1 -1
  76. package/src/cli/combo.ts +2 -1
  77. package/src/cli/cross-home-owner.ts +17 -14
  78. package/src/cli/dispatch.ts +25 -4
  79. package/src/cli/doctor.ts +6 -1
  80. package/src/cli/ensure-desktop-cold-start.ts +60 -7
  81. package/src/cli/help.ts +3 -2
  82. package/src/cli/index.ts +1 -1
  83. package/src/cli/integration-plan-dto.ts +1 -0
  84. package/src/cli/log-history.ts +172 -0
  85. package/src/cli/models-runtime-subcommands.ts +1 -0
  86. package/src/cli/models-runtime.ts +72 -8
  87. package/src/cli/observe-stream.ts +2 -0
  88. package/src/cli/observe.ts +16 -4
  89. package/src/cli/provider.ts +27 -5
  90. package/src/cli/registry.ts +28 -8
  91. package/src/cli/runtime-api.ts +5 -3
  92. package/src/cli/system-command.ts +40 -8
  93. package/src/cli/uninstall-client-state.ts +32 -0
  94. package/src/cli/usage-compare.ts +309 -0
  95. package/src/cli/usage-model-search.ts +4 -4
  96. package/src/cli/usage-report.ts +2 -2
  97. package/src/cli/v2.ts +1 -1
  98. package/src/client/machine-listener.ts +1 -3
  99. package/src/client/runtime.ts +1 -0
  100. package/src/clients/client-path.ts +51 -0
  101. package/src/clients/config-export/contracts.ts +2 -1
  102. package/src/clients/config-export.ts +18 -44
  103. package/src/clients/gentle-shell-path.ts +87 -0
  104. package/src/codex/account-auto-switch.ts +2 -0
  105. package/src/codex/account-identity.ts +29 -0
  106. package/src/codex/account-label.ts +1 -1
  107. package/src/codex/account-lifecycle.ts +21 -5
  108. package/src/codex/account-preservation.ts +28 -0
  109. package/src/codex/account-use-remaining.ts +23 -0
  110. package/src/codex/app-server-processes.ts +97 -7
  111. package/src/codex/app-server-restart-service.ts +55 -4
  112. package/src/codex/auth-api/account-list.ts +4 -0
  113. package/src/codex/auth-api/account-pause-group.ts +6 -50
  114. package/src/codex/auth-api/account-preservation.ts +57 -0
  115. package/src/codex/auth-api/routes.ts +8 -0
  116. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  117. package/src/codex/auth-api.ts +0 -1
  118. package/src/codex/auth-context.ts +2 -2
  119. package/src/codex/catalog/auto-review.ts +20 -3
  120. package/src/codex/catalog/build-entries.ts +9 -1
  121. package/src/codex/catalog/combo-member.ts +14 -5
  122. package/src/codex/catalog/configured-tiers.ts +1 -1
  123. package/src/codex/catalog/gather-capture.ts +2 -0
  124. package/src/codex/catalog/metadata.ts +11 -9
  125. package/src/codex/catalog/model-hints.ts +21 -16
  126. package/src/codex/catalog/model-visibility.ts +3 -1
  127. package/src/codex/catalog/parsing.ts +30 -86
  128. package/src/codex/catalog/provider-models.ts +36 -34
  129. package/src/codex/catalog/routed-gather.ts +12 -13
  130. package/src/codex/cli-install-provenance.ts +105 -17
  131. package/src/codex/cli-update-plan.ts +545 -0
  132. package/src/codex/config-write-lock.ts +23 -2
  133. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  134. package/src/codex/desktop-app-restart.ts +21 -0
  135. package/src/codex/desktop-compatibility/installed-build.ts +24 -6
  136. package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
  137. package/src/codex/desktop-compatibility/runtime.ts +12 -3
  138. package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
  139. package/src/codex/features.ts +85 -45
  140. package/src/codex/inject/history-failure.ts +27 -0
  141. package/src/codex/inject/restore.ts +24 -2
  142. package/src/codex/inject/routing-classify.ts +4 -3
  143. package/src/codex/inject.ts +18 -27
  144. package/src/codex/main-account-hard-lock.ts +30 -1
  145. package/src/codex/native-profile-startup.ts +1 -1
  146. package/src/codex/ocx-compaction-history.ts +12 -10
  147. package/src/codex/paths.ts +86 -1
  148. package/src/codex/provider-routing.ts +174 -0
  149. package/src/codex/refresh.ts +2 -2
  150. package/src/codex/routing/health-store.ts +1 -1
  151. package/src/codex/routing/selection.ts +4 -3
  152. package/src/codex/routing/thread-affinity.ts +1 -1
  153. package/src/codex/signed-in-identity.ts +43 -0
  154. package/src/codex/subagent-model-fallback.ts +1 -1
  155. package/src/codex/sync.ts +6 -1
  156. package/src/codex/user-identity.ts +16 -5
  157. package/src/codex/windows-local-appdata.ts +84 -0
  158. package/src/combos/failover.ts +6 -1
  159. package/src/combos/index.ts +1 -0
  160. package/src/combos/resolve.ts +46 -10
  161. package/src/combos/types.ts +15 -0
  162. package/src/config/diagnostics.ts +33 -6
  163. package/src/config/live-reconcile.ts +38 -2
  164. package/src/config/load-degrade.ts +17 -0
  165. package/src/config/opencode-wire-upgrade.ts +54 -0
  166. package/src/config/opencode-zen-upgrade.ts +52 -0
  167. package/src/config/owner-registry.ts +22 -17
  168. package/src/config/provider-validation.ts +45 -2
  169. package/src/config/schema/config-schema.ts +22 -0
  170. package/src/config/schema/leaf-validators.ts +38 -9
  171. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  172. package/src/config/schema/native-reasoning-retention.ts +26 -0
  173. package/src/config.ts +2 -0
  174. package/src/generated/compatibility-version.json +608 -284
  175. package/src/grok/sync.ts +1 -1
  176. package/src/images/loop.ts +46 -8
  177. package/src/integrations/catalog-refresh.ts +1 -1
  178. package/src/integrations/mutation-plan.ts +1 -0
  179. package/src/integrations/registry.ts +8 -0
  180. package/src/lib/codex-restart-contract.ts +15 -0
  181. package/src/lib/desktop-upstream-tunnel.ts +18 -53
  182. package/src/lib/empty-tool-catalog.ts +62 -0
  183. package/src/lib/errors.ts +19 -3
  184. package/src/lib/gcp-adc.ts +96 -9
  185. package/src/lib/gcp-credential-input.ts +12 -0
  186. package/src/lib/lab-live-route-production.ts +5 -0
  187. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  188. package/src/lib/redact.ts +2 -2
  189. package/src/oauth/account-quota-rank.ts +69 -2
  190. package/src/oauth/anthropic-account-refusal.ts +20 -2
  191. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  192. package/src/oauth/anthropic-routing.ts +1 -21
  193. package/src/oauth/anthropic.ts +38 -7
  194. package/src/oauth/callback-server.ts +27 -1
  195. package/src/oauth/chatgpt-device.ts +1 -1
  196. package/src/oauth/chatgpt-shared.ts +83 -0
  197. package/src/oauth/chatgpt.ts +8 -72
  198. package/src/oauth/dsh.ts +203 -0
  199. package/src/oauth/generic-account-failover.ts +33 -7
  200. package/src/oauth/generic-session-affinity.ts +125 -0
  201. package/src/oauth/index.ts +184 -109
  202. package/src/oauth/local-token-detect.ts +21 -1
  203. package/src/oauth/login-cli-errors.ts +44 -0
  204. package/src/oauth/login-cli.ts +69 -7
  205. package/src/oauth/login-flow-state.ts +371 -23
  206. package/src/oauth/meta-muse-errors.ts +41 -0
  207. package/src/oauth/meta-muse-keychain.ts +52 -0
  208. package/src/oauth/meta-muse.ts +51 -84
  209. package/src/oauth/model-discovery-auth.ts +3 -0
  210. package/src/oauth/pool-settings-capability.ts +4 -2
  211. package/src/oauth/refresh-flight-retirement.ts +48 -0
  212. package/src/oauth/session-affinity-key.ts +23 -0
  213. package/src/oauth/store-anthropic-instance.ts +9 -2
  214. package/src/oauth/store.ts +12 -5
  215. package/src/protocols/encoders/adapter-events.ts +5 -4
  216. package/src/protocols/encoders/messages.ts +9 -6
  217. package/src/protocols/provider-summary.ts +2 -1
  218. package/src/providers/api-key-cooldowns.ts +49 -0
  219. package/src/providers/api-key-resolve.ts +11 -6
  220. package/src/providers/api-key-response-ownership.ts +57 -0
  221. package/src/providers/api-key-selection.ts +31 -6
  222. package/src/providers/api-keys.ts +214 -3
  223. package/src/providers/claude-cli-usage.ts +96 -0
  224. package/src/providers/dsh-account-destination.ts +31 -0
  225. package/src/providers/dsh-account-models.ts +41 -0
  226. package/src/providers/gcp-credential-cleanup.ts +59 -0
  227. package/src/providers/key-failover.ts +54 -50
  228. package/src/providers/key-store.ts +7 -2
  229. package/src/providers/model-rename-fields.ts +3 -0
  230. package/src/providers/model-rename-migration.ts +2 -2
  231. package/src/providers/openai-tiers-destination.ts +11 -2
  232. package/src/providers/openai-tiers.ts +5 -12
  233. package/src/providers/opencode-go-transport.ts +3 -1
  234. package/src/providers/quota/account-cache.ts +6 -5
  235. package/src/providers/quota/account-row.ts +34 -0
  236. package/src/providers/quota/antigravity.ts +28 -7
  237. package/src/providers/quota/dsh-account.ts +70 -0
  238. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  239. package/src/providers/quota-types.ts +2 -0
  240. package/src/providers/quota.ts +14 -20
  241. package/src/providers/registry/entries-core.ts +37 -8
  242. package/src/providers/registry/types.ts +1 -1
  243. package/src/providers/request-pacing.ts +77 -19
  244. package/src/providers/routed-config.ts +339 -0
  245. package/src/providers/service-tier.ts +2 -1
  246. package/src/quota/window-mapping.ts +5 -2
  247. package/src/responses/anthropic-progress.ts +34 -0
  248. package/src/responses/assistant-replay.ts +35 -0
  249. package/src/responses/compaction-history-framing.ts +65 -0
  250. package/src/responses/compaction.ts +6 -1
  251. package/src/responses/incomplete-reason.ts +31 -0
  252. package/src/responses/parser.ts +50 -5
  253. package/src/responses/reasoning-envelope.ts +4 -0
  254. package/src/responses/reasoning-replay-cache.ts +24 -2
  255. package/src/responses/reasoning-retention.ts +173 -0
  256. package/src/responses/retained-compaction.ts +46 -0
  257. package/src/responses/schema.ts +23 -0
  258. package/src/router.ts +8 -321
  259. package/src/routing/health.ts +37 -22
  260. package/src/routing/history/indexer.ts +6 -5
  261. package/src/routing/history/ingest.ts +6 -2
  262. package/src/routing/profile.ts +1 -1
  263. package/src/server/admission-model-scope.ts +17 -0
  264. package/src/server/auth-cors.ts +11 -0
  265. package/src/server/chat-completions.ts +7 -1
  266. package/src/server/chat-native-reasoning.ts +123 -0
  267. package/src/server/chat-native-sse.ts +110 -7
  268. package/src/server/chat-native.ts +76 -17
  269. package/src/server/claude-messages.ts +55 -414
  270. package/src/server/effort-policy.ts +1 -1
  271. package/src/server/images.ts +15 -2
  272. package/src/server/index/model-alias-id.ts +36 -0
  273. package/src/server/index/optional-listeners.ts +2 -0
  274. package/src/server/index/serve-options.ts +9 -1
  275. package/src/server/index/startup-warnings.ts +2 -0
  276. package/src/server/index.ts +1 -1
  277. package/src/server/inference/advisor-diagnostics.ts +31 -0
  278. package/src/server/management/advisor-routes.ts +15 -4
  279. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  280. package/src/server/management/combo-routes.ts +176 -50
  281. package/src/server/management/companion-routes.ts +1 -1
  282. package/src/server/management/config-routes.ts +75 -4
  283. package/src/server/management/cursor-integration-routes.ts +1 -1
  284. package/src/server/management/model-routes.ts +11 -6
  285. package/src/server/management/model-rows.ts +14 -0
  286. package/src/server/management/native-integration-routes.ts +2 -7
  287. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  288. package/src/server/management/oauth-account-routes.ts +87 -14
  289. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  290. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  291. package/src/server/management/provider-overwrite-carry.ts +7 -0
  292. package/src/server/management/provider-routes.ts +25 -30
  293. package/src/server/management/route-registry.ts +10 -1
  294. package/src/server/management-api.ts +3 -1
  295. package/src/server/messages-native-tool-guard.ts +185 -0
  296. package/src/server/messages-native.ts +101 -21
  297. package/src/server/messages-shared.ts +431 -0
  298. package/src/server/relay.ts +1 -1
  299. package/src/server/request-log-terminal-status.ts +1 -1
  300. package/src/server/request-log.ts +6 -1
  301. package/src/server/responses/account-change-state.ts +8 -2
  302. package/src/server/responses/adapter-continuation.ts +13 -1
  303. package/src/server/responses/adapter-delivery.ts +19 -6
  304. package/src/server/responses/adapter-dispatch.ts +19 -2
  305. package/src/server/responses/advisor-retention.ts +117 -0
  306. package/src/server/responses/advisor-slot.ts +73 -41
  307. package/src/server/responses/codex-ws-wire.ts +15 -8
  308. package/src/server/responses/compact.ts +303 -125
  309. package/src/server/responses/compaction-recovery.ts +18 -10
  310. package/src/server/responses/compaction-retention.ts +61 -0
  311. package/src/server/responses/context-overflow.ts +11 -6
  312. package/src/server/responses/core-auth.ts +6 -0
  313. package/src/server/responses/core-codex-account.ts +11 -1
  314. package/src/server/responses/core-combo-failure.ts +2 -0
  315. package/src/server/responses/core-combo.ts +120 -36
  316. package/src/server/responses/core-errors.ts +3 -3
  317. package/src/server/responses/core-lifetime.ts +5 -2
  318. package/src/server/responses/core-opaque-recovery.ts +35 -0
  319. package/src/server/responses/core-options.ts +5 -0
  320. package/src/server/responses/core-pipeline.ts +141 -0
  321. package/src/server/responses/core-replay.ts +18 -2
  322. package/src/server/responses/core.ts +2 -115
  323. package/src/server/responses/fetch-helpers.ts +73 -19
  324. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  325. package/src/server/responses/native-response-control.ts +4 -2
  326. package/src/server/responses/passthrough-delivery.ts +3 -1
  327. package/src/server/responses/passthrough-dispatch.ts +15 -13
  328. package/src/server/responses/request-prepare.ts +76 -12
  329. package/src/server/responses/request-sidecar-auth.ts +2 -2
  330. package/src/server/responses/request-transport.ts +104 -27
  331. package/src/server/responses/response-effects.ts +1 -0
  332. package/src/server/responses/run-turn-execution.ts +96 -30
  333. package/src/server/responses/sidecar-execution.ts +20 -2
  334. package/src/server/responses/terminal-guard.ts +2 -16
  335. package/src/service/auth-preflight.ts +71 -0
  336. package/src/service/cli.ts +4 -2
  337. package/src/service/definition.ts +97 -0
  338. package/src/service/guards.ts +1 -61
  339. package/src/service/health-sentinel-linux.ts +112 -0
  340. package/src/service/health-sentinel-main.ts +76 -57
  341. package/src/service/health.ts +5 -89
  342. package/src/service/launchd.ts +27 -7
  343. package/src/service/logging.ts +72 -9
  344. package/src/service/repair.ts +2 -1
  345. package/src/service/state.ts +7 -8
  346. package/src/service/systemd.ts +1 -1
  347. package/src/service/windows-guarded-manager.ts +2 -1
  348. package/src/service/windows-ops.ts +1 -1
  349. package/src/service/windows-scheduler.ts +2 -7
  350. package/src/service/windows-taskxml.ts +2 -2
  351. package/src/service/windows-wscript.ts +14 -0
  352. package/src/service.ts +4 -2
  353. package/src/sidecar/auth.ts +2 -0
  354. package/src/types/config.ts +64 -3
  355. package/src/types/opencode-go.ts +61 -0
  356. package/src/types/opencode-zen.ts +29 -0
  357. package/src/types/provider.ts +32 -6
  358. package/src/types/request.ts +23 -2
  359. package/src/types/wire.ts +29 -37
  360. package/src/types.ts +1 -0
  361. package/src/update/npm-invocation.mjs +12 -2
  362. package/src/usage/cost.ts +48 -12
  363. package/src/usage/expected-prices.ts +3 -0
  364. package/src/usage/ledger-retention.ts +2 -1
  365. package/src/usage/ledger-scanner.ts +6 -5
  366. package/src/usage/log.ts +17 -8
  367. package/src/usage/summary.ts +22 -9
  368. package/src/usage/user-cost-overlays.ts +14 -19
  369. package/src/usage/user-cost-validation.ts +38 -0
  370. package/src/vision/eligibility.ts +13 -0
  371. package/src/web-search/loop.ts +55 -5
  372. package/src/web-search/progress-stream.ts +4 -1
  373. package/src/web-search/run-turn-loop.ts +9 -1
  374. package/gui/dist/assets/App-BjlttVLy.js +0 -58
  375. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  376. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  377. package/gui/dist/assets/index-BMssP4k3.js +0 -94
  378. package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
@@ -61,7 +61,9 @@ import {
61
61
  type UpstreamSendRecovery,
62
62
  UPSTREAM_RESET_REPLAY_REFUSED_CODE,
63
63
  } from "../lib/upstream-retry";
64
+ import { distributedKeyPhysicalFetch, distributedKeyResponseOwner, recordDistributedResponseFailure } from "../providers/api-key-response-ownership";
64
65
  import { providerApiKeySelectionIsCurrent, resolveCurrentProviderApiKeyTransport } from "../providers/api-key-selection";
66
+ import { RequestPacingQueueOverloadError } from "../providers/request-pacing";
65
67
  import {
66
68
  hasKeyPoolFailover,
67
69
  rateLimitRetryDelayMs,
@@ -91,12 +93,14 @@ import {
91
93
  resolvePassthroughBodyGuard,
92
94
  sanitizePassthroughToolCallIds,
93
95
  tapAnthropicSseForLog,
94
- } from "./claude-messages";
96
+ } from "./messages-shared";
95
97
  import { beginInferenceAttempt } from "./inference/attempt";
96
98
  import { createFinalRequestLog, type FinalRequestLogMeta } from "./inference/final-log";
97
99
  import { registerTurn, unregisterTurn } from "./lifecycle";
98
100
  import { nativeMessagesDeclineReason, type NativeMessagesSelector } from "./messages-native-eligibility";
99
101
  import { messagesSecondaryInstanceUnavailable } from "./messages-native-selector";
102
+ import { guardNativeMessagesEmptyToolCatalog, nativeMessagesClientToolName, nativeMessagesInlineToolNames } from "./messages-native-tool-guard";
103
+ import { UNDECLARED_TOOL_CALL_ERROR_CODE, undeclaredToolCallMessage } from "./responses-undeclared-tool-guard";
100
104
  import {
101
105
  nativeOAuthBindingIsCurrent,
102
106
  NativeOAuthSelectionChangedError,
@@ -315,6 +319,8 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
315
319
  const { req, config, logCtx, logIds, route, body, requestedModel, translatorBudget } = options;
316
320
  const nativeInstance = configuredAnthropicInstance(config, route.providerName);
317
321
  const requestedStream = body.stream === true;
322
+ const clientDeclaredEmptyToolCatalog = Array.isArray(body.tools) && body.tools.length === 0;
323
+ const declaredInlineTools = clientDeclaredEmptyToolCatalog ? nativeMessagesInlineToolNames(body) : undefined;
318
324
  logCtx.inboundProtocol = "messages";
319
325
  logCtx.model = route.modelId;
320
326
  logCtx.provider = route.providerName;
@@ -436,8 +442,16 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
436
442
  }
437
443
  } else {
438
444
  // Same pre-dispatch key preference every direct send path applies (see chat-native.ts).
439
- const proactiveKeyProvider = selectProactiveApiKeyTransport(config, route.providerName, route.provider);
440
- if (proactiveKeyProvider) route.provider = proactiveKeyProvider;
445
+ try {
446
+ const proactiveKeyProvider = selectProactiveApiKeyTransport(config, route.providerName, route.provider);
447
+ if (proactiveKeyProvider) route.provider = proactiveKeyProvider;
448
+ } catch (error) {
449
+ cleanupAbort(); upstream.abort();
450
+ if (!(error instanceof RequestPacingQueueOverloadError)) throw error;
451
+ const refusal = fail(429, error.message, "rate_limit_error");
452
+ refusal.headers.set("Retry-After", String(error.retryAfterSeconds));
453
+ return refusal;
454
+ }
441
455
  }
442
456
  let activeProvider: OcxProviderConfig = oauthBinding ? oauthProvider(oauthBinding) : route.provider;
443
457
  stampApiKeyAccountLabel(logCtx, route.providerName, activeProvider);
@@ -521,6 +535,15 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
521
535
  && nativeMessagesDeclineReason({ ...route, provider: routed }, body, config, selector) === undefined;
522
536
  };
523
537
 
538
+ // Keep the actual failed wire identity if a later recovery is refused before sending.
539
+ // A 429 remains eligible for explicit same-key retries until that ladder is finished.
540
+ let pendingDistributedFailure: (() => void) | undefined;
541
+ let distributedResponseOwnsKey = true;
542
+ const settleDistributedFailure = () => {
543
+ const record = pendingDistributedFailure;
544
+ pendingDistributedFailure = undefined;
545
+ record?.();
546
+ };
524
547
  const send = async (recovery?: "rate-limit-429" | "oauth-401" | "oauth-account-403" | "key-429" | "key-401"): Promise<Response> => {
525
548
  const remaining = remainingTransientSends();
526
549
  if (requestTransientPolicy && remaining <= 0) {
@@ -538,7 +561,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
538
561
  providerFetch(activeProvider, undefined, {
539
562
  providerName: route.providerName,
540
563
  modelId: route.modelId,
541
- dispatchOverride: async (_input, init, execute) => {
564
+ dispatchOverride: async (_input, init, execute, reacquirePacing) => {
542
565
  if (oauthBinding) {
543
566
  if (!nativeOAuthRouteIsCurrent(oauthBinding)) throw new NativeOAuthSelectionChangedError();
544
567
  // The OAuth twin of the key check below: re-resolve through the same selection
@@ -555,12 +578,25 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
555
578
  rebuildFor(oauthProvider(oauthBinding));
556
579
  logCtx.provider = formatAnthropicProviderForLog(route.providerName, oauthBinding.snapshot.accountId, config);
557
580
  }
558
- } else if (!providerApiKeySelectionIsCurrent(config, route.providerName, activeProvider)) {
559
- const current = resolveCurrentProviderApiKeyTransport(config, route.providerName, activeProvider);
560
- if (!current || nativeMessagesDeclineReason({ ...route, provider: current }, body, config, selector) !== undefined) {
561
- throw new Error("Provider key selection is no longer available for native Messages");
581
+ } else {
582
+ for (let attempt = 0; !providerApiKeySelectionIsCurrent(config, route.providerName, activeProvider); attempt++) {
583
+ if (attempt >= 3) throw new Error("Provider key selection changed repeatedly before native Messages dispatch");
584
+ let current = resolveCurrentProviderApiKeyTransport(config, route.providerName, activeProvider);
585
+ if (!current && activeProvider.requestPacing?.enabled
586
+ && (activeProvider.requestPacing.keys || activeProvider.requestPacing.keySelection === "round-robin")) {
587
+ const fresh = resolveCurrentProviderApiKeyTransport(config, route.providerName, { ...activeProvider, _apiKeyAttempt: undefined });
588
+ if (fresh) current = selectProactiveApiKeyTransport(config, route.providerName, fresh) ?? fresh;
589
+ }
590
+ if (!current || nativeMessagesDeclineReason({ ...route, provider: current }, body, config, selector) !== undefined) {
591
+ throw new Error("Provider key selection is no longer available for native Messages");
592
+ }
593
+ rebuildFor(current);
594
+ if (current.requestPacing?.enabled
595
+ && (current.requestPacing.keys || current.requestPacing.keySelection === "round-robin")) {
596
+ if (!reacquirePacing) throw new Error("Refreshed native Messages dispatch requires pacing readmission");
597
+ await reacquirePacing(current, route.modelId);
598
+ }
562
599
  }
563
- rebuildFor(current);
564
600
  }
565
601
  // The retry closure may hold a pre-reselection request: send the current one whole.
566
602
  const wire = activeRequest;
@@ -579,6 +615,7 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
579
615
  }
580
616
  const releaseFamily = snapshot && oauthBinding ? anthropicModelQuotaFor(oauthBinding.instance).claimAnthropicFamilyRevalidation(snapshot.accountId, route.modelId) : () => {};
581
617
  if (!releaseFamily) throw new AnthropicAccountCooldownError(1);
618
+ const selection = activeProvider._apiKeyAttempt;
582
619
  let dispatched: Response;
583
620
  const spendReport = createPhysicalSendReporter(physicalBudget, () => ({
584
621
  poolId: logCtx.spendPoolId ?? route.providerName, identityId: logCtx.accountLogLabel,
@@ -599,7 +636,9 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
599
636
  if (snapshot && !sendOwner) throw new NativeOAuthSelectionChangedError();
600
637
  physicalSendsUsed++;
601
638
  dispatched = await sendWithConnectionPolicy(
602
- anthropicBillingPhysicalFetch((activeProvider as OcxProviderTransport).fetch ?? execute, route.providerName, activeProvider),
639
+ distributedKeyPhysicalFetch(
640
+ anthropicBillingPhysicalFetch((activeProvider as OcxProviderTransport).fetch ?? execute, route.providerName, activeProvider),
641
+ route.providerName, activeProvider, wire.url, activeProvider.apiKeyTransport === "bearer" ? "authorization" : "x-api-key"),
603
642
  wire.url,
604
643
  applyUpstreamRecoveryInit({ ...init, method: "POST", headers, body: wire.body }, transportRecovery),
605
644
  { providerName: route.providerName, provider: activeProvider },
@@ -623,6 +662,14 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
623
662
  }
624
663
  } catch { /* Passive observation must not fail the response. */ }
625
664
  }
665
+ const ownsDistributedKey = !selection?.distributed
666
+ || distributedKeyResponseOwner(dispatched, config, route.providerName) !== null;
667
+ distributedResponseOwnsKey = ownsDistributedKey;
668
+ const failureStatus = dispatched.status;
669
+ pendingDistributedFailure = selection?.distributed && ownsDistributedKey && (failureStatus === 401 || failureStatus === 429)
670
+ && !isNonReplayableResponse(dispatched)
671
+ ? () => { recordDistributedResponseFailure(dispatched, config, route.providerName, req.signal); }
672
+ : undefined;
626
673
  if (!dispatched.ok) await recordKeyAttemptFailure(logCtx, dispatched, init.signal ?? upstream.signal);
627
674
  return dispatched;
628
675
  },
@@ -647,25 +694,28 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
647
694
  let response: Response;
648
695
  try {
649
696
  response = await send();
650
- const retryPolicy = oauthBinding ? undefined : rateLimitRetryPolicyFor(activeProvider);
697
+ let retryPolicy: ReturnType<typeof rateLimitRetryPolicyFor> | undefined;
651
698
  let retries = 0;
652
699
  let billingRecoveryActive = false;
653
700
  const keySendAvailable = () => transientSendAvailable()
654
701
  && (!billingRecoveryActive || Math.max(physicalSendsUsed, physicalBudget.used) < physicalBudget.policy.maxTotalModelSends);
655
- for (;;) {
702
+ const recoveryPasses = activeProvider._apiKeyAttempt?.distributed ? activeProvider.apiKeyPool?.length ?? 1 : 1;
703
+ for (let pass = 0; ; pass++) {
656
704
  const priorResponse = response;
657
705
  // A credential-scoped 401 on a static key pool says nothing about the sibling keys.
658
- while (response.status === 401 && hasKeyPoolFailover(activeProvider) && keySendAvailable()) {
706
+ while (response.status === 401 && distributedResponseOwnsKey && hasKeyPoolFailover(activeProvider) && keySendAvailable()) {
659
707
  const rotated = rotateProviderTransportOn401(config, route.providerName, activeProvider, {
660
708
  now: Date.now(),
661
709
  attemptedKey: activeProvider.apiKey,
710
+ attemptedSelection: activeProvider._apiKeyAttempt,
662
711
  });
712
+ pendingDistributedFailure = undefined; // Rotation already records this physical failure.
663
713
  if (!rotated) break;
664
714
  discard(response);
665
715
  rebuildFor(rotated);
666
716
  response = await send("key-401");
667
717
  }
668
- while ((response.status === 400 || response.status === 402) && hasKeyPoolFailover(activeProvider)) {
718
+ while ((response.status === 400 || response.status === 402) && distributedResponseOwnsKey && hasKeyPoolFailover(activeProvider)) {
669
719
  const rotated = await rotateAnthropicBillingKeyOnResponse(response, config, route.providerName, activeProvider, { signal: upstream.signal });
670
720
  if (!rotated) break;
671
721
  billingRecoveryActive = true;
@@ -674,9 +724,11 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
674
724
  rebuildFor(rotated);
675
725
  response = await send("key-401");
676
726
  }
727
+ if (pass === 0) retryPolicy = oauthBinding ? undefined : rateLimitRetryPolicyFor(activeProvider);
677
728
  // A refusal this proxy synthesized for a reset replay is not a provider rate limit.
678
729
  while (
679
730
  response.status === 429
731
+ && distributedResponseOwnsKey
680
732
  && !isNonReplayableResponse(response)
681
733
  && retryPolicy
682
734
  && retries < retryPolicy.attempts
@@ -691,13 +743,14 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
691
743
  if (upstream.signal.aborted) throw upstream.signal.reason;
692
744
  response = await send("rate-limit-429");
693
745
  }
694
- while (response.status === 429 && !isNonReplayableResponse(response) && hasKeyPoolFailover(activeProvider)) {
746
+ while (response.status === 429 && distributedResponseOwnsKey && !isNonReplayableResponse(response) && hasKeyPoolFailover(activeProvider)) {
695
747
  const rotated = rotateProviderTransportOn429(config, route.providerName, activeProvider, {
696
748
  retryAfter: response.headers.get("retry-after"),
697
749
  now: Date.now(),
698
750
  attemptedKey: activeProvider.apiKey,
699
751
  attemptedSelection: activeProvider._apiKeyAttempt,
700
752
  });
753
+ pendingDistributedFailure = undefined; // Rotation already records this physical failure.
701
754
  if (!rotated) break;
702
755
  // Rotation already recorded the cooldown; keep the terminal 429 when no send remains.
703
756
  if (!keySendAvailable()) break;
@@ -705,13 +758,17 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
705
758
  rebuildFor(rotated);
706
759
  response = await send("key-429");
707
760
  }
708
- // A sibling's next refusal may change class; the same counters survive that transition.
709
- if (response !== priorResponse && (
761
+ if (response === priorResponse || isNonReplayableResponse(response) || !keySendAvailable()) break;
762
+ // Billing class transitions share the physical-send ceiling; distributed-only
763
+ // transitions additionally retain their original bounded pool-pass allowance.
764
+ if (distributedResponseOwnsKey && (
710
765
  (billingRecoveryActive && [401, 429].includes(response.status))
711
766
  || (activeProvider.adapter === "anthropic" && hasKeyPoolFailover(activeProvider) && [400, 402].includes(response.status))
712
767
  )) continue;
713
- break;
768
+ if (!activeProvider._apiKeyAttempt?.distributed || !distributedResponseOwnsKey
769
+ || (response.status !== 401 && response.status !== 429) || pass + 1 >= recoveryPasses) break;
714
770
  }
771
+ settleDistributedFailure();
715
772
  const oauthRetryKey = {};
716
773
  const triedAccountIds = new Set<string>();
717
774
  let oauthFailovers = 0;
@@ -749,7 +806,13 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
749
806
  cleanupAbort();
750
807
  upstream.abort();
751
808
  if (req.signal.aborted) return fail(499, "Client cancelled request", "api_error");
809
+ settleDistributedFailure();
752
810
  const sendError = error instanceof UpstreamRetryEvidenceError ? error.cause : error;
811
+ if (sendError instanceof RequestPacingQueueOverloadError) {
812
+ const refusal = fail(429, sendError.message, "rate_limit_error");
813
+ refusal.headers.set("Retry-After", String(sendError.retryAfterSeconds));
814
+ return refusal;
815
+ }
753
816
  if (sendError instanceof SendBudgetExhaustedError) return fail(429, sendError.message, "send_budget_exhausted", "send_budget_exhausted");
754
817
  if (sendError instanceof NativeMessagesSpendRefusal) {
755
818
  const refusal = workflowRefusalResponse("workflow-spend-exhausted", logCtx);
@@ -794,15 +857,22 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
794
857
  const renamed = activeRequest.oauthToolNames
795
858
  ? restoreOAuthToolNamesInSse(observed, activeRequest.oauthToolNames, translatorBudget)
796
859
  : observed;
797
- const source = echoRequestedModel(renamed, requestedModel);
860
+ const echoed = echoRequestedModel(renamed, requestedModel);
861
+ let toolRefusal: string | undefined;
862
+ const source = clientDeclaredEmptyToolCatalog
863
+ ? guardNativeMessagesEmptyToolCatalog(echoed, translatorBudget, message => {
864
+ toolRefusal = message;
865
+ logCtx.errorCode = UNDECLARED_TOOL_CALL_ERROR_CODE;
866
+ }, declaredInlineTools)
867
+ : echoed;
798
868
  if (requestedStream) {
799
869
  transferTurnToStream();
800
870
  const relayed = tapAnthropicSseForLog(source, logCtx, (status, meta) => {
801
871
  try {
802
872
  cleanupAbort();
803
873
  bindUsage(logCtx.usage);
804
- finishLog(status, undefined, meta);
805
- if (meta.closeReason !== "terminal") upstream.abort();
874
+ finishLog(toolRefusal ? 502 : status, toolRefusal, toolRefusal ? { ...meta, terminalStatus: "failed" } : meta);
875
+ if (toolRefusal || meta.closeReason !== "terminal") upstream.abort();
806
876
  } finally {
807
877
  releaseStreamTurn();
808
878
  }
@@ -833,6 +903,11 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
833
903
  if (message.type === "error") {
834
904
  const error = isRec(message.error) ? message.error : {};
835
905
  const text = typeof error.message === "string" ? error.message : "upstream stream failed";
906
+ if (toolRefusal) {
907
+ upstream.abort();
908
+ finishLog(502, toolRefusal, { closeReason: "terminal", terminalStatus: "failed" });
909
+ return fail(502, toolRefusal, "api_error", UNDECLARED_TOOL_CALL_ERROR_CODE);
910
+ }
836
911
  // The tap's own verdict (a reset, a stall, the byte cap) closes this row with the same meta
837
912
  // as the streaming lane's row; a plain upstream error event keeps the non_stream row.
838
913
  const tapMeta = tapState.meta;
@@ -884,6 +959,11 @@ export async function handleNativeMessages(options: HandleNativeMessagesOptions)
884
959
  const message = activeRequest.oauthToolNames
885
960
  ? restoreOAuthToolNamesInMessage(parsedMessage, activeRequest.oauthToolNames)
886
961
  : parsedMessage;
962
+ const undeclaredTool = clientDeclaredEmptyToolCatalog ? nativeMessagesClientToolName(message, declaredInlineTools) : undefined;
963
+ if (undeclaredTool !== undefined) {
964
+ logCtx.errorCode = UNDECLARED_TOOL_CALL_ERROR_CODE;
965
+ return fail(502, undeclaredToolCallMessage(undeclaredTool), "api_error", UNDECLARED_TOOL_CALL_ERROR_CODE);
966
+ }
887
967
  bindUsage(anthropicUsageToOcx(isRec(message.usage) ? message.usage : undefined));
888
968
  if (logIds) recordFirstOutput(logCtx, logIds.start);
889
969
  try {