@bitkyc08/opencodex 2.83.0 → 2.86.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 (405) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-BjlttVLy.js +58 -0
  4. package/gui/dist/assets/Tray-DyR9yBee.js +1 -0
  5. package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
  6. package/gui/dist/assets/index-BMssP4k3.js +94 -0
  7. package/gui/dist/assets/quota-summary-D8M1bc_R.js +1 -0
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +2 -3
  10. package/src/adapters/anthropic/beta-allowlist.ts +87 -8
  11. package/src/adapters/anthropic/client-identity.ts +1 -1
  12. package/src/adapters/anthropic/passthrough.ts +33 -4
  13. package/src/adapters/azure.ts +19 -1
  14. package/src/adapters/client-fingerprint.ts +24 -0
  15. package/src/adapters/coding-agent/protocol.ts +15 -8
  16. package/src/adapters/coding-agent/turn.ts +22 -9
  17. package/src/adapters/cursor/arg-codec.ts +17 -5
  18. package/src/adapters/cursor/catalog.ts +8 -0
  19. package/src/adapters/cursor/effort-map.ts +3 -0
  20. package/src/adapters/cursor/freeform-args.ts +75 -0
  21. package/src/adapters/cursor/protobuf-events.ts +36 -23
  22. package/src/adapters/google-antigravity-wire.ts +22 -1
  23. package/src/adapters/google.ts +2 -2
  24. package/src/adapters/ollama-native.ts +29 -1
  25. package/src/adapters/openai-chat/messages.ts +8 -4
  26. package/src/adapters/openai-chat/response-events.ts +59 -1
  27. package/src/adapters/openai-chat.ts +8 -5
  28. package/src/adapters/openai-responses/empty-assistant.ts +32 -0
  29. package/src/adapters/openai-responses/passthrough.ts +3 -0
  30. package/src/adapters/openai-responses/request-strips.ts +10 -2
  31. package/src/adapters/qoder/adapter.ts +56 -15
  32. package/src/advisor/consult.ts +220 -0
  33. package/src/advisor/context.ts +226 -0
  34. package/src/advisor/disclosure.ts +20 -0
  35. package/src/advisor/runtime.ts +305 -0
  36. package/src/advisor/settings.ts +135 -0
  37. package/src/advisor/state.ts +369 -0
  38. package/src/advisor/synthetic-tool.ts +33 -0
  39. package/src/bridge/errors.ts +24 -0
  40. package/src/bridge.ts +1 -1
  41. package/src/chat/inbound.ts +30 -6
  42. package/src/chat/outbound.ts +5 -0
  43. package/src/claude/context-windows.ts +54 -1
  44. package/src/claude/desktop-3p-library.ts +43 -3
  45. package/src/claude/desktop-3p.ts +22 -8
  46. package/src/claude/desktop-picker-profile.ts +11 -4
  47. package/src/claude/desktop-profile.ts +10 -1
  48. package/src/claude/desktop-remote-store-io.ts +5 -3
  49. package/src/claude/inbound.ts +121 -50
  50. package/src/claude/intercept/connect-proxy.ts +10 -3
  51. package/src/claude/intercept/local-ca.ts +8 -1
  52. package/src/claude/intercept/picker-ca-persistence.ts +3 -1
  53. package/src/claude/intercept/picker-listener.ts +49 -1
  54. package/src/claude/intercept/settings-no-proxy.ts +146 -0
  55. package/src/claude/intercept/settings.ts +101 -9
  56. package/src/claude/model-default-settings.ts +154 -0
  57. package/src/claude/model-info.ts +7 -7
  58. package/src/claude/outbound.ts +13 -6
  59. package/src/cli/account-api.ts +65 -0
  60. package/src/cli/account-auth.ts +26 -2
  61. package/src/cli/account.ts +41 -5
  62. package/src/cli/advisor.ts +151 -0
  63. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  64. package/src/cli/capabilities-access-remote.ts +8 -0
  65. package/src/cli/capabilities-agents-routing.ts +18 -0
  66. package/src/cli/capabilities-base.ts +14 -2
  67. package/src/cli/catalog-command-result.ts +2 -1
  68. package/src/cli/chatgpt-command.ts +35 -14
  69. package/src/cli/claude.ts +60 -11
  70. package/src/cli/config-command.ts +8 -2
  71. package/src/cli/debug.ts +8 -0
  72. package/src/cli/dispatch.ts +21 -4
  73. package/src/cli/doctor.ts +9 -0
  74. package/src/cli/ensure-desktop-cold-start.ts +329 -0
  75. package/src/cli/export-command.ts +15 -2
  76. package/src/cli/help.ts +2 -1
  77. package/src/cli/hub-disable.ts +99 -0
  78. package/src/cli/hub.ts +4 -2
  79. package/src/cli/index.ts +26 -8
  80. package/src/cli/integration-plan-dto.ts +2 -0
  81. package/src/cli/link-child.ts +104 -0
  82. package/src/cli/link-enrollment.ts +96 -0
  83. package/src/cli/link.ts +39 -24
  84. package/src/cli/observe-stream.ts +6 -1
  85. package/src/cli/observe.ts +10 -2
  86. package/src/cli/provider.ts +12 -0
  87. package/src/cli/registry.ts +20 -7
  88. package/src/cli/restart-scope.ts +3 -1
  89. package/src/cli/runtime-api.ts +27 -2
  90. package/src/cli/status.ts +104 -20
  91. package/src/client/connect.ts +39 -4
  92. package/src/client/hub-client.ts +14 -7
  93. package/src/client/hub-state.ts +25 -14
  94. package/src/client/link-home.ts +248 -0
  95. package/src/client/link-management-relay.ts +61 -0
  96. package/src/client/link-relay.ts +6 -1
  97. package/src/client/link-status.ts +14 -0
  98. package/src/client/machine-api.ts +14 -7
  99. package/src/client/machine-listener.ts +29 -4
  100. package/src/clients/config-export/contracts.ts +1 -0
  101. package/src/clients/config-export/model-metadata.ts +3 -2
  102. package/src/clients/config-export/qoder.ts +93 -0
  103. package/src/clients/config-export.ts +59 -16
  104. package/src/clients/omo-role-models.ts +6 -3
  105. package/src/codex/agent-role-models.ts +26 -8
  106. package/src/codex/auth-api/account-list.ts +17 -2
  107. package/src/codex/auth-api/account-selection.ts +17 -7
  108. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  109. package/src/codex/auth-api/routes.ts +5 -1
  110. package/src/codex/auth-context.ts +79 -5
  111. package/src/codex/autostart-health.ts +7 -1
  112. package/src/codex/catalog/build-entries.ts +11 -2
  113. package/src/codex/catalog/bundled.ts +5 -1
  114. package/src/codex/catalog/configured-tiers.ts +50 -0
  115. package/src/codex/catalog/effort.ts +6 -3
  116. package/src/codex/catalog/metadata.ts +4 -2
  117. package/src/codex/catalog/model-hints.ts +21 -3
  118. package/src/codex/catalog/native-models.ts +16 -2
  119. package/src/codex/catalog/native-ultrafast.ts +106 -0
  120. package/src/codex/catalog/parsing.ts +5 -0
  121. package/src/codex/catalog/provider-models.ts +4 -0
  122. package/src/codex/catalog/retained-sync.ts +2 -0
  123. package/src/codex/catalog/routed-gather.ts +22 -4
  124. package/src/codex/catalog/vendor-context.ts +82 -0
  125. package/src/codex/config-write-lock.ts +40 -2
  126. package/src/codex/convergence.ts +2 -0
  127. package/src/codex/desktop-app/types.ts +2 -0
  128. package/src/codex/desktop-app/windows.ts +29 -16
  129. package/src/codex/desktop-app-restart.ts +4 -2
  130. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  131. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  132. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  133. package/src/codex/desktop-compatibility/installed-build.ts +55 -0
  134. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  135. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  136. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  137. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  138. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  139. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  140. package/src/codex/desktop-compatibility/runtime.ts +173 -0
  141. package/src/codex/desktop-compatibility/service.ts +30 -0
  142. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  143. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  144. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  145. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  146. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  147. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  148. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  149. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  150. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  151. package/src/codex/desktop-compatibility/windows-key-protection.ts +59 -0
  152. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  153. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  154. package/src/codex/history-provider.ts +1 -1
  155. package/src/codex/inject/config-write-section.ts +4 -1
  156. package/src/codex/inject/remove.ts +18 -6
  157. package/src/codex/journal.ts +16 -1
  158. package/src/codex/main-account.ts +99 -15
  159. package/src/codex/native-main-refresh-mode.ts +217 -0
  160. package/src/codex/native-profile-startup.ts +4 -1
  161. package/src/codex/prompt-journal.ts +20 -2
  162. package/src/codex/prompt-layers/encoding.ts +49 -0
  163. package/src/codex/prompt-layers/import-source.ts +30 -0
  164. package/src/codex/prompt-layers/toml-edit.ts +269 -37
  165. package/src/codex/prompt-layers/toml-read.ts +52 -4
  166. package/src/codex/prompt-layers.ts +383 -20
  167. package/src/codex/prompt-lock-claim.ts +13 -3
  168. package/src/codex/prompt-lock-evidence.ts +3 -3
  169. package/src/codex/prompt-lock-owner.ts +14 -6
  170. package/src/codex/prompt-text-probe.ts +34 -5
  171. package/src/codex/quota.ts +13 -6
  172. package/src/codex/role-auto-assign.ts +2 -5
  173. package/src/codex/routing/active-account.ts +21 -0
  174. package/src/codex/routing/health-store.ts +1 -1
  175. package/src/codex/routing/pool-recovery.ts +79 -0
  176. package/src/codex/routing.ts +1 -1
  177. package/src/codex/runtime.ts +196 -8
  178. package/src/codex/shim-diagnostics.ts +6 -0
  179. package/src/codex/shim-overlay.ts +15 -6
  180. package/src/codex/shim.ts +1 -1
  181. package/src/combos/failover.ts +3 -0
  182. package/src/config/api-key-reconcile.ts +34 -0
  183. package/src/config/diagnostics.ts +18 -1
  184. package/src/config/feature-flags.ts +5 -0
  185. package/src/config/live-reconcile.ts +56 -29
  186. package/src/config/live-topology.ts +29 -0
  187. package/src/config/load-degrade.ts +8 -2
  188. package/src/config/paths.ts +13 -3
  189. package/src/config/salvage.ts +3 -1
  190. package/src/config/schema/config-schema.ts +19 -0
  191. package/src/config/schema/desktop-compatibility.ts +9 -0
  192. package/src/config/schema/leaf-validators.ts +1 -0
  193. package/src/generated/compatibility-version.json +713 -285
  194. package/src/generated/model-metadata.ts +1 -1
  195. package/src/grok/grpc-web.ts +23 -1
  196. package/src/grok/reset-coupon-ledger.ts +75 -5
  197. package/src/grok/reset-coupons.ts +4 -2
  198. package/src/integrations/catalog-refresh.ts +1 -1
  199. package/src/integrations/config-io.ts +20 -2
  200. package/src/integrations/journal.ts +15 -1
  201. package/src/integrations/mutation-plan.ts +14 -7
  202. package/src/integrations/registry.ts +52 -11
  203. package/src/integrations/state.ts +19 -7
  204. package/src/integrations/store.ts +3 -0
  205. package/src/integrations/target.ts +42 -5
  206. package/src/integrations/writer.ts +24 -7
  207. package/src/lib/advisor-activation.ts +16 -0
  208. package/src/lib/app-owned-memory-stores.ts +4 -0
  209. package/src/lib/app-owned-memory.ts +11 -0
  210. package/src/lib/background-work.ts +65 -0
  211. package/src/lib/bounded-file-read.ts +58 -0
  212. package/src/lib/bun-path-runtime.d.mts +10 -1
  213. package/src/lib/bun-path-runtime.mjs +62 -3
  214. package/src/lib/desktop-proxy-route.ts +30 -0
  215. package/src/lib/desktop-upstream-tunnel.ts +107 -0
  216. package/src/lib/errors.ts +7 -0
  217. package/src/lib/json-byte-size.ts +31 -0
  218. package/src/lib/local-internal-call-capability.ts +55 -0
  219. package/src/lib/optional-shutdown-hooks.ts +35 -0
  220. package/src/lib/outbound-credential-registry.ts +210 -0
  221. package/src/lib/package-tree-integrity.ts +2 -2
  222. package/src/lib/passive-read.ts +35 -0
  223. package/src/lib/shadow-call.ts +44 -5
  224. package/src/lib/socks5-fetch.ts +48 -71
  225. package/src/lib/socks5-handshake.ts +112 -0
  226. package/src/lib/translator-budget.ts +7 -1
  227. package/src/link/codex-settings.ts +155 -0
  228. package/src/link/management-routes.ts +89 -0
  229. package/src/link/remote-probe.ts +38 -0
  230. package/src/link/ssh-argv.ts +26 -5
  231. package/src/link/ssh-runner.ts +58 -27
  232. package/src/link/status-projection.ts +22 -4
  233. package/src/link/supervisor.ts +144 -49
  234. package/src/link/tunnel-state.ts +11 -4
  235. package/src/oauth/anthropic-continuity.ts +4 -3
  236. package/src/oauth/anthropic-identity.ts +13 -2
  237. package/src/oauth/anthropic-model-quota.ts +13 -1
  238. package/src/oauth/anthropic-routing.ts +230 -23
  239. package/src/oauth/anthropic.ts +18 -4
  240. package/src/oauth/generic-account-failover.ts +29 -5
  241. package/src/oauth/google-antigravity.ts +24 -7
  242. package/src/oauth/index.ts +28 -17
  243. package/src/oauth/kiro-account-identity.ts +106 -0
  244. package/src/oauth/kiro-device-login.ts +2 -1
  245. package/src/oauth/kiro.ts +55 -6
  246. package/src/oauth/plan.ts +29 -0
  247. package/src/oauth/store.ts +40 -3
  248. package/src/oauth/types.ts +2 -0
  249. package/src/plugins/loader.ts +11 -0
  250. package/src/protocols/codecs/chat-ir.ts +467 -0
  251. package/src/protocols/codecs/chat.ts +10 -1
  252. package/src/protocols/codecs/messages-ir.ts +500 -0
  253. package/src/protocols/features.ts +10 -2
  254. package/src/protocols/path.ts +11 -1
  255. package/src/protocols/plan-snapshot.ts +39 -16
  256. package/src/protocols/plan.ts +9 -1
  257. package/src/protocols/settings.ts +8 -0
  258. package/src/providers/anthropic-billing-ownership.ts +74 -0
  259. package/src/providers/anthropic-billing-recovery.ts +31 -0
  260. package/src/providers/anthropic-billing-refusal.ts +55 -0
  261. package/src/providers/anthropic-reset-admission.ts +62 -0
  262. package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
  263. package/src/providers/antigravity-account-roster.ts +102 -0
  264. package/src/providers/antigravity-models.ts +85 -7
  265. package/src/providers/api-key-resolve.ts +17 -2
  266. package/src/providers/key-failover.ts +52 -5
  267. package/src/providers/model-rename-fields.ts +2 -0
  268. package/src/providers/model-rename-migration.ts +5 -3
  269. package/src/providers/model-rename-startup.ts +5 -3
  270. package/src/providers/quota/account-cache.ts +11 -0
  271. package/src/providers/quota/anthropic-account-quota.ts +4 -2
  272. package/src/providers/quota/report-cache.ts +14 -0
  273. package/src/providers/quota/vendor-probes-oauth.ts +3 -3
  274. package/src/providers/quota.ts +4 -1
  275. package/src/providers/registry/entries-core.ts +21 -20
  276. package/src/providers/registry/entries-extended.ts +19 -1
  277. package/src/providers/registry/model-seeds.ts +17 -0
  278. package/src/providers/request-pacing.ts +9 -0
  279. package/src/providers/stale-no-vision-seed-migration.ts +72 -0
  280. package/src/responses/compaction.ts +14 -0
  281. package/src/responses/parser.ts +37 -20
  282. package/src/responses/state.ts +32 -29
  283. package/src/routing/history/build-file.ts +92 -0
  284. package/src/routing/history/indexer.ts +248 -216
  285. package/src/routing/history/ingest.ts +238 -0
  286. package/src/routing/history/rebuild-job.ts +111 -0
  287. package/src/routing/history/rebuild-protocol.ts +15 -0
  288. package/src/routing/history/rebuild-worker.ts +34 -0
  289. package/src/server/admission-model-scope.ts +4 -1
  290. package/src/server/antigravity-direct-eligibility.ts +208 -0
  291. package/src/server/auth-cors.ts +46 -0
  292. package/src/server/chat-completions.ts +75 -9
  293. package/src/server/claude-messages.ts +110 -20
  294. package/src/server/configured-credentials.ts +194 -0
  295. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  296. package/src/server/index/link-management.ts +58 -0
  297. package/src/server/index/optional-listeners.ts +5 -1
  298. package/src/server/index/serve-options.ts +15 -2
  299. package/src/server/index/startup-warnings.ts +9 -0
  300. package/src/server/index/websocket-handler.ts +9 -0
  301. package/src/server/index.ts +13 -10
  302. package/src/server/inference/client-encoder-delivery.ts +6 -5
  303. package/src/server/lifecycle.ts +2 -1
  304. package/src/server/login-shell-path.ts +146 -0
  305. package/src/server/management/advisor-routes.ts +191 -0
  306. package/src/server/management/agent-settings-routes.ts +50 -14
  307. package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
  308. package/src/server/management/codex-agent-role-routes.ts +19 -6
  309. package/src/server/management/codex-prompt-routes.ts +136 -5
  310. package/src/server/management/codex-role-auto-assign.ts +1 -1
  311. package/src/server/management/companion-routes.ts +4 -0
  312. package/src/server/management/config-routes.ts +2 -2
  313. package/src/server/management/context.ts +4 -0
  314. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  315. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  316. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  317. package/src/server/management/grok-coupon-routes.ts +78 -27
  318. package/src/server/management/link-enrollment.ts +192 -0
  319. package/src/server/management/link-route-state.ts +185 -0
  320. package/src/server/management/link-routes.ts +82 -402
  321. package/src/server/management/model-routes.ts +2 -2
  322. package/src/server/management/model-rows.ts +3 -1
  323. package/src/server/management/protocol-settings-patch.ts +1 -0
  324. package/src/server/management/provider-overwrite-carry.ts +10 -0
  325. package/src/server/management/provider-routes.ts +1 -0
  326. package/src/server/management/route-registry.ts +16 -3
  327. package/src/server/management/shared.ts +3 -0
  328. package/src/server/management/sibling-guard.ts +1 -0
  329. package/src/server/management-api.ts +9 -1
  330. package/src/server/messages-antigravity-direct.ts +195 -0
  331. package/src/server/messages-native.ts +73 -43
  332. package/src/server/relay-eager.ts +2 -0
  333. package/src/server/request-log-filter.ts +6 -1
  334. package/src/server/request-log-terminal-status.ts +63 -6
  335. package/src/server/request-log.ts +7 -0
  336. package/src/server/responses/adapter-continuation.ts +35 -18
  337. package/src/server/responses/adapter-delivery.ts +24 -2
  338. package/src/server/responses/adapter-dispatch.ts +261 -81
  339. package/src/server/responses/advisor-plan-slot.ts +27 -0
  340. package/src/server/responses/advisor-slot.ts +336 -0
  341. package/src/server/responses/code-mode-collab-surface.ts +313 -0
  342. package/src/server/responses/codex-ws-exchange.ts +147 -6
  343. package/src/server/responses/codex-ws-wire.ts +203 -0
  344. package/src/server/responses/collaboration.ts +36 -7
  345. package/src/server/responses/compact.ts +126 -48
  346. package/src/server/responses/context-overflow.ts +13 -1
  347. package/src/server/responses/core-codex-account.ts +105 -1
  348. package/src/server/responses/core-combo-failure.ts +24 -1
  349. package/src/server/responses/core-combo.ts +28 -2
  350. package/src/server/responses/core-errors.ts +29 -1
  351. package/src/server/responses/core-lifetime.ts +2 -0
  352. package/src/server/responses/core-normalize.ts +11 -0
  353. package/src/server/responses/core-options.ts +48 -0
  354. package/src/server/responses/core-replay.ts +7 -4
  355. package/src/server/responses/core.ts +1 -1
  356. package/src/server/responses/deferred-collab-surface.ts +158 -0
  357. package/src/server/responses/deferred-send-reporter.ts +25 -0
  358. package/src/server/responses/fetch-helpers.ts +12 -1
  359. package/src/server/responses/passthrough-delivery.ts +20 -11
  360. package/src/server/responses/passthrough-dispatch.ts +29 -7
  361. package/src/server/responses/request-prepare.ts +74 -9
  362. package/src/server/responses/request-transport.ts +22 -8
  363. package/src/server/responses/run-turn-execution.ts +70 -8
  364. package/src/server/responses/sidecar-execution.ts +89 -5
  365. package/src/server/responses/terminal-guard.ts +2 -1
  366. package/src/server/responses/ws-upstream.ts +9 -1
  367. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  368. package/src/server/startup-action-control.ts +38 -4
  369. package/src/server/startup-health-cache.ts +5 -0
  370. package/src/server/system-env-shell.ts +7 -2
  371. package/src/server/system-env.ts +11 -1
  372. package/src/service/cli.ts +5 -1
  373. package/src/service/desktop-startup.ts +161 -34
  374. package/src/service/desktop-supervision.d.mts +27 -1
  375. package/src/service/desktop-supervision.mjs +111 -5
  376. package/src/service/health-sentinel-launch.ts +176 -0
  377. package/src/service/health-sentinel-main.ts +466 -0
  378. package/src/service/health-sentinel-policy.ts +241 -0
  379. package/src/service/health-sentinel-probe.ts +71 -0
  380. package/src/service/health-sentinel-windows.ts +87 -0
  381. package/src/service/linux-linger.ts +20 -0
  382. package/src/service/logging.ts +72 -0
  383. package/src/tray/windows-tray.ps1 +43 -24
  384. package/src/types/config.ts +68 -6
  385. package/src/types/provider.ts +7 -0
  386. package/src/types/request.ts +15 -0
  387. package/src/types/tools.ts +2 -0
  388. package/src/update/async-check.ts +1 -1
  389. package/src/update/badge.ts +2 -2
  390. package/src/update/index.ts +88 -15
  391. package/src/update/job-supervised-restart.ts +107 -0
  392. package/src/update/job.ts +32 -32
  393. package/src/update/notify.ts +1 -1
  394. package/src/update/refresh-scheduler.ts +2 -2
  395. package/src/update/supervised-recovery.d.mts +27 -0
  396. package/src/update/supervised-recovery.mjs +157 -0
  397. package/src/usage/cache-diagnostic.ts +2 -0
  398. package/src/usage/expected-prices.ts +16 -0
  399. package/src/usage/log.ts +19 -1
  400. package/src/web-search/gemini-executor.ts +2 -2
  401. package/assets/sponsors/packycode.png +0 -0
  402. package/gui/dist/assets/App-Ba8uG696.js +0 -54
  403. package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
  404. package/gui/dist/assets/index-BHRZdJuv.js +0 -94
  405. package/gui/dist/assets/quota-summary-DiJO6sYY.js +0 -1
@@ -0,0 +1,369 @@
1
+ import { createHash } from "node:crypto";
2
+ import { advisorResultIsAdvice } from "./context";
3
+
4
+ /**
5
+ * Conversation- and task-scoped advisor state.
6
+ *
7
+ * Three scopes, deliberately separate:
8
+ *
9
+ * 1. REQUEST-scoped state lives in the per-request plan closure (see runtime.ts) — consultation
10
+ * count, dedup fingerprints, the preflight flag. Born and dies with one request.
11
+ *
12
+ * 2. TASK-scoped preflight ledger (this file): a bounded, process-local claim table that makes
13
+ * "at most one automatic consultation per task" atomic across concurrent requests. A claim
14
+ * requires a STABLE conversation identity plus the current task boundary; a client that sends
15
+ * no identity never enters this ledger (see `advisorLedgerKey`) and therefore fails open —
16
+ * it may be consulted once per request rather than risk two independent tasks suppressing
17
+ * each other through a shared guess.
18
+ *
19
+ * 3. PROVENANCE, split by authority:
20
+ * - MANUAL advice is verifiable history: a `toolResult` whose `toolName` is the synthetic
21
+ * advisor tool and whose content parses as a runtime-written advice object. Ordinary tool
22
+ * output, developer text, user text, and failure notices can never match it. Bytes inside
23
+ * the quoted advice field cannot change the runtime-owned status.
24
+ * - AUTOMATIC preflight dedup is NOT decided from history at all. The developer transport
25
+ * envelope labels the payload for the worker; any client could echo or forge such a message,
26
+ * so the ledger below is the authoritative source for "this task was already consulted" —
27
+ * `success`, `inflight` and `cooldown` states. A conversation with no stable identity gets
28
+ * no ledger and therefore fails open (at most one extra attempt). A forged marker or a
29
+ * forged developer message cannot suppress the policy.
30
+ *
31
+ * Ledger entries are plain state records — no message bodies, no credentials. Every state is
32
+ * bounded by entry count and its own TTL.
33
+ */
34
+
35
+ /** Long-lived success suppression: the task lifetime approximation (also the ledger TTL cap). */
36
+ export const ADVISOR_SUCCESS_TTL_MS = 24 * 60 * 60 * 1000;
37
+ /**
38
+ * Failure cooldown. One minute is the repository's standing minute-scale unit (tray polling,
39
+ * subagent availability polling); it turns a transient 503 into a short pause instead of
40
+ * silencing the policy for the rest of the coding session.
41
+ */
42
+ export const ADVISOR_FAILURE_COOLDOWN_MS = 60 * 1000;
43
+ /**
44
+ * In-flight claim expiry. Longer than the largest configurable consultation timeout (600s
45
+ * upper bound on `advisor.timeoutMs`, default 120s) so a slow-but-alive consultation is never
46
+ * mistaken for a wedged one, while a crashed claim cannot block a task forever.
47
+ */
48
+ export const ADVISOR_INFLIGHT_TTL_MS = 10 * 60 * 1000;
49
+
50
+ const MAX_ENTRIES = 512;
51
+
52
+ export type AdvisorClaimState =
53
+ /** The caller now owns the consultation for this task. */
54
+ | "claimed"
55
+ /** Another request for the same task is consulting right now. */
56
+ | "inflight"
57
+ /** This task already received advice; suppression holds until the success TTL expires. */
58
+ | "complete"
59
+ /** A recent consultation failed; suppression holds for the short failure cooldown. */
60
+ | "cooldown"
61
+ /**
62
+ * The ledger is full of live claims and granted nothing. Fail-open: the worker continues
63
+ * without a new automatic consultation rather than evicting a claim that is still running.
64
+ */
65
+ | "saturated";
66
+
67
+ /**
68
+ * The result of a claim attempt. `token` identifies THIS claim and is present only on
69
+ * `"claimed"`; settlement must present it, so a slow consultation whose in-flight entry expired
70
+ * cannot settle the successor claim that took its place.
71
+ */
72
+ export interface AdvisorClaim {
73
+ state: AdvisorClaimState;
74
+ token?: string;
75
+ }
76
+
77
+ interface LedgerEntry {
78
+ state: "inflight" | "success" | "failed";
79
+ at: number;
80
+ /** Present on in-flight entries: which claim owns this entry. */
81
+ token?: string;
82
+ }
83
+
84
+ export interface AdvisorPreflightLedger {
85
+ /**
86
+ * Atomically try to own the consultation for a task key. Returns `claimed` exactly once per
87
+ * task (with the ownership token) until a matching settlement releases it, so two concurrent
88
+ * requests cannot both consult.
89
+ */
90
+ claim(key: string, now?: number): AdvisorClaim;
91
+ /**
92
+ * Settle a claim the caller owns. A settlement whose token does not match the current entry
93
+ * is a no-op: the entry now belongs to a successor claim (the caller's in-flight window
94
+ * expired), and it must not be able to erase or overwrite that successor's state.
95
+ */
96
+ complete(key: string, token: string, now?: number): void;
97
+ fail(key: string, token: string, now?: number): void;
98
+ release(key: string, token: string, now?: number): void;
99
+ /**
100
+ * Fact, not settlement: this task received advice (used by a successful MANUAL consultation,
101
+ * which owns no preflight claim). Records success regardless of the current entry, because
102
+ * "the task was advised" is true whichever consultation produced it.
103
+ */
104
+ markAdvised(key: string, now?: number): void;
105
+ /** Test/observability seam: current entry count. */
106
+ size(): number;
107
+ }
108
+
109
+ export function createAdvisorPreflightLedger(): AdvisorPreflightLedger {
110
+ const entries = new Map<string, LedgerEntry>();
111
+ let claimSequence = 0;
112
+
113
+ const isExpired = (entry: LedgerEntry, now: number): boolean => {
114
+ const ttl = entry.state === "success"
115
+ ? ADVISOR_SUCCESS_TTL_MS
116
+ : entry.state === "failed"
117
+ ? ADVISOR_FAILURE_COOLDOWN_MS
118
+ : ADVISOR_INFLIGHT_TTL_MS;
119
+ return now - entry.at > ttl;
120
+ };
121
+
122
+ /**
123
+ * Make room for ONE new claim without ever evicting a live in-flight entry, because that entry
124
+ * is the only thing preventing a second automatic consultation for its task. Order: expired
125
+ * entries first, then settled ones (success before failure), oldest first. Returns false when
126
+ * every entry is a live claim — the caller then reports `saturated` instead of breaking the
127
+ * "at most one in-flight consultation per task" guarantee.
128
+ */
129
+ const makeRoom = (now: number): boolean => {
130
+ if (entries.size < MAX_ENTRIES) return true;
131
+ for (const [key, entry] of entries) {
132
+ if (isExpired(entry, now)) entries.delete(key);
133
+ }
134
+ if (entries.size < MAX_ENTRIES) return true;
135
+ for (const state of ["success", "failed"] as const) {
136
+ for (const [key, entry] of entries) {
137
+ if (entry.state === state) {
138
+ entries.delete(key);
139
+ return true;
140
+ }
141
+ }
142
+ }
143
+ return false;
144
+ };
145
+
146
+ const liveEntry = (key: string, now: number): LedgerEntry | undefined => {
147
+ const entry = entries.get(key);
148
+ if (!entry) return undefined;
149
+ if (isExpired(entry, now)) {
150
+ entries.delete(key);
151
+ return undefined;
152
+ }
153
+ return entry;
154
+ };
155
+
156
+ // Replacing an existing key never grows the map, and every NEW key is admitted only through
157
+ // claim()'s makeRoom gate, so the table cannot exceed MAX_ENTRIES.
158
+ const set = (key: string, state: LedgerEntry["state"], now: number, token?: string): void => {
159
+ if (entries.has(key)) entries.delete(key);
160
+ entries.set(key, { state, at: now, ...(token !== undefined ? { token } : {}) });
161
+ };
162
+
163
+ /** True when the caller's token still owns the current in-flight entry for this key. */
164
+ const owns = (key: string, token: string, now: number): boolean => {
165
+ const entry = liveEntry(key, now);
166
+ return entry?.state === "inflight" && entry.token === token;
167
+ };
168
+
169
+ return {
170
+ claim(key, now = Date.now()) {
171
+ const entry = liveEntry(key, now);
172
+ if (entry?.state === "success") return { state: "complete" };
173
+ if (entry?.state === "failed") return { state: "cooldown" };
174
+ if (entry?.state === "inflight") return { state: "inflight" };
175
+ if (!makeRoom(now)) return { state: "saturated" };
176
+ claimSequence += 1;
177
+ const token = `claim-${claimSequence.toString(36)}`;
178
+ set(key, "inflight", now, token);
179
+ return { state: "claimed", token };
180
+ },
181
+ complete(key, token, now = Date.now()) {
182
+ // A settlement that no longer owns the entry is a no-op: the claim it belonged to expired
183
+ // and a successor now owns the state.
184
+ if (owns(key, token, now)) set(key, "success", now);
185
+ },
186
+ fail(key, token, now = Date.now()) {
187
+ if (owns(key, token, now)) set(key, "failed", now);
188
+ },
189
+ release(key, token, now = Date.now()) {
190
+ if (owns(key, token, now)) entries.delete(key);
191
+ },
192
+ markAdvised(key, now = Date.now()) {
193
+ // Recording the FACT must respect the same cap as claiming: a key that is not present and a
194
+ // table holding nothing but live claims means no room, so the record is skipped rather than
195
+ // evicting a claim that is still running. Fail-open: at worst one extra automatic attempt.
196
+ const present = liveEntry(key, now) !== undefined;
197
+ if (!present && !makeRoom(now)) return;
198
+ set(key, "success", now);
199
+ },
200
+ size() {
201
+ return entries.size;
202
+ },
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Stable conversation identity for the ledger, reusing the repository's existing request
208
+ * identities in specificity order: the client's own thread, the shared parent thread, the
209
+ * Cursor conversation, the Cursor client thread, then the reasoning-replay scope's thread.
210
+ * Returns undefined for a client that sends no identity at all — such a caller stays out of the
211
+ * process-global ledger on purpose (fail-open, see the module header).
212
+ */
213
+ export function advisorConversationIdentity(parsed: {
214
+ _codexOwnThreadId?: string;
215
+ _clientThreadId?: string;
216
+ _cursorConversationId?: string;
217
+ _cursorClientThreadId?: string;
218
+ _reasoningReplayScope?: { clientThreadId?: string };
219
+ }): string | undefined {
220
+ return parsed._codexOwnThreadId
221
+ ?? parsed._clientThreadId
222
+ ?? parsed._cursorConversationId
223
+ ?? parsed._cursorClientThreadId
224
+ ?? parsed._reasoningReplayScope?.clientThreadId
225
+ ?? undefined;
226
+ }
227
+
228
+ /**
229
+ * Domain-separated SHA-256 digest. Task identity and suppression are CORRECTNESS boundaries, so a
230
+ * 32-bit non-cryptographic hash is not an acceptable primary digest: distinct tasks must not
231
+ * collide because of a short fold. The ledger stores only this digest, never the raw text, so a
232
+ * captured key reveals nothing about the conversation. The domain prefix keeps digests from
233
+ * different purposes apart even if their inputs coincide.
234
+ */
235
+ function sha256Hex(domain: string, value: string, hexChars: number): string {
236
+ return createHash("sha256").update(`${domain}\0${value}`, "utf8").digest("hex").slice(0, hexChars);
237
+ }
238
+
239
+ /**
240
+ * The current task boundary inside a conversation: how many user turns the history carries and a
241
+ * digest of the FULL latest user text. A new user message moves the boundary (a new task gets its
242
+ * own claim); the same turn re-sent by a stateless full-history client keeps the same boundary,
243
+ * and a `previous_response_id` expansion replays the same user turns, so continuations dedup.
244
+ * The whole text participates — no truncation — so two tasks that share an opening prefix still
245
+ * get different boundaries.
246
+ */
247
+ export function advisorTaskBoundary(parsed: {
248
+ context: { messages: readonly { role: string; content: unknown }[] };
249
+ }): string {
250
+ let userTurns = 0;
251
+ let lastUserText = "";
252
+ for (const message of parsed.context.messages) {
253
+ if (message.role !== "user") continue;
254
+ userTurns += 1;
255
+ lastUserText = contentText(message.content);
256
+ }
257
+ return `t${userTurns}:${sha256Hex("advisor-task-boundary", lastUserText, 32)}`;
258
+ }
259
+
260
+ /**
261
+ * The ledger key: one domain-separated SHA-256 digest over conversation identity + task boundary
262
+ * + worker model. Returns undefined when the caller has no stable conversation identity — the
263
+ * caller then relies on request-scoped dedup and genuine in-history provenance instead of a
264
+ * shared guess (documented fail-open).
265
+ */
266
+ export function advisorLedgerKey(
267
+ parsed: {
268
+ context: { messages: readonly { role: string; content: unknown }[] };
269
+ _codexOwnThreadId?: string;
270
+ _clientThreadId?: string;
271
+ _cursorConversationId?: string;
272
+ _cursorClientThreadId?: string;
273
+ _reasoningReplayScope?: { clientThreadId?: string };
274
+ },
275
+ workerModelId: string,
276
+ ): string | undefined {
277
+ const identity = advisorConversationIdentity(parsed);
278
+ if (!identity) return undefined;
279
+ const material = `${identity}\0${advisorTaskBoundary(parsed)}\0${workerModelId}`;
280
+ return `ak-${sha256Hex("advisor-task-key", material, 40)}`;
281
+ }
282
+
283
+ /** Text projection for string-or-parts content; used only for hashing, never transmitted. */
284
+ export function contentText(content: unknown): string {
285
+ if (typeof content === "string") return content;
286
+ if (!Array.isArray(content)) return "";
287
+ return content
288
+ .filter((part): part is { type: "text"; text: string } =>
289
+ !!part && typeof part === "object" && (part as { type?: unknown }).type === "text"
290
+ && typeof (part as { text?: unknown }).text === "string")
291
+ .map(part => part.text)
292
+ .join("");
293
+ }
294
+
295
+ /**
296
+ * Legacy marker spellings. Failure text still neutralizes them so an upstream error cannot
297
+ * look like an old wrapper. They are not suppression authority and are not emitted.
298
+ */
299
+ export const ADVISOR_PREFLIGHT_MARKER = "<opencodex_advisor_preflight>";
300
+
301
+ /** The synthetic advisor tool's wire name; kept in sync with the tool definition by test. */
302
+ export const ADVISOR_RESULT_TOOL_NAME = "advisor";
303
+
304
+ /** Legacy manual wrapper spelling. Detection does not search for this substring. */
305
+ export const ADVISOR_ADVICE_MARKER = "<opencodex_advisor>";
306
+
307
+ /**
308
+ * Detect an ALREADY-PRESENT MANUAL advisor result — by provenance, never by a bare string.
309
+ * The message must be a `toolResult` whose `toolName` is the synthetic advisor tool, and its
310
+ * content must parse as a runtime-written advice object (`status === "advice"` on the sibling
311
+ * field the runtime sets). Text inside `advice` cannot flip that field.
312
+ *
313
+ * Only messages after the latest user turn count. Manual advice from an earlier task in the
314
+ * same thread does not suppress preflight for a later task; the ledger keys those separately.
315
+ * Developer messages are deliberately NOT inspected. Automatic preflight dedup lives in the
316
+ * ledger. A client-echoed developer envelope, a shell result, or a failure notice matches nothing.
317
+ */
318
+ export function historyHasManualAdvisorResult(parsed: {
319
+ context: { messages: readonly { role: string; content?: unknown; toolName?: string }[] };
320
+ }): boolean {
321
+ for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
322
+ const message = parsed.context.messages[i]!;
323
+ if (message.role === "user") break;
324
+ if (message.role !== "toolResult") continue;
325
+ if (message.toolName !== ADVISOR_RESULT_TOOL_NAME) continue;
326
+ if (advisorResultIsAdvice(contentText(message.content))) return true;
327
+ }
328
+ return false;
329
+ }
330
+
331
+ /** Kept for callers that only need the first user text (payload building, tests). */
332
+ export function firstUserText(parsed: { context: { messages: readonly { role: string; content: unknown }[] } }): string {
333
+ for (const message of parsed.context.messages) {
334
+ if (message.role !== "user") continue;
335
+ const text = contentText(message.content);
336
+ if (text.trim() !== "") return text;
337
+ }
338
+ return "";
339
+ }
340
+
341
+ /**
342
+ * Deterministic preflight trigger: has this conversation already produced orientation evidence
343
+ * since the latest user message? The documented approximation for "before the first substantive
344
+ * implementation" — the protocol layer offers no safe pre-mutation checkpoint, so OpenCodex fires
345
+ * the automatic attempt on the first worker reasoning turn that arrives with that evidence.
346
+ * Evidence is an assistant tool call OR a tool result after the latest user message (both forms
347
+ * are genuinely accepted; the docs say so). Only text/toolResult content is inspected — never
348
+ * reasoning, never encrypted items.
349
+ */
350
+ export function hasOrientationEvidence(parsed: { context: { messages: readonly { role: string; content: unknown }[] } }): boolean {
351
+ let latestUserIndex = -1;
352
+ for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
353
+ if (parsed.context.messages[i]?.role === "user") {
354
+ latestUserIndex = i;
355
+ break;
356
+ }
357
+ }
358
+ if (latestUserIndex < 0) return false;
359
+ for (let i = latestUserIndex + 1; i < parsed.context.messages.length; i += 1) {
360
+ const message = parsed.context.messages[i];
361
+ if (message.role === "toolResult") return true;
362
+ if (message.role === "assistant" && Array.isArray(message.content)
363
+ && message.content.some(part =>
364
+ !!part && typeof part === "object" && (part as { type?: unknown }).type === "toolCall")) {
365
+ return true;
366
+ }
367
+ }
368
+ return false;
369
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The synthetic `advisor` tool injected into routed worker turns.
3
+ *
4
+ * Mirrors src/web-search/synthetic-tool.ts: the proxy OWNS this tool — the worker only expresses
5
+ * "I want to consult the expert" (optionally with a focus), and OpenCodex builds everything else
6
+ * (task context, conversation, tool catalog, identities) itself. The worker never passes a
7
+ * transcript, provider, model, or files.
8
+ */
9
+ import type { OcxTool } from "../types";
10
+
11
+ export const ADVISOR_TOOL_NAME = "advisor";
12
+
13
+ export function buildAdvisorTool(): OcxTool {
14
+ return {
15
+ name: ADVISOR_TOOL_NAME,
16
+ description:
17
+ "Consult an independent expert advisor model about the current task. The advisor receives a "
18
+ + "summary of what you have done so far (task, conversation, tool results) and returns "
19
+ + "strategic advice, critique, root-cause reasoning, or alternative approaches. Use it when "
20
+ + "you are stuck, before starting substantive implementation on a hard task, or when you want "
21
+ + "a second opinion on a plan or diagnosis. The advisor cannot execute tools or edit files; "
22
+ + "you remain responsible for all execution. Optionally pass a short `question` to focus the "
23
+ + "consultation.",
24
+ parameters: {
25
+ type: "object",
26
+ properties: {
27
+ question: { type: "string", description: "Optional short focus question for the advisor." },
28
+ },
29
+ required: [],
30
+ },
31
+ advisor: true,
32
+ };
33
+ }
@@ -13,6 +13,7 @@ import {
13
13
  CYBER_POLICY_ERROR_CODE,
14
14
  isCyberPolicyCode,
15
15
  SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE,
16
+ UNDECLARED_TOOL_CALL_ERROR_CODE,
16
17
  type OcxErrorPayload,
17
18
  } from "../lib/errors";
18
19
 
@@ -31,6 +32,10 @@ export function formatErrorResponse(
31
32
  error.code = CYBER_POLICY_ERROR_CODE;
32
33
  error.type = cyberPolicyErrorType(type);
33
34
  }
35
+ // The undeclared-tool refusal is deterministic, not a transient upstream failure: its code
36
+ // survives so the Anthropic translator can classify it instead of folding it into a
37
+ // retryable overload (and so clients see the SSE path's rejection identity).
38
+ if (options?.code === UNDECLARED_TOOL_CALL_ERROR_CODE) error.code = options.code;
34
39
  // Only the allowlisted transport verdicts survive this formatter. Do not forward
35
40
  // arbitrary provider codes, and preserve the existing cyber-policy precedence.
36
41
  const replayBlocked = error.code !== CYBER_POLICY_ERROR_CODE
@@ -68,3 +73,22 @@ export function formatErrorResponse(
68
73
  if (refusal) retainReplayRefusal(response);
69
74
  return response;
70
75
  }
76
+
77
+ /**
78
+ * A spent usage window, in the shape the Codex client treats as final (#6991): HTTP 429 with
79
+ * `error.type: "usage_limit_reached"` and, when the reset is known, `resets_at` in epoch
80
+ * seconds. Codex stops (and shows the reset time when present) instead of backing off through its
81
+ * retry budget, which is what it does with `rate_limit_exceeded`. No `plan_type` is sent: Codex
82
+ * would read it as a ChatGPT plan and print that plan's upgrade advice. `formatErrorResponse`
83
+ * cannot build this, because every 429 it classifies becomes `rate_limit_exceeded` and its
84
+ * payload has no `resets_at`.
85
+ */
86
+ export function formatUsageLimitReachedResponse(message: string, resetsAtMs: number | null, retryAfterSeconds: number | null): Response {
87
+ const error = {
88
+ message, type: "usage_limit_reached", code: "usage_limit_reached",
89
+ ...(resetsAtMs !== null ? { resets_at: Math.ceil(resetsAtMs / 1000) } : {}),
90
+ };
91
+ const headers = new Headers({ "Content-Type": "application/json" });
92
+ if (retryAfterSeconds !== null) headers.set("Retry-After", String(retryAfterSeconds));
93
+ return new Response(JSON.stringify({ error }), { status: 429, headers });
94
+ }
package/src/bridge.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { adapterFailureFromMessage } from "./lib/errors";
2
2
 
3
- export { formatErrorResponse } from "./bridge/errors";
3
+ export { formatErrorResponse, formatUsageLimitReachedResponse } from "./bridge/errors";
4
4
  export { setOwnedBudgetAbandonedMsForTests } from "./bridge/internal";
5
5
  export { buildResponseJSON } from "./bridge/response-json";
6
6
  export { bridgeToResponsesSSE } from "./bridge/sse";
@@ -36,7 +36,7 @@ export function assertChatCompletionsRoutingBody(raw: unknown): asserts raw is C
36
36
  const OUTPUT_CONFIG_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
37
37
  const OUTPUT_CONFIG_SUMMARIES = new Set(["auto", "concise", "detailed", "none"]);
38
38
 
39
- function contentToText(content: unknown): string {
39
+ export function contentToText(content: unknown): string {
40
40
  if (typeof content === "string") return content;
41
41
  if (!Array.isArray(content)) return "";
42
42
  const parts: string[] = [];
@@ -62,7 +62,7 @@ const imageUrlFromPart = chatImageUrlFromPart;
62
62
  * understanding is requested by (#3271). The object form is the only one that
63
63
  * can carry it — `video_url` as a bare string has nowhere to put it.
64
64
  */
65
- function videoFromPart(part: Rec): { url: string; processing?: string } | null {
65
+ export function videoFromPart(part: Rec): { url: string; processing?: string } | null {
66
66
  if (part.type !== "video_url") return null;
67
67
  const videoUrl = part.video_url;
68
68
  if (typeof videoUrl === "string" && videoUrl.length > 0) return { url: videoUrl };
@@ -123,7 +123,7 @@ function userContentToBlocks(content: unknown): Rec[] {
123
123
  * was dropped in silence (#5212). A part with no inline bytes is still not translatable and is
124
124
  * left to the untranslated-media refusal, which runs before this loop.
125
125
  */
126
- function fileFromPart(part: Rec): Rec | null {
126
+ export function fileFromPart(part: Rec): Rec | null {
127
127
  if (part.type !== "file" && part.type !== "input_file") return null;
128
128
  const file = isRec(part.file) ? part.file : part;
129
129
  const fileData = file.file_data;
@@ -143,7 +143,7 @@ function fileFromPart(part: Rec): Rec | null {
143
143
  * Only representable plaintext is read. No signature, encrypted payload or
144
144
  * provider-issued item id is reconstructed — see the reasoning item built below.
145
145
  */
146
- function assistantReasoningText(msg: Rec): string | undefined {
146
+ export function assistantReasoningText(msg: Rec): string | undefined {
147
147
  if (typeof msg.reasoning_content === "string" && msg.reasoning_content.length > 0) {
148
148
  return msg.reasoning_content;
149
149
  }
@@ -601,6 +601,32 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
601
601
  ...(legacyFunctionsToResponses(raw.functions) ?? []),
602
602
  ];
603
603
  if (tools.length > 0) body.tools = tools;
604
+ applyChatSettings(raw, body);
605
+ return body;
606
+ }
607
+
608
+ /**
609
+ * The request settings of a Chat body as a Responses body with no conversation: `input` is
610
+ * empty and there are no `instructions` or `tools`. The Antigravity direct lane hands this to
611
+ * the Responses core so routing, effort policy, tier and logging keep reading the fields they
612
+ * read today, while the conversation is decoded straight into the IR
613
+ * (`src/protocols/codecs/chat-ir.ts`). Field order and validation are the bridge body's own:
614
+ * both are built by `applyChatSettings`.
615
+ */
616
+ export function chatSettingsToResponsesBody(raw: unknown): Rec {
617
+ assertChatCompletionsRoutingBody(raw);
618
+ const body: Rec = {
619
+ model: raw.model,
620
+ input: [],
621
+ stream: raw.stream === true,
622
+ store: false,
623
+ };
624
+ applyChatSettings(raw, body);
625
+ return body;
626
+ }
627
+
628
+ /** Everything after the conversation and tool catalog: choice, limits, sampling, reasoning, format. */
629
+ function applyChatSettings(raw: ChatCompletionsRoutingBody, body: Rec): void {
604
630
  if (raw.tool_choice !== undefined) toolChoiceToResponses(raw.tool_choice, body);
605
631
  else legacyFunctionChoiceToResponses(raw.function_call, body);
606
632
 
@@ -637,6 +663,4 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
637
663
 
638
664
  const text = responseFormatToText(raw.response_format);
639
665
  if (text) body.text = text;
640
-
641
- return body;
642
666
  }
@@ -19,6 +19,7 @@ import {
19
19
  CYBER_POLICY_ERROR_CODE,
20
20
  isCyberPolicyCode,
21
21
  isCyberPolicyMessage,
22
+ UNDECLARED_TOOL_CALL_ERROR_CODE,
22
23
  } from "../lib/errors";
23
24
  import { redactSecretString } from "../lib/redact";
24
25
  import { createSseBlockBuffer, sseDataPayload } from "../server/sse-payload-rewrite";
@@ -196,6 +197,10 @@ export function chatCompletionsStreamErrorPayload(
196
197
  } else if (isCyberPolicyCode(details?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
197
198
  classified.code = CYBER_POLICY_ERROR_CODE;
198
199
  classified.type = cyberPolicyErrorType(details?.type);
200
+ } else if (details?.code === UNDECLARED_TOOL_CALL_ERROR_CODE) {
201
+ // The guard's deterministic refusal keeps its wire identity past classifyError's
202
+ // generic 5xx remap — same code the buffered Chat error and the SSE frame carry.
203
+ classified.code = UNDECLARED_TOOL_CALL_ERROR_CODE;
199
204
  } else if (details?.code !== undefined && details.code !== null && !classified.code) {
200
205
  classified.code = details.code;
201
206
  }
@@ -10,7 +10,7 @@
10
10
  * adapter-reported CatalogModel.contextWindow / the Anthropic registry for passthrough ids);
11
11
  * nothing is guessed.
12
12
  */
13
- import { aliasForNative, aliasForRoute, currentClaudeAliasSpelling, legacyAliasForNative, legacyAliasForRoute } from "./alias";
13
+ import { aliasForNative, aliasForRoute, currentClaudeAliasSpelling, legacyAliasForNative, legacyAliasForRoute, resolveAlias } from "./alias";
14
14
  import { desktop3pAlias } from "./desktop-3p";
15
15
  import { nativeOpenAiContextWindow, type CatalogModel, type NativeContextLimitsInput } from "../codex/catalog";
16
16
  import { ANTHROPIC_MODEL_CONTEXT_WINDOWS } from "../providers/registry/model-seeds";
@@ -277,6 +277,59 @@ export function effectiveModelEnv(
277
277
  return out;
278
278
  }
279
279
 
280
+ /**
281
+ * Tier slots Claude Code pairs with a `<slot>_NAME` label (#6926). Its /model tier row is
282
+ * `label: <slot>_NAME ?? <first-party display name> ?? <slot value>` (2.1.296), and an
283
+ * `ocx-claude-*` alias is not a first-party id, so without the label the row prints the alias.
284
+ * The alias itself stays the submitted id: saved selections and inbound resolution depend on it.
285
+ */
286
+ export const CLAUDE_TIER_LABEL_SLOTS = [
287
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
288
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
289
+ "ANTHROPIC_DEFAULT_FABLE_MODEL",
290
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
291
+ ] as const;
292
+
293
+ export type ClaudeTierLabelSlot = (typeof CLAUDE_TIER_LABEL_SLOTS)[number];
294
+
295
+ export function isClaudeTierLabelSlot(name: string): name is ClaudeTierLabelSlot {
296
+ return (CLAUDE_TIER_LABEL_SLOTS as readonly string[]).includes(name);
297
+ }
298
+
299
+ /**
300
+ * The picker label for a tier slot holding an OpenCodex alias, spelled like the discovery
301
+ * row's `display_name` (model-info.ts): `<slug> (native)`, `<model> (<provider>)` and a
302
+ * ` · 1M` variant. The native-namespaced `claude-*` selector is the Anthropic Fable picker
303
+ * alias, which discovery labels `(anthropic)`. A trailing `--fast` stays in the model name:
304
+ * without the catalog this cannot tell a synthetic Fast sibling from a real model whose id ends
305
+ * in `--fast`, and naming the exact model beats a prettier label that could name the wrong one.
306
+ * Anything that is not an alias (a first-party id, `provider/model`, a bare slug) gets no
307
+ * label: Claude Code either names it itself or the id is already what the user typed.
308
+ */
309
+ export function claudeTierDisplayName(selector: string): string | undefined {
310
+ const trimmed = selector.trim();
311
+ const oneMillion = hasOneMillionMarker(trimmed);
312
+ const route = resolveAlias(stripOneMillionMarker(trimmed));
313
+ if (route === null) return undefined;
314
+ const slash = route.indexOf("/");
315
+ let provider = slash > 0 ? route.slice(0, slash) : "native";
316
+ const model = slash > 0 ? route.slice(slash + 1) : route;
317
+ if (!model) return undefined;
318
+ if (provider === "native" && model.startsWith("claude-")) provider = "anthropic";
319
+ return `${model} (${provider})${oneMillion ? " · 1M" : ""}`;
320
+ }
321
+
322
+ /** `{ "<slot>_NAME": label }` for each tier slot in `env` that holds an OpenCodex alias. */
323
+ export function claudeTierDisplayNameEnv(env: Readonly<Record<string, string | undefined>>): Record<string, string> {
324
+ const out: Record<string, string> = {};
325
+ for (const slot of CLAUDE_TIER_LABEL_SLOTS) {
326
+ const value = env[slot];
327
+ const label = value ? claudeTierDisplayName(value) : undefined;
328
+ if (label !== undefined) out[`${slot}_NAME`] = label;
329
+ }
330
+ return out;
331
+ }
332
+
280
333
  /** Shared 3s bounded acquisition (audit R4#3) for context-window sources. */
281
334
  export async function boundedContextWindows(
282
335
  acquire: () => Promise<Record<string, number>>,