@bitkyc08/opencodex 2.86.0 → 2.88.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 (400) hide show
  1. package/gui/dist/assets/App-CHC89EXQ.js +58 -0
  2. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  3. package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
  4. package/gui/dist/assets/index-B5bsvxor.js +94 -0
  5. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  6. package/gui/dist/assets/quota-summary-D8y2wTsB.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-rejection-memo.ts +71 -0
  14. package/src/adapters/anthropic-replay.ts +135 -0
  15. package/src/adapters/anthropic-tool-catalog.ts +11 -0
  16. package/src/adapters/anthropic.ts +151 -33
  17. package/src/adapters/base.ts +2 -0
  18. package/src/adapters/claude-cli/adapter.ts +111 -23
  19. package/src/adapters/claude-cli/profiles.ts +2 -1
  20. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  21. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  22. package/src/adapters/coding-agent/protocol.ts +1 -1
  23. package/src/adapters/coding-agent/turn.ts +18 -2
  24. package/src/adapters/dsh-account.ts +97 -0
  25. package/src/adapters/google-progress-history.ts +53 -0
  26. package/src/adapters/google.ts +52 -9
  27. package/src/adapters/openai-chat/messages.ts +4 -3
  28. package/src/adapters/openai-chat/passthrough.ts +8 -2
  29. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  30. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  31. package/src/adapters/openai-responses/passthrough.ts +57 -125
  32. package/src/adapters/openai-responses/request-strips.ts +25 -3
  33. package/src/adapters/openai-responses/transport.ts +81 -0
  34. package/src/adapters/qoder/adapter.ts +4 -2
  35. package/src/adapters/registry.ts +5 -0
  36. package/src/adapters/zed.ts +26 -1
  37. package/src/advisor/activity.ts +55 -0
  38. package/src/advisor/budget.ts +62 -0
  39. package/src/advisor/consult.ts +4 -1
  40. package/src/advisor/context.ts +16 -4
  41. package/src/advisor/evidence-tool.ts +21 -0
  42. package/src/advisor/evidence.ts +117 -0
  43. package/src/advisor/runtime.ts +79 -31
  44. package/src/advisor/settings.ts +11 -2
  45. package/src/bridge/errors.ts +19 -1
  46. package/src/bridge/internal.ts +7 -1
  47. package/src/bridge/response-json.ts +18 -3
  48. package/src/bridge/sse.ts +59 -8
  49. package/src/bridge/terminal-refusal.ts +59 -0
  50. package/src/claude/desktop-3p-library.ts +32 -7
  51. package/src/claude/desktop-3p.ts +55 -13
  52. package/src/claude/desktop-first-party.ts +1 -1
  53. package/src/claude/desktop-picker-platform.ts +4 -0
  54. package/src/claude/desktop-picker.ts +15 -8
  55. package/src/claude/desktop-system-proxy.ts +50 -4
  56. package/src/claude/inbound-content-options.ts +77 -0
  57. package/src/claude/inbound.ts +33 -0
  58. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  59. package/src/claude/intercept/picker-runtime.ts +6 -5
  60. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  61. package/src/claude/intercept/picker-trust.ts +19 -0
  62. package/src/claude/intercept/port-policy.ts +30 -0
  63. package/src/claude/intercept/runtime.ts +97 -94
  64. package/src/claude/outbound.ts +46 -13
  65. package/src/cli/account-api.ts +1 -0
  66. package/src/cli/account-policy-dto.ts +6 -0
  67. package/src/cli/account-policy.ts +34 -5
  68. package/src/cli/account-preservation.ts +44 -0
  69. package/src/cli/account.ts +8 -2
  70. package/src/cli/advisor.ts +17 -3
  71. package/src/cli/capabilities-accounts.ts +20 -1
  72. package/src/cli/capabilities-agents-routing.ts +5 -2
  73. package/src/cli/capabilities-base.ts +38 -10
  74. package/src/cli/capabilities-observe-system.ts +63 -3
  75. package/src/cli/capabilities-provider-models.ts +11 -0
  76. package/src/cli/codex-cli-update.ts +151 -41
  77. package/src/cli/codex-provider.ts +44 -0
  78. package/src/cli/combo-input.ts +10 -1
  79. package/src/cli/combo-result.ts +1 -1
  80. package/src/cli/combo.ts +2 -1
  81. package/src/cli/cross-home-owner.ts +33 -16
  82. package/src/cli/dispatch.ts +25 -4
  83. package/src/cli/doctor.ts +6 -1
  84. package/src/cli/ensure-desktop-cold-start.ts +60 -7
  85. package/src/cli/help.ts +3 -2
  86. package/src/cli/index.ts +1 -1
  87. package/src/cli/integration-plan-dto.ts +1 -0
  88. package/src/cli/log-history.ts +172 -0
  89. package/src/cli/loopback-family.ts +31 -0
  90. package/src/cli/models-runtime-subcommands.ts +1 -0
  91. package/src/cli/models-runtime.ts +72 -8
  92. package/src/cli/observe-stream.ts +2 -0
  93. package/src/cli/observe.ts +16 -4
  94. package/src/cli/provider.ts +27 -5
  95. package/src/cli/registry.ts +28 -8
  96. package/src/cli/runtime-api.ts +5 -3
  97. package/src/cli/system-command.ts +40 -8
  98. package/src/cli/uninstall-client-state.ts +32 -0
  99. package/src/cli/usage-compare.ts +309 -0
  100. package/src/cli/usage-model-search.ts +4 -4
  101. package/src/cli/usage-report.ts +2 -2
  102. package/src/cli/v2.ts +1 -1
  103. package/src/client/machine-listener.ts +1 -3
  104. package/src/client/runtime.ts +1 -0
  105. package/src/clients/client-path.ts +51 -0
  106. package/src/clients/config-export/contracts.ts +2 -1
  107. package/src/clients/config-export.ts +18 -44
  108. package/src/clients/gentle-shell-path.ts +87 -0
  109. package/src/codex/account-auto-switch.ts +2 -0
  110. package/src/codex/account-identity.ts +29 -0
  111. package/src/codex/account-label.ts +1 -1
  112. package/src/codex/account-lifecycle.ts +21 -5
  113. package/src/codex/account-preservation.ts +28 -0
  114. package/src/codex/account-use-remaining.ts +23 -0
  115. package/src/codex/app-server-processes.ts +97 -7
  116. package/src/codex/app-server-restart-service.ts +55 -4
  117. package/src/codex/auth-api/account-list.ts +4 -0
  118. package/src/codex/auth-api/account-pause-group.ts +6 -50
  119. package/src/codex/auth-api/account-preservation.ts +57 -0
  120. package/src/codex/auth-api/routes.ts +8 -0
  121. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  122. package/src/codex/auth-api.ts +0 -1
  123. package/src/codex/auth-context.ts +2 -2
  124. package/src/codex/catalog/auto-review.ts +20 -3
  125. package/src/codex/catalog/build-entries.ts +9 -1
  126. package/src/codex/catalog/combo-member.ts +14 -5
  127. package/src/codex/catalog/configured-tiers.ts +1 -1
  128. package/src/codex/catalog/gather-capture.ts +2 -0
  129. package/src/codex/catalog/metadata.ts +11 -9
  130. package/src/codex/catalog/model-hints.ts +21 -16
  131. package/src/codex/catalog/model-visibility.ts +3 -1
  132. package/src/codex/catalog/parsing.ts +30 -86
  133. package/src/codex/catalog/provider-models.ts +36 -34
  134. package/src/codex/catalog/routed-gather.ts +14 -13
  135. package/src/codex/cli-install-provenance.ts +105 -17
  136. package/src/codex/cli-update-plan.ts +545 -0
  137. package/src/codex/config-write-lock.ts +23 -2
  138. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  139. package/src/codex/desktop-app-restart.ts +21 -0
  140. package/src/codex/desktop-compatibility/installed-build.ts +24 -6
  141. package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
  142. package/src/codex/desktop-compatibility/runtime.ts +12 -3
  143. package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
  144. package/src/codex/features.ts +85 -45
  145. package/src/codex/inject/history-failure.ts +27 -0
  146. package/src/codex/inject/restore.ts +24 -2
  147. package/src/codex/inject/routing-classify.ts +4 -3
  148. package/src/codex/inject.ts +18 -27
  149. package/src/codex/main-account-hard-lock.ts +30 -1
  150. package/src/codex/native-profile-startup.ts +1 -1
  151. package/src/codex/ocx-compaction-history.ts +12 -10
  152. package/src/codex/paths.ts +86 -1
  153. package/src/codex/provider-routing.ts +174 -0
  154. package/src/codex/refresh.ts +2 -2
  155. package/src/codex/routing/health-store.ts +1 -1
  156. package/src/codex/routing/selection.ts +4 -3
  157. package/src/codex/routing/thread-affinity.ts +1 -1
  158. package/src/codex/signed-in-identity.ts +43 -0
  159. package/src/codex/subagent-model-fallback.ts +1 -1
  160. package/src/codex/sync.ts +6 -1
  161. package/src/codex/user-identity.ts +16 -5
  162. package/src/codex/websocket-activity.ts +161 -0
  163. package/src/codex/websocket-registry.ts +25 -1
  164. package/src/codex/windows-local-appdata.ts +84 -0
  165. package/src/combos/failover.ts +6 -1
  166. package/src/combos/index.ts +1 -0
  167. package/src/combos/resolve.ts +46 -10
  168. package/src/combos/types.ts +15 -0
  169. package/src/config/diagnostics.ts +33 -6
  170. package/src/config/live-reconcile.ts +38 -2
  171. package/src/config/load-degrade.ts +17 -0
  172. package/src/config/opencode-wire-upgrade.ts +54 -0
  173. package/src/config/opencode-zen-upgrade.ts +53 -0
  174. package/src/config/owner-registry.ts +22 -17
  175. package/src/config/provider-validation.ts +45 -2
  176. package/src/config/schema/config-schema.ts +22 -0
  177. package/src/config/schema/leaf-validators.ts +38 -9
  178. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  179. package/src/config/schema/native-reasoning-retention.ts +26 -0
  180. package/src/config.ts +2 -0
  181. package/src/generated/compatibility-version.json +657 -297
  182. package/src/grok/sync.ts +1 -1
  183. package/src/images/loop.ts +116 -27
  184. package/src/integrations/catalog-refresh.ts +1 -1
  185. package/src/integrations/mutation-plan.ts +1 -0
  186. package/src/integrations/registry.ts +8 -0
  187. package/src/lib/codex-restart-contract.ts +15 -0
  188. package/src/lib/desktop-upstream-tunnel.ts +18 -53
  189. package/src/lib/empty-tool-catalog.ts +62 -0
  190. package/src/lib/errors.ts +19 -3
  191. package/src/lib/gcp-adc.ts +96 -9
  192. package/src/lib/gcp-credential-input.ts +12 -0
  193. package/src/lib/lab-live-route-production.ts +5 -0
  194. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  195. package/src/lib/redact.ts +2 -2
  196. package/src/lib/request-failure-model.ts +2 -0
  197. package/src/oauth/account-quota-rank.ts +69 -2
  198. package/src/oauth/anthropic-account-refusal.ts +20 -2
  199. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  200. package/src/oauth/anthropic-routing.ts +1 -21
  201. package/src/oauth/anthropic.ts +38 -7
  202. package/src/oauth/callback-server.ts +27 -1
  203. package/src/oauth/chatgpt-device.ts +1 -1
  204. package/src/oauth/chatgpt-shared.ts +83 -0
  205. package/src/oauth/chatgpt.ts +8 -72
  206. package/src/oauth/dsh.ts +203 -0
  207. package/src/oauth/generic-account-failover.ts +40 -11
  208. package/src/oauth/generic-session-affinity.ts +125 -0
  209. package/src/oauth/index.ts +184 -109
  210. package/src/oauth/local-token-detect.ts +21 -1
  211. package/src/oauth/login-cli-errors.ts +44 -0
  212. package/src/oauth/login-cli.ts +69 -7
  213. package/src/oauth/login-flow-state.ts +371 -23
  214. package/src/oauth/meta-muse-errors.ts +41 -0
  215. package/src/oauth/meta-muse-keychain.ts +52 -0
  216. package/src/oauth/meta-muse.ts +51 -84
  217. package/src/oauth/model-discovery-auth.ts +3 -0
  218. package/src/oauth/pool-settings-capability.ts +4 -2
  219. package/src/oauth/refresh-flight-retirement.ts +48 -0
  220. package/src/oauth/session-affinity-key.ts +23 -0
  221. package/src/oauth/store-anthropic-instance.ts +9 -2
  222. package/src/oauth/store.ts +12 -5
  223. package/src/protocols/encoders/adapter-events.ts +6 -5
  224. package/src/protocols/encoders/messages.ts +9 -6
  225. package/src/protocols/provider-summary.ts +2 -1
  226. package/src/providers/api-key-cooldowns.ts +49 -0
  227. package/src/providers/api-key-resolve.ts +11 -6
  228. package/src/providers/api-key-response-ownership.ts +57 -0
  229. package/src/providers/api-key-selection.ts +31 -6
  230. package/src/providers/api-keys.ts +214 -3
  231. package/src/providers/claude-cli-usage.ts +96 -0
  232. package/src/providers/dsh-account-destination.ts +31 -0
  233. package/src/providers/dsh-account-models.ts +41 -0
  234. package/src/providers/gcp-credential-cleanup.ts +59 -0
  235. package/src/providers/key-failover.ts +54 -50
  236. package/src/providers/key-store.ts +7 -2
  237. package/src/providers/model-rename-fields.ts +3 -0
  238. package/src/providers/model-rename-migration.ts +2 -2
  239. package/src/providers/openai-tiers-destination.ts +11 -2
  240. package/src/providers/openai-tiers.ts +5 -12
  241. package/src/providers/opencode-go-transport.ts +3 -1
  242. package/src/providers/opencode-zen-rate-limit.ts +7 -2
  243. package/src/providers/quota/account-cache.ts +6 -5
  244. package/src/providers/quota/account-row.ts +34 -0
  245. package/src/providers/quota/antigravity.ts +28 -7
  246. package/src/providers/quota/dsh-account.ts +70 -0
  247. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  248. package/src/providers/quota-types.ts +2 -0
  249. package/src/providers/quota.ts +14 -20
  250. package/src/providers/registry/entries-core.ts +37 -8
  251. package/src/providers/registry/types.ts +1 -1
  252. package/src/providers/request-pacing.ts +77 -19
  253. package/src/providers/routed-config.ts +339 -0
  254. package/src/providers/service-tier.ts +2 -1
  255. package/src/quota/window-mapping.ts +5 -2
  256. package/src/responses/anthropic-progress.ts +34 -0
  257. package/src/responses/assistant-replay.ts +35 -0
  258. package/src/responses/compaction-history-framing.ts +65 -0
  259. package/src/responses/compaction.ts +6 -1
  260. package/src/responses/incomplete-reason.ts +31 -0
  261. package/src/responses/parser.ts +50 -5
  262. package/src/responses/reasoning-envelope.ts +4 -0
  263. package/src/responses/reasoning-replay-cache.ts +74 -6
  264. package/src/responses/reasoning-retention.ts +173 -0
  265. package/src/responses/retained-compaction.ts +46 -0
  266. package/src/responses/schema.ts +23 -0
  267. package/src/router.ts +8 -321
  268. package/src/routing/analytics.ts +7 -15
  269. package/src/routing/health.ts +37 -22
  270. package/src/routing/history/indexer.ts +6 -5
  271. package/src/routing/history/ingest.ts +6 -2
  272. package/src/routing/profile.ts +1 -1
  273. package/src/server/admission-model-scope.ts +17 -0
  274. package/src/server/auth-cors.ts +11 -0
  275. package/src/server/chat-completions.ts +7 -1
  276. package/src/server/chat-native-reasoning.ts +123 -0
  277. package/src/server/chat-native-sse.ts +110 -7
  278. package/src/server/chat-native.ts +76 -17
  279. package/src/server/claude-messages.ts +56 -414
  280. package/src/server/effort-policy.ts +1 -1
  281. package/src/server/images.ts +15 -2
  282. package/src/server/index/live-sideband.ts +3 -2
  283. package/src/server/index/model-alias-id.ts +36 -0
  284. package/src/server/index/optional-listeners.ts +2 -0
  285. package/src/server/index/serve-options.ts +20 -6
  286. package/src/server/index/startup-warnings.ts +2 -0
  287. package/src/server/index/websocket-handler.ts +18 -4
  288. package/src/server/index.ts +8 -8
  289. package/src/server/inference/advisor-diagnostics.ts +31 -0
  290. package/src/server/lifecycle.ts +13 -1
  291. package/src/server/management/advisor-routes.ts +15 -4
  292. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  293. package/src/server/management/combo-routes.ts +176 -50
  294. package/src/server/management/companion-routes.ts +1 -1
  295. package/src/server/management/config-routes.ts +75 -4
  296. package/src/server/management/cursor-integration-routes.ts +1 -1
  297. package/src/server/management/model-routes.ts +11 -6
  298. package/src/server/management/model-rows.ts +14 -0
  299. package/src/server/management/native-integration-routes.ts +2 -7
  300. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  301. package/src/server/management/oauth-account-routes.ts +87 -14
  302. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  303. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  304. package/src/server/management/provider-overwrite-carry.ts +7 -0
  305. package/src/server/management/provider-routes.ts +25 -30
  306. package/src/server/management/route-registry.ts +10 -1
  307. package/src/server/management-api.ts +3 -1
  308. package/src/server/messages-native-tool-guard.ts +185 -0
  309. package/src/server/messages-native.ts +117 -24
  310. package/src/server/messages-shared.ts +431 -0
  311. package/src/server/relay.ts +1 -1
  312. package/src/server/request-log-terminal-status.ts +1 -1
  313. package/src/server/request-log.ts +6 -1
  314. package/src/server/responses/account-change-state.ts +8 -2
  315. package/src/server/responses/adapter-continuation.ts +47 -19
  316. package/src/server/responses/adapter-delivery.ts +72 -12
  317. package/src/server/responses/adapter-dispatch.ts +46 -3
  318. package/src/server/responses/advisor-retention.ts +117 -0
  319. package/src/server/responses/advisor-slot.ts +73 -41
  320. package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
  321. package/src/server/responses/codex-ws-wire.ts +15 -8
  322. package/src/server/responses/combo-stream-preflight.ts +3 -2
  323. package/src/server/responses/compact.ts +303 -125
  324. package/src/server/responses/compaction-recovery.ts +20 -10
  325. package/src/server/responses/compaction-retention.ts +61 -0
  326. package/src/server/responses/context-overflow.ts +11 -6
  327. package/src/server/responses/core-auth.ts +6 -0
  328. package/src/server/responses/core-codex-account.ts +11 -1
  329. package/src/server/responses/core-combo-failure.ts +2 -0
  330. package/src/server/responses/core-combo.ts +121 -36
  331. package/src/server/responses/core-errors.ts +3 -3
  332. package/src/server/responses/core-lifetime.ts +8 -4
  333. package/src/server/responses/core-opaque-recovery.ts +35 -0
  334. package/src/server/responses/core-options.ts +7 -0
  335. package/src/server/responses/core-pipeline.ts +141 -0
  336. package/src/server/responses/core-replay.ts +23 -3
  337. package/src/server/responses/core.ts +2 -115
  338. package/src/server/responses/fetch-helpers.ts +73 -19
  339. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  340. package/src/server/responses/native-response-control.ts +4 -2
  341. package/src/server/responses/passthrough-delivery.ts +3 -1
  342. package/src/server/responses/passthrough-dispatch.ts +15 -13
  343. package/src/server/responses/passthrough-execution.ts +3 -2
  344. package/src/server/responses/request-prepare.ts +77 -12
  345. package/src/server/responses/request-sidecar-auth.ts +2 -2
  346. package/src/server/responses/request-transport.ts +104 -27
  347. package/src/server/responses/response-effects.ts +2 -0
  348. package/src/server/responses/response-tool-ownership.ts +21 -0
  349. package/src/server/responses/run-turn-execution.ts +96 -30
  350. package/src/server/responses/sidecar-execution.ts +63 -9
  351. package/src/server/responses/sidecar-send-budget.ts +2 -2
  352. package/src/server/responses/terminal-guard.ts +2 -16
  353. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  354. package/src/server/ws-bridge.ts +2 -0
  355. package/src/service/auth-preflight.ts +71 -0
  356. package/src/service/cli.ts +4 -2
  357. package/src/service/definition.ts +97 -0
  358. package/src/service/guards.ts +1 -61
  359. package/src/service/health-sentinel-linux.ts +112 -0
  360. package/src/service/health-sentinel-main.ts +76 -57
  361. package/src/service/health.ts +5 -89
  362. package/src/service/launchd.ts +27 -7
  363. package/src/service/logging.ts +72 -9
  364. package/src/service/repair.ts +2 -1
  365. package/src/service/state.ts +7 -8
  366. package/src/service/systemd.ts +1 -1
  367. package/src/service/windows-guarded-manager.ts +2 -1
  368. package/src/service/windows-ops.ts +1 -1
  369. package/src/service/windows-scheduler.ts +2 -7
  370. package/src/service/windows-taskxml.ts +2 -2
  371. package/src/service/windows-wscript.ts +14 -0
  372. package/src/service.ts +4 -2
  373. package/src/sidecar/auth.ts +2 -0
  374. package/src/types/config.ts +64 -3
  375. package/src/types/opencode-go.ts +61 -0
  376. package/src/types/opencode-zen.ts +35 -0
  377. package/src/types/provider.ts +32 -6
  378. package/src/types/request.ts +31 -2
  379. package/src/types/wire.ts +31 -37
  380. package/src/types.ts +2 -0
  381. package/src/update/npm-invocation.mjs +12 -2
  382. package/src/usage/cost.ts +48 -12
  383. package/src/usage/expected-prices.ts +3 -0
  384. package/src/usage/ledger-retention.ts +2 -1
  385. package/src/usage/ledger-scanner.ts +6 -5
  386. package/src/usage/log.ts +17 -8
  387. package/src/usage/summary.ts +30 -11
  388. package/src/usage/telemetry-contract.ts +1 -0
  389. package/src/usage/user-cost-overlays.ts +14 -19
  390. package/src/usage/user-cost-validation.ts +38 -0
  391. package/src/vision/eligibility.ts +13 -0
  392. package/src/web-search/anthropic-history.ts +59 -0
  393. package/src/web-search/loop.ts +236 -73
  394. package/src/web-search/progress-stream.ts +15 -2
  395. package/src/web-search/run-turn-loop.ts +9 -1
  396. package/gui/dist/assets/App-BjlttVLy.js +0 -58
  397. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  398. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  399. package/gui/dist/assets/index-BMssP4k3.js +0 -94
  400. package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
@@ -30,7 +30,7 @@ import {
30
30
  rotateGenericOAuthAccountOn429,
31
31
  failoverAccountSnapshot,
32
32
  } from "../../oauth/generic-account-failover";
33
- import { OAuthAccountPausedError, publicOAuthAuthenticationErrorMessage, type OAuthAccessSnapshot } from "../../oauth/index";
33
+ import { OAuthAccountPausedError, OAuthTokenRefreshBusyError, publicOAuthAuthenticationErrorMessage, type OAuthAccessSnapshot } from "../../oauth/index";
34
34
  import { tryAlternateAfterTerminalRefresh } from "../../oauth/kiro-terminal-failover";
35
35
  import { resolveWireProtocolOverride } from "../adapter-resolve";
36
36
  import { formatErrorResponse, bridgeToResponsesSSE, buildResponseJSON } from "../../bridge";
@@ -49,12 +49,14 @@ import { rememberResponseState } from "../../responses/state";
49
49
  import { trackStreamLifetime } from "../lifecycle";
50
50
  import { awaitThoughtSignatureDurability } from "../../responses/thought-signature-replay";
51
51
  import { undeclaredToolCallMessage } from "../responses-undeclared-tool-guard";
52
+ import { emptyToolCatalogRefusal, guardEmptyToolCatalogEvents, guardEmptyToolCatalogStream } from "../../lib/empty-tool-catalog";
52
53
  // LOCAL PATCH (runturn-websearch)
53
54
  import { planWebSearch } from "../../web-search";
54
55
  import { runTurnWebSearchInitialParsed, runTurnWebSearchLoop } from "../../web-search/run-turn-loop";
55
56
  import { WEB_SEARCH_TOOL_NAME } from "../../web-search/synthetic-tool";
56
57
  import { orderDevinMessagesOutput } from "../../claude/devin-output-order";
57
58
  import { unboundPoolSpendRefusalMessage } from "../workflow-refusal";
59
+ import { claudeUsagePreflight } from "../../adapters/claude-cli/usage-admission";
58
60
 
59
61
  // LOCAL PATCH (runturn-websearch): top-level fields route binding or the
60
62
  // adapter itself may write during a turn. Iteration-local `turnParsed` objects
@@ -71,13 +73,16 @@ const RUNTURN_WS_ROUTE_STATE_KEYS = [
71
73
  "_providerContinuationOwner",
72
74
  "_providerContinuationCandidate",
73
75
  "_stripReasoningEncryptedContent",
76
+ "_stripNativeCompactionEncryptedContent",
77
+ "_nativeReasoningRetention",
78
+ "_nativeReasoningRetainedAcrossRoute",
74
79
  "_dropForeignReasoningItemIds",
75
80
  "_reasoningReplayScope",
76
81
  ] as const;
77
82
 
78
83
  /** One responsibility of the Responses request pipeline; state owners are explicit. */
79
84
  export async function executeResponsesRunTurn(
80
- requestContext: Pick<ResponsesRequestContext, "options" | "logCtx" | "config">,
85
+ requestContext: Pick<ResponsesRequestContext, "req" | "options" | "logCtx" | "config">,
81
86
  admissionState: ResponsesAdmissionState,
82
87
  requestState: Pick<
83
88
  PreparedResponsesRequest,
@@ -87,6 +92,7 @@ export async function executeResponsesRunTurn(
87
92
  | "translatorBudget"
88
93
  | "inboundWire"
89
94
  | "toolBridgeMaps"
95
+ | "clientToolCatalogRestriction"
90
96
  | "rememberKiroDeliveredFinalAnswer"
91
97
  | "responseStateOptions"
92
98
  >,
@@ -127,7 +133,7 @@ export async function executeResponsesRunTurn(
127
133
  >,
128
134
  completionPolicy: Pick<ResponsesCompletionPolicy, "emptyCompletionGuardEnabled">,
129
135
  ): Promise<Response> {
130
- const { options, logCtx, config } = requestContext;
136
+ const { req, options, logCtx, config } = requestContext;
131
137
  const {
132
138
  selectionIsCurrent,
133
139
  adapterBindings,
@@ -169,6 +175,33 @@ export async function executeResponsesRunTurn(
169
175
  const releaseSearchProbeLease = (): void => {
170
176
  sidecarState.openAiSidecar?.releaseProbeLease?.();
171
177
  };
178
+ // Publish the known subscription pause before headers or a CLI turn, preserving its reset
179
+ // across both Responses and translated Chat instead of presenting a retryable generic 502.
180
+ if (transportState.runTurnAdapter.name === "claude-cli") {
181
+ let usage: Awaited<ReturnType<typeof claudeUsagePreflight>>;
182
+ try {
183
+ usage = await claudeUsagePreflight(parsed.modelId);
184
+ } catch (error) {
185
+ // The outer cleanup does not own the sidecar's probe lease: hand it back before rethrowing.
186
+ releaseSearchProbeLease();
187
+ throw error;
188
+ }
189
+ if (usage.state === "exhausted") {
190
+ cancelResponseCompletion();
191
+ releaseSearchProbeLease();
192
+ const refusal = new Response(JSON.stringify({ error: {
193
+ type: "rate_limit_error", code: "claude_subscription_cooldown", param: null,
194
+ message: usage.message ?? "Claude subscription usage is exhausted.",
195
+ } }), {
196
+ status: 429,
197
+ headers: { "Content-Type": "application/json",
198
+ ...(usage.resetAt ? { "Retry-After": String(Math.max(1, Math.ceil((usage.resetAt - Date.now()) / 1000))) } : {}),
199
+ },
200
+ });
201
+ markResponseNonReplayable(refusal);
202
+ return refusal;
203
+ }
204
+ }
172
205
  // When Codex declared hosted web_search and a sidecar plan resolves, drive
173
206
  // the routed model through the same search interception the fetch-path loop
174
207
  // runs — injected as a function tool, calls intercepted, results appended to
@@ -186,7 +219,9 @@ export async function executeResponsesRunTurn(
186
219
  }
187
220
 
188
221
  const runTurnAbort = new AbortController();
189
- const cleanupRunTurnAbort = linkAbortSignal(runTurnAbort, options.abortSignal);
222
+ const cleanupRequestAbort = linkAbortSignal(runTurnAbort, req.signal);
223
+ const cleanupOptionAbort = linkAbortSignal(runTurnAbort, options.abortSignal);
224
+ const cleanupRunTurnAbort = (): void => { cleanupRequestAbort(); cleanupOptionAbort(); };
190
225
  const devinProducers = new Set<AbortController>();
191
226
  const orderMessagesOutput = (source: AsyncIterable<AdapterEvent>): AsyncIterable<AdapterEvent> =>
192
227
  inboundWire === "anthropic" && transportState.runTurnAdapter.name === "devin" && !routedCompaction
@@ -197,12 +232,13 @@ export async function executeResponsesRunTurn(
197
232
  const queue = createAdapterEventQueue({
198
233
  onBacklogExceeded: () => runTurnAbort.abort(),
199
234
  });
200
- const refreshRunTurnSelection = async (): Promise<void> => {
201
- if (selectionIsCurrent(adapterBindings.get(transportState.runTurnAdapter))) return;
235
+ const refreshRunTurnSelection = async (): Promise<boolean> => {
236
+ if (selectionIsCurrent(adapterBindings.get(transportState.runTurnAdapter))) return false;
202
237
  await refreshRunTurnAdapter(parsed);
203
238
  bindRouteReasoningReplayScope({ parsed, providerName: route.providerName, provider: route.provider,
204
239
  adapterName: transportState.runTurnAdapter.name, oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot });
205
240
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.runTurnAdapter.name, logCtx.accountLogLabel);
241
+ return true;
206
242
  };
207
243
  // Initial admission must settle before the streaming Response commits HTTP 200.
208
244
  // Let the outer Responses facade preserve the local retryable-429 contract.
@@ -256,13 +292,18 @@ export async function executeResponsesRunTurn(
256
292
  if (!pacingSlotAcquired) {
257
293
  pacingSlot = await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
258
294
  }
259
- await refreshRunTurnSelection();
295
+ const selectionChanged = await refreshRunTurnSelection();
260
296
  const selectedBinding = adapterBindings.get(transportState.runTurnAdapter);
261
297
  if (lastKeyFailure && selectedBinding?.kind === "api-key"
262
298
  && failedKeys.has(selectedBinding.provider.apiKey ?? "")) {
263
299
  emit(lastKeyFailure);
264
300
  return;
265
301
  }
302
+ if (selectionChanged) {
303
+ releaseProviderRequestSlot(pacingSlot);
304
+ pacingSlot = undefined;
305
+ pacingSlot = await waitForProviderRequestSlot(route.providerName, route.provider, route.modelId, runTurnAbort.signal);
306
+ }
266
307
  // LOCAL PATCH (runturn-websearch): refreshRunTurnSelection binds route
267
308
  // state onto `parsed`; mirror it onto the iteration-local copy the
268
309
  // adapter actually receives.
@@ -436,7 +477,19 @@ export async function executeResponsesRunTurn(
436
477
  // selected; the turn then moves to a surviving stored account, or, with none, the client
437
478
  // gets the login instruction instead of an opaque upstream 401.
438
479
  let oauth401ReplayAttempted = false;
439
- let pausedAuthRecovery = false;
480
+ let authRecoveryRefusal: Response | undefined;
481
+ const refuseLocalAuthRecovery = (error: Extract<AdapterEvent, { type: "error" }>, cause: unknown): boolean => {
482
+ const cancelled = runTurnAbort.signal.aborted;
483
+ if (!cancelled && !(cause instanceof OAuthTokenRefreshBusyError) && !(cause instanceof OAuthAccountPausedError)) return false;
484
+ const status = cancelled ? 499 : cause instanceof OAuthTokenRefreshBusyError ? 503 : 403;
485
+ const errorType = cancelled ? "client_cancelled" : status === 503 ? "server_error" : "permission_error";
486
+ const message = cancelled ? "Client cancelled request" : publicOAuthAuthenticationErrorMessage(cause);
487
+ // If a readiness heartbeat already committed SSE, the mapped event remains retryable
488
+ // capacity rather than retaining the rejected credential's authentication error/code.
489
+ Object.assign(error, { status, errorType, message, code: undefined, retryable: status === 503 });
490
+ authRecoveryRefusal = formatErrorResponse(status, errorType, message, status === 503 ? { retryAfter: "1" } : undefined);
491
+ return true;
492
+ };
440
493
  const recoverRunTurnAdapterOnPreflight401 = async (
441
494
  error: Extract<AdapterEvent, { type: "error" }>,
442
495
  ): Promise<boolean> => {
@@ -450,9 +503,7 @@ export async function executeResponsesRunTurn(
450
503
  try {
451
504
  admitted = await applyFailoverSnapshot(await refreshResolvedOAuthSelection(sent));
452
505
  } catch (err) {
453
- if (err instanceof OAuthAccountPausedError) {
454
- pausedAuthRecovery = true;
455
- Object.assign(error, { status: 403, errorType: "permission_error", message: publicOAuthAuthenticationErrorMessage(err) });
506
+ if (refuseLocalAuthRecovery(error, err)) {
456
507
  hop.permit?.release();
457
508
  return false;
458
509
  }
@@ -475,7 +526,9 @@ export async function executeResponsesRunTurn(
475
526
  } catch (err) {
476
527
  // Applying the alternate can still fail, e.g. when a newer manual selection points back
477
528
  // at the flagged account; the client then needs the login instruction, not the raw 401.
478
- Object.assign(error, { errorType: "authentication_error", message: publicOAuthAuthenticationErrorMessage(err) });
529
+ if (!refuseLocalAuthRecovery(error, err)) {
530
+ Object.assign(error, { errorType: "authentication_error", message: publicOAuthAuthenticationErrorMessage(err) });
531
+ }
479
532
  hop.permit?.release();
480
533
  return false;
481
534
  }
@@ -699,14 +752,19 @@ export async function executeResponsesRunTurn(
699
752
  return orderMessagesOutput(retryQueue.stream());
700
753
  };
701
754
 
702
- const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, bareCustomToolNames, toolSearchToolNames } = toolBridgeMaps;
755
+ const clientToolCatalog = requestState.clientToolCatalogRestriction && route.provider.authMode !== "forward"
756
+ ? requestState.clientToolCatalogRestriction : undefined;
757
+ const { toolNsMap, declaredToolNames, toolParameterSchemas, freeformToolNames, bareCustomToolNames, toolSearchToolNames } = clientToolCatalog
758
+ ? { ...toolBridgeMaps, ...clientToolCatalog } : toolBridgeMaps;
703
759
  const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";
704
760
  const classifyUndeclaredFirstTool = (
705
761
  event: AdapterEvent,
706
762
  ): Extract<AdapterEvent, { type: "error" }> | undefined => {
707
- if (!enforceDeclaredToolNames || event.type !== "tool_call_start") return undefined;
708
763
  // This tool is declared to the adapter by the private search loop.
709
- if (wsPlan && event.name === WEB_SEARCH_TOOL_NAME) return undefined;
764
+ if (wsPlan && event.type === "tool_call_start" && event.name === WEB_SEARCH_TOOL_NAME) return undefined;
765
+ const emptyCatalogRefusal = emptyToolCatalogRefusal(event, clientToolCatalog);
766
+ if (emptyCatalogRefusal) return emptyCatalogRefusal;
767
+ if (!enforceDeclaredToolNames || event.type !== "tool_call_start") return undefined;
710
768
  const effectiveName = normalizeDeclaredToolName(event.name, declaredToolNames, undefined, bareCustomToolNames);
711
769
  if (declaredToolNames.has(effectiveName)) return undefined;
712
770
  return {
@@ -752,13 +810,13 @@ export async function executeResponsesRunTurn(
752
810
  // Preflight holds only heartbeats and the first meaningful event. A first-event 429 can be
753
811
  // replayed transparently; after any output reaches the bridge, a later error stays terminal.
754
812
  eventSource = await preflightRunTurnFailover(eventSource, wsFirstParsed, preflightDeadlineAt);
755
- if (pausedAuthRecovery) {
813
+ if (authRecoveryRefusal) {
756
814
  cancelResponseCompletion();
757
815
  runTurnAbort.abort();
758
816
  queue.close();
759
817
  cleanupRunTurnAbort();
760
818
  releaseSearchProbeLease();
761
- return formatErrorResponse(403, "permission_error", publicOAuthAuthenticationErrorMessage(new OAuthAccountPausedError()));
819
+ return authRecoveryRefusal;
762
820
  }
763
821
  }
764
822
  if (grokDevinPreflight) {
@@ -776,9 +834,13 @@ export async function executeResponsesRunTurn(
776
834
  if (preflight.error || preflight.empty) {
777
835
  runTurnAbort.abort();
778
836
  queue.close();
779
- const message = preflight.error?.message ?? "Adapter ended before producing a response";
780
- const failure = formatErrorResponse(preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? 503 : 502,
781
- preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : "upstream_error", redactSecretString(message));
837
+ const classified = adapterFailureFromEvent(preflight.error ?? {
838
+ type: "error", status: 502, message: "Adapter ended before producing a response",
839
+ });
840
+ const storageRefusal = preflight.error?.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
841
+ const failure = formatErrorResponse(storageRefusal ? 503 : classified.httpStatus,
842
+ storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : classified.error.type,
843
+ redactSecretString(classified.error.message), { code: classified.error.code, classified: true });
782
844
  // A replay-unsafe heartbeat means the adapter already ran a local side effect, so the
783
845
  // combo must not send this turn to another target: the failure stays with this child.
784
846
  if (preflight.replayUnsafe) markResponseNonReplayable(failure);
@@ -824,7 +886,7 @@ export async function executeResponsesRunTurn(
824
886
  console.warn(emptyCompletionNotice(route.providerName, route.modelId));
825
887
  });
826
888
  const sseStream = bridgeToResponsesSSE(
827
- guardedSource, parsed._responseModelId ?? parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
889
+ guardEmptyToolCatalogStream(guardedSource, clientToolCatalog, translatorBudget), parsed._responseModelId ?? parsed.modelId, toolNsMap, freeformToolNames, toolSearchToolNames,
828
890
  () => {
829
891
  cancelResponseCompletion();
830
892
  runTurnAbort.abort();
@@ -840,9 +902,11 @@ export async function executeResponsesRunTurn(
840
902
  declaredToolNames,
841
903
  bareCustomToolNames,
842
904
  enforceDeclaredToolNames,
905
+ // Same wire split: only a Responses client (Codex) needs the refusal as a failure.
906
+ terminalRefusalAsFailed: enforceDeclaredToolNames,
843
907
  toolParameterSchemas,
844
908
  ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
845
- ...(routedCompaction ? { compaction: true } : {}),
909
+ ...(routedCompaction ? { compaction: true, compactionRetention: parsed._compactionRetention } : {}),
846
910
  // grok-build's strict decoder dies on the typed response.heartbeat frame; its
847
911
  // eventsource layer tolerates comment keep-alives. Codex needs the opposite.
848
912
  ...(logCtx.surface === "grok" ? { heartbeatStyle: "comment" as const } : {}),
@@ -897,13 +961,13 @@ export async function executeResponsesRunTurn(
897
961
  for await (const event of await preflightRunTurnFailover(
898
962
  (async function* () { yield* firstAttemptEvents; })(),
899
963
  )) runTurnEvents.push(event);
900
- if (pausedAuthRecovery) {
964
+ if (authRecoveryRefusal) {
901
965
  cancelResponseCompletion();
902
966
  runTurnAbort.abort();
903
967
  queue.close();
904
968
  cleanupRunTurnAbort();
905
969
  releaseSearchProbeLease();
906
- return formatErrorResponse(403, "permission_error", publicOAuthAuthenticationErrorMessage(new OAuthAccountPausedError()));
970
+ return authRecoveryRefusal;
907
971
  }
908
972
  }
909
973
  if (grokDevinPreflight) {
@@ -961,6 +1025,7 @@ export async function executeResponsesRunTurn(
961
1025
  }
962
1026
  events = searched;
963
1027
  }
1028
+ events = guardEmptyToolCatalogEvents(events, clientToolCatalog, translatorBudget);
964
1029
  if (options.comboAttempt) {
965
1030
  const firstMeaningfulIndex = events.findIndex(event => event.type !== "heartbeat");
966
1031
  const firstMeaningful = firstMeaningfulIndex === -1 ? undefined : events[firstMeaningfulIndex];
@@ -974,12 +1039,13 @@ export async function executeResponsesRunTurn(
974
1039
  ? classifyUndeclaredFirstTool(firstMeaningful)
975
1040
  : undefined;
976
1041
  if (!firstMeaningful || firstMeaningful.type === "error" || classifiedError) {
977
- const message = classifiedError?.message ?? (firstMeaningful?.type === "error"
978
- ? firstMeaningful.message
979
- : "Adapter ended before producing a response");
1042
+ const classified = adapterFailureFromEvent(classifiedError ?? (firstMeaningful?.type === "error"
1043
+ ? firstMeaningful
1044
+ : { type: "error", status: 502, message: "Adapter ended before producing a response" }));
980
1045
  const storageRefusal = firstMeaningful?.type === "error" && firstMeaningful.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE;
981
- const failure = formatErrorResponse(storageRefusal ? 503 : 502,
982
- storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : "upstream_error", redactSecretString(message));
1046
+ const failure = formatErrorResponse(storageRefusal ? 503 : classified.httpStatus,
1047
+ storageRefusal ? SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE : classified.error.type,
1048
+ redactSecretString(classified.error.message), { code: classified.error.code, classified: true });
983
1049
  if (replayUnsafe) markResponseNonReplayable(failure);
984
1050
  return failure;
985
1051
  }
@@ -997,7 +1063,7 @@ export async function executeResponsesRunTurn(
997
1063
  freeformToolNames,
998
1064
  bareCustomToolNames,
999
1065
  toolSearchToolNames,
1000
- ...(routedCompaction ? { compaction: true } : {}),
1066
+ ...(routedCompaction ? { compaction: true, compactionRetention: parsed._compactionRetention } : {}),
1001
1067
  onProviderState: state => { providerState = state; },
1002
1068
  onUsage: usage => {
1003
1069
  transportState.bindKeyUsageFromBridge(usage);
@@ -1,3 +1,4 @@
1
+ import { createAnthropicThinkingRecovery } from "./anthropic-thinking-recovery";
1
2
  import { rotateAnthropicAccountOnResponseForInstance } from "../../oauth/anthropic-account-refusal";
2
3
  import type { ResponsesRequestContext } from "./core-options";
3
4
  import type { PreparedResponsesRequest } from "./request-prepare";
@@ -20,8 +21,8 @@ import {
20
21
  runWithImageBridge,
21
22
  clampImageMaxRounds,
22
23
  } from "../../images";
23
- import type { ProviderAdapter } from "../../adapters/base";
24
- import type { OcxParsedRequest } from "../../types";
24
+ import type { AdapterRequest, ProviderAdapter } from "../../adapters/base";
25
+ import type { OcxParsedRequest, OcxUsage } from "../../types";
25
26
  import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
26
27
  import {
27
28
  isGenericOAuthFailoverEnabled,
@@ -32,7 +33,7 @@ import {
32
33
  failoverAccountSnapshot,
33
34
  } from "../../oauth/generic-account-failover";
34
35
  import { classifyKiroRefusal } from "../../adapters/kiro-refusal";
35
- import { isNonReplayableResponse } from "../../lib/upstream-retry";
36
+ import { isNonReplayableResponse, SendBudgetExhaustedError } from "../../lib/upstream-retry";
36
37
  import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
37
38
  import { AdmissionModelDeniedError, admissionModelDeniedResponse } from "../admission-model-scope";
38
39
  import { noteKiroMonthlyRefusal, noteKiroServedSuccess } from "../../providers/kiro-usage";
@@ -50,6 +51,7 @@ import { providerFetch } from "./fetch-helpers";
50
51
  import type { AttemptRecoveryKind } from "../../usage/log";
51
52
  import { recordAdapterReasoning, recordAdapterTier } from "../request-log";
52
53
  import { normalizeLogConversationId } from "../request-log-conversation";
54
+ import { attemptAccountChanged } from "../request-log-account-rotation";
53
55
  import { rememberResponseState } from "../../responses/state";
54
56
  import { trackStreamLifetime } from "../lifecycle";
55
57
  import { isAntigravityRawValidationRefusal } from "./antigravity-validation-refusal";
@@ -65,6 +67,7 @@ export async function executeResponsesSidecars(
65
67
  | "inboundWire"
66
68
  | "selectedForwardHeaders"
67
69
  | "translatorBudget"
70
+ | "clientToolCatalogRestriction"
68
71
  | "toolBridgeMaps"
69
72
  | "rememberKiroDeliveredFinalAnswer"
70
73
  | "responseStateOptions"
@@ -98,7 +101,7 @@ export async function executeResponsesSidecars(
98
101
  | "notifyResponseComplete"
99
102
  | "cancelResponseCompletion"
100
103
  >,
101
- sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
104
+ sendBudgetState: Pick<ResponsesSendBudget, "sendBudgetExhausted" | "reserveCredentialHop" | "refundableAdapterDispatchBudget" | "adapterDispatchBudget" | "noteInitialDispatch" | "noteAdapterPhysicalSend" | "noteAdapterRecoveryWithheld" | "pendingHopPermit">,
102
105
  ) {
103
106
  const { config, options, logCtx } = requestContext;
104
107
  const antigravityPoolActivated = requestState.route.providerName === "google-antigravity"
@@ -377,7 +380,7 @@ export async function executeResponsesSidecars(
377
380
  recoveryKind = "oauth-account-429";
378
381
  if (!options.comboInitialSend && canRunWebSearch && route.providerName === "google-antigravity")
379
382
  sendBudgetState.pendingHopPermit = hop.permit;
380
- else sidecarBudget.ownCredentialHop(hop.permit);
383
+ else sidecarBudget.ownCredentialHop(hop.permit, (canRunWebSearch || !!imgPlan || !!vidPlan) && transportState.adapter.name === "anthropic");
381
384
  } else if (
382
385
  // Anthropic's pool is excluded from generic failover, so without this arm a 429 inside a
383
386
  // web-search or image-bridge turn was terminal even with the pool fully enabled -- while
@@ -417,8 +420,9 @@ export async function executeResponsesSidecars(
417
420
  hop.permit?.release();
418
421
  return null;
419
422
  }
420
- recoveryKind = originalResponse?.status === 401 ? "oauth-401" : "anthropic-oauth-429";
421
- sidecarBudget.ownCredentialHop(hop.permit);
423
+ recoveryKind = originalResponse?.status === 401 ? "oauth-401"
424
+ : originalResponse?.status === 403 ? "oauth-account-403" : "anthropic-oauth-429";
425
+ sidecarBudget.ownCredentialHop(hop.permit, (canRunWebSearch || !!imgPlan || !!vidPlan) && transportState.adapter.name === "anthropic");
422
426
  } else {
423
427
  // No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
424
428
  // credential. The 429 is terminal for this sidecar turn.
@@ -451,6 +455,44 @@ export async function executeResponsesSidecars(
451
455
  });
452
456
  return { adapter: rotatedAdapter, recoveryKind };
453
457
  };
458
+ // One guard follows the active sidecar across all iterations. Enforced spend has no
459
+ // authoritative receipt for earlier legacy sends, so recovery cannot claim headroom there.
460
+ const thinkingRecovery = createAnthropicThinkingRecovery(parsed,
461
+ options.abortSignal ?? new AbortController().signal, { attempted: false },
462
+ () => !sendBudgetState.sendBudgetExhausted()
463
+ && !!sendBudgetState.adapterDispatchBudget
464
+ && sendBudgetState.adapterDispatchBudget.spendEnforced !== true);
465
+ const thinkingReceipt = (request: AdapterRequest, adapter: ProviderAdapter, recovery?: AttemptRecoveryKind) =>
466
+ adapter.name !== "anthropic" ? {} : { onPhysicalDispatch: () => {
467
+ if (recovery === "opaque-blob-rejection") {
468
+ const decision = sendBudgetState.adapterDispatchBudget?.reserveDispatch({
469
+ sendClass: "repair", targetKey: `${route.providerName}|${request.url}|${route.modelId}`,
470
+ });
471
+ if (sendBudgetState.adapterDispatchBudget?.spendEnforced || !decision?.allowed) {
472
+ if (decision?.allowed) decision.permit.release();
473
+ throw new SendBudgetExhaustedError("sidecar thinking recovery");
474
+ }
475
+ try { sendBudgetState.noteInitialDispatch(decision.permit); }
476
+ finally { decision.permit.release(); }
477
+ } else {
478
+ const existing = sidecarBudget.fetchOptions(adapter).onPhysicalDispatch;
479
+ if (existing) existing();
480
+ else {
481
+ const prepaid = sendBudgetState.pendingHopPermit;
482
+ sendBudgetState.pendingHopPermit = undefined;
483
+ sendBudgetState.noteInitialDispatch(prepaid);
484
+ }
485
+ }
486
+ } };
487
+ // A loop's hidden rounds before a rotation were spent by the account it left. Bill them to that
488
+ // account's attempt while it is still open; the next send seals it. Declined when that send
489
+ // keeps the account (no new attempt), so the loop's final total still carries them.
490
+ const billRotatedUsage = (usage: OcxUsage): boolean => {
491
+ const attempt = logCtx.activeAttempt;
492
+ if (!attempt || !attemptAccountChanged(attempt.accountLogLabel, logCtx.accountLogLabel, attempt.provider, logCtx.provider)) return false;
493
+ transportState.bindKeyUsageFromBridge(usage);
494
+ return true;
495
+ };
454
496
  if ((imgPlan || vidPlan) && wsPlan) {
455
497
  // Web search takes priority when both are active — the media bridge cannot run
456
498
  // alongside runWithWebSearch. Surface a runtime signal so the user knows their
@@ -508,6 +550,8 @@ export async function executeResponsesSidecars(
508
550
  );
509
551
  const imgResponse = await runWithImageBridge({
510
552
  parsed, adapter: transportState.adapter,
553
+ ...(transportState.adapter.name === "anthropic" ? { thinkingRecovery } : {}),
554
+ clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
511
555
  incomingMeta: { headers: requestState.selectedForwardHeaders, providerName: route.providerName, abortSignal: options.abortSignal, translatorBudget, ...(options.comboInitialSend ? { providerFetch: imageProviderFetch } : {}), ...sidecarBudget.incomingMeta },
512
556
  onProducerStart: sidecarBudget.takeProducerOwnership,
513
557
  onProducerEnd: sidecarBudget.release,
@@ -527,10 +571,11 @@ export async function executeResponsesSidecars(
527
571
  stallTimeoutSec: config.stallTimeoutSec,
528
572
  waitForRequestSlot: imageProviderFetch.waitForPacing,
529
573
  fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
530
- fetchForRequest: (request, iterParsed, adapter) => {
574
+ fetchForRequest: (request, iterParsed, adapter, recovery) => {
531
575
  const fetch = providerFetch(route.provider, options.codexWsRuntimeIdentity, {
532
576
  dispatchOverride: oauthDispatch(request, iterParsed),
533
577
  providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
578
+ ...thinkingReceipt(request, adapter, recovery),
534
579
  });
535
580
  return fetch.unpacedFetch ?? fetch;
536
581
  },
@@ -549,11 +594,14 @@ export async function executeResponsesSidecars(
549
594
  transportState.bindKeyUsageFromBridge(usage);
550
595
  },
551
596
  on429: rotateSidecarProviderOn429,
597
+ onRotatedUsage: billRotatedUsage,
552
598
  retryOn429Policy: route.providerName === "kiro" && isGenericOAuthFailoverEnabled(config, "kiro")
553
599
  ? null : rateLimitRetryPolicyFor(route.provider),
554
600
  onFirstOutput: noteSidecarOutput,
555
601
  ...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
602
+ terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
556
603
  onCompletedResponse: (response, providerState) => {
604
+ if (!options.abortSignal?.aborted) thinkingRecovery.rememberSuccess(response);
557
605
  const served = transportState.replayOAuthCredentialSnapshot;
558
606
  if (route.providerName === "kiro" && response.status === "completed" && served
559
607
  && noteKiroServedSuccess(served.accountId, served.generation)) persistKiroAccountState();
@@ -603,10 +651,13 @@ export async function executeResponsesSidecars(
603
651
  })(input, init)) as typeof globalThis.fetch;
604
652
  const loopResponse = await runWithWebSearch({
605
653
  parsed, adapter: transportState.adapter,
654
+ ...(transportState.adapter.name === "anthropic" ? { thinkingRecovery } : {}),
655
+ clientToolCatalogRestriction: route.provider.authMode === "forward" ? undefined : requestState.clientToolCatalogRestriction,
606
656
  onIterationEnd: sidecarBudget.releaseUnsentHop,
607
- fetchForRequest: (request, iterParsed, adapter) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
657
+ fetchForRequest: (request, iterParsed, adapter, recovery) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
608
658
  dispatchOverride: oauthDispatch(request, iterParsed),
609
659
  providerName: route.providerName, modelId: route.modelId, ...sidecarBudget.fetchOptions(adapter),
660
+ ...thinkingReceipt(request, adapter, recovery),
610
661
  }),
611
662
  incomingMeta: {
612
663
  headers: requestState.selectedForwardHeaders,
@@ -633,6 +684,7 @@ export async function executeResponsesSidecars(
633
684
  settings: wsPlan.settings,
634
685
  maxSearches: wsPlan.maxSearches,
635
686
  forceEmptyResponseId: true,
687
+ terminalRefusalAsFailed: inboundWire !== "chat" && inboundWire !== "anthropic",
636
688
  abortSignal: options.abortSignal,
637
689
  onFirstOutput: noteSidecarOutput,
638
690
  onRequestBuilt: request => {
@@ -651,6 +703,7 @@ export async function executeResponsesSidecars(
651
703
  stallTimeoutSec: wsPlan.stallTimeoutSec,
652
704
  streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
653
705
  on429: rotateSidecarProviderOn429,
706
+ onRotatedUsage: billRotatedUsage,
654
707
  ...(!options.comboInitialSend && route.providerName === "google-antigravity" ? { onIterationEnd: () => {
655
708
  // The next Antigravity adapter dispatch claims this permit. If request construction,
656
709
  // admission or cancellation stopped it first, return the unused send allowance.
@@ -665,6 +718,7 @@ export async function executeResponsesSidecars(
665
718
  if (route.providerName === "kiro" && response.status === "completed" && served
666
719
  && noteKiroServedSuccess(served.accountId, served.generation)) persistKiroAccountState();
667
720
  commitReasoningReplayServingRoute();
721
+ if (!options.abortSignal?.aborted) thinkingRecovery.rememberSuccess(response);
668
722
  notifyResponseComplete(response);
669
723
  },
670
724
  });
@@ -22,8 +22,8 @@ export function createSidecarSendBudget(
22
22
  };
23
23
  return {
24
24
  /** Retain a Combo hop until dispatch; direct requests keep eager settlement. */
25
- ownCredentialHop(permit?: SingleUseDispatchPermit): void {
26
- if (!initial) { permit?.use(); return; } // Preserve direct callers' reporting contract.
25
+ ownCredentialHop(permit?: SingleUseDispatchPermit, retainUntilDispatch = false): void {
26
+ if (!initial && !retainUntilDispatch) { permit?.use(); return; } // Preserve direct callers' reporting contract.
27
27
  releaseHop();
28
28
  hopPermit = permit;
29
29
  budget.pendingHopPermit = permit;
@@ -1,6 +1,6 @@
1
+ import { assistantReplayContent } from "../../responses/assistant-replay";
1
2
  import type {
2
3
  AdapterEvent,
3
- OcxAssistantContentPart,
4
4
  OcxAssistantMessage,
5
5
  OcxParsedRequest,
6
6
  OcxUsage,
@@ -121,21 +121,7 @@ export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly A
121
121
  }
122
122
 
123
123
  function assistantMessageFromEvents(events: readonly AdapterEvent[], timestamp: number): OcxAssistantMessage | undefined {
124
- let text = "";
125
- let thinking = "";
126
- let signature: string | undefined;
127
- const redacted: string[] = [];
128
- for (const event of events) {
129
- if (event.type === "text_delta") text += event.text;
130
- else if (event.type === "thinking_delta") thinking += event.thinking;
131
- else if (event.type === "thinking_signature") signature = event.signature;
132
- else if (event.type === "redacted_thinking") redacted.push(event.data);
133
- }
134
- const content: OcxAssistantContentPart[] = [];
135
- if (thinking || signature || redacted.length > 0) {
136
- content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
137
- }
138
- if (text) content.push({ type: "text", text });
124
+ const content = assistantReplayContent(events);
139
125
  if (content.length === 0) return undefined;
140
126
  return { role: "assistant", content, timestamp };
141
127
  }
@@ -298,7 +298,7 @@ export function collectProviderExecutedCallTypes(body: unknown): Set<ProviderExe
298
298
  return callTypes;
299
299
  }
300
300
 
301
- function isAuthorizedProviderExecutedCall(
301
+ export function isAuthorizedProviderExecutedCall(
302
302
  item: Record<string, unknown>,
303
303
  callTypes: ProviderExecutedCallTypes,
304
304
  ): boolean {
@@ -11,6 +11,7 @@ import { BoundedSseFrameBuffer } from "./sse-frame-buffer";
11
11
  import { sseDataPayload as parseSseBlock } from "./sse-payload-rewrite";
12
12
  import { safeResponseHeaders } from "./safe-response-headers";
13
13
  import type { AudioSocketTarget } from "./audio-dictation";
14
+ import type { CodexWebSocketActivity } from "../codex/websocket-activity";
14
15
 
15
16
  export { safeResponseHeaders } from "./safe-response-headers";
16
17
 
@@ -19,6 +20,7 @@ type ResponsesTerminalReporter = (status: ResponsesTerminalStatus) => void;
19
20
  type ResponsesPayloadObserver = (payload: string) => void;
20
21
 
21
22
  export interface WsData {
23
+ responsesActivity?: CodexWebSocketActivity;
22
24
  nativeControl?: NativeResponseControl;
23
25
  /** Content-free per-turn explanation; never a model capability assertion. */
24
26
  nativeSteeringUnavailable?: string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The install/repair preflight over the data-plane credential, plus the retry command it
3
+ * names. Split out of `guards.ts` so the guard module stops importing `diagnostics`.
4
+ *
5
+ * Direction of the remaining edge is deliberate: this module asks `guards` for
6
+ * `assertNotAdminToken` and `diagnostics` for the install verdict, and neither imports it back.
7
+ */
8
+ import { loadConfig } from "../config";
9
+ import { isLoopbackHostname } from "../codex/loopback-target";
10
+ import { readServiceApiTokenState, serviceApiTokenFilePath } from "../lib/service-secrets";
11
+ import { diagnoseService } from "./diagnostics";
12
+ import type { ServiceDiagnostic } from "./diagnostics";
13
+ import { assertNotAdminToken } from "./guards";
14
+
15
+ /**
16
+ * The `ocx` command a user should rerun for the service state they actually have.
17
+ *
18
+ * `installed` alone is not enough: `repairService()` refuses a Task-Scheduler-plus-WinSW
19
+ * conflict outright, so recommending repair there names a command guaranteed to fail.
20
+ * Install IS the valid conflict recovery, because `installWindows` removes the native
21
+ * backend first. Exported so the guard tests the real selector rather than a copy of it.
22
+ */
23
+ export function serviceRetryCommand(
24
+ diag: Pick<ServiceDiagnostic, "installed" | "conflict"> = diagnoseService(),
25
+ ): string {
26
+ return diag.installed && !diag.conflict ? "ocx service repair" : "ocx service install";
27
+ }
28
+
29
+ /**
30
+ * Preflight for `service install` / `service repair` on the data-plane credential.
31
+ *
32
+ * It used to DEMAND `OPENCODEX_API_AUTH_TOKEN` for a non-loopback hostname, and it threw
33
+ * even when `~/.opencodex/service-api-token` already held a perfectly good token. That is
34
+ * the defect behind the incident this unit exists to close (#4236): an operator exported the
35
+ * ADMIN token as OPENCODEX_API_AUTH_TOKEN because `install` asked for a token, the hub then
36
+ * crash-looped on `assertNotAdminToken`, and `service repair` asked for the same env var
37
+ * again — so the only remembered way to make the command proceed was the thing that broke it.
38
+ *
39
+ * Nobody should have to export a token by hand to run a hub. {@link writeServiceApiTokenFile}
40
+ * provisions one, so the only conditions left that install cannot fix are an admin-token
41
+ * collision in the environment and a token file that exists but cannot be used.
42
+ */
43
+ export function assertServiceAuthEnvironment(): void {
44
+ const config = loadConfig();
45
+ // Both collision checks come BEFORE the loopback short-circuit, because the launch wrapper
46
+ // exports the token file unconditionally (`buildServiceShellCommand` cats it whenever it
47
+ // exists, whatever the hostname): a management token in either source fences the whole
48
+ // management plane closed at boot, even on a loopback install that needs no admission
49
+ // secret. Returning early is what let that broken state through.
50
+ const present = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
51
+ if (present) assertNotAdminToken(present);
52
+ const state = readServiceApiTokenState();
53
+ // An existing FILE holding the admin token is the incident shape itself, and the first round
54
+ // only checked the env var — so install/repair reused it and the hub crash-looped at boot.
55
+ // On a machine connected to a hub this same file holds that hub's issued client key, which
56
+ // is never a management token, so the check is a no-op there.
57
+ if (state.kind === "present") assertNotAdminToken(state.token, process.env, "file");
58
+ if (isLoopbackHostname(config.hostname)) return;
59
+ if (present) return;
60
+ // Absent is fine — install/repair generates one below. `unsafe` is not: the writer refuses
61
+ // to replace a path it cannot vouch for, so say so here, where the operator can still act,
62
+ // instead of failing mid-install. Reached from `service repair` as well as `install`, so
63
+ // name a command that can actually succeed (see serviceRetryCommand).
64
+ if (state.kind !== "unsafe") return;
65
+ const diag = diagnoseService();
66
+ throw new Error(
67
+ `The data-plane token file cannot be used (${state.reason}): ${serviceApiTokenFilePath()}. `
68
+ + `Move it aside, then rerun \`${serviceRetryCommand(diag)}\`; the service provisions a `
69
+ + "fresh owner-only token and needs nothing from the environment.",
70
+ );
71
+ }