@bitkyc08/opencodex 2.84.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -30,6 +30,7 @@ import {
30
30
  providerForwardClientHeadersConfigError,
31
31
  reasoningSummaryDeliveryRecordConfigError,
32
32
  upstreamHttpVersionConfigError,
33
+ credentialPinnedProviderConfigError,
33
34
  } from "../config/provider-validation";
34
35
  import { providerDestinationConfigError } from "../lib/destination-policy";
35
36
  import { providerEgressConfigError } from "../lib/provider-egress";
@@ -784,6 +785,8 @@ export function providerManagementConfigError(
784
785
  delete canonicalCandidate.pinnedReasoningEffort;
785
786
  delete canonicalCandidate.modelPinnedReasoningEfforts;
786
787
  delete canonicalCandidate.responsesSnapshotRepair;
788
+ // Explicit history-loss opt-in changes request content, never auth or transport identity.
789
+ delete canonicalCandidate.projectResponsesLiteHostedSearchHistory;
787
790
  // modelCosts is a user-owned display overlay, not part of the canonical
788
791
  // forward seed; it is validated separately below (providerModelCostsConfigError).
789
792
  delete canonicalCandidate.modelCosts;
@@ -912,6 +915,8 @@ export function providerManagementConfigError(
912
915
  if (reasoningSummaryDeliveryError) return `provider ${name} ${reasoningSummaryDeliveryError}`;
913
916
  const modelAdaptersError = modelAdapterRecordConfigError(raw.modelAdapters, "modelAdapters", name, typed);
914
917
  if (modelAdaptersError) return `provider ${name} ${modelAdaptersError}`;
918
+ const credentialPinError = credentialPinnedProviderConfigError(name, typed);
919
+ if (credentialPinError) return `provider ${name} ${credentialPinError}`;
915
920
  const preferHostedToolsError = modelPreferHostedToolsConfigError(
916
921
  raw.modelPreferHostedTools,
917
922
  "modelPreferHostedTools",
@@ -919,12 +924,17 @@ export function providerManagementConfigError(
919
924
  typed,
920
925
  );
921
926
  if (preferHostedToolsError) return `provider ${name} ${preferHostedToolsError}`;
927
+ if (raw.projectResponsesLiteHostedSearchHistory !== undefined && typeof raw.projectResponsesLiteHostedSearchHistory !== "boolean") {
928
+ return `provider ${name} projectResponsesLiteHostedSearchHistory must be a boolean`;
929
+ }
922
930
  if (raw.responsesSnapshotRepair !== undefined && typeof raw.responsesSnapshotRepair !== "boolean") {
923
931
  return `provider ${name} responsesSnapshotRepair must be a boolean`;
924
932
  }
925
933
  if (raw.xaiResponsesXSearch !== undefined && typeof raw.xaiResponsesXSearch !== "boolean") {
926
934
  return `provider ${name} xaiResponsesXSearch must be a boolean`;
927
935
  }
936
+ const minMaxOutputError = positiveIntegerConfigError(raw.minMaxOutputTokens, "minMaxOutputTokens");
937
+ if (minMaxOutputError) return `provider ${name} ${minMaxOutputError}`;
928
938
  const defaultMaxOutputError = positiveIntegerConfigError(raw.defaultMaxOutputTokens, "defaultMaxOutputTokens");
929
939
  if (defaultMaxOutputError) return `provider ${name} ${defaultMaxOutputError}`;
930
940
  const maxOutputError = positiveIntegerRecordConfigError(raw.modelMaxOutputTokens, "modelMaxOutputTokens");
@@ -1051,9 +1061,12 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
1051
1061
  preserveResponsesReasoningContent: "editor",
1052
1062
  preserveResponsesInputItemIds: "editor",
1053
1063
  preserveResponsesMessageMetadata: "editor",
1064
+ projectResponsesLiteHostedSearchHistory: "editor",
1054
1065
  dropResponsesReasoningItems: "editor",
1055
1066
  modelReasoningEffortsAuthoritative: "editor",
1056
1067
  decodesNativeCompactionBlobs: "editor",
1068
+ supportsNativeCompactionTrigger: "editor",
1069
+ supportsNativeCompactEndpoint: "editor",
1057
1070
  allowEncryptedV2AgentTasks: "editor",
1058
1071
  allowPrivateNetwork: "editor",
1059
1072
  // A proxy URL routinely embeds `user:password@`, so it never reaches the dashboard DTO and
@@ -1085,6 +1098,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
1085
1098
  newModelPolicy: "editor",
1086
1099
  modelPreset: "editor",
1087
1100
  contextWindow: "editor",
1101
+ inferContextWindow: "editor",
1088
1102
  modelContextWindows: "editor",
1089
1103
  modelContextTiers: "editor",
1090
1104
  modelInputModalities: "editor",
@@ -1092,6 +1106,7 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
1092
1106
  modelMaxInputTokens: "runtime",
1093
1107
  modelAutoCompactTokenLimits: "editor",
1094
1108
  defaultMaxOutputTokens: "editor",
1109
+ minMaxOutputTokens: "editor",
1095
1110
  modelMaxOutputTokens: "editor",
1096
1111
  modelCosts: "editor",
1097
1112
  headers: "redacted",
@@ -10,7 +10,9 @@ import {
10
10
  assertChatCompletionsRoutingBody,
11
11
  ChatCompletionsRequestError,
12
12
  } from "../chat/inbound";
13
- import { chatToResponsesBody } from "../protocols/codecs/chat";
13
+ import { chatSettingsBody, chatToResponsesBody } from "../protocols/codecs/chat";
14
+ import { chatConversationToIr, chatIrRouteEvidence, withDirectChatConversation, type ChatIrConversation } from "../protocols/codecs/chat-ir";
15
+ import { antigravityDirectDeclineReason } from "./antigravity-direct-eligibility";
14
16
  import { normalizeChatImageParts } from "../chat/image-parts";
15
17
  import {
16
18
  chatCompletionsErrorResponse,
@@ -19,7 +21,8 @@ import {
19
21
  responsesJsonToChatCompletion,
20
22
  responsesSseToChatCompletionsSse,
21
23
  } from "../chat/outbound";
22
- import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
24
+ import { ADVISOR_INTERNAL_CAPABILITY_HEADER, isInternalCallCapability } from "../lib/local-internal-call-capability";
25
+ import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
23
26
  import { redactSecretString } from "../lib/redact";
24
27
  import { resolveClientRetryAfter } from "../lib/retry-after";
25
28
  import {
@@ -44,12 +47,14 @@ import type { OcxConfig } from "../types";
44
47
  import { readJsonRequestBody, resolveInboundBodyLimitBytes } from "./request-decompress";
45
48
  import {
46
49
  addFinalRequestLog,
50
+ cacheDiagnosticsObserving,
47
51
  httpStatusForRequestLogTerminal,
48
52
  recordFirstOutput,
49
53
  type RequestLogContext,
50
54
  } from "./request-log";
51
55
  import { createFinalRequestLog } from "./inference/final-log";
52
56
  import { clientWireLogOf, clientWireOf } from "./inference/client-wire";
57
+ import { attachAdvisorReasoning } from "./inference/advisor-diagnostics";
53
58
  import { directEncodersApply } from "./inference/client-encoder-delivery";
54
59
  import { responseWithDeferredRequestLog } from "./relay";
55
60
  import { handleResponses } from "./responses";
@@ -110,8 +115,10 @@ export async function handleChatCompletions(
110
115
  ): Promise<Response> {
111
116
  const translatorBudget = createTranslatorBudget();
112
117
  try {
118
+ let response = await handleChatCompletionsWithBudget(req, config, logCtx, translatorBudget, logIds);
119
+ if (isInternalCallCapability(req.headers.get(ADVISOR_INTERNAL_CAPABILITY_HEADER))) response = attachAdvisorReasoning(response, logCtx);
113
120
  return finalizeTranslatorBudgetResponse(
114
- await handleChatCompletionsWithBudget(req, config, logCtx, translatorBudget, logIds),
121
+ response,
115
122
  translatorBudget,
116
123
  req.signal,
117
124
  );
@@ -272,6 +279,12 @@ async function handleChatCompletionsWithBudget(
272
279
  const protocolSettings = resolveProtocolSettings(config);
273
280
  const nativeChatCombos = protocolSettings.rollout.nativeChatCombos
274
281
  && settledRoute?.combo !== undefined && !effortRow;
282
+ // Antigravity direct lane (off by default): the conversation is decoded straight into the IR
283
+ // and the answer is encoded straight into Chat, with no internal Responses body either way.
284
+ const antigravityDecline = settledRoute && !chatNativeRoute
285
+ ? antigravityDirectDeclineReason(settledRoute, chatBody, config, { cacheDiagnostics: cacheDiagnosticsObserving() })
286
+ : undefined;
287
+ const directRoute = settledRoute && !chatNativeRoute && antigravityDecline === undefined ? settledRoute : null;
275
288
  const envelope = protocolSettings.unrepresentable === "reject" || nativeChatCombos
276
289
  ? createProtocolEnvelope({ inbound: "chat", body: chatBody, translatorBudget })
277
290
  : undefined;
@@ -281,7 +294,7 @@ async function handleChatCompletionsWithBudget(
281
294
  inbound: "chat",
282
295
  requestPath: chatNativeRoute
283
296
  ? requestPathForLane("chat", "native", "chat")
284
- : requestPathForLane("chat", "bridge", upstreamWireForAdapter(settledRoute.provider.adapter)),
297
+ : requestPathForLane("chat", directRoute ? "direct" : "bridge", upstreamWireForAdapter(settledRoute.provider.adapter)),
285
298
  features: envelope.features(),
286
299
  policy: "reject",
287
300
  });
@@ -294,8 +307,15 @@ async function handleChatCompletionsWithBudget(
294
307
  }
295
308
  markProtocolEntry(logCtx, {
296
309
  inbound: "chat",
297
- lane: chatNativeRoute ? "native" : "bridge",
298
- reasonCodes: !chatNativeRoute && nativeDecline ? [nativeDecline] : [],
310
+ lane: chatNativeRoute ? "native" : directRoute ? "direct" : "bridge",
311
+ reasonCodes: [
312
+ ...(!chatNativeRoute && !directRoute && nativeDecline ? [nativeDecline] : []),
313
+ // Only a decline that says something about this route: with the switch off or a
314
+ // non-Antigravity target the path reason already explains the bridge.
315
+ ...(antigravityDecline && antigravityDecline !== "rollout-disabled" && antigravityDecline !== "not-migrated"
316
+ ? [antigravityDecline]
317
+ : []),
318
+ ],
299
319
  features: envelope ? () => envelope.features() : () => featuresFromChatBody(chatBody),
300
320
  });
301
321
  recordProtocolShadowPlan(logCtx, config, { inbound: "chat", model: requestedModel });
@@ -314,10 +334,18 @@ async function handleChatCompletionsWithBudget(
314
334
  }
315
335
 
316
336
  let internalBody: Rec;
337
+ let directConversation: ChatIrConversation | undefined;
317
338
  try {
318
339
  // Validate the full Chat boundary after routing. Native Chat keeps `chatBody` as
319
340
  // its wire source; this Responses projection is used only by the fallback path.
320
- internalBody = chatToResponsesBody(chatBody);
341
+ // The direct lane validates the same boundary while decoding the conversation into the IR,
342
+ // and sends the core only the request settings.
343
+ if (directRoute) {
344
+ directConversation = chatConversationToIr(chatBody, chatBody.model as string);
345
+ internalBody = chatSettingsBody(chatBody);
346
+ } else {
347
+ internalBody = chatToResponsesBody(chatBody);
348
+ }
321
349
  if (effortRow) {
322
350
  internalBody.reasoning = {
323
351
  ...(isRec(internalBody.reasoning) ? internalBody.reasoning : {}),
@@ -370,6 +398,17 @@ async function handleChatCompletionsWithBudget(
370
398
  }
371
399
 
372
400
  const visionDescribeTerminal = req.headers.get("x-opencodex-vision-describe") === "1";
401
+ // Terminal advisor marker: the advisor sidecar's own loopback consultation re-enters through
402
+ // this surface. The bridge rebuilds headers from the FORWARD_HEADERS allowlist, which would
403
+ // drop the raw marker header — so the fact is detected here and carried as an option flag
404
+ // (same structure as the vision-describe fence above, depth cap 1).
405
+ //
406
+ // The header value is NOT evidence by itself: any external caller can send it, and the peer
407
+ // address proves nothing (Docker/WSL/tunnels/port-forwarding end on loopback). Internal
408
+ // authority comes from a process-owned capability minted at random per process and compared in
409
+ // constant time, so a forged header — or a token captured from an older process — is treated
410
+ // as an ordinary external request.
411
+ const advisorInternal = isInternalCallCapability(req.headers.get(ADVISOR_INTERNAL_CAPABILITY_HEADER));
373
412
  // Concrete helper targets must fail before optional stored-main credential enrichment.
374
413
  // Unresolved combos are checked after their concrete child route is selected in Responses.
375
414
  if (settledRoute && !settledRoute.combo && isCanonicalOpenAiForwardProvider(settledRoute.provider)
@@ -455,6 +494,10 @@ async function handleChatCompletionsWithBudget(
455
494
  abortSignal: req.signal,
456
495
  // Body is Responses-shaped by now, but the client spoke Chat Completions.
457
496
  inboundWire: "chat",
497
+ // A Chat caller may send an explicit empty `tools` array — a deny-all declaration the
498
+ // translation drops along with the whole catalog. Carried separately so the
499
+ // undeclared-tool guard can still enforce it; absent/partial catalogs stay deferred.
500
+ clientDeclaredEmptyToolCatalog: Array.isArray(chatBody.tools) && chatBody.tools.length === 0,
458
501
  ...((settledRoute?.combo || settledRoute?.routeKind === "policy") && droidDefaultEffort
459
502
  ? { droidDefaultEffort }
460
503
  : {}),
@@ -468,13 +511,35 @@ async function handleChatCompletionsWithBudget(
468
511
  // headers from the FORWARD_HEADERS allowlist, which would drop the raw
469
512
  // header — so the fact is detected here and carried as an option flag.
470
513
  ...(visionDescribeTerminal ? { visionDescribeTerminal: true } : {}),
514
+ ...(advisorInternal ? { advisorInternal: true } : {}),
471
515
  translatorBudget,
472
516
  ...(logIds ? { onFirstOutput: () => recordFirstOutput(logCtx, logIds.start) } : {}),
473
517
  onNativePassthroughTerminal: status => finalizeNativeLog(httpStatusForRequestLogTerminal(status, logCtx), { terminalStatus: status, closeReason: "terminal" }),
474
518
  onNativePassthroughCancel: () => finalizeNativeLog(499, { closeReason: "client_cancel" }),
475
- ...(directEncodersApply(config, settledRoute)
476
- ? { clientEncoder: { protocol: "chat" as const, stream, model: requestedModel } }
477
- : {}),
519
+ // The direct lane answers through the direct encoder whatever `directEncoders` says: the
520
+ // point of the lane is that neither direction passes through the internal Responses body.
521
+ ...(directRoute && directConversation
522
+ ? {
523
+ clientEncoder: { protocol: "chat" as const, stream, model: requestedModel, requestLane: "direct" as const },
524
+ directRequest: {
525
+ protocol: "chat" as const,
526
+ providerName: directRoute.providerName,
527
+ modelId: directRoute.modelId,
528
+ evidence: chatIrRouteEvidence(directConversation),
529
+ decode: base => withDirectChatConversation(base, chatBody, directConversation!),
530
+ bridgeConversation: () => {
531
+ const bridge = chatToResponsesBody(chatBody);
532
+ return {
533
+ input: bridge.input,
534
+ ...(bridge.instructions !== undefined ? { instructions: bridge.instructions } : {}),
535
+ ...(bridge.tools !== undefined ? { tools: bridge.tools } : {}),
536
+ };
537
+ },
538
+ },
539
+ }
540
+ : directEncodersApply(config, settledRoute)
541
+ ? { clientEncoder: { protocol: "chat" as const, stream, model: requestedModel } }
542
+ : {}),
478
543
  });
479
544
  // Already in the Chat wire: no conversion. A direct-encoder body (PF-09) reports its own log
480
545
  // facts to the deferred request log. A native combo child (PF-07) carries none: its row is
@@ -539,10 +604,17 @@ async function handleChatCompletionsWithBudget(
539
604
  // below could never restore this one -- the translated client was told the provider
540
605
  // throttled the turn, and handed a two-second wait to send it again.
541
606
  classified.code = UPSTREAM_RESET_REPLAY_REFUSED_CODE;
607
+ } else if (upstreamCode === "claude_subscription_cooldown") {
608
+ classified.code = "claude_subscription_cooldown";
609
+ classified.type = "rate_limit_error";
542
610
  } else if (upstreamCode === "model_not_found") {
543
611
  // Structured model_not_found must win over classifyError's generic remaps.
544
612
  classified.code = "model_not_found";
545
613
  classified.type = "invalid_request_error";
614
+ } else if (upstreamCode === UNDECLARED_TOOL_CALL_ERROR_CODE) {
615
+ // The guard's deterministic refusal keeps its wire identity — same code the
616
+ // streamed `response.failed` frame and the Anthropic surface carry.
617
+ classified.code = UNDECLARED_TOOL_CALL_ERROR_CODE;
546
618
  } else if (upstreamCode !== undefined && upstreamCode !== null && classified.code == null) {
547
619
  classified.code = upstreamCode;
548
620
  }
@@ -0,0 +1,123 @@
1
+ import type { OcxProviderConfig, OcxReasoningReplayScopeRef } from "../types";
2
+ import type { TranslatorBudget } from "../lib/translator-budget";
3
+ import { chatReasoningReplayPolicy, isChatRecord } from "../adapters/openai-chat/reasoning-replay";
4
+ import { bindReasoningReplayScope, reasoningReplayDestinationIdentity,
5
+ reasoningReplayKeyCredentialIdentity, rememberReasoningForCall } from "../responses/reasoning-replay-cache";
6
+ import { reasoningReplayConversationIdFromResponsesRequest, sessionIdHeaderFromRequest } from "./request-log-conversation";
7
+
8
+ type Rec = Record<string, unknown>;
9
+
10
+ export function nativeChatReplayScope(headers: Headers): OcxReasoningReplayScopeRef | undefined {
11
+ const clientThreadId = reasoningReplayConversationIdFromResponsesRequest({
12
+ threadIdHeader: headers.get("thread-id"), sessionIdHeader: sessionIdHeaderFromRequest(headers),
13
+ });
14
+ return clientThreadId ? { clientThreadId } : undefined;
15
+ }
16
+
17
+ /** Rebind before EVERY build: a key rotation must not retrieve another credential's reasoning. */
18
+ export function bindNativeChatReplayScope(
19
+ scope: OcxReasoningReplayScopeRef | undefined,
20
+ provider: OcxProviderConfig,
21
+ providerName: string,
22
+ modelId: string,
23
+ ): void {
24
+ const providerDestinationIdentity = reasoningReplayDestinationIdentity(provider.baseUrl);
25
+ const credentialIdentity = reasoningReplayKeyCredentialIdentity(provider);
26
+ bindReasoningReplayScope(scope, providerDestinationIdentity && credentialIdentity ? {
27
+ providerName, modelId, adapterName: "openai-chat",
28
+ providerDestinationIdentity, credentialIdentity,
29
+ } : undefined);
30
+ }
31
+
32
+ /** Observe the native wire, never translate it or retain tool arguments/content. */
33
+ export function nativeChatReasoningCapture(
34
+ provider: OcxProviderConfig,
35
+ modelId: string,
36
+ scope: OcxReasoningReplayScopeRef | undefined,
37
+ budget: TranslatorBudget,
38
+ ) {
39
+ const choices = new Map<number, { text: string; calls: Map<number, string> }>();
40
+ const accounting = { kind: "retained_collectors" as const };
41
+ // This optional fallback cannot retain more than the cache it feeds (256 KiB / 64 calls).
42
+ const maxBytes = 256 * 1024;
43
+ let bytes = 0;
44
+ let callCount = 0;
45
+ let disabled = !scope?.current || !chatReasoningReplayPolicy(provider, modelId).preserve;
46
+ const discard = () => {
47
+ budget.releaseRetained(bytes, accounting);
48
+ bytes = 0;
49
+ choices.clear();
50
+ disabled = true;
51
+ };
52
+ const remember = (text: string, calls: Iterable<string>) => {
53
+ for (const id of calls) rememberReasoningForCall(id, text, scope);
54
+ };
55
+ const observe = (value: Rec) => {
56
+ if (disabled || !Array.isArray(value.choices)) return;
57
+ for (const [ordinal, raw] of value.choices.entries()) {
58
+ if (!isChatRecord(raw) || !isChatRecord(raw.delta)) continue;
59
+ const index = typeof raw.index === "number" ? raw.index : ordinal;
60
+ if (!choices.has(index)) {
61
+ if (choices.size >= 64) { discard(); return; }
62
+ choices.set(index, { text: "", calls: new Map() });
63
+ }
64
+ const state = choices.get(index)!;
65
+ const delta = raw.delta;
66
+ const append = (previous: string, fragment: string): string | undefined => {
67
+ const added = Buffer.byteLength(fragment) * 2;
68
+ if (bytes + added > maxBytes) { discard(); return undefined; }
69
+ // Account the new retained fragment before concatenation; the transient reservation
70
+ // covers the old string while its replacement is allocated.
71
+ const reservation = budget.reserveTransient(Buffer.byteLength(previous) * 2, accounting);
72
+ try {
73
+ budget.chargeRetained(added, accounting);
74
+ bytes += added;
75
+ return previous + fragment;
76
+ } finally { reservation.release(); }
77
+ };
78
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
79
+ const text = append(state.text, delta.reasoning_content);
80
+ if (text === undefined) return;
81
+ state.text = text;
82
+ }
83
+ if (Array.isArray(delta.tool_calls)) {
84
+ for (const [toolOrdinal, tool] of delta.tool_calls.entries()) {
85
+ if (!isChatRecord(tool) || typeof tool.id !== "string" || tool.id.length === 0) continue;
86
+ const toolIndex = typeof tool.index === "number" ? tool.index : toolOrdinal;
87
+ if (!state.calls.has(toolIndex)) {
88
+ if (callCount >= 64) { discard(); return; }
89
+ callCount += 1;
90
+ }
91
+ const previous = state.calls.get(toolIndex) ?? "";
92
+ if (previous === tool.id) continue;
93
+ const id = append(previous, tool.id);
94
+ if (id === undefined) return;
95
+ state.calls.set(toolIndex, id);
96
+ }
97
+ }
98
+ // The completed choice is valid even if a caller dispatches its tool before [DONE].
99
+ if (typeof raw.finish_reason === "string" && raw.finish_reason.length > 0) {
100
+ remember(state.text, state.calls.values());
101
+ }
102
+ }
103
+ };
104
+ return {
105
+ observe,
106
+ finish(success: boolean) {
107
+ if (success && !disabled) {
108
+ for (const state of choices.values()) remember(state.text, state.calls.values());
109
+ }
110
+ discard();
111
+ },
112
+ json(value: Rec) {
113
+ if (disabled || !Array.isArray(value.choices)) return;
114
+ for (const choice of value.choices) {
115
+ if (!isChatRecord(choice) || !isChatRecord(choice.message)) continue;
116
+ const message = choice.message;
117
+ if (typeof message.reasoning_content !== "string" || !Array.isArray(message.tool_calls)) continue;
118
+ remember(message.reasoning_content, message.tool_calls.filter(isChatRecord)
119
+ .flatMap(tool => typeof tool.id === "string" ? [tool.id] : []));
120
+ }
121
+ },
122
+ };
123
+ }
@@ -1,5 +1,5 @@
1
1
  import { chatCompletionsErrorBody } from "../chat/outbound";
2
- import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode } from "../lib/errors";
2
+ import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, UNDECLARED_TOOL_CALL_ERROR_CODE } from "../lib/errors";
3
3
  import { redactSecretString } from "../lib/redact";
4
4
  import {
5
5
  isTranslatorBudgetExceededError,
@@ -16,6 +16,39 @@ function isRec(value: unknown): value is Rec {
16
16
  return value !== null && typeof value === "object" && !Array.isArray(value);
17
17
  }
18
18
 
19
+ export const UNDECLARED_CHAT_TOOL_MESSAGE = "Upstream emitted a client tool call outside this request's declared tool catalog.";
20
+
21
+ function* chatToolCalls(value: Rec): Generator<{ key: string; name: string; snapshot: boolean; invalid?: boolean }> {
22
+ const choices = Array.isArray(value.choices) ? value.choices : [];
23
+ for (const [position, choice] of choices.entries()) {
24
+ if (!isRec(choice)) continue;
25
+ const choiceIndex = typeof choice.index === "number" ? choice.index : position;
26
+ for (const [payload, snapshot] of [[choice.delta, false], [choice.message, true]] as const) {
27
+ if (!isRec(payload)) continue;
28
+ const tools = Array.isArray(payload.tool_calls) ? payload.tool_calls : [];
29
+ for (const [index, tool] of tools.entries()) {
30
+ const fn = isRec(tool) && isRec(tool.function) ? tool.function : {};
31
+ const toolIndex = isRec(tool) && typeof tool.index === "number" ? tool.index : index;
32
+ yield {
33
+ key: `${choiceIndex}:tool:${toolIndex}`, name: typeof fn.name === "string" ? fn.name : "", snapshot,
34
+ invalid: !isRec(tool) || (tool.type !== undefined && tool.type !== "function"),
35
+ };
36
+ }
37
+ if (payload.function_call !== undefined && payload.function_call !== null) {
38
+ const fn = isRec(payload.function_call) ? payload.function_call : {};
39
+ yield { key: `${choiceIndex}:legacy`, name: typeof fn.name === "string" ? fn.name : "", snapshot };
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ export function chatResponseHasUndeclaredToolCalls(value: Rec, declared: ReadonlySet<string>): boolean {
46
+ for (const call of chatToolCalls(value)) {
47
+ if (call.invalid || !declared.has(call.name)) return true;
48
+ }
49
+ return false;
50
+ }
51
+
19
52
  export function usageFromChat(value: unknown): OcxUsage | undefined {
20
53
  if (!isRec(value)) return undefined;
21
54
  const prompt = typeof value.prompt_tokens === "number" ? value.prompt_tokens : undefined;
@@ -134,8 +167,11 @@ interface NativeChatSseOptions {
134
167
  * `stallTimeoutSec` then resolves to disabled so a slow local model is not cut mid-turn.
135
168
  */
136
169
  localUpstream?: boolean;
170
+ clientDeclaredEmptyToolCatalog?: boolean;
171
+ declaredLegacyFunctionNames?: ReadonlySet<string>;
137
172
  onFirstOutput?: () => void;
138
173
  onUsage: (usage: OcxUsage) => void;
174
+ onChunk?: (value: Rec) => void;
139
175
  onTerminal?: (status: number, message?: string) => void;
140
176
  onCancel?: () => void;
141
177
  }
@@ -154,6 +190,29 @@ export function nativeChatSse(
154
190
  }
155
191
  });
156
192
  let queuedBytes = 0;
193
+ const declared = options.declaredLegacyFunctionNames ?? new Set<string>();
194
+ const toolNames = new Map<string, string>();
195
+ const pendingToolNames = new Set<string>();
196
+ const heldFrames: string[] = [];
197
+ let heldBytes = 0;
198
+ let toolNameBytes = 0;
199
+ const hasPendingToolName = () => pendingToolNames.size > 0;
200
+ const inspectToolCalls = (value: Rec): boolean => {
201
+ for (const call of chatToolCalls(value)) {
202
+ if (call.invalid) return false;
203
+ const previous = toolNames.get(call.key);
204
+ const name = call.snapshot ? call.name : (previous ?? "") + call.name;
205
+ if (!declared.has(name) && ![...declared].some(allowed => allowed.startsWith(name))) return false;
206
+ const bytes = previous === undefined ? 128 + Buffer.byteLength(call.key) * 2 + Buffer.byteLength(name) * 2
207
+ : Math.max(0, Buffer.byteLength(name) - Buffer.byteLength(previous)) * 2;
208
+ options.translatorBudget.chargeRetained(bytes, scope);
209
+ toolNameBytes += bytes;
210
+ toolNames.set(call.key, name);
211
+ if (declared.has(name)) pendingToolNames.delete(call.key);
212
+ else pendingToolNames.add(call.key);
213
+ }
214
+ return true;
215
+ };
157
216
  let sawFinish = false;
158
217
  let sawDone = false;
159
218
  let firstOutput = false;
@@ -191,7 +250,31 @@ export function nativeChatSse(
191
250
  throw error;
192
251
  }
193
252
  };
194
- const releaseBuffer = () => buffer.clear();
253
+ const releaseHeld = () => {
254
+ options.translatorBudget.releaseRetained(heldBytes, scope);
255
+ heldBytes = 0;
256
+ heldFrames.length = 0;
257
+ };
258
+ const relay = (controller: ReadableStreamDefaultController<Uint8Array>, text: string) => {
259
+ if (hasPendingToolName()) {
260
+ const bytes = Buffer.byteLength(text) * 2;
261
+ options.translatorBudget.chargeRetained(bytes, scope);
262
+ heldBytes += bytes;
263
+ heldFrames.push(text);
264
+ return;
265
+ }
266
+ for (const frame of heldFrames) enqueue(controller, frame);
267
+ releaseHeld();
268
+ enqueue(controller, text);
269
+ };
270
+ const releaseBuffer = () => {
271
+ buffer.clear();
272
+ releaseHeld();
273
+ options.translatorBudget.releaseRetained(toolNameBytes, scope);
274
+ toolNameBytes = 0;
275
+ toolNames.clear();
276
+ pendingToolNames.clear();
277
+ };
195
278
  const settle = (status: number, message?: string) => {
196
279
  if (settled) return;
197
280
  settled = true;
@@ -206,12 +289,12 @@ export function nativeChatSse(
206
289
  type = "upstream_error",
207
290
  ) => {
208
291
  const safeMessage = redactSecretString(message);
292
+ releaseBuffer();
209
293
  try {
210
294
  const frame = `data: ${JSON.stringify(chatCompletionsErrorBody(status, safeMessage, type, code))}\n\n`;
211
295
  if (code === "translation_buffer_limit") controller.enqueue(encoder.encode(frame));
212
296
  else enqueue(controller, frame);
213
297
  } catch { /* controller may already be closed */ }
214
- releaseBuffer();
215
298
  settle(status, safeMessage);
216
299
  try { void reader.cancel(new Error(safeMessage)).catch(() => {}); } catch { /* already closed */ }
217
300
  try { controller.close(); } catch { /* already closed */ }
@@ -224,16 +307,20 @@ export function nativeChatSse(
224
307
  const payload = sseDataPayload(block);
225
308
  if (payload === null) {
226
309
  if (performance.now() >= pullDeadline) throw stalled;
227
- enqueue(controller, block + delimiter);
310
+ relay(controller, block + delimiter);
228
311
  return;
229
312
  }
230
313
  const trimmed = payload.trim();
231
314
  if (trimmed === "") {
232
315
  if (performance.now() >= pullDeadline) throw stalled;
233
- enqueue(controller, block + delimiter);
316
+ relay(controller, block + delimiter);
234
317
  return;
235
318
  }
236
319
  if (trimmed === "[DONE]") {
320
+ if (hasPendingToolName()) {
321
+ fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
322
+ return;
323
+ }
237
324
  sawDone = true;
238
325
  enqueue(controller, replaceSseDataPayload(block, "[DONE]") + delimiter);
239
326
  releaseBuffer();
@@ -272,9 +359,20 @@ export function nativeChatSse(
272
359
  return;
273
360
  }
274
361
  const usage = usageFromChat(parsed.usage);
362
+ options.onChunk?.(parsed);
275
363
  if (usage) options.onUsage(usage);
364
+ if (options.clientDeclaredEmptyToolCatalog && !inspectToolCalls(parsed)) {
365
+ fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
366
+ return;
367
+ }
276
368
  const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
277
369
  const finished = choices.some(choice => isRec(choice) && typeof choice.finish_reason === "string" && choice.finish_reason.length > 0);
370
+ if (hasPendingToolName() && choices.some((choice, position) => isRec(choice)
371
+ && typeof choice.finish_reason === "string" && choice.finish_reason.length > 0
372
+ && [...pendingToolNames].some(key => key.startsWith(`${typeof choice.index === "number" ? choice.index : position}:`)))) {
373
+ fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
374
+ return;
375
+ }
278
376
  if (finished) sawFinish = true;
279
377
  const hasText = choices.some(choice => {
280
378
  if (!isRec(choice) || !isRec(choice.delta)) return false;
@@ -302,11 +400,11 @@ export function nativeChatSse(
302
400
  const hasProgress = hasText || hasTool;
303
401
  if (hasProgress || finished) pullDeadline = performance.now() + stallMs;
304
402
  else if (performance.now() >= pullDeadline) throw stalled;
305
- if (!firstOutput && hasProgress) {
403
+ if (!firstOutput && hasProgress && !hasPendingToolName()) {
306
404
  firstOutput = true;
307
405
  options.onFirstOutput?.();
308
406
  }
309
- enqueue(controller, replaceSseDataPayload(block, JSON.stringify(normalizedChunk(parsed, options.requestedModel))) + delimiter);
407
+ relay(controller, replaceSseDataPayload(block, JSON.stringify(normalizedChunk(parsed, options.requestedModel))) + delimiter);
310
408
  };
311
409
 
312
410
  function onAbort() {
@@ -336,6 +434,7 @@ export function nativeChatSse(
336
434
  if (next) {
337
435
  buffer.compact();
338
436
  processBlock(controller, next.block, next.delimiter);
437
+ if (heldFrames.length > 0 && !settled) continue;
339
438
  return;
340
439
  }
341
440
  if (performance.now() >= pullDeadline) throw stalled;
@@ -370,6 +469,10 @@ export function nativeChatSse(
370
469
  continue;
371
470
  }
372
471
  buffer.append(decoder.decode());
472
+ if (hasPendingToolName()) {
473
+ fail(controller, UNDECLARED_CHAT_TOOL_MESSAGE, UNDECLARED_TOOL_CALL_ERROR_CODE);
474
+ return;
475
+ }
373
476
  if (buffer.tail().trim().length > 0) {
374
477
  fail(controller, "upstream SSE ended with an unterminated event", "upstream_sse_unterminated");
375
478
  return;