@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,75 @@
1
+ import { freeformFallbackKeys } from "../../responses/apply-patch-envelope";
2
+
3
+ /**
4
+ * Pre-emission gate for Cursor calls to request-declared freeform tools (#7049).
5
+ *
6
+ * Cursor carries a freeform body as a JSON object, and the canonical shape is `{input:string}`.
7
+ * Claude models routed through Cursor also name the body after its content — `{"code": "..."}`
8
+ * for the code-mode `exec` cell — and the shared Responses repair downstream already unwraps
9
+ * exactly that (`unwrapFreeformToolInput`: no `input` key, ONE string-valued fallback key).
10
+ * This gate used to accept only `input`, so it turned a payload the bridge knows how to read
11
+ * into a fatal adapter error before any host execution. It now applies the same single faithful
12
+ * reading and re-emits it canonically; anything ambiguous or non-string still fails closed.
13
+ *
14
+ * A canonical wrapper is returned byte-identical. Only the repaired shape is re-serialized.
15
+ */
16
+ export type CursorFreeformArgs =
17
+ | { ok: true; args: string }
18
+ | { ok: false; reason: string };
19
+
20
+ /**
21
+ * The only key names a diagnostic may print. They are constants written here, so the error can
22
+ * never carry request-derived text: a model can put body text or an identifier in key position,
23
+ * and the message is persisted to request logs. Every other key is counted, not named.
24
+ */
25
+ const NAMEABLE_KEYS: ReadonlySet<string> = new Set([
26
+ "input",
27
+ "code", "script", "js", "javascript", "command", "cmd", "content", "patch",
28
+ "source", "program", "body", "text", "args", "arguments",
29
+ ]);
30
+ const MAX_LISTED_KEYS = 8;
31
+
32
+ function valueKind(value: unknown): string {
33
+ if (value === null) return "null";
34
+ if (Array.isArray(value)) return "array";
35
+ return typeof value;
36
+ }
37
+
38
+ /**
39
+ * Shape-only description of a rejected wrapper: allowlisted key names with their value kinds,
40
+ * then a count of every other key. Never values, never an unlisted key name.
41
+ */
42
+ function describeKeys(record: Record<string, unknown>): string {
43
+ const keys = Object.keys(record);
44
+ if (keys.length === 0) return "received an empty object";
45
+ const listed = keys.filter(key => NAMEABLE_KEYS.has(key))
46
+ .slice(0, MAX_LISTED_KEYS)
47
+ .map(key => `${key}:${valueKind(record[key])}`);
48
+ const hidden = keys.length - listed.length;
49
+ if (hidden > 0) listed.push(`+${hidden} other`);
50
+ return `received keys [${listed.join(", ")}]`;
51
+ }
52
+
53
+ export function canonicalCursorFreeformArgs(args: string, toolName: string): CursorFreeformArgs {
54
+ if (args.length === 0) return { ok: false, reason: "received no arguments" };
55
+ let parsed: unknown;
56
+ try {
57
+ parsed = JSON.parse(args);
58
+ } catch {
59
+ return { ok: false, reason: "received arguments that are not JSON" };
60
+ }
61
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
62
+ return { ok: false, reason: `received a JSON ${valueKind(parsed)}` };
63
+ }
64
+ const record = parsed as Record<string, unknown>;
65
+ if (Object.prototype.hasOwnProperty.call(record, "input")) {
66
+ return typeof record.input === "string"
67
+ ? { ok: true, args }
68
+ : { ok: false, reason: `input was ${valueKind(record.input)}` };
69
+ }
70
+ const candidates = freeformFallbackKeys(toolName).filter(key => typeof record[key] === "string");
71
+ if (candidates.length === 1) {
72
+ return { ok: true, args: JSON.stringify({ input: record[candidates[0]] as string }) };
73
+ }
74
+ return { ok: false, reason: describeKeys(record) };
75
+ }
@@ -2,6 +2,7 @@ import type { OcxUsage } from "../../types";
2
2
  import type { AgentServerMessage, McpArgs, ToolCall } from "./gen/agent_pb";
3
3
  import { decodeCursorArgsMap } from "./arg-codec";
4
4
  import { normalizeArgKeys } from "./arg-normalize";
5
+ import { canonicalCursorFreeformArgs } from "./freeform-args";
5
6
  import {
6
7
  CODEX_APPLY_PATCH_TOOL,
7
8
  CURSOR_MULTI_EDIT_TOOL,
@@ -334,9 +335,20 @@ function toolSchemaForWireName(state: CursorProtobufEventState, toolName: string
334
335
  return resolveShellBridgeAliasKey(toolName, alias => state.toolSchemas!.get(alias));
335
336
  }
336
337
 
337
- function decodeMcpArgsNormalized(args: McpArgs | undefined, state: CursorProtobufEventState): string {
338
- const decoded = decodeCursorArgsMap(args?.args);
338
+ function isFreeformCall(state: CursorProtobufEventState, responsesName: string | undefined): boolean {
339
+ return responsesName !== undefined && state.freeformToolNames?.has(responsesName) === true;
340
+ }
341
+
342
+ function decodeMcpArgsNormalized(
343
+ args: McpArgs | undefined,
344
+ state: CursorProtobufEventState,
345
+ openResponsesName?: string,
346
+ ): string {
339
347
  const toolName = mcpWireNameFromArgs(args);
348
+ const responsesName = toolName
349
+ ? responsesToolNameFromCursorWire(toolName, state.cursorToolNameMap)
350
+ : openResponsesName;
351
+ const decoded = decodeCursorArgsMap(args?.args, { preserveStrings: isFreeformCall(state, responsesName) });
340
352
  const schema = toolSchemaForWireName(state, toolName);
341
353
  if (schema) return JSON.stringify(normalizeArgKeys(decoded, schema));
342
354
  return JSON.stringify(decoded);
@@ -381,8 +393,13 @@ function normalizeJsonText(text: string, toolName: string | undefined, state: Cu
381
393
  * preserved verbatim so the bridge can reject malformed or truncated JSON instead of silently
382
394
  * converting it to `{}`. A genuinely empty buffer remains the no-argument case.
383
395
  */
384
- function resolveCompletedArgs(buffered: string, args: McpArgs | undefined, state: CursorProtobufEventState): string {
385
- if (hasMcpArgBytes(args)) return decodeMcpArgsNormalized(args, state);
396
+ function resolveCompletedArgs(
397
+ buffered: string,
398
+ args: McpArgs | undefined,
399
+ state: CursorProtobufEventState,
400
+ openResponsesName?: string,
401
+ ): string {
402
+ if (hasMcpArgBytes(args)) return decodeMcpArgsNormalized(args, state, openResponsesName);
386
403
  const name = mcpWireNameFromArgs(args);
387
404
  if (isCompleteJson(buffered)) return normalizeJsonText(buffered, name, state);
388
405
  return buffered;
@@ -1092,7 +1109,7 @@ export function mapSyntheticMcpExecToToolEvents(
1092
1109
  const out: CursorServerMessage[] = [...recordToolCall(options.state, callId, cursorWireName)];
1093
1110
  if (out.some(event => event.type === "error")) return out;
1094
1111
  const open = options.state.openToolCalls.get(callId);
1095
- const finalArgs = resolveCompletedArgs(open?.args ?? "", args, options.state);
1112
+ const finalArgs = resolveCompletedArgs(open?.args ?? "", args, options.state, open?.name);
1096
1113
  out.push(...commitToolCall(options.state, callId, finalArgs));
1097
1114
  return out;
1098
1115
  }
@@ -1167,23 +1184,16 @@ function discardBufferedTextToolCalls(state: CursorProtobufEventState): void {
1167
1184
  * recorded in `openToolCalls`. Because each completion emits a whole non-interleaved unit, the bridge
1168
1185
  * (which tracks a single current tool call) serializes parallel Cursor calls correctly.
1169
1186
  */
1170
- function cursorFreeformWrapperValid(args: string): boolean {
1171
- try {
1172
- const parsed = JSON.parse(args) as unknown;
1173
- return !!parsed
1174
- && typeof parsed === "object"
1175
- && !Array.isArray(parsed)
1176
- && typeof (parsed as Record<string, unknown>).input === "string";
1177
- } catch {
1178
- return false;
1179
- }
1187
+ /** True when the args already carry a body this call can commit, canonical or repairable (#7049). */
1188
+ function cursorFreeformArgsCommittable(args: string, toolName: string): boolean {
1189
+ return canonicalCursorFreeformArgs(args, toolName).ok;
1180
1190
  }
1181
1191
 
1182
- function dropInvalidFreeformCall(state: CursorProtobufEventState, callId: string, toolName: string): CursorServerMessage[] {
1192
+ function dropInvalidFreeformCall(state: CursorProtobufEventState, callId: string, toolName: string, reason: string): CursorServerMessage[] {
1183
1193
  state.openToolCalls.delete(callId);
1184
1194
  state.translatorBudget?.closeCall(callId);
1185
1195
  state.completedToolCalls.add(callId);
1186
- return [{ type: "error", message: `${toolName} call had invalid freeform arguments; expected {input:string}` }];
1196
+ return [{ type: "error", message: `${toolName} call had invalid freeform arguments; expected {input:string} (${reason})` }];
1187
1197
  }
1188
1198
 
1189
1199
  function dropShellBridgeCall(state: CursorProtobufEventState, callId: string, toolName: string): CursorServerMessage[] {
@@ -1202,11 +1212,14 @@ function dropStructuredEditCall(state: CursorProtobufEventState, callId: string,
1202
1212
  return [{ type: "text", text: `\n${toolName} call was not converted to apply_patch: ${reason}` }];
1203
1213
  }
1204
1214
 
1205
- function commitToolCall(state: CursorProtobufEventState, callId: string, finalArgs: string): CursorServerMessage[] {
1215
+ function commitToolCall(state: CursorProtobufEventState, callId: string, committedArgs: string): CursorServerMessage[] {
1206
1216
  const open = state.openToolCalls.get(callId);
1207
1217
  if (!open) return [];
1208
- if (state.freeformToolNames?.has(open.name) && !cursorFreeformWrapperValid(finalArgs)) {
1209
- return dropInvalidFreeformCall(state, callId, open.name);
1218
+ let finalArgs = committedArgs;
1219
+ if (isFreeformCall(state, open.name)) {
1220
+ const freeform = canonicalCursorFreeformArgs(finalArgs, open.name);
1221
+ if (!freeform.ok) return dropInvalidFreeformCall(state, callId, open.name, freeform.reason);
1222
+ finalArgs = freeform.args;
1210
1223
  }
1211
1224
  const schema = toolSchemaForWireName(state, open.name);
1212
1225
  if (!cursorShellBridgeArgsValid(finalArgs, open.name, schema)) {
@@ -1386,7 +1399,7 @@ export function mapCursorProtobufServerMessage(
1386
1399
  && (
1387
1400
  (name !== undefined && openBeforeStart.args.length === 0)
1388
1401
  || (state.freeformToolNames?.has(openBeforeStart.name) === true
1389
- && !cursorFreeformWrapperValid(openBeforeStart.args)
1402
+ && !cursorFreeformArgsCommittable(openBeforeStart.args, openBeforeStart.name)
1390
1403
  )
1391
1404
  )
1392
1405
  ) {
@@ -1410,13 +1423,13 @@ export function mapCursorProtobufServerMessage(
1410
1423
  if (
1411
1424
  !openBeforeStart && open && !hasMcpArgBytes(args)
1412
1425
  && state.freeformToolNames?.has(open.name) === true
1413
- && !cursorFreeformWrapperValid(open.args)
1426
+ && !cursorFreeformArgsCommittable(open.args, open.name)
1414
1427
  ) {
1415
1428
  open.awaitingNativeArgs = true;
1416
1429
  return [];
1417
1430
  }
1418
1431
  if (open) {
1419
- const finalArgs = resolveCompletedArgs(open.args, args, state);
1432
+ const finalArgs = resolveCompletedArgs(open.args, args, state, open.name);
1420
1433
  out.push(...commitToolCall(state, update.value.callId, finalArgs));
1421
1434
  }
1422
1435
  return out;
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { OcxContentPart, OcxParsedRequest } from "../types";
3
- import { antigravityUserAgent } from "./client-fingerprint";
3
+ import { antigravityCliUserAgent, antigravityUserAgent } from "./client-fingerprint";
4
4
 
5
5
  /**
6
6
  * Antigravity request User-Agent. Mirrors the real Antigravity IDE UA
@@ -11,6 +11,27 @@ import { antigravityUserAgent } from "./client-fingerprint";
11
11
  */
12
12
  export const ANTIGRAVITY_REQUEST_UA = antigravityUserAgent();
13
13
 
14
+ /**
15
+ * Antigravity CLI User-Agent. CCA serves Gemini 4 Argon only to the CLI client family, and it
16
+ * 404s other agent models (`gemini-3.7-flash-tiered`, `gemini-3.1-pro-low`) under it, so the
17
+ * choice is per wire model: see `antigravityUserAgentForWireModel`.
18
+ */
19
+ export const ANTIGRAVITY_CLI_REQUEST_UA = antigravityCliUserAgent();
20
+
21
+ /**
22
+ * Model discovery (`:fetchAvailableModels`) uses the CLI UA so Argon's wire ids are listed;
23
+ * under the IDE UA they are absent.
24
+ */
25
+ export const ANTIGRAVITY_DISCOVERY_UA = ANTIGRAVITY_CLI_REQUEST_UA;
26
+
27
+ /** Wire ids CCA serves only to the CLI client family (verified 2026-10-14). */
28
+ const ANTIGRAVITY_CLI_ONLY_WIRE_MODEL = /^gemini-4-argon(?:-(?:low|medium|high))?$/;
29
+
30
+ /** The User-Agent an Antigravity inference request for `wireModelId` must carry. */
31
+ export function antigravityUserAgentForWireModel(wireModelId: string): string {
32
+ return ANTIGRAVITY_CLI_ONLY_WIRE_MODEL.test(wireModelId) ? ANTIGRAVITY_CLI_REQUEST_UA : ANTIGRAVITY_REQUEST_UA;
33
+ }
34
+
14
35
  /**
15
36
  * Whether a stored `OcxToolCall.thoughtSignature` is a REAL upstream Gemini signature versus a
16
37
  * foreign id that must not be forwarded to Gemini/Antigravity.
@@ -21,7 +21,7 @@ import { getVertexAccessToken } from "../lib/gcp-adc";
21
21
  import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
22
22
  import { safeAntigravityHttpErrorMessage, safeVertexHttpErrorMessage } from "./google-errors";
23
23
  import { isVertexTruncatedTurn, vertexTruncationErrorEvent } from "./google-truncation";
24
- import { ANTIGRAVITY_REQUEST_UA, antigravitySessionAnchor, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
24
+ import { antigravitySessionAnchor, antigravityUserAgentForWireModel, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
25
25
  import { summarizeGoogleWireShape } from "./google-wire-shape";
26
26
  import { compileGoogleWireBody } from "./google-wire-compiler";
27
27
  import type { GoogleToolSchemaLossReport, GoogleToolSchemaProfile } from "./google-tool-schema";
@@ -1155,7 +1155,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1155
1155
  requestId: `agent-${crypto.randomUUID()}`,
1156
1156
  request,
1157
1157
  };
1158
- headers["User-Agent"] = ANTIGRAVITY_REQUEST_UA;
1158
+ headers["User-Agent"] = antigravityUserAgentForWireModel(wireModelId);
1159
1159
  headers["Authorization"] = `Bearer ${token}`;
1160
1160
  return { url, method: "POST", headers, body: JSON.stringify(envelope) };
1161
1161
  }
@@ -31,6 +31,7 @@ import {
31
31
  } from "../lib/translator-budget";
32
32
  import { redactSecretString, SENSITIVE_KEY_PATTERN } from "../lib/redact";
33
33
  import { parseDataUrl } from "./image";
34
+ import { jsonStringPartsUtf8Bytes, jsonUtf8Bytes } from "../lib/json-byte-size";
34
35
  import {
35
36
  ollamaNativeChatUrl,
36
37
  ollamaNativeEndpointKind,
@@ -110,6 +111,21 @@ const NATIVE_THINK_VALUES = new Set(["low", "medium", "high", "max"]);
110
111
  const NATIVE_TOOL_ID_MAX_LENGTH = 256;
111
112
  const NATIVE_TOOL_NAME_MAX_BYTES = 1024;
112
113
  const NATIVE_MAX_PENDING_TOOL_CALLS = 128;
114
+ const NATIVE_MAX_LATE_ATTRIBUTION_BYTES = 256 * 1024;
115
+ /** Proxy-owned refusal text for an exhausted late-attribution budget. A compaction on the same
116
+ * Ollama route resends the same history, so the only reliable recovery is a new thread. */
117
+ export const OLLAMA_LATE_ATTRIBUTION_LIMIT_MESSAGE =
118
+ "Late tool-output attribution for this conversation exceeded the 256 KiB per-request limit. Start a new thread to continue.";
119
+
120
+ /** The late-attribution budget refusal, reporting the full limit rather than the bytes left. */
121
+ export class OllamaLateAttributionBudgetError extends TranslatorBudgetExceededError {
122
+ readonly publicMessage = OLLAMA_LATE_ATTRIBUTION_LIMIT_MESSAGE;
123
+
124
+ constructor() {
125
+ super("request_copies", NATIVE_MAX_LATE_ATTRIBUTION_BYTES);
126
+ this.name = "OllamaLateAttributionBudgetError";
127
+ }
128
+ }
113
129
  // Account for the retained Map key and call bookkeeping in addition to the provider's name.
114
130
  const NATIVE_TOOL_CALL_BOOKKEEPING_BYTES = 128;
115
131
  const NATIVE_TOOL_ID_CONTROL = /[\u0000-\u001f\u007f]/u;
@@ -325,6 +341,7 @@ function buildNativeMessages(
325
341
  reservedToolCallIds.clear();
326
342
  let pending: PendingToolBatch | undefined;
327
343
  const issuedCalls = new Map<string, PendingToolCall>();
344
+ let lateAttributionBytes = 0;
328
345
  // Codex records mid-turn injections (a PostToolUse hook verdict, a context notice) between an
329
346
  // assistant tool call and that call's own tool result. Native Ollama needs the call and its
330
347
  // results adjacent, so those conversational messages wait here instead of closing the batch
@@ -392,9 +409,20 @@ function buildNativeMessages(
392
409
  // Reopening a settled batch would split a later call/result pair. Keep known late
393
410
  // output explicitly attributed as conversation text instead of dropping it or
394
411
  // fabricating another executable call. Unknown/mismatched identities still fail above.
412
+ // Generated attribution is not present in the inbound size estimate. Reserve its
413
+ // JSON wire bytes before joining strings, across all late outputs and all calls.
414
+ // Count the joined JSON string exactly, without constructing a measurement copy.
415
+ const attribution = ['[ocx] additional output for previously issued tool "',
416
+ call.wireName, '" (', call.id, '):\n', message.isError ? "ERROR: " : ""];
417
+ try {
418
+ lateAttributionBytes += jsonStringPartsUtf8Bytes(attribution, NATIVE_MAX_LATE_ATTRIBUTION_BYTES - lateAttributionBytes);
419
+ } catch (error) {
420
+ if (error instanceof TranslatorBudgetExceededError) throw new OllamaLateAttributionBudgetError();
421
+ throw error;
422
+ }
395
423
  const translated = contentToNative(message.content, "late tool result");
396
424
  const late: OllamaNativeMessage = { role: "user",
397
- content: `[ocx] additional output for previously issued tool "${call.wireName}" (${call.id}):\n${message.isError ? "ERROR: " : ""}${translated.content}`,
425
+ content: attribution.join("") + translated.content,
398
426
  ...(translated.images ? { images: translated.images } : {}),
399
427
  };
400
428
  if (pending) deferred.push(late);
@@ -142,12 +142,12 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv
142
142
  };
143
143
 
144
144
  const nativeOpenAI = isNativeOpenAIChatTarget(provider);
145
- // Two pinned chat templates reject non-leading system and every developer role while a
146
- // later user renders in place: Qwen3.8-27B and the OrcaSAQ-2-Cyber-27B GGUF family.
145
+ // Leading-system chat templates reject non-leading system and every developer role while a
146
+ // later user renders in place: Qwen3.8-27B, Flash-Next, and the OrcaSAQ-2-Cyber-27B GGUF family.
147
147
  // Gateways serve them under namespaced ids (LiteLLM's openai/Qwen3.8-27B, #6674), so any
148
148
  // number of leading path segments is accepted; the final segment names the checkpoint.
149
149
  // Orca's GGUF marker must precede any colon quant tag; other Orca variants are unchanged.
150
- const leadingSystemTemplate = !nativeOpenAI && /^(?:[^/]+\/)*(?:Qwen3\.8-27B|qwen3-8-27b(?:-(?:fp8|lora))?|OrcaSAQ-2-Cyber-27B(?=[^:/\s]*GGUF)[^:/\s]*(?::[\w-]+)?)$/i.test(parsed.modelId);
150
+ const leadingSystemTemplate = !nativeOpenAI && /^(?:[^/]+\/)*(?:flash-next|Qwen3\.8-Flash-Next|Qwen3\.8-27B|qwen3-8-27b(?:-(?:fp8|lora))?|OrcaSAQ-2-Cyber-27B(?=[^:/\s]*GGUF)[^:/\s]*(?::[\w-]+)?)$/i.test(parsed.modelId);
151
151
  // Which role a developer message carries, and why the unrecorded state folds, is stated once
152
152
  // in ./developer-role.ts and read from there by the native passthrough as well. Either way the
153
153
  // message keeps the slot it arrived in — only the role changes, never the position.
@@ -292,7 +292,11 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv
292
292
  }
293
293
  }
294
294
  const hasReplayedReasoning = chatMsg.reasoning_content !== undefined || chatMsg.reasoning_details !== undefined;
295
- if (chatMsg.content === undefined && toolCalls.length === 0 && !hasReplayedReasoning) break;
295
+ // A turn with no answer text, tools or reasoning carries nothing. Between a call and its
296
+ // result it would also split the pair, so drop it there; a standalone empty answer keeps
297
+ // its slot so templates that require strict user/assistant alternation still accept it.
298
+ const carriesNothing = !chatMsg.content && toolCalls.length === 0 && !hasReplayedReasoning;
299
+ if (carriesNothing && (chatMsg.content === undefined || pendingToolCalls.length > 0)) break;
296
300
  flushPendingToolCalls();
297
301
  const wireToolCalls = toolCalls.map(tc => {
298
302
  let id = tc.id;
@@ -1,6 +1,7 @@
1
1
  import { diagnoseInvalidToolCalls, isRecord, type InvalidToolCallDiagnostic } from "./tool-call-validation";
2
2
  import { TranslatorBudgetExceededError, type TranslatorBudget } from "../../lib/translator-budget";
3
- import type { AdapterEvent, OcxUsage } from "../../types";
3
+ import type { AdapterEvent, OcxProviderConfig, OcxUsage } from "../../types";
4
+ import { createInlineThinkContentSplitter } from "../inline-think-tags";
4
5
 
5
6
  export function stopReasonFor(finishReason: unknown): "max_tokens" | "content_filter" | undefined {
6
7
  return finishReason === "length"
@@ -18,6 +19,63 @@ export function reasoningTextFrom(record: Record<string, unknown>): string | und
18
19
  : undefined;
19
20
  }
20
21
 
22
+ type ChatContentEvent = Extract<AdapterEvent, { type: "text_delta" | "reasoning_raw_delta" | "error" }>;
23
+
24
+ /** Mistral can mix typed blocks and strings in one response. Other destinations keep their parser. */
25
+ export function chatContentEvents(content: unknown, baseUrl: string, usage?: OcxUsage, buffered = false): ChatContentEvent[] {
26
+ if (typeof content === "string") return content.length > 0 || buffered ? [{ type: "text_delta", text: content }] : [];
27
+ if (!Array.isArray(content)) return [];
28
+ let host: string;
29
+ try { host = new URL(baseUrl).hostname; } catch { return []; }
30
+ if (host !== "api.mistral.ai") return [];
31
+ const invalid = (): ChatContentEvent[] => [{
32
+ type: "error", status: 502, errorType: "upstream_error",
33
+ message: "unsupported Mistral content block", ...(usage ? { usage } : {}),
34
+ }];
35
+ const events: ChatContentEvent[] = [];
36
+ for (const block of content) {
37
+ if (!isRecord(block)) return invalid();
38
+ if (block.type === "text" && typeof block.text === "string") {
39
+ if (block.text.length > 0 || buffered) events.push({ type: "text_delta", text: block.text });
40
+ } else if (block.type === "thinking" && Array.isArray(block.thinking)) {
41
+ for (const inner of block.thinking) {
42
+ if (!isRecord(inner) || inner.type !== "text" || typeof inner.text !== "string") return invalid();
43
+ if (inner.text.length > 0) events.push({ type: "reasoning_raw_delta", text: inner.text });
44
+ }
45
+ } else return invalid();
46
+ }
47
+ return events;
48
+ }
49
+
50
+ /** Keep adjacent text together; typed reasoning flushes and resets inline-tag parsing. */
51
+ export function createChatContentSplitter(provider: OcxProviderConfig, modelId: string | undefined, budget: TranslatorBudget) {
52
+ let splitter = createInlineThinkContentSplitter(provider.inlineThinkTagModels, modelId, budget);
53
+ return {
54
+ feed(event: ChatContentEvent): AdapterEvent[] {
55
+ if (event.type === "error") return [event];
56
+ if (event.type === "text_delta") return splitter.feed(event.text);
57
+ const pending = splitter.flush();
58
+ splitter.dispose();
59
+ splitter = createInlineThinkContentSplitter(provider.inlineThinkTagModels, modelId, budget);
60
+ return [...pending, event];
61
+ },
62
+ flush: () => splitter.flush(),
63
+ dispose: () => splitter.dispose(),
64
+ };
65
+ }
66
+
67
+ /** Split a buffered answer with the same typed-reasoning boundaries as a stream. */
68
+ export function splitChatContentEvents(content: unknown, provider: OcxProviderConfig, modelId: string | undefined, budget: TranslatorBudget, usage?: OcxUsage): AdapterEvent[] {
69
+ const events = chatContentEvents(content, provider.baseUrl, usage, true);
70
+ if (events.some(event => event.type === "error")) return events;
71
+ const splitter = createChatContentSplitter(provider, modelId, budget);
72
+ try {
73
+ return [...events.flatMap(event => splitter.feed(event)), ...splitter.flush()];
74
+ } finally {
75
+ splitter.dispose();
76
+ }
77
+ }
78
+
21
79
  export interface ReasoningDetailSegment {
22
80
  key: string;
23
81
  text: string;
@@ -5,7 +5,6 @@ import { applyExplicitChatReasoningWirePolicy } from "./openai-chat/reasoning-wi
5
5
  import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "./base";
6
6
  import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig, OcxUsage } from "../types";
7
7
  import { modelInList } from "../types";
8
- import { createInlineThinkContentSplitter, splitInlineThinkContent } from "./inline-think-tags";
9
8
  import { mapReasoningEffort } from "../reasoning-effort";
10
9
  import { debugProviderDiagnostic } from "../lib/debug";
11
10
  import { sseFieldValue } from "../lib/sse-decoder";
@@ -28,6 +27,7 @@ import {
28
27
  type InvalidToolCallDiagnostic,
29
28
  } from "./openai-chat/tool-call-validation";
30
29
  import {
30
+ chatContentEvents, createChatContentSplitter, splitChatContentEvents,
31
31
  createReasoningDetailSnapshotTracker,
32
32
  invalidChoicesEvent,
33
33
  invalidToolCallsEvent,
@@ -368,7 +368,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
368
368
  const reasoningDetailsOptIn = modelInList(provider.reasoningDetailsModels, lastRequestedModelId ?? "");
369
369
  // A gateway with no server-side reasoning parser leaves thinking inline in `content` as
370
370
  // <think> blocks, which would otherwise render as the answer. Passthrough unless opted in.
371
- const inlineThink = createInlineThinkContentSplitter(provider.inlineThinkTagModels, lastRequestedModelId, budget);
371
+ const inlineThink = createChatContentSplitter(provider, lastRequestedModelId, budget);
372
372
  const emitContent = function* (events: AdapterEvent[]): Generator<AdapterEvent> {
373
373
  for (const event of events) {
374
374
  // Any other event keeps its place behind held text instead of overtaking it.
@@ -444,8 +444,9 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
444
444
  const reasoningText = reasoningTextFrom(delta);
445
445
  if (reasoningText !== undefined) yield* toolCallContent.hold({ type: "reasoning_raw_delta", text: reasoningText });
446
446
  }
447
- if (typeof delta.content === "string" && delta.content.length > 0) {
448
- yield* emitContent(inlineThink.feed(delta.content));
447
+ for (const event of chatContentEvents(delta.content, provider.baseUrl, pendingUsage)) {
448
+ if (event.type === "error") return yield* terminateWithError(event);
449
+ yield* emitContent(inlineThink.feed(event));
449
450
  }
450
451
 
451
452
  const rawToolCalls = delta.tool_calls;
@@ -767,7 +768,9 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
767
768
  }
768
769
  if (reasoningText !== undefined) events.push({ type: "reasoning_raw_delta", text: reasoningText });
769
770
  const contentStart = events.length;
770
- if (typeof msg.content === "string") events.push(...splitInlineThinkContent(provider.inlineThinkTagModels, lastRequestedModelId, budget, msg.content));
771
+ const contentEvents = splitChatContentEvents(msg.content, provider, lastRequestedModelId, budget, usage);
772
+ if (contentEvents.some(event => event.type === "error")) return contentEvents;
773
+ events.push(...contentEvents);
771
774
  const contentEnd = events.length;
772
775
  const answerText = events.slice(contentStart).map(event => (event.type === "text_delta" ? event.text : "")).join("");
773
776
  // Each call holds the delta event it emitted, so the batch repair sets its arguments later.
@@ -0,0 +1,32 @@
1
+ import { isPlainObject } from "./internal";
2
+
3
+ const MESSAGE_FIELDS = new Set(["type", "role", "content", "id", "status", "phase"]);
4
+
5
+ function isEmptyTextPart(part: unknown): boolean {
6
+ if (!isPlainObject(part) || !["text", "input_text", "output_text"].includes(String(part.type)) || part.text !== "") return false;
7
+ // Nonempty annotations and unknown fields may carry information despite the blank text.
8
+ return Object.keys(part).every(key => key === "type" || key === "text"
9
+ || ((key === "annotations" || key === "logprobs") && Array.isArray(part[key]) && part[key].length === 0));
10
+ }
11
+
12
+ function isEmptyAssistantMessage(item: unknown): boolean {
13
+ if (!isPlainObject(item) || item.role !== "assistant" || (item.type !== undefined && item.type !== "message")) return false;
14
+ // Never discard tools, reasoning, opaque state or unknown extension fields on a message.
15
+ if (!Object.keys(item).every(key => MESSAGE_FIELDS.has(key))) return false;
16
+ return item.content === "" || (Array.isArray(item.content) && item.content.every(isEmptyTextPart));
17
+ }
18
+
19
+ /** Kimi Code rejects content-empty assistant messages even on its native Responses endpoint. */
20
+ export function stripKimiEmptyAssistantMessages(body: unknown, responseUrl: string): unknown {
21
+ let url: URL;
22
+ try { url = new URL(responseUrl); } catch { return body; }
23
+ if (url.hostname !== "api.kimi.com" || !/^\/coding\/v1\/responses\/?$/.test(url.pathname)) return body;
24
+ if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
25
+ const referencedIds = new Set<string>();
26
+ for (const item of body.input) {
27
+ if (isPlainObject(item) && item.type === "item_reference" && typeof item.id === "string") referencedIds.add(item.id);
28
+ }
29
+ const input = body.input.filter(item => !isEmptyAssistantMessage(item)
30
+ || (isPlainObject(item) && typeof item.id === "string" && referencedIds.has(item.id)));
31
+ return input.length === body.input.length ? body : { ...body, input };
32
+ }
@@ -50,6 +50,7 @@ import { observeOutbound } from "../../usage/cache-diagnostic";
50
50
  import { normalizeForwardedClientHeaderName } from "../../lib/provider-client-headers";
51
51
  import { normalizeMuseToolChoice } from "./muse-tool-choice";
52
52
  import { renderCompactionSearchHistoryItems } from "./compaction-search-history";
53
+ import { stripKimiEmptyAssistantMessages } from "./empty-assistant";
53
54
 
54
55
  /**
55
56
  * Identifies DeepSeek's strict Responses replay contract: tool-bearing continuations need
@@ -477,6 +478,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
477
478
  // Run after routed compaction so nested input_image parts are replaced before a malformed
478
479
  // tool output is flattened to text and can no longer be inspected structurally.
479
480
  outBody = repairUnidentifiedToolOutputItems(outBody);
481
+ outBody = stripKimiEmptyAssistantMessages(outBody, url);
480
482
  if (parsed._plaintextV2AgentMessages === true && isCanonicalOpenAiForwardProvider(provider)) {
481
483
  const prepared = preparePlaintextV2AgentMessages(outBody);
482
484
  outBody = prepared.body;
@@ -499,6 +501,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
499
501
  outBody,
500
502
  destinationDecodesNativeCompactionBlob(provider),
501
503
  threadServingIdentityChanged,
504
+ parsed._compactionRequest !== true,
502
505
  ),
503
506
  {
504
507
  preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true,
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { COMPACT_PROMPT, compactionItemToText, decodeCompactionSummary, isCompactionItemType } from "../../responses/compaction";
2
+ import { COMPACT_PROMPT, appendCheckpointContinuationNote, compactionItemToText, decodeCompactionSummary, isCompactionItemType } from "../../responses/compaction";
3
3
  import { debugProviderDiagnostic } from "../../lib/debug";
4
4
  import { modelInList } from "../../types";
5
5
  import { isPlainObject } from "./internal";
@@ -260,10 +260,12 @@ export function scrubOcxCompactionItems(
260
260
  body: unknown,
261
261
  destinationDecodesNativeBlob: boolean,
262
262
  threadServingIdentityChanged: boolean,
263
+ addContinuationNote = false,
263
264
  ): unknown {
264
265
  if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
265
266
 
266
267
  let changed = false;
268
+ let decodedCheckpoint = false;
267
269
  const input = body.input.map(item => {
268
270
  if (!isPlainObject(item) || !isCompactionItemType(item.type)) return item;
269
271
  const encrypted = typeof item.encrypted_content === "string" ? item.encrypted_content : undefined;
@@ -274,6 +276,7 @@ export function scrubOcxCompactionItems(
274
276
  && !threadServingIdentityChanged
275
277
  ) return item;
276
278
  changed = true;
279
+ if (decodeCompactionSummary(encrypted) !== null) decodedCheckpoint = true;
277
280
  return {
278
281
  type: "message",
279
282
  role: "user",
@@ -281,5 +284,10 @@ export function scrubOcxCompactionItems(
281
284
  };
282
285
  });
283
286
 
284
- return changed ? { ...body, input } : body;
287
+ if (!changed) return body;
288
+ // #6764: a decoded checkpoint is plain user text from here on, so name its role in the
289
+ // instructions. Compaction requests skip this; the summarizer must not be told to continue.
290
+ return addContinuationNote && decodedCheckpoint
291
+ ? { ...body, input, instructions: appendCheckpointContinuationNote(body.instructions) }
292
+ : { ...body, input };
285
293
  }