@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,500 @@
1
+ /**
2
+ * Anthropic Messages -> IR, directly (the Antigravity direct lane).
3
+ *
4
+ * The bridge decodes a Messages conversation twice: `anthropicToResponsesTranslation`
5
+ * (`src/claude/inbound.ts`) projects it into Responses items, then `parseRequest`
6
+ * (`src/responses/parser.ts`) turns those items into the IR. This module walks the Messages body
7
+ * once and builds the IR conversation itself: the system prompt (including the
8
+ * `stabilizePromptCache` relocation), user turns with images and documents, tool calls and
9
+ * results (including `tool_reference` markers, tool errors and blocked-skill elision), thinking
10
+ * and redacted thinking with their ocxr1 envelopes, the native reasoning replay map, and the
11
+ * tool catalog. It is the Messages twin of `chat-ir.ts`.
12
+ *
13
+ * Contract: for every Messages body the bridge accepts, `messagesConversationToIr(body, cc,
14
+ * budget, model)` equals the `context` of `parseRequest(anthropicToResponsesTranslation(body,
15
+ * cc).body)` (reasoning item ids are random on both sides), and every body the bridge refuses in
16
+ * its conversation walk is refused here with the same `AnthropicRequestError` message.
17
+ * `tests/responses/protocol-messages-ir-parity.test.ts` holds both sides over a fixture corpus.
18
+ * Envelopes are encoded and charged against the request budget exactly as the translator does,
19
+ * then decoded exactly as the parser does, so the signature an adapter reads is the same string.
20
+ *
21
+ * Request settings (model resolution, sampling, tool choice, format, prompt-cache key, reasoning)
22
+ * are not decoded here: they travel through the Responses core on `anthropicSettingsTranslation`.
23
+ * A hosted web-search tool never reaches this decoder: the lane's eligibility rule keeps it on the
24
+ * bridge, where the sidecar reads it.
25
+ */
26
+ import {
27
+ anthropicDocumentData,
28
+ anthropicImageBlockUrl,
29
+ anthropicSystemLayout,
30
+ blockedSkillCallIds,
31
+ documentTitle,
32
+ effectiveBlockedSkillNames,
33
+ maybeElideSkillText,
34
+ skillElisionStub,
35
+ systemMessageText,
36
+ } from "../../claude/inbound";
37
+ import { toolsToResponses } from "../../claude/inbound-content-options";
38
+ import { AnthropicRequestError, isRec, type Rec } from "../../claude/inbound-records";
39
+ import { hasRoutedIdentity, nameRoutedIdentity } from "../../adapters/identity";
40
+ import type { TranslatorBudget } from "../../lib/translator-budget";
41
+ import { inlineDocumentFromDataUrl, inlineDocumentMarker } from "../../responses/inline-document";
42
+ import { functionCallArgumentsFromText } from "../../responses/parser";
43
+ import { decodeReasoningEnvelope, encodeReasoningEnvelope, OCX_REASONING_PREFIX } from "../../responses/reasoning-envelope";
44
+ import { normalizeVisualizationContext } from "../../responses/visualization-directives";
45
+ import type { PolicyRequestEvidence } from "../../routing/evaluator";
46
+ import { createToolChoiceResolver, namespacedToolName } from "../../types";
47
+ import type {
48
+ OcxAssistantMessage,
49
+ OcxClaudeCodeConfig,
50
+ OcxContentPart,
51
+ OcxContext,
52
+ OcxMessage,
53
+ OcxParsedRequest,
54
+ OcxTextContent,
55
+ OcxThinkingContent,
56
+ OcxTool,
57
+ } from "../../types";
58
+ import { normalizedParameters } from "./chat-ir";
59
+
60
+ /** The translator refuses an undecodable proxy envelope with this message; the ingress retries it. */
61
+ export const MALFORMED_REASONING_SIGNATURE = "malformed ocxr1 reasoning signature";
62
+
63
+ function nameDestination(text: string, modelId: string): string {
64
+ return hasRoutedIdentity(text) ? nameRoutedIdentity(text, modelId) : text;
65
+ }
66
+
67
+ function reasoningItemId(): string {
68
+ return `rs_${crypto.randomUUID().replace(/-/g, "")}`;
69
+ }
70
+
71
+ /** The parser collapses a lone text part to a plain string; images and documents stay structured. */
72
+ function collapsed(parts: OcxContentPart[]): string | OcxContentPart[] {
73
+ return parts.length === 1 && parts[0]!.type === "text" ? parts[0]!.text : parts;
74
+ }
75
+
76
+ /** One entry of a tool result as the translator lists it, before the parser reads it. */
77
+ type ToolResultEntry = { text: string } | { image: string };
78
+
79
+ /**
80
+ * A `tool_result` block's content as the IR carries it: the translator's `toolResultOutput`
81
+ * (error prefix, `tool_reference` markers, image references, document markers) read through the
82
+ * parser's `outputToToolResultContent` (text joined unless an image survives).
83
+ */
84
+ function toolResultContent(block: Rec): string | OcxContentPart[] {
85
+ const isError = block.is_error === true;
86
+ const content = block.content;
87
+ if (typeof content === "string") return isError ? `[tool error] ${content}` : content;
88
+ if (!Array.isArray(content)) return isError ? "[tool error]" : "";
89
+ const out: ToolResultEntry[] = [];
90
+ for (const item of content) {
91
+ if (!isRec(item)) continue;
92
+ if (item.type === "text" && typeof item.text === "string") {
93
+ out.push({ text: item.text });
94
+ } else if (item.type === "tool_reference" && typeof item.tool_name === "string" && item.tool_name.length > 0) {
95
+ const prev = out[out.length - 1];
96
+ const lead = prev !== undefined && "text" in prev && !prev.text.endsWith("\n") ? "\n" : "";
97
+ out.push({ text: `${lead}Tool loaded: ${item.tool_name}\n` });
98
+ } else if (item.type === "image") {
99
+ const url = anthropicImageBlockUrl(item);
100
+ if (url !== null) out.push({ image: url });
101
+ } else if (item.type === "document") {
102
+ out.push({ text: inlineDocumentMarker(documentTitle(item)) });
103
+ }
104
+ }
105
+ if (isError) out.unshift({ text: "[tool error]" });
106
+ if (out.length === 0) return isError ? "[tool error]" : "";
107
+ const parts: OcxContentPart[] = [];
108
+ let hasImage = false;
109
+ for (const entry of out) {
110
+ if ("text" in entry) parts.push({ type: "text", text: entry.text });
111
+ else if (entry.image.length > 0) {
112
+ parts.push({ type: "image", imageUrl: entry.image });
113
+ hasImage = true;
114
+ }
115
+ }
116
+ if (!hasImage) return parts.map(part => (part.type === "text" ? part.text : "")).join("");
117
+ return parts;
118
+ }
119
+
120
+ /**
121
+ * The tool catalog: the translator's function declarations (a hosted web-search tool and
122
+ * schema-less server tools are not function tools) read through the parser's `buildTools` and
123
+ * deduplicated by name as its catalog merge does.
124
+ */
125
+ function toolCatalog(raw: unknown): OcxTool[] {
126
+ if (!Array.isArray(raw)) return [];
127
+ const declared: OcxTool[] = [];
128
+ for (const entry of raw) {
129
+ if (!isRec(entry)) continue;
130
+ const type = typeof entry.type === "string" ? entry.type : "";
131
+ if (type.startsWith("web_search")) continue;
132
+ if (typeof entry.name !== "string" || entry.name.length === 0 || !isRec(entry.input_schema)) continue;
133
+ const tool: OcxTool = {
134
+ name: entry.name,
135
+ description: typeof entry.description === "string" ? entry.description : "",
136
+ parameters: normalizedParameters(entry.input_schema),
137
+ strict: typeof entry.strict === "boolean" ? entry.strict : false,
138
+ };
139
+ if (Array.isArray(entry.allowed_callers)) {
140
+ tool.allowedCallers = entry.allowed_callers.filter((caller): caller is string => typeof caller === "string");
141
+ }
142
+ declared.push(tool);
143
+ }
144
+ const seen = new Set<string>();
145
+ return declared.filter(tool => {
146
+ const key = namespacedToolName(tool.namespace, tool.name);
147
+ if (seen.has(key)) return false;
148
+ seen.add(key);
149
+ return true;
150
+ });
151
+ }
152
+
153
+ export interface MessagesIrConversation {
154
+ /** The resolved model the conversation was decoded for; identity naming and placeholders use it. */
155
+ modelId: string;
156
+ context: OcxContext;
157
+ /** The native reasoning blobs the translator would have recorded, keyed by blob. */
158
+ nativeReasoningReplay: ReadonlyMap<string, string>;
159
+ }
160
+
161
+ /**
162
+ * Decode a Messages body's conversation straight into the IR context. `modelId` is the resolved
163
+ * model the bridge body would carry. Throws `AnthropicRequestError` exactly where the translator's
164
+ * conversation walk would, and `TranslatorBudgetExceededError` where its envelope charges would.
165
+ */
166
+ export function messagesConversationToIr(
167
+ raw: unknown,
168
+ cc: OcxClaudeCodeConfig | undefined,
169
+ budget: TranslatorBudget,
170
+ modelId: string,
171
+ ): MessagesIrConversation {
172
+ if (!isRec(raw)) throw new AnthropicRequestError("request body must be a JSON object");
173
+ if (typeof raw.model !== "string" || raw.model.length === 0) throw new AnthropicRequestError("model is required");
174
+ if (!Array.isArray(raw.messages) || raw.messages.length === 0) {
175
+ throw new AnthropicRequestError("messages must be a non-empty array");
176
+ }
177
+ const requestedModel = raw.model;
178
+ const now = Date.now();
179
+ const messages: OcxMessage[] = [];
180
+ const nativeReasoningReplay = new Map<string, string>();
181
+ const blockedNames = effectiveBlockedSkillNames(cc);
182
+ const blockedCalls = blockedSkillCallIds(raw.messages, blockedNames);
183
+ // The parser's pending reasoning: it belongs to the next assistant turn, and turn boundaries
184
+ // clear it after keeping any signed or redacted replay.
185
+ const pending: Array<{ part: OcxThinkingContent; envelopeSigned: boolean }> = [];
186
+
187
+ const placeholder = (): OcxAssistantMessage => {
188
+ const last = messages[messages.length - 1];
189
+ if (last && last.role === "assistant") return last;
190
+ const holder: OcxAssistantMessage = { role: "assistant", content: [], model: modelId, timestamp: now };
191
+ messages.push(holder);
192
+ return holder;
193
+ };
194
+ const holderWithReasoning = (): OcxAssistantMessage => {
195
+ const holder = placeholder();
196
+ if (pending.length > 0) {
197
+ holder.content.push(...pending.map(entry => entry.part));
198
+ pending.length = 0;
199
+ }
200
+ return holder;
201
+ };
202
+ const preservePendingReplay = (): void => {
203
+ const replay = pending.filter(entry => entry.envelopeSigned || entry.part.redacted?.length);
204
+ if (replay.length > 0) placeholder().content.push(...replay.map(entry => entry.part));
205
+ };
206
+ const pushUser = (content: string | OcxContentPart[]): void => {
207
+ preservePendingReplay();
208
+ pending.length = 0;
209
+ messages.push({ role: "user", content, timestamp: now });
210
+ };
211
+ const pushDeveloper = (text: string): void => {
212
+ preservePendingReplay();
213
+ pending.length = 0;
214
+ messages.push({ role: "developer", content: nameDestination(text, modelId), timestamp: now });
215
+ };
216
+ const pushAssistantText = (parts: OcxTextContent[]): void => {
217
+ messages.push({
218
+ role: "assistant",
219
+ content: pending.length > 0 ? [...pending.map(entry => entry.part), ...parts] : parts,
220
+ model: modelId,
221
+ timestamp: now,
222
+ });
223
+ pending.length = 0;
224
+ };
225
+ const pushToolCall = (id: string, name: string, input: unknown): void => {
226
+ holderWithReasoning().content.push({
227
+ type: "toolCall",
228
+ id,
229
+ name,
230
+ arguments: functionCallArgumentsFromText(id, JSON.stringify(input ?? {})),
231
+ });
232
+ };
233
+ const pushToolResult = (callId: string, content: string | OcxContentPart[]): void => {
234
+ // Reasoning replayed after the call it belongs to joins that call's assistant turn (#950).
235
+ if (pending.length > 0) {
236
+ outer: for (let i = messages.length - 1; i >= 0; i--) {
237
+ const message = messages[i]!;
238
+ if (message.role !== "assistant") continue;
239
+ for (const part of message.content) {
240
+ if (part.type === "toolCall" && part.id === callId) {
241
+ message.content = [...pending.map(entry => entry.part), ...message.content];
242
+ break outer;
243
+ }
244
+ }
245
+ }
246
+ }
247
+ pending.length = 0;
248
+ let owner: { name: string; namespace?: string } = { name: "" };
249
+ search: for (let i = messages.length - 1; i >= 0; i--) {
250
+ const message = messages[i]!;
251
+ if (message.role !== "assistant") continue;
252
+ for (const part of message.content) {
253
+ if (part.type === "toolCall" && part.id === callId) {
254
+ owner = { name: part.name, namespace: part.namespace };
255
+ break search;
256
+ }
257
+ }
258
+ }
259
+ messages.push({
260
+ role: "toolResult", toolCallId: callId, toolName: owner.name, toolNamespace: owner.namespace,
261
+ content, isError: false, timestamp: now,
262
+ });
263
+ };
264
+ /**
265
+ * One translator reasoning item, read as the parser reads it. `encrypted` is the item's
266
+ * `encrypted_content`; the placeholder signature is the item's serialized form, spelled in the
267
+ * schema's key order.
268
+ */
269
+ const pushReasoning = (id: string, summaryText: string, encrypted: string | undefined): void => {
270
+ const item: Rec = {
271
+ type: "reasoning",
272
+ id,
273
+ summary: summaryText.length > 0 ? [{ type: "summary_text", text: summaryText }] : [],
274
+ ...(encrypted !== undefined ? { encrypted_content: encrypted } : {}),
275
+ };
276
+ const envelope = encrypted !== undefined ? decodeReasoningEnvelope(encrypted) : null;
277
+ const thinkingText = envelope?.txt ?? summaryText;
278
+ if (envelope?.krc && thinkingText.length === 0) {
279
+ const previous = messages[messages.length - 1];
280
+ if (previous?.role === "assistant") previous.kiroRedactedReasoning = envelope.krc;
281
+ return;
282
+ }
283
+ if (!(thinkingText.length > 0 || envelope?.sig || envelope?.red?.length)) return;
284
+ const part: OcxThinkingContent = {
285
+ type: "thinking",
286
+ thinking: thinkingText,
287
+ signature: envelope?.sig ?? JSON.stringify(item),
288
+ ...(envelope?.red ? { redacted: envelope.red } : {}),
289
+ itemId: id,
290
+ };
291
+ const envelopeSigned = typeof envelope?.sig === "string";
292
+ const previous = pending[pending.length - 1];
293
+ if (!envelopeSigned && !part.redacted && previous && !previous.envelopeSigned && !previous.part.redacted) {
294
+ previous.part = { ...part, thinking: `${previous.part.thinking}\n${part.thinking}` };
295
+ } else {
296
+ pending.push({ part, envelopeSigned });
297
+ }
298
+ };
299
+
300
+ const userContent = (content: unknown): void => {
301
+ if (typeof content === "string") {
302
+ if (content.length > 0) pushUser(content);
303
+ return;
304
+ }
305
+ if (!Array.isArray(content)) return;
306
+ // Contiguous text, image and document blocks form one user turn; a tool_result ends it.
307
+ let parts: OcxContentPart[] = [];
308
+ let blocks = 0;
309
+ const flush = (): void => {
310
+ if (blocks > 0) pushUser(collapsed(parts));
311
+ parts = [];
312
+ blocks = 0;
313
+ };
314
+ for (const block of content) {
315
+ if (!isRec(block)) continue;
316
+ switch (block.type) {
317
+ case "text":
318
+ if (typeof block.text === "string") {
319
+ blocks++;
320
+ parts.push({ type: "text", text: maybeElideSkillText(block.text, blockedNames) });
321
+ }
322
+ break;
323
+ case "image": {
324
+ const url = anthropicImageBlockUrl(block);
325
+ if (url !== null) {
326
+ blocks++;
327
+ // An empty reference is a block the parser drops.
328
+ if (url.length > 0) parts.push({ type: "image", imageUrl: url });
329
+ }
330
+ break;
331
+ }
332
+ case "tool_result": {
333
+ flush();
334
+ if (typeof block.tool_use_id !== "string" || block.tool_use_id.length === 0) {
335
+ throw new AnthropicRequestError("tool_result requires tool_use_id");
336
+ }
337
+ pushToolResult(
338
+ block.tool_use_id,
339
+ blockedCalls.has(block.tool_use_id) ? skillElisionStub(block.tool_use_id) : toolResultContent(block),
340
+ );
341
+ break;
342
+ }
343
+ case "document": {
344
+ blocks++;
345
+ const document = anthropicDocumentData(block);
346
+ parts.push(document
347
+ ? inlineDocumentFromDataUrl(document.fileData, document.title)
348
+ ?? { type: "text", text: document.title ? `[file: ${document.title}]` : "[file: inline data]" }
349
+ : { type: "text", text: inlineDocumentMarker(documentTitle(block)) });
350
+ break;
351
+ }
352
+ default:
353
+ break;
354
+ }
355
+ }
356
+ flush();
357
+ };
358
+
359
+ const assistantContent = (content: unknown): void => {
360
+ if (typeof content === "string") {
361
+ if (content.length > 0) pushAssistantText([{ type: "text", text: content }]);
362
+ return;
363
+ }
364
+ if (!Array.isArray(content)) return;
365
+ let text: OcxTextContent[] = [];
366
+ const flush = (): void => {
367
+ if (text.length > 0) pushAssistantText(text);
368
+ text = [];
369
+ };
370
+ for (const block of content) {
371
+ if (!isRec(block)) continue;
372
+ switch (block.type) {
373
+ case "text":
374
+ if (typeof block.text === "string") text.push({ type: "text", text: block.text });
375
+ break;
376
+ case "tool_use":
377
+ flush();
378
+ if (typeof block.id !== "string" || block.id.length === 0 || typeof block.name !== "string" || block.name.length === 0) {
379
+ throw new AnthropicRequestError("tool_use requires id and name");
380
+ }
381
+ pushToolCall(block.id, block.name, block.input);
382
+ break;
383
+ case "thinking": {
384
+ flush();
385
+ const thinking = typeof block.thinking === "string" ? block.thinking : "";
386
+ const signature = typeof block.signature === "string" ? block.signature : "";
387
+ if (signature.startsWith(OCX_REASONING_PREFIX)) {
388
+ const owned = decodeReasoningEnvelope(signature, budget);
389
+ if (!owned) throw new AnthropicRequestError(MALFORMED_REASONING_SIGNATURE);
390
+ if (Object.hasOwn(owned, "sig")) {
391
+ throw new AnthropicRequestError("OpenCodex reasoning continuity cannot be replayed as an Anthropic signature");
392
+ }
393
+ if (owned.nat) {
394
+ // The provider's own blob goes back only to the model that minted it.
395
+ const nat = owned.nat.model === requestedModel ? owned.nat : undefined;
396
+ if (nat) {
397
+ const previous = nativeReasoningReplay.get(nat.enc);
398
+ nativeReasoningReplay.set(nat.enc, previous === undefined || previous === nat.tag ? nat.tag : "");
399
+ budget.chargeRetained(2 * nat.enc.length, { kind: "reasoning" });
400
+ }
401
+ if (thinking.length === 0 && !nat) break;
402
+ pushReasoning(nat?.id ?? reasoningItemId(), thinking, nat?.enc);
403
+ break;
404
+ }
405
+ }
406
+ const encrypted = signature.length === 0
407
+ ? undefined
408
+ : signature.startsWith(OCX_REASONING_PREFIX) ? signature : encodeReasoningEnvelope({ sig: signature }, budget);
409
+ if (encrypted) budget.chargeRetained(2 * encrypted.length, { kind: "reasoning" });
410
+ if (thinking.length === 0 && !encrypted) break;
411
+ pushReasoning(reasoningItemId(), thinking, encrypted);
412
+ break;
413
+ }
414
+ case "redacted_thinking": {
415
+ flush();
416
+ const data = typeof block.data === "string" ? block.data : "";
417
+ if (data.length > 0) {
418
+ const encrypted = encodeReasoningEnvelope({ red: [data] }, budget);
419
+ budget.chargeRetained(2 * encrypted.length, { kind: "reasoning" });
420
+ pushReasoning(reasoningItemId(), "", encrypted);
421
+ }
422
+ break;
423
+ }
424
+ default:
425
+ break;
426
+ }
427
+ }
428
+ flush();
429
+ };
430
+
431
+ for (const message of raw.messages) {
432
+ if (!isRec(message)) throw new AnthropicRequestError("each message must be an object");
433
+ if (message.role === "user") userContent(message.content);
434
+ else if (message.role === "assistant") assistantContent(message.content);
435
+ else if (message.role === "system") {
436
+ const text = systemMessageText(message.content);
437
+ if (text.length > 0) pushDeveloper(text);
438
+ } else throw new AnthropicRequestError(`unsupported message role: ${String(message.role)}`);
439
+ }
440
+
441
+ const system = anthropicSystemLayout(raw.system, cc);
442
+ if (system.dynamicNotice !== undefined) pushUser(system.dynamicNotice);
443
+ preservePendingReplay();
444
+
445
+ const tools = toolCatalog(raw.tools);
446
+ const context: OcxContext = {
447
+ ...(system.instructions ? { systemPrompt: [nameDestination(system.instructions, modelId)] } : {}),
448
+ messages,
449
+ ...(tools.length > 0 ? { tools } : {}),
450
+ };
451
+ return { modelId, context: normalizeVisualizationContext(context), nativeReasoningReplay };
452
+ }
453
+
454
+ /**
455
+ * Put a directly decoded conversation onto the request the core parsed from the settings-only
456
+ * body, re-decoding if the core settled a different model id than the one decoded for. The tool
457
+ * choice is checked against the decoded catalog, which the settings-only parse could not see.
458
+ */
459
+ export function withDirectMessagesConversation(
460
+ base: OcxParsedRequest,
461
+ decoded: MessagesIrConversation,
462
+ decodeFor: (modelId: string) => MessagesIrConversation,
463
+ ): OcxParsedRequest {
464
+ const conversation = decoded.modelId === base.modelId ? decoded : decodeFor(base.modelId);
465
+ const choice = base.options.toolChoice;
466
+ if (choice && typeof choice === "object") {
467
+ const selectors = "allowedTools" in choice ? choice.allowedTools : [choice.name];
468
+ const resolver = createToolChoiceResolver(conversation.context.tools ?? []);
469
+ for (const selector of selectors) {
470
+ if (resolver.candidateCount(selector) > 1) throw new Error(`ambiguous tool_choice name: ${selector}`);
471
+ }
472
+ }
473
+ return { ...base, context: conversation.context };
474
+ }
475
+
476
+ /**
477
+ * The policy-routing evidence the core reads from the bridge body (`evidenceFromBody`): a
478
+ * non-empty translated tool catalog, and an image block in a user turn. Images inside a
479
+ * tool_result sit under `output` on the bridge body, which that scan does not read.
480
+ */
481
+ export function messagesRouteEvidence(raw: unknown): PolicyRequestEvidence {
482
+ if (!isRec(raw)) return {};
483
+ const tools = toolsToResponses(raw.tools) !== undefined;
484
+ const image = Array.isArray(raw.messages) && raw.messages.some(message => isRec(message)
485
+ && message.role === "user" && Array.isArray(message.content)
486
+ && message.content.some(block => isRec(block) && block.type === "image" && imageReference(block)));
487
+ return {
488
+ ...(tools ? { toolsRequired: true } : {}),
489
+ ...(image ? { imageInputRequired: true } : {}),
490
+ };
491
+ }
492
+
493
+ function imageReference(block: Rec): boolean {
494
+ try {
495
+ return anthropicImageBlockUrl(block) !== null;
496
+ } catch {
497
+ return false;
498
+ }
499
+ }
500
+
@@ -152,6 +152,12 @@ export function featureEffectsForPath(
152
152
  present: Iterable<ProtocolFeature>,
153
153
  ): PathFeatureEffects {
154
154
  const nodes = protocolNodes(path);
155
+ // A Chat or Messages body decoded straight into the IR (the direct lane, `[inbound, ir, other]`)
156
+ // keeps exactly what its Responses projection keeps: the direct codecs are held to
157
+ // `parseRequest(projection)` by parity tests. The decode hop therefore reads the
158
+ // `<inbound>>responses` row, and the hop from the IR into `other` stays unknown as before.
159
+ const directIrDecode = inbound !== "responses" && path.length === 3
160
+ && path[0] === inbound && path[1] === "ir" && path[2] === "other";
155
161
  const effects: FeatureEffect[] = [];
156
162
  const unknown: ProtocolFeature[] = [];
157
163
  const seen = new Set<ProtocolFeature>();
@@ -160,8 +166,10 @@ export function featureEffectsForPath(
160
166
  seen.add(feature);
161
167
  let worst: FeatureDisposition = "passthrough";
162
168
  let unknownHop = false;
163
- for (let i = 1; i < nodes.length; i++) {
164
- const hop = featureHopDisposition(feature, nodes[i - 1]!, nodes[i]!);
169
+ const hops = directIrDecode
170
+ ? [featureHopDisposition(feature, inbound, "responses"), undefined]
171
+ : nodes.slice(1).map((node, i) => featureHopDisposition(feature, nodes[i]!, node));
172
+ for (const hop of hops) {
165
173
  if (hop === undefined) unknownHop = true;
166
174
  else if (RANK[hop] > RANK[worst]) worst = hop;
167
175
  }
@@ -10,14 +10,23 @@ import { deliveryModeForPath, type DeliveryMode, type Protocol, type ProtocolHop
10
10
  /**
11
11
  * `native`: the ingress sent its own wire to a same-wire upstream.
12
12
  * `bridge`: the ingress projected the request into the Responses pipeline.
13
+ * `direct`: the ingress decoded its own wire straight into the IR the final adapter compiles
14
+ * from, with no internal Responses body in between (the Antigravity direct lane). Only a Chat or
15
+ * Messages ingress toward a non-protocol (`other`) upstream takes it; anything else falls back
16
+ * to the bridge rule so an inconsistent mark cannot claim a path that does not exist.
13
17
  */
14
- export type ProtocolLane = "native" | "bridge";
18
+ export type ProtocolLane = "native" | "bridge" | "direct";
19
+
20
+ function directApplies(inbound: Protocol, lane: ProtocolLane, upstream: UpstreamWire): boolean {
21
+ return lane === "direct" && inbound !== "responses" && upstream === "other";
22
+ }
15
23
 
16
24
  export function requestPathForLane(inbound: Protocol, lane: ProtocolLane, upstream: UpstreamWire): ProtocolHop[] {
17
25
  if (inbound === "responses") {
18
26
  return upstream === "responses" ? ["responses", "responses"] : ["responses", "ir", upstream];
19
27
  }
20
28
  if (lane === "native") return [inbound, inbound];
29
+ if (directApplies(inbound, lane, upstream)) return [inbound, "ir", upstream];
21
30
  if (upstream === "responses") return [inbound, "responses"];
22
31
  return [inbound, "responses-internal", "ir", upstream];
23
32
  }
@@ -26,6 +35,7 @@ export function requestPathForLane(inbound: Protocol, lane: ProtocolLane, upstre
26
35
  export function responsePathForLane(inbound: Protocol, lane: ProtocolLane, upstream: UpstreamWire): ProtocolHop[] {
27
36
  if (inbound === "responses") return upstream === "responses" ? ["responses", "responses"] : [upstream, "ir", "responses"];
28
37
  if (lane === "native") return [inbound, inbound];
38
+ if (directApplies(inbound, lane, upstream)) return [upstream, "ir", inbound];
29
39
  if (upstream === "responses") return ["responses", inbound];
30
40
  return [upstream, "ir", "responses-internal", inbound];
31
41
  }
@@ -23,6 +23,7 @@ import { getRoutingProfile, POLICY_NAMESPACE, resolvePolicyProfileId } from "../
23
23
  import { resolveWireProtocolOverride } from "../server/adapter-resolve";
24
24
  import { nativeChatDeclineReason } from "../server/chat-native-eligibility";
25
25
  import { nativeMessagesDeclineReason } from "../server/messages-native-eligibility";
26
+ import { antigravityDirectDeclineReason, antigravityMessagesDeclineReason } from "../server/antigravity-direct-eligibility";
26
27
  import { messagesSelectorTargetsSecondaryInstance, messagesSecondaryInstanceUnavailable } from "../server/messages-native-selector";
27
28
  import { parseSyntheticRowId } from "../server/fast-row";
28
29
  import type { OcxConfig } from "../types";
@@ -125,6 +126,7 @@ function candidateFor(
125
126
  const adapter = provider.adapter ?? "openai-responses";
126
127
  let declineReasons: ProtocolReasonCode[] = [];
127
128
  let nativeEligible = false;
129
+ let directEligible = false;
128
130
  // With `nativeChatCombos` on, the combo loop judges each Chat candidate as the concrete route it
129
131
  // is (PF-07), so the preview must too; a policy still resolves one candidate on the bridge.
130
132
  const comboChildNative = inbound === "chat" && routeKind === "combo"
@@ -136,25 +138,46 @@ function candidateFor(
136
138
  ...(routeKind === "direct" || comboChildNative ? {} : { routeKind }),
137
139
  };
138
140
  if (inbound === "chat") {
139
- const reason = rows.effortRow ? "effort-row" : nativeChatDeclineReason(settled, chatBodyForFeatures(features), config);
140
- nativeEligible = reason === undefined;
141
- if (reason) declineReasons = [reason];
142
- } else if (inbound === "messages" && resolveProtocolSettings(config, settled.providerName).rollout.managedMessagesNative) {
143
- // The runtime rule itself. With the switch off nothing is judged, so the default preview
144
- // is exactly what it was before the managed native lane existed.
145
- // Pinned effort is judged from config and the route; blocked-skill elision and the web-search
146
- // sidecar depend on body content no feature describes, so a preview cannot predict them.
147
- // Anthropic OAuth (PF-10) is judged from config alone: the rollout switch, the provider, its
148
- // host and `anthropicAccountPool.enabled`. The stored-account quorum a sender supplies is
149
- // never read here, so a preview selects, resolves and refreshes no account.
150
- const reason = nativeMessagesDeclineReason(settled, messagesBodyForFeatures(features), config, {
151
- ...rows,
152
- claudeCode: config.claudeCode,
153
- });
141
+ const body = chatBodyForFeatures(features);
142
+ const reason = rows.effortRow ? "effort-row" : nativeChatDeclineReason(settled, body, config);
154
143
  nativeEligible = reason === undefined;
155
144
  if (reason) declineReasons = [reason];
145
+ // The Antigravity direct lane, judged by the ingress's own rule, for a standalone route only:
146
+ // a combo child is dispatched from the full Responses body even when `nativeChatCombos`
147
+ // judges it as a concrete route above. A hosted web-search tool and the other body-content
148
+ // declines are not described by any Chat feature, so a preview cannot predict them.
149
+ directEligible = !nativeEligible && routeKind === "direct"
150
+ && antigravityDirectDeclineReason(settled, { ...body, model: rows.routeSelector }, config) === undefined;
151
+ } else if (inbound === "messages") {
152
+ const body = messagesBodyForFeatures(features);
153
+ if (resolveProtocolSettings(config, settled.providerName).rollout.managedMessagesNative) {
154
+ // The runtime rule itself. With the switch off nothing is judged, so the default preview
155
+ // is exactly what it was before the managed native lane existed.
156
+ // Pinned effort is judged from config and the route; blocked-skill elision and the web-search
157
+ // sidecar depend on body content no feature describes, so a preview cannot predict them.
158
+ // Anthropic OAuth (PF-10) is judged from config alone: the rollout switch, the provider, its
159
+ // host and `anthropicAccountPool.enabled`. The stored-account quorum a sender supplies is
160
+ // never read here, so a preview selects, resolves and refreshes no account.
161
+ const reason = nativeMessagesDeclineReason(settled, body, config, {
162
+ ...rows,
163
+ claudeCode: config.claudeCode,
164
+ });
165
+ nativeEligible = reason === undefined;
166
+ if (reason) declineReasons = [reason];
167
+ }
168
+ // The Antigravity direct lane for a standalone route, as for Chat. The ingress plans it on the
169
+ // resolved selector, which is `rows.routeSelector`.
170
+ directEligible = !nativeEligible && routeKind === "direct"
171
+ && antigravityMessagesDeclineReason(settled, body, rows.routeSelector, config) === undefined;
156
172
  }
157
- return { provider: route.providerName, model: route.modelId, adapter, nativeEligible, declineReasons };
173
+ return {
174
+ provider: route.providerName,
175
+ model: route.modelId,
176
+ adapter,
177
+ nativeEligible,
178
+ ...(directEligible ? { directEligible } : {}),
179
+ declineReasons,
180
+ };
158
181
  }
159
182
 
160
183
  /** Whether the Messages ingress could forward this selector with a caller's own credential. */
@@ -28,6 +28,12 @@ export interface ProtocolPlanCandidateInput {
28
28
  adapter: string;
29
29
  /** True when the ingress would send its own wire to this candidate. */
30
30
  nativeEligible: boolean;
31
+ /**
32
+ * True when the ingress would decode its own wire straight into the IR for this candidate (the
33
+ * Antigravity direct lane). Only meaningful toward an `other` upstream; `path.ts` ignores it
34
+ * anywhere else.
35
+ */
36
+ directEligible?: boolean;
31
37
  /** Why the ingress declined its native lane, when it did. */
32
38
  declineReasons: readonly ProtocolReasonCode[];
33
39
  }
@@ -80,7 +86,9 @@ function planCandidate(
80
86
  const upstream = upstreamWireForAdapter(candidate.adapter);
81
87
  // A native lane only exists toward the ingress's own wire; an inconsistent snapshot must
82
88
  // not produce a path that claims the source body reached a different wire untouched.
83
- const lane: ProtocolLane = candidate.nativeEligible && upstream === input.inbound ? "native" : "bridge";
89
+ const lane: ProtocolLane = candidate.nativeEligible && upstream === input.inbound
90
+ ? "native"
91
+ : candidate.directEligible === true ? "direct" : "bridge";
84
92
  const requestPath = requestPathForLane(input.inbound, lane, upstream);
85
93
  const responsePath = responsePathForLane(input.inbound, lane, upstream);
86
94
  const mode = deliveryModeForLane(input.inbound, lane, upstream);