@bitkyc08/opencodex 2.86.0 → 2.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. package/gui/dist/assets/App-CHC89EXQ.js +58 -0
  2. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  3. package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
  4. package/gui/dist/assets/index-B5bsvxor.js +94 -0
  5. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  6. package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic/beta-allowlist.ts +9 -8
  10. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  11. package/src/adapters/anthropic/passthrough.ts +7 -23
  12. package/src/adapters/anthropic-progress.ts +84 -0
  13. package/src/adapters/anthropic-rejection-memo.ts +71 -0
  14. package/src/adapters/anthropic-replay.ts +135 -0
  15. package/src/adapters/anthropic-tool-catalog.ts +11 -0
  16. package/src/adapters/anthropic.ts +151 -33
  17. package/src/adapters/base.ts +2 -0
  18. package/src/adapters/claude-cli/adapter.ts +111 -23
  19. package/src/adapters/claude-cli/profiles.ts +2 -1
  20. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  21. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  22. package/src/adapters/coding-agent/protocol.ts +1 -1
  23. package/src/adapters/coding-agent/turn.ts +18 -2
  24. package/src/adapters/dsh-account.ts +97 -0
  25. package/src/adapters/google-progress-history.ts +53 -0
  26. package/src/adapters/google.ts +52 -9
  27. package/src/adapters/openai-chat/messages.ts +4 -3
  28. package/src/adapters/openai-chat/passthrough.ts +8 -2
  29. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  30. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  31. package/src/adapters/openai-responses/passthrough.ts +57 -125
  32. package/src/adapters/openai-responses/request-strips.ts +25 -3
  33. package/src/adapters/openai-responses/transport.ts +81 -0
  34. package/src/adapters/qoder/adapter.ts +4 -2
  35. package/src/adapters/registry.ts +5 -0
  36. package/src/adapters/zed.ts +26 -1
  37. package/src/advisor/activity.ts +55 -0
  38. package/src/advisor/budget.ts +62 -0
  39. package/src/advisor/consult.ts +4 -1
  40. package/src/advisor/context.ts +16 -4
  41. package/src/advisor/evidence-tool.ts +21 -0
  42. package/src/advisor/evidence.ts +117 -0
  43. package/src/advisor/runtime.ts +79 -31
  44. package/src/advisor/settings.ts +11 -2
  45. package/src/bridge/errors.ts +19 -1
  46. package/src/bridge/internal.ts +7 -1
  47. package/src/bridge/response-json.ts +18 -3
  48. package/src/bridge/sse.ts +59 -8
  49. package/src/bridge/terminal-refusal.ts +59 -0
  50. package/src/claude/desktop-3p-library.ts +32 -7
  51. package/src/claude/desktop-3p.ts +55 -13
  52. package/src/claude/desktop-first-party.ts +1 -1
  53. package/src/claude/desktop-picker-platform.ts +4 -0
  54. package/src/claude/desktop-picker.ts +15 -8
  55. package/src/claude/desktop-system-proxy.ts +50 -4
  56. package/src/claude/inbound-content-options.ts +77 -0
  57. package/src/claude/inbound.ts +33 -0
  58. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  59. package/src/claude/intercept/picker-runtime.ts +6 -5
  60. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  61. package/src/claude/intercept/picker-trust.ts +19 -0
  62. package/src/claude/intercept/port-policy.ts +30 -0
  63. package/src/claude/intercept/runtime.ts +97 -94
  64. package/src/claude/outbound.ts +46 -13
  65. package/src/cli/account-api.ts +1 -0
  66. package/src/cli/account-policy-dto.ts +6 -0
  67. package/src/cli/account-policy.ts +34 -5
  68. package/src/cli/account-preservation.ts +44 -0
  69. package/src/cli/account.ts +8 -2
  70. package/src/cli/advisor.ts +17 -3
  71. package/src/cli/capabilities-accounts.ts +20 -1
  72. package/src/cli/capabilities-agents-routing.ts +5 -2
  73. package/src/cli/capabilities-base.ts +38 -10
  74. package/src/cli/capabilities-observe-system.ts +63 -3
  75. package/src/cli/capabilities-provider-models.ts +11 -0
  76. package/src/cli/codex-cli-update.ts +151 -41
  77. package/src/cli/codex-provider.ts +44 -0
  78. package/src/cli/combo-input.ts +10 -1
  79. package/src/cli/combo-result.ts +1 -1
  80. package/src/cli/combo.ts +2 -1
  81. package/src/cli/cross-home-owner.ts +33 -16
  82. package/src/cli/dispatch.ts +25 -4
  83. package/src/cli/doctor.ts +6 -1
  84. package/src/cli/ensure-desktop-cold-start.ts +60 -7
  85. package/src/cli/help.ts +3 -2
  86. package/src/cli/index.ts +1 -1
  87. package/src/cli/integration-plan-dto.ts +1 -0
  88. package/src/cli/log-history.ts +172 -0
  89. package/src/cli/loopback-family.ts +31 -0
  90. package/src/cli/models-runtime-subcommands.ts +1 -0
  91. package/src/cli/models-runtime.ts +72 -8
  92. package/src/cli/observe-stream.ts +2 -0
  93. package/src/cli/observe.ts +16 -4
  94. package/src/cli/provider.ts +27 -5
  95. package/src/cli/registry.ts +28 -8
  96. package/src/cli/runtime-api.ts +5 -3
  97. package/src/cli/system-command.ts +40 -8
  98. package/src/cli/uninstall-client-state.ts +32 -0
  99. package/src/cli/usage-compare.ts +309 -0
  100. package/src/cli/usage-model-search.ts +4 -4
  101. package/src/cli/usage-report.ts +2 -2
  102. package/src/cli/v2.ts +1 -1
  103. package/src/client/machine-listener.ts +1 -3
  104. package/src/client/runtime.ts +1 -0
  105. package/src/clients/client-path.ts +51 -0
  106. package/src/clients/config-export/contracts.ts +2 -1
  107. package/src/clients/config-export.ts +18 -44
  108. package/src/clients/gentle-shell-path.ts +87 -0
  109. package/src/codex/account-auto-switch.ts +2 -0
  110. package/src/codex/account-identity.ts +29 -0
  111. package/src/codex/account-label.ts +1 -1
  112. package/src/codex/account-lifecycle.ts +21 -5
  113. package/src/codex/account-preservation.ts +28 -0
  114. package/src/codex/account-use-remaining.ts +23 -0
  115. package/src/codex/app-server-processes.ts +97 -7
  116. package/src/codex/app-server-restart-service.ts +55 -4
  117. package/src/codex/auth-api/account-list.ts +4 -0
  118. package/src/codex/auth-api/account-pause-group.ts +6 -50
  119. package/src/codex/auth-api/account-preservation.ts +57 -0
  120. package/src/codex/auth-api/routes.ts +8 -0
  121. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  122. package/src/codex/auth-api.ts +0 -1
  123. package/src/codex/auth-context.ts +2 -2
  124. package/src/codex/catalog/auto-review.ts +20 -3
  125. package/src/codex/catalog/build-entries.ts +9 -1
  126. package/src/codex/catalog/combo-member.ts +14 -5
  127. package/src/codex/catalog/configured-tiers.ts +1 -1
  128. package/src/codex/catalog/gather-capture.ts +2 -0
  129. package/src/codex/catalog/metadata.ts +11 -9
  130. package/src/codex/catalog/model-hints.ts +21 -16
  131. package/src/codex/catalog/model-visibility.ts +3 -1
  132. package/src/codex/catalog/parsing.ts +30 -86
  133. package/src/codex/catalog/provider-models.ts +36 -34
  134. package/src/codex/catalog/routed-gather.ts +14 -13
  135. package/src/codex/cli-install-provenance.ts +105 -17
  136. package/src/codex/cli-update-plan.ts +545 -0
  137. package/src/codex/config-write-lock.ts +23 -2
  138. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  139. package/src/codex/desktop-app-restart.ts +21 -0
  140. package/src/codex/desktop-compatibility/installed-build.ts +24 -6
  141. package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
  142. package/src/codex/desktop-compatibility/runtime.ts +12 -3
  143. package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
  144. package/src/codex/features.ts +85 -45
  145. package/src/codex/inject/history-failure.ts +27 -0
  146. package/src/codex/inject/restore.ts +24 -2
  147. package/src/codex/inject/routing-classify.ts +4 -3
  148. package/src/codex/inject.ts +18 -27
  149. package/src/codex/main-account-hard-lock.ts +30 -1
  150. package/src/codex/native-profile-startup.ts +1 -1
  151. package/src/codex/ocx-compaction-history.ts +12 -10
  152. package/src/codex/paths.ts +86 -1
  153. package/src/codex/provider-routing.ts +174 -0
  154. package/src/codex/refresh.ts +2 -2
  155. package/src/codex/routing/health-store.ts +1 -1
  156. package/src/codex/routing/selection.ts +4 -3
  157. package/src/codex/routing/thread-affinity.ts +1 -1
  158. package/src/codex/signed-in-identity.ts +43 -0
  159. package/src/codex/subagent-model-fallback.ts +1 -1
  160. package/src/codex/sync.ts +6 -1
  161. package/src/codex/user-identity.ts +16 -5
  162. package/src/codex/websocket-activity.ts +161 -0
  163. package/src/codex/websocket-registry.ts +25 -1
  164. package/src/codex/windows-local-appdata.ts +84 -0
  165. package/src/combos/failover.ts +6 -1
  166. package/src/combos/index.ts +1 -0
  167. package/src/combos/resolve.ts +46 -10
  168. package/src/combos/types.ts +15 -0
  169. package/src/config/diagnostics.ts +33 -6
  170. package/src/config/live-reconcile.ts +38 -2
  171. package/src/config/load-degrade.ts +17 -0
  172. package/src/config/opencode-wire-upgrade.ts +54 -0
  173. package/src/config/opencode-zen-upgrade.ts +53 -0
  174. package/src/config/owner-registry.ts +22 -17
  175. package/src/config/provider-validation.ts +45 -2
  176. package/src/config/schema/config-schema.ts +22 -0
  177. package/src/config/schema/leaf-validators.ts +38 -9
  178. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  179. package/src/config/schema/native-reasoning-retention.ts +26 -0
  180. package/src/config.ts +2 -0
  181. package/src/generated/compatibility-version.json +657 -297
  182. package/src/grok/sync.ts +1 -1
  183. package/src/images/loop.ts +116 -27
  184. package/src/integrations/catalog-refresh.ts +1 -1
  185. package/src/integrations/mutation-plan.ts +1 -0
  186. package/src/integrations/registry.ts +8 -0
  187. package/src/lib/codex-restart-contract.ts +15 -0
  188. package/src/lib/desktop-upstream-tunnel.ts +18 -53
  189. package/src/lib/empty-tool-catalog.ts +62 -0
  190. package/src/lib/errors.ts +19 -3
  191. package/src/lib/gcp-adc.ts +96 -9
  192. package/src/lib/gcp-credential-input.ts +12 -0
  193. package/src/lib/lab-live-route-production.ts +5 -0
  194. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  195. package/src/lib/redact.ts +2 -2
  196. package/src/lib/request-failure-model.ts +2 -0
  197. package/src/oauth/account-quota-rank.ts +69 -2
  198. package/src/oauth/anthropic-account-refusal.ts +20 -2
  199. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  200. package/src/oauth/anthropic-routing.ts +1 -21
  201. package/src/oauth/anthropic.ts +38 -7
  202. package/src/oauth/callback-server.ts +27 -1
  203. package/src/oauth/chatgpt-device.ts +1 -1
  204. package/src/oauth/chatgpt-shared.ts +83 -0
  205. package/src/oauth/chatgpt.ts +8 -72
  206. package/src/oauth/dsh.ts +203 -0
  207. package/src/oauth/generic-account-failover.ts +40 -11
  208. package/src/oauth/generic-session-affinity.ts +125 -0
  209. package/src/oauth/index.ts +184 -109
  210. package/src/oauth/local-token-detect.ts +21 -1
  211. package/src/oauth/login-cli-errors.ts +44 -0
  212. package/src/oauth/login-cli.ts +69 -7
  213. package/src/oauth/login-flow-state.ts +371 -23
  214. package/src/oauth/meta-muse-errors.ts +41 -0
  215. package/src/oauth/meta-muse-keychain.ts +52 -0
  216. package/src/oauth/meta-muse.ts +51 -84
  217. package/src/oauth/model-discovery-auth.ts +3 -0
  218. package/src/oauth/pool-settings-capability.ts +4 -2
  219. package/src/oauth/refresh-flight-retirement.ts +48 -0
  220. package/src/oauth/session-affinity-key.ts +23 -0
  221. package/src/oauth/store-anthropic-instance.ts +9 -2
  222. package/src/oauth/store.ts +12 -5
  223. package/src/protocols/encoders/adapter-events.ts +6 -5
  224. package/src/protocols/encoders/messages.ts +9 -6
  225. package/src/protocols/provider-summary.ts +2 -1
  226. package/src/providers/api-key-cooldowns.ts +49 -0
  227. package/src/providers/api-key-resolve.ts +11 -6
  228. package/src/providers/api-key-response-ownership.ts +57 -0
  229. package/src/providers/api-key-selection.ts +31 -6
  230. package/src/providers/api-keys.ts +214 -3
  231. package/src/providers/claude-cli-usage.ts +96 -0
  232. package/src/providers/dsh-account-destination.ts +31 -0
  233. package/src/providers/dsh-account-models.ts +41 -0
  234. package/src/providers/gcp-credential-cleanup.ts +59 -0
  235. package/src/providers/key-failover.ts +54 -50
  236. package/src/providers/key-store.ts +7 -2
  237. package/src/providers/model-rename-fields.ts +3 -0
  238. package/src/providers/model-rename-migration.ts +2 -2
  239. package/src/providers/openai-tiers-destination.ts +11 -2
  240. package/src/providers/openai-tiers.ts +5 -12
  241. package/src/providers/opencode-go-transport.ts +3 -1
  242. package/src/providers/opencode-zen-rate-limit.ts +7 -2
  243. package/src/providers/quota/account-cache.ts +6 -5
  244. package/src/providers/quota/account-row.ts +34 -0
  245. package/src/providers/quota/antigravity.ts +28 -7
  246. package/src/providers/quota/dsh-account.ts +70 -0
  247. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  248. package/src/providers/quota-types.ts +2 -0
  249. package/src/providers/quota.ts +14 -20
  250. package/src/providers/registry/entries-core.ts +37 -8
  251. package/src/providers/registry/types.ts +1 -1
  252. package/src/providers/request-pacing.ts +77 -19
  253. package/src/providers/routed-config.ts +339 -0
  254. package/src/providers/service-tier.ts +2 -1
  255. package/src/quota/window-mapping.ts +5 -2
  256. package/src/responses/anthropic-progress.ts +34 -0
  257. package/src/responses/assistant-replay.ts +35 -0
  258. package/src/responses/compaction-history-framing.ts +65 -0
  259. package/src/responses/compaction.ts +6 -1
  260. package/src/responses/incomplete-reason.ts +31 -0
  261. package/src/responses/parser.ts +50 -5
  262. package/src/responses/reasoning-envelope.ts +4 -0
  263. package/src/responses/reasoning-replay-cache.ts +74 -6
  264. package/src/responses/reasoning-retention.ts +173 -0
  265. package/src/responses/retained-compaction.ts +46 -0
  266. package/src/responses/schema.ts +23 -0
  267. package/src/router.ts +8 -321
  268. package/src/routing/analytics.ts +7 -15
  269. package/src/routing/health.ts +37 -22
  270. package/src/routing/history/indexer.ts +6 -5
  271. package/src/routing/history/ingest.ts +6 -2
  272. package/src/routing/profile.ts +1 -1
  273. package/src/server/admission-model-scope.ts +17 -0
  274. package/src/server/auth-cors.ts +11 -0
  275. package/src/server/chat-completions.ts +7 -1
  276. package/src/server/chat-native-reasoning.ts +123 -0
  277. package/src/server/chat-native-sse.ts +110 -7
  278. package/src/server/chat-native.ts +76 -17
  279. package/src/server/claude-messages.ts +56 -414
  280. package/src/server/effort-policy.ts +1 -1
  281. package/src/server/images.ts +15 -2
  282. package/src/server/index/live-sideband.ts +3 -2
  283. package/src/server/index/model-alias-id.ts +36 -0
  284. package/src/server/index/optional-listeners.ts +2 -0
  285. package/src/server/index/serve-options.ts +20 -6
  286. package/src/server/index/startup-warnings.ts +2 -0
  287. package/src/server/index/websocket-handler.ts +18 -4
  288. package/src/server/index.ts +8 -8
  289. package/src/server/inference/advisor-diagnostics.ts +31 -0
  290. package/src/server/lifecycle.ts +13 -1
  291. package/src/server/management/advisor-routes.ts +15 -4
  292. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  293. package/src/server/management/combo-routes.ts +176 -50
  294. package/src/server/management/companion-routes.ts +1 -1
  295. package/src/server/management/config-routes.ts +75 -4
  296. package/src/server/management/cursor-integration-routes.ts +1 -1
  297. package/src/server/management/model-routes.ts +11 -6
  298. package/src/server/management/model-rows.ts +14 -0
  299. package/src/server/management/native-integration-routes.ts +2 -7
  300. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  301. package/src/server/management/oauth-account-routes.ts +87 -14
  302. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  303. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  304. package/src/server/management/provider-overwrite-carry.ts +7 -0
  305. package/src/server/management/provider-routes.ts +25 -30
  306. package/src/server/management/route-registry.ts +10 -1
  307. package/src/server/management-api.ts +3 -1
  308. package/src/server/messages-native-tool-guard.ts +185 -0
  309. package/src/server/messages-native.ts +117 -24
  310. package/src/server/messages-shared.ts +431 -0
  311. package/src/server/relay.ts +1 -1
  312. package/src/server/request-log-terminal-status.ts +1 -1
  313. package/src/server/request-log.ts +6 -1
  314. package/src/server/responses/account-change-state.ts +8 -2
  315. package/src/server/responses/adapter-continuation.ts +47 -19
  316. package/src/server/responses/adapter-delivery.ts +72 -12
  317. package/src/server/responses/adapter-dispatch.ts +46 -3
  318. package/src/server/responses/advisor-retention.ts +117 -0
  319. package/src/server/responses/advisor-slot.ts +73 -41
  320. package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
  321. package/src/server/responses/codex-ws-wire.ts +15 -8
  322. package/src/server/responses/combo-stream-preflight.ts +3 -2
  323. package/src/server/responses/compact.ts +303 -125
  324. package/src/server/responses/compaction-recovery.ts +20 -10
  325. package/src/server/responses/compaction-retention.ts +61 -0
  326. package/src/server/responses/context-overflow.ts +11 -6
  327. package/src/server/responses/core-auth.ts +6 -0
  328. package/src/server/responses/core-codex-account.ts +11 -1
  329. package/src/server/responses/core-combo-failure.ts +2 -0
  330. package/src/server/responses/core-combo.ts +121 -36
  331. package/src/server/responses/core-errors.ts +3 -3
  332. package/src/server/responses/core-lifetime.ts +8 -4
  333. package/src/server/responses/core-opaque-recovery.ts +35 -0
  334. package/src/server/responses/core-options.ts +7 -0
  335. package/src/server/responses/core-pipeline.ts +141 -0
  336. package/src/server/responses/core-replay.ts +23 -3
  337. package/src/server/responses/core.ts +2 -115
  338. package/src/server/responses/fetch-helpers.ts +73 -19
  339. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  340. package/src/server/responses/native-response-control.ts +4 -2
  341. package/src/server/responses/passthrough-delivery.ts +3 -1
  342. package/src/server/responses/passthrough-dispatch.ts +15 -13
  343. package/src/server/responses/passthrough-execution.ts +3 -2
  344. package/src/server/responses/request-prepare.ts +77 -12
  345. package/src/server/responses/request-sidecar-auth.ts +2 -2
  346. package/src/server/responses/request-transport.ts +104 -27
  347. package/src/server/responses/response-effects.ts +2 -0
  348. package/src/server/responses/response-tool-ownership.ts +21 -0
  349. package/src/server/responses/run-turn-execution.ts +96 -30
  350. package/src/server/responses/sidecar-execution.ts +63 -9
  351. package/src/server/responses/sidecar-send-budget.ts +2 -2
  352. package/src/server/responses/terminal-guard.ts +2 -16
  353. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  354. package/src/server/ws-bridge.ts +2 -0
  355. package/src/service/auth-preflight.ts +71 -0
  356. package/src/service/cli.ts +4 -2
  357. package/src/service/definition.ts +97 -0
  358. package/src/service/guards.ts +1 -61
  359. package/src/service/health-sentinel-linux.ts +112 -0
  360. package/src/service/health-sentinel-main.ts +76 -57
  361. package/src/service/health.ts +5 -89
  362. package/src/service/launchd.ts +27 -7
  363. package/src/service/logging.ts +72 -9
  364. package/src/service/repair.ts +2 -1
  365. package/src/service/state.ts +7 -8
  366. package/src/service/systemd.ts +1 -1
  367. package/src/service/windows-guarded-manager.ts +2 -1
  368. package/src/service/windows-ops.ts +1 -1
  369. package/src/service/windows-scheduler.ts +2 -7
  370. package/src/service/windows-taskxml.ts +2 -2
  371. package/src/service/windows-wscript.ts +14 -0
  372. package/src/service.ts +4 -2
  373. package/src/sidecar/auth.ts +2 -0
  374. package/src/types/config.ts +64 -3
  375. package/src/types/opencode-go.ts +61 -0
  376. package/src/types/opencode-zen.ts +35 -0
  377. package/src/types/provider.ts +32 -6
  378. package/src/types/request.ts +31 -2
  379. package/src/types/wire.ts +31 -37
  380. package/src/types.ts +2 -0
  381. package/src/update/npm-invocation.mjs +12 -2
  382. package/src/usage/cost.ts +48 -12
  383. package/src/usage/expected-prices.ts +3 -0
  384. package/src/usage/ledger-retention.ts +2 -1
  385. package/src/usage/ledger-scanner.ts +6 -5
  386. package/src/usage/log.ts +17 -8
  387. package/src/usage/summary.ts +30 -11
  388. package/src/usage/telemetry-contract.ts +1 -0
  389. package/src/usage/user-cost-overlays.ts +14 -19
  390. package/src/usage/user-cost-validation.ts +38 -0
  391. package/src/vision/eligibility.ts +13 -0
  392. package/src/web-search/anthropic-history.ts +59 -0
  393. package/src/web-search/loop.ts +236 -73
  394. package/src/web-search/progress-stream.ts +15 -2
  395. package/src/web-search/run-turn-loop.ts +9 -1
  396. package/gui/dist/assets/App-BjlttVLy.js +0 -58
  397. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  398. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  399. package/gui/dist/assets/index-BMssP4k3.js +0 -94
  400. package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
@@ -1,3 +1,4 @@
1
+ import { anthropicProgressEnvelope, isAnthropicProgressMirror } from "./anthropic-progress";
1
2
  import type {
2
3
  OcxAssistantMessage,
3
4
  OcxContentPart,
@@ -15,7 +16,8 @@ import { createToolChoiceResolver, namespacedToolName } from "../types";
15
16
  import { responsesRequestSchema } from "./schema";
16
17
  import { providerMetadataFromResponsesFunctionCall } from "./provider-opaque-metadata";
17
18
  import { lookupReplayThoughtSignature } from "./thought-signature-replay";
18
- import { CHECKPOINT_CONTINUATION_NOTE, compactionItemToText, decodeCompactionSummary, isCompactionItemType } from "./compaction";
19
+ import { decodeRetainedCompaction, RETAINED_COMPACTION_PREFIX } from "./retained-compaction";
20
+ import { CHECKPOINT_CONTINUATION_NOTE, compactionItemToText, decodeCompactionSummary, isCompactionItemType, SUMMARY_PREFIX } from "./compaction";
19
21
  import { previousResponseReplayPrefixLength } from "./state";
20
22
  import { decodeReasoningEnvelope } from "./reasoning-envelope";
21
23
  import { hasRoutedIdentity, nameRoutedIdentity } from "../adapters/identity";
@@ -28,6 +30,7 @@ import { mapToolChoice, buildTools, customToolNamespaces } from "./parser-tools"
28
30
  import { parseTextFormat } from "./parser-text-format";
29
31
  import { externalTaskInputContent } from "./task-input";
30
32
  import { normalizeVisualizationContext } from "./visualization-directives";
33
+ import { frameCompactionHistory } from "./compaction-history-framing";
31
34
 
32
35
  /**
33
36
  * Wrap a remembered proxy-side signature as provider metadata for a replayed tool call.
@@ -66,10 +69,35 @@ function repairJsonObjectEnvelope(text: string): Record<string, unknown> | undef
66
69
  return undefined;
67
70
  }
68
71
 
72
+ /**
73
+ * Repair the observed MiMo tool-call corruption: the model emits its proprietary
74
+ * `</function></tool_call>` closing tags INSIDE the JSON string value of the
75
+ * function arguments, and drops the JSON object's closing brace. Strip the
76
+ * suffix, restore the brace, and only accept the repair when the result parses
77
+ * into an object.
78
+ */
79
+ function repairMimoToolCallEnvelope(text: string): Record<string, unknown> | undefined {
80
+ const suffix = "</function></tool_call>";
81
+ if (!text.endsWith(suffix)) return undefined;
82
+ const stripped = text.slice(0, -suffix.length);
83
+ if (!stripped.startsWith("{")) return undefined;
84
+ // A trailing brace or quote can itself belong to the unterminated string.
85
+ // Try only the three observed envelope endings, accepting an object parse.
86
+ for (const candidate of [stripped, `${stripped}}`, `${stripped}"}`]) {
87
+ try {
88
+ const parsed: unknown = JSON.parse(candidate);
89
+ if (isObj(parsed)) return parsed;
90
+ } catch {
91
+ /* try the next bounded envelope ending */
92
+ }
93
+ }
94
+ return undefined;
95
+ }
96
+
69
97
  /**
70
98
  * Decode a function call's serialized arguments the way history replay tolerates them: empty
71
- * or non-JSON text becomes `{}` instead of failing every later turn, and the one bounded
72
- * lost-brace corruption above is repaired. Shared with the direct Chat decoder
99
+ * or non-JSON text becomes `{}` instead of failing every later turn, and the bounded
100
+ * lost-brace and MiMo closing-tag corruptions above are repaired. Shared with the direct Chat decoder
73
101
  * (`src/protocols/codecs/chat-ir.ts`) so both decode the same call to the same object.
74
102
  */
75
103
  export function functionCallArgumentsFromText(callId: string, rawArguments: string | undefined): Record<string, unknown> {
@@ -80,6 +108,15 @@ export function functionCallArgumentsFromText(callId: string, rawArguments: stri
80
108
  const parsed: unknown = JSON.parse(rawArgs);
81
109
  if (isObj(parsed)) args = parsed;
82
110
  } catch {
111
+ // Corruptions observed on Xiaomi MiMo routed requests, each with a bounded
112
+ // repair so a serialized call the model can still see is restored instead
113
+ // of replaying {} forever.
114
+ const mimoRepaired = repairMimoToolCallEnvelope(rawArgs);
115
+ if (mimoRepaired !== undefined) {
116
+ args = mimoRepaired;
117
+ console.warn(`[parser] function_call ${callId} repaired MiMo tool-call envelope (tags inside string)`);
118
+ return args;
119
+ }
83
120
  // One observed serialization corruption loses exactly the JSON object's opening
84
121
  // brace; the closed envelope is tight enough to repair back into a call the
85
122
  // routed model can still see and retry, instead of replaying {} forever.
@@ -189,7 +226,7 @@ export function parseRequest(
189
226
  if (!parsed.success) {
190
227
  throw new Error(`responses parse error: ${parsed.error.message}`);
191
228
  }
192
- const data = parsed.data;
229
+ const data = frameCompactionHistory(parsed.data);
193
230
  const now = Date.now();
194
231
  const messages: OcxMessage[] = [];
195
232
  // Built before the item loop: a custom_tool_call echoed back in `input` needs the
@@ -239,6 +276,7 @@ export function parseRequest(
239
276
  } else if (data.input) {
240
277
  for (let inputIndex = 0; inputIndex < data.input.length; inputIndex++) {
241
278
  const item = data.input[inputIndex];
279
+ if (inputIndex > 0 && isAnthropicProgressMirror(item, anthropicProgressEnvelope(data.input[inputIndex - 1]))) continue;
242
280
  const effectiveType = (item as { type?: string }).type ?? ("role" in item ? "message" : undefined);
243
281
  const itemRole = (item as { role?: string }).role;
244
282
  const externalTaskInput = effectiveType === "function_call_output" ? externalTaskInputContent(item) : undefined;
@@ -281,7 +319,7 @@ export function parseRequest(
281
319
  }
282
320
 
283
321
  if (isCompactionItemType(effectiveType)) {
284
- // A stored summary from a previous compaction. Decode our ocx1 envelope into plain text so
322
+ // A stored summary from a previous compaction. Decode our ocx1/ocx2 envelopes into plain text so
285
323
  // the routed model keeps the compacted context; real OpenAI-encrypted blobs degrade to a note.
286
324
  // `context_compaction` (encrypted_content optional) is codex-rs's local-compaction marker;
287
325
  // with no payload it is a pure marker (the summary follows as its own user message), so it
@@ -293,6 +331,13 @@ export function parseRequest(
293
331
  if (effectiveType === "context_compaction" && typeof encrypted !== "string") continue;
294
332
  if (typeof encrypted === "string" && decodeCompactionSummary(encrypted) !== null) decodedCheckpoint = true;
295
333
  pendingReasoning.length = 0;
334
+ if (typeof encrypted === "string" && encrypted.startsWith(RETAINED_COMPACTION_PREFIX)) {
335
+ const retained = decodeRetainedCompaction(encrypted);
336
+ if (!retained) throw new Error("Invalid OpenCodex retained compaction envelope");
337
+ messages.push({ role: "user", content: `${SUMMARY_PREFIX}\n\n${retained.summary}`, timestamp: now });
338
+ messages.push(...retained.reasoning.map(content => ({ role: "user" as const, content, timestamp: now })));
339
+ continue;
340
+ }
296
341
  messages.push({
297
342
  role: "user",
298
343
  content: compactionItemToText(typeof encrypted === "string" ? encrypted : undefined),
@@ -20,6 +20,8 @@ export const OCX_REASONING_PREFIX = "ocxr1:";
20
20
  export interface ReasoningEnvelope {
21
21
  /** Anthropic thinking-block signature (signature_delta), if captured. */
22
22
  sig?: string;
23
+ /** ID of the immediately following commentary copy of this signed thinking block. */
24
+ upd?: string;
23
25
  /** Raw redacted_thinking block data payloads, order preserved. */
24
26
  red?: string[];
25
27
  /**
@@ -133,6 +135,8 @@ export function decodeReasoningEnvelope(encryptedContent: string, budget?: Trans
133
135
  const obj = parsed as { sig?: unknown; red?: unknown };
134
136
  const envelope: ReasoningEnvelope = {};
135
137
  if (typeof obj.sig === "string") envelope.sig = obj.sig;
138
+ const upd = (parsed as { upd?: unknown }).upd;
139
+ if (typeof upd === "string" && /^msg_[a-f0-9]{32}$/.test(upd)) envelope.upd = upd;
136
140
  if (Array.isArray(obj.red)) {
137
141
  const red = obj.red.filter((r): r is string => typeof r === "string");
138
142
  if (red.length > 0) envelope.red = red;
@@ -46,6 +46,7 @@ export function nativeReasoningTagMatches(expected: string | undefined, presente
46
46
  const MAX_TOTAL_BYTES = 256 * 1024;
47
47
  const TTL_MS = 60 * 60 * 1000;
48
48
  const OPAQUE_BLOB_REJECTION_TTL_MS = 5 * 60 * 1000;
49
+ const MAX_OPAQUE_BLOB_REJECTION_FINGERPRINTS = 256;
49
50
  const replayIdentityKey = randomBytes(32);
50
51
  const CREDENTIAL_HEADER_NAMES = new Set([
51
52
  "authorization",
@@ -78,8 +79,14 @@ interface ServingIdentityEntry {
78
79
  interface OpaqueBlobRejectionEntry {
79
80
  bytes: number;
80
81
  at: number;
82
+ /** Undefined retains the legacy blanket rejection; only payload digests are stored here. */
83
+ fingerprints?: ReadonlySet<string>;
81
84
  }
82
85
 
86
+ export type ReasoningReplayOpaqueBlobRejection =
87
+ | { mode: "blanket" }
88
+ | { mode: "selective"; fingerprints: ReadonlySet<string> };
89
+
83
90
  const entries = new Map<string, CacheEntry>();
84
91
  const servingIdentities = new Map<string, ServingIdentityEntry>();
85
92
  const opaqueBlobRejections = new Map<string, OpaqueBlobRejectionEntry>();
@@ -200,12 +207,34 @@ function sweepExpiredOpaqueBlobRejections(at: number): void {
200
207
  export function reasoningReplayServingIdentityChanged(
201
208
  scope: OcxReasoningReplayScopeRef | undefined,
202
209
  ): boolean {
210
+ return reasoningReplayServingIdentityChange(scope) !== undefined;
211
+ }
212
+
213
+ /** Known durable route differences; an unknown issuer never establishes compatibility. */
214
+ export function reasoningReplayServingIdentityChange(
215
+ scope: OcxReasoningReplayScopeRef | undefined,
216
+ ): {
217
+ providerChanged: boolean;
218
+ destinationChanged: boolean;
219
+ adapterChanged: boolean;
220
+ modelChanged: boolean;
221
+ credentialChanged: boolean;
222
+ } | undefined {
203
223
  const current = servingIdentityFor(scope);
204
- if (!current) return false;
224
+ if (!current) return undefined;
205
225
  const at = now();
206
226
  sweepExpiredServingIdentities(at);
207
227
  const previous = servingIdentities.get(current.threadId);
208
- return previous !== undefined && previous.identity !== current.identity;
228
+ if (previous === undefined || previous.identity === current.identity) return undefined;
229
+ const before = JSON.parse(previous.identity) as ReasoningReplayIdentityTuple;
230
+ const after = JSON.parse(current.identity) as ReasoningReplayIdentityTuple;
231
+ return {
232
+ providerChanged: before[0] !== after[0],
233
+ destinationChanged: before[1] !== after[1],
234
+ adapterChanged: before[2] !== after[2],
235
+ modelChanged: before[3] !== after[3],
236
+ credentialChanged: before[4] !== after[4],
237
+ };
209
238
  }
210
239
 
211
240
  /**
@@ -288,18 +317,57 @@ export function reasoningReplayOpaqueBlobRejectionMemoized(
288
317
  return opaqueBlobRejections.has(key);
289
318
  }
290
319
 
320
+ /** Undefined means no live memo; a selective result owns a defensive copy of its digests. */
321
+ export function getReasoningReplayOpaqueBlobRejection(
322
+ scope: OcxReasoningReplayScopeRef | undefined,
323
+ ): ReasoningReplayOpaqueBlobRejection | undefined {
324
+ const key = opaqueBlobRejectionKeyFor(scope);
325
+ if (!key) return undefined;
326
+ sweepExpiredOpaqueBlobRejections(now());
327
+ const entry = opaqueBlobRejections.get(key);
328
+ return entry === undefined ? undefined : entry.fingerprints === undefined ? { mode: "blanket" }
329
+ : { mode: "selective", fingerprints: new Set(entry.fingerprints) };
330
+ }
331
+
332
+ /** Incomplete, invalid or unbounded enumerations cannot safely narrow the legacy rejection. */
333
+ function boundedRejectionFingerprints(fingerprints: ReadonlySet<string> | undefined): Set<string> | undefined {
334
+ if (fingerprints === undefined) return undefined;
335
+ const copy = new Set<string>();
336
+ try {
337
+ // The intrinsic validates a real Set without trusting an overridden iterator or size getter.
338
+ for (const value of Set.prototype.values.call(fingerprints) as IterableIterator<unknown>) {
339
+ if (typeof value !== "string" || value.length !== 64 || !/^[a-f0-9]{64}$/.test(value)
340
+ || copy.size >= MAX_OPAQUE_BLOB_REJECTION_FINGERPRINTS) return undefined;
341
+ copy.add(value);
342
+ }
343
+ } catch { return undefined; }
344
+ return copy.size > 0 ? copy : undefined;
345
+ }
346
+
291
347
  /** Record only after a blobless retry succeeded for this durable serving identity. */
292
348
  export function rememberReasoningReplayOpaqueBlobRejection(
293
349
  scope: OcxReasoningReplayScopeRef | undefined,
350
+ fingerprints?: ReadonlySet<string>,
294
351
  ): void {
295
352
  const key = opaqueBlobRejectionKeyFor(scope);
296
353
  if (!key) return;
297
- const bytes = Buffer.byteLength(key, "utf8");
298
- if (bytes > MAX_TOTAL_BYTES) return;
354
+ const keyBytes = Buffer.byteLength(key, "utf8");
355
+ if (keyBytes > MAX_TOTAL_BYTES) return;
299
356
  const at = now();
300
357
  sweepExpiredOpaqueBlobRejections(at);
301
- if (opaqueBlobRejections.has(key)) deleteOpaqueBlobRejection(key);
302
- opaqueBlobRejections.set(key, { bytes, at });
358
+ const previous = opaqueBlobRejections.get(key);
359
+ let merged = previous && previous.fingerprints === undefined ? undefined : boundedRejectionFingerprints(fingerprints);
360
+ if (merged && previous?.fingerprints) {
361
+ for (const fingerprint of previous.fingerprints) {
362
+ merged.add(fingerprint);
363
+ if (merged.size > MAX_OPAQUE_BLOB_REJECTION_FINGERPRINTS) { merged = undefined; break; }
364
+ }
365
+ }
366
+ // A near-limit key can still retain its existing blanket memo if the digest set will not fit.
367
+ if (merged && keyBytes + merged.size * 64 > MAX_TOTAL_BYTES) merged = undefined;
368
+ const bytes = keyBytes + (merged?.size ?? 0) * 64;
369
+ if (previous) deleteOpaqueBlobRejection(key);
370
+ opaqueBlobRejections.set(key, { bytes, at, ...(merged ? { fingerprints: merged } : {}) });
303
371
  opaqueBlobRejectionTotalBytes += bytes;
304
372
  while (
305
373
  (opaqueBlobRejectionTotalBytes > MAX_TOTAL_BYTES || opaqueBlobRejections.size > MAX_ENTRIES)
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Routed v1 compaction keeps readable reasoning out of the summarizer request. Text within
3
+ * the configured budget is held locally until success; oversized text is archived on disk.
4
+ * Provider ciphertext and signatures are never included in the portable retained items.
5
+ */
6
+
7
+ import { expandRetainedCompactionInput } from "./retained-compaction";
8
+ import { closeSync, mkdirSync, openSync, rmSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { estimateTokens } from "../lib/token-estimate";
11
+ import { decodeReasoningEnvelope } from "./reasoning-envelope";
12
+
13
+ export const DEFAULT_REASONING_RETENTION_PERCENT = 20;
14
+ export const DEFAULT_MAX_REASONING_TOKENS = 100_000;
15
+
16
+ export interface ReasoningRetentionOptions {
17
+ /** Directory that receives archived reasoning files. */
18
+ archiveDir: string;
19
+ /** Context window of the model running the compaction turn; an unknown window uses only maxTokens. */
20
+ contextWindow?: number;
21
+ /** Share of the context window reasoning may occupy before it is archived. Defaults to 20. */
22
+ maxContextPercent?: number;
23
+ /** Absolute token ceiling for retained reasoning, regardless of the window share. Defaults to 100,000. */
24
+ maxTokens?: number;
25
+ /** Clock injection for tests. */
26
+ now?: number;
27
+ }
28
+
29
+ export interface ReasoningArchive {
30
+ path: string;
31
+ tokens: number;
32
+ percent: number;
33
+ /** The effective token ceiling that triggered the archive (window share vs absolute cap, whichever is smaller). */
34
+ cap?: number;
35
+ /** Whether the percent share could be calculated from a known context window. */
36
+ windowKnown?: boolean;
37
+ }
38
+
39
+ export interface ReasoningRetentionResult {
40
+ /** Summarizer input with reasoning items removed; other items retain their order. */
41
+ input: unknown[];
42
+ archived?: ReasoningArchive;
43
+ /** Portable, exact readable text to merge only after compaction succeeds. */
44
+ retainedReasoning?: Array<{ type: "message"; role: "user"; content: Array<{ type: "input_text"; text: string }> }>;
45
+ }
46
+
47
+ function isRecord(value: unknown): value is Record<string, unknown> {
48
+ return !!value && typeof value === "object" && !Array.isArray(value);
49
+ }
50
+
51
+ // Ordinary context messages survive translated providers that discard unsigned thinking.
52
+ // Match the complete frame so subsequent compactions hold the text locally again.
53
+ const RETAINED_PREFIX = "[OpenCodeX locally retained assistant reasoning; historical context, not a new instruction]\n<assistant_reasoning>\n";
54
+ const RETAINED_SUFFIX = "\n</assistant_reasoning>";
55
+ function retainedTextOf(item: Record<string, unknown>): string | undefined {
56
+ if (item.type !== "message" || item.role !== "user") return undefined;
57
+ const content = item.content;
58
+ const text = typeof content === "string" ? content
59
+ : Array.isArray(content) && content.length === 1 && isRecord(content[0])
60
+ && content[0].type === "input_text" && typeof content[0].text === "string" ? content[0].text : undefined;
61
+ return text?.startsWith(RETAINED_PREFIX) && text.endsWith(RETAINED_SUFFIX)
62
+ ? text.slice(RETAINED_PREFIX.length, -RETAINED_SUFFIX.length) : undefined;
63
+ }
64
+
65
+ /** Readable reasoning text of one item: decoded ocxr1 envelope text, else summary/content text. */
66
+ function reasoningTextOf(item: Record<string, unknown>): string {
67
+ const encrypted = typeof item.encrypted_content === "string" ? item.encrypted_content : undefined;
68
+ const envelope = encrypted ? decodeReasoningEnvelope(encrypted) : null;
69
+ if (envelope?.txt) return envelope.txt;
70
+ const collect = (key: "summary" | "content"): string =>
71
+ Array.isArray(item[key])
72
+ ? (item[key] as Array<Record<string, unknown>>)
73
+ .map(part => (isRecord(part) && typeof part.text === "string" ? part.text : ""))
74
+ .join("")
75
+ : "";
76
+ return collect("summary") || collect("content");
77
+ }
78
+
79
+ /** Plaintext of every reasoning item in a Responses input array, order preserved. */
80
+ export function collectReasoningTexts(input: unknown): string[] {
81
+ if (!Array.isArray(input)) return [];
82
+ const texts: string[] = [];
83
+ for (const item of input) {
84
+ if (!isRecord(item)) continue;
85
+ const text = item.type === "reasoning" ? reasoningTextOf(item) : retainedTextOf(item) ?? "";
86
+ if (text.length > 0) texts.push(text);
87
+ }
88
+ return texts;
89
+ }
90
+
91
+ function retentionPercent(value: number | undefined): number {
92
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= 100
93
+ ? value
94
+ : DEFAULT_REASONING_RETENTION_PERCENT;
95
+ }
96
+
97
+ function retentionTokenCap(value: number | undefined): number {
98
+ return typeof value === "number" && Number.isInteger(value) && value > 0
99
+ ? value
100
+ : DEFAULT_MAX_REASONING_TOKENS;
101
+ }
102
+
103
+ /** Separate reasoning locally before dispatch; unknown windows enforce the absolute cap. */
104
+ export function applyReasoningRetention(
105
+ input: unknown,
106
+ options: ReasoningRetentionOptions,
107
+ ): ReasoningRetentionResult {
108
+ if (!Array.isArray(input)) return { input: [] };
109
+ input = expandRetainedCompactionInput(input);
110
+ if (!Array.isArray(input)) return { input: [] };
111
+ const texts = collectReasoningTexts(input);
112
+ const retained = input.filter(item => !(isRecord(item) && (item.type === "reasoning" || retainedTextOf(item) !== undefined)));
113
+ if (texts.length === 0) return { input: retained.length === input.length ? input : retained };
114
+ const window = options.contextWindow;
115
+
116
+ const percent = retentionPercent(options.maxContextPercent);
117
+ const cap = retentionTokenCap(options.maxTokens);
118
+ // The window share alone inflates without bound on 1M-window models (20% of 1M is ~210K
119
+ // tokens), so an absolute ceiling applies on top of it: reasoning keeps at most the
120
+ // SMALLER of the two limits. An unknown window leaves the percent share unusable, so the
121
+ // absolute cap alone stands guard.
122
+ const windowKnown = typeof window === "number" && Number.isInteger(window) && window > 0;
123
+ const limit = windowKnown ? Math.min(Math.floor(window * (percent / 100)), cap) : cap;
124
+ const framedTexts = texts.map(text => RETAINED_PREFIX + text + RETAINED_SUFFIX);
125
+ // Budget the historical context that is actually replayed, including every block's frame.
126
+ const tokens = estimateTokens(framedTexts.join("\n"));
127
+ if (tokens <= limit) return {
128
+ input: retained,
129
+ retainedReasoning: framedTexts.map(text => ({ type: "message", role: "user",
130
+ content: [{ type: "input_text", text }] })),
131
+ };
132
+
133
+ mkdirSync(options.archiveDir, { recursive: true, mode: 0o700 });
134
+ const stamp = new Date(options.now ?? Date.now()).toISOString().replace(/[:.]/g, "-");
135
+ const path = join(options.archiveDir, "reasoning-" + stamp + "-" + Math.random().toString(36).slice(2, 8) + ".md");
136
+ // Only a successful exclusive open grants ownership; an occupied path is never removed.
137
+ const fd = openSync(path, "wx", 0o600);
138
+ let failure: unknown;
139
+ try { writeFileSync(fd, texts.join("\n\n---\n\n"), "utf-8"); } catch (error) { failure = error; }
140
+ try { closeSync(fd); } catch (error) { failure ??= error; }
141
+ if (failure !== undefined) {
142
+ try { rmSync(path); } catch { /* preserve the original I/O error; cleanup is best-effort */ }
143
+ throw failure;
144
+ }
145
+
146
+ const limitDescription = windowKnown
147
+ ? percent + "% of the context window, capped at " + limit + " tokens"
148
+ : "absolute cap of " + limit + " tokens (context window unknown)";
149
+ const note = [
150
+ "[reasoning archived locally] " + texts.length + " reasoning block(s) totalling ~" + tokens + " tokens",
151
+ "exceeded the configured retention limit (" + limitDescription + ") and were moved out of band.",
152
+ "Full text: " + path,
153
+ ].join(" ");
154
+ const triggerIndex = retained.findIndex(item => isRecord(item) && item.type === "compaction_trigger");
155
+ const noteItem = { type: "message", role: "user", content: note };
156
+ if (triggerIndex >= 0) retained.splice(triggerIndex, 0, noteItem);
157
+ else retained.push(noteItem);
158
+ return { input: retained, archived: { path, tokens, percent, cap: limit, windowKnown } };
159
+ }
160
+
161
+ /**
162
+ * User-visible notice appended to a compaction summary when reasoning was archived. The summary
163
+ * is what the next model and the user both read, so the notice lives there rather than in a log.
164
+ */
165
+ export function appendRetentionNotice(summary: string, archived: ReasoningArchive | undefined): string {
166
+ if (!archived) return summary;
167
+ const limitDescription = archived.windowKnown === false
168
+ ? "absolute cap of " + (archived.cap ?? "unknown") + " tokens (context window unknown)"
169
+ : archived.percent + "% of the context window (effective cap: " + (archived.cap ?? "unknown") + " tokens)";
170
+ return summary + "\n\n[reasoning retention notice] Reasoning of ~" + archived.tokens + " tokens exceeded " + limitDescription
171
+ + " and was archived locally at " + archived.path
172
+ + ". This summary covers conclusions only; read that file for the full reasoning.";
173
+ }
@@ -0,0 +1,46 @@
1
+ /** Versioned, proxy-owned compaction data. This is encoding, never provider ciphertext. */
2
+ export const RETAINED_COMPACTION_PREFIX = "ocx2:";
3
+
4
+ export interface RetainedCompaction {
5
+ version: 2;
6
+ summary: string;
7
+ /** Already framed historical context, preserved byte-for-byte on replay. */
8
+ reasoning: string[];
9
+ }
10
+
11
+ export function encodeRetainedCompaction(summary: string, reasoning: string[]): string {
12
+ return RETAINED_COMPACTION_PREFIX + Buffer.from(JSON.stringify({ version: 2, summary, reasoning }), "utf-8").toString("base64");
13
+ }
14
+
15
+ export function decodeRetainedCompaction(value: string): RetainedCompaction | null {
16
+ if (!value.startsWith(RETAINED_COMPACTION_PREFIX)) return null;
17
+ try {
18
+ const payload = value.slice(RETAINED_COMPACTION_PREFIX.length);
19
+ const bytes = Buffer.from(payload, "base64");
20
+ if (bytes.toString("base64") !== payload) return null;
21
+ const parsed: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
22
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
23
+ const item = parsed as Record<string, unknown>;
24
+ if (Object.keys(item).some(key => !["version", "summary", "reasoning"].includes(key))
25
+ || item.version !== 2 || typeof item.summary !== "string" || !item.summary.trim()
26
+ || !Array.isArray(item.reasoning) || !item.reasoning.every(text => typeof text === "string")) return null;
27
+ return item as unknown as RetainedCompaction;
28
+ } catch { return null; }
29
+ }
30
+
31
+ /** Expand only our structured envelopes; callers retain their existing native/ocx1 policy. */
32
+ export function expandRetainedCompactionInput(input: unknown): unknown {
33
+ if (!Array.isArray(input)) return input;
34
+ if (!input.some(item => item && typeof item === "object" && ["compaction", "compaction_summary", "context_compaction"].includes(item.type)
35
+ && typeof item.encrypted_content === "string" && item.encrypted_content.startsWith(RETAINED_COMPACTION_PREFIX))) return input;
36
+ return input.flatMap(item => {
37
+ if (!item || typeof item !== "object" || !["compaction", "compaction_summary", "context_compaction"].includes(item.type)
38
+ || typeof item.encrypted_content !== "string" || !item.encrypted_content.startsWith(RETAINED_COMPACTION_PREFIX)) return [item];
39
+ const data = decodeRetainedCompaction(item.encrypted_content);
40
+ if (!data) throw new Error("Invalid OpenCodex retained compaction envelope");
41
+ return [
42
+ { ...item, encrypted_content: "ocx1:" + Buffer.from(data.summary, "utf-8").toString("base64") },
43
+ ...data.reasoning.map(text => ({ type: "message", role: "user", content: [{ type: "input_text", text }] })),
44
+ ];
45
+ });
46
+ }
@@ -1,4 +1,5 @@
1
1
  import * as z from "zod/v4";
2
+ import { isCompactionItemType } from "./compaction";
2
3
 
3
4
  const inputTextSchema = z.object({ type: z.literal("input_text"), text: z.string() });
4
5
  const plainTextSchema = z.object({ type: z.literal("text"), text: z.string() });
@@ -75,6 +76,28 @@ const reasoningItemSchema = z.object({
75
76
  // Round-tripped opaque payload (native OpenAI encryption OR the proxy's ocxr1 envelope).
76
77
  encrypted_content: z.string().optional(),
77
78
  });
79
+
80
+ /** A native v1 replacement history may establish replay provenance only after body success. */
81
+ export const nativeCompactResponseSchema = z.object({
82
+ status: z.literal("completed").optional(),
83
+ error: z.null().optional(),
84
+ output: z.array(z.union([
85
+ userMessageItemSchema.required({ content: true }),
86
+ systemMessageItemSchema.required({ content: true }),
87
+ assistantMessageItemSchema.required({ content: true }),
88
+ reasoningItemSchema,
89
+ z.object({
90
+ type: z.string().refine(isCompactionItemType),
91
+ encrypted_content: z.string().refine(value => value.trim().length > 0),
92
+ }),
93
+ ])).min(1).refine(items => items.some(item => {
94
+ if (isCompactionItemType(item.type)) return true;
95
+ if (!("role" in item)) return false;
96
+ return typeof item.content === "string" ? item.content.trim().length > 0
97
+ : item.content.some(part => "text" in part && part.text.trim().length > 0);
98
+ })),
99
+ });
100
+
78
101
  const functionCallItemSchema = z.object({
79
102
  type: z.literal("function_call"),
80
103
  id: z.string().optional(),