@bitkyc08/opencodex 2.84.0 → 2.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -0,0 +1,157 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ /**
4
+ * Manager-mediated runtime recovery after an update (#7043).
5
+ *
6
+ * When the service refresh after an update fails or leaves a non-viable manager, both update
7
+ * lanes used to fall straight back to a detached `ocx start`. That proxy answers, but launchd
8
+ * or systemd no longer owns it: a crash is not restarted, and the next `launchctl kickstart`
9
+ * races the detached process for the port. Before that fallback, the lanes now ask the manager
10
+ * to restart the job it already holds, and count it as recovered only when the serving proxy is
11
+ * provably the manager's process: the runtime record's PID is the manager's main PID, or that
12
+ * PID's direct child (the job execs the Node launcher, which runs the Bun proxy as its child).
13
+ *
14
+ * Plain synchronous ESM because `bin/ocx.mjs` runs under Node, cannot import TypeScript, and
15
+ * keeps straight-line control flow; `src/update/index.ts` composes the same pieces with its own
16
+ * asynchronous identity wait.
17
+ */
18
+
19
+ /** Must match `LABEL` / `TASK` in src/service/state.ts; a test pins the pairing. */
20
+ export const LAUNCHD_SERVICE_LABEL = "com.opencodex.proxy";
21
+ export const SYSTEMD_SERVICE_UNIT = "opencodex-proxy";
22
+
23
+ function positivePid(value) {
24
+ if (typeof value !== "string" || !/^\d+$/.test(value.trim())) return null;
25
+ const pid = Number(value.trim());
26
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
27
+ }
28
+
29
+ function defaultRun(file, args) {
30
+ // An armed test process must never reach the developer's real launchd or systemd job;
31
+ // same refusal as `assertLiveServiceManagerAllowed` for the TypeScript service layer.
32
+ if (process.env.OCX_TEST_HOME_GUARD === "1") {
33
+ return { ok: false, stdout: "", stderr: `refusing to run ${file} from an armed test process` };
34
+ }
35
+ // A bare SSH session has no user-bus address; systemd's user manager still lives at the
36
+ // conventional runtime directory (same fallback as the service layer's ensureUserBusEnv).
37
+ const env = file === "systemctl" && !process.env.XDG_RUNTIME_DIR && typeof process.getuid === "function"
38
+ ? { ...process.env, XDG_RUNTIME_DIR: `/run/user/${process.getuid()}` }
39
+ : process.env;
40
+ const result = spawnSync(file, args, { encoding: "utf8", timeout: 20_000, windowsHide: true, env });
41
+ if (result.error) {
42
+ // A timeout kills only the waiting client; the manager's queued job keeps running.
43
+ const timedOut = result.error.code === "ETIMEDOUT";
44
+ return { ok: false, stdout: "", stderr: String(result.error.message ?? ""), ...(timedOut ? { timedOut } : {}) };
45
+ }
46
+ return { ok: result.status === 0, stdout: String(result.stdout ?? ""), stderr: String(result.stderr ?? "").trim() };
47
+ }
48
+
49
+ function launchdTarget(uid) {
50
+ return `gui/${uid}/${LAUNCHD_SERVICE_LABEL}`;
51
+ }
52
+
53
+ /** `launchctl print` reports a running job as a `pid = <n>` line. */
54
+ export function parseLaunchdJobPid(printOutput) {
55
+ const match = /^\s*pid\s*=\s*(\d+)\s*$/m.exec(String(printOutput ?? ""));
56
+ return positivePid(match?.[1]);
57
+ }
58
+
59
+ /** `systemctl show -p MainPID` prints `MainPID=<n>`; 0 means not running. */
60
+ export function parseSystemdMainPid(showOutput) {
61
+ const match = /^MainPID=(\d+)\s*$/m.exec(String(showOutput ?? ""));
62
+ return positivePid(match?.[1]);
63
+ }
64
+
65
+ function resolve(deps) {
66
+ const platform = deps.platform ?? process.platform;
67
+ const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
68
+ return { platform, uid, run: deps.run ?? defaultRun };
69
+ }
70
+
71
+ /** Whether this platform has a manager this module can ask (launchd or systemd). */
72
+ export function hasRestartableServiceManager(deps = {}) {
73
+ const { platform } = resolve(deps);
74
+ return platform === "darwin" || platform === "linux";
75
+ }
76
+
77
+ /**
78
+ * Ask the manager to restart the job in place. Never throws. `unsettled` means the client gave up
79
+ * waiting while the manager may still complete the restart: callers must not start a competing
80
+ * proxy on that answer.
81
+ */
82
+ export function restartServiceManagerJob(deps = {}) {
83
+ const { platform, uid, run } = resolve(deps);
84
+ const result = platform === "darwin"
85
+ ? run("/bin/launchctl", ["kickstart", "-k", launchdTarget(uid)])
86
+ : platform === "linux"
87
+ ? run("systemctl", ["--user", "restart", SYSTEMD_SERVICE_UNIT])
88
+ : null;
89
+ if (result === null) return { ok: false, reason: "no launchd or systemd manager on this platform" };
90
+ if (result.timedOut) return { ok: false, unsettled: true, reason: "the service manager did not finish the restart in time" };
91
+ if (!result.ok) return { ok: false, reason: result.stderr.split("\n")[0] || "the service manager refused the restart" };
92
+ return { ok: true };
93
+ }
94
+
95
+ /** The manager's main PID for the job, or null when it reports none. Never throws. */
96
+ export function readServiceManagerPid(deps = {}) {
97
+ const { platform, uid, run } = resolve(deps);
98
+ if (platform === "darwin") {
99
+ const printed = run("/bin/launchctl", ["print", launchdTarget(uid)]);
100
+ return printed.ok ? parseLaunchdJobPid(printed.stdout) : null;
101
+ }
102
+ if (platform === "linux") {
103
+ const shown = run("systemctl", ["--user", "show", "-p", "MainPID", SYSTEMD_SERVICE_UNIT]);
104
+ return shown.ok ? parseSystemdMainPid(shown.stdout) : null;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /** Parent PID of a process, or null when it cannot be read. Never throws. */
110
+ export function readParentPid(pid, deps = {}) {
111
+ const { run } = resolve(deps);
112
+ if (!Number.isSafeInteger(pid) || pid <= 0) return null;
113
+ const result = run("ps", ["-o", "ppid=", "-p", String(pid)]);
114
+ return result.ok ? positivePid(result.stdout) : null;
115
+ }
116
+
117
+ /**
118
+ * Whether the serving proxy is the manager's job. `parentPid` is consulted only when the
119
+ * serving PID is not the manager's own.
120
+ */
121
+ export function supervisionVerdict({ servingPid, managerPid, parentPid }) {
122
+ if (!Number.isSafeInteger(servingPid) || servingPid <= 0) {
123
+ return { kind: "failed", reason: "the manager restarted the job but no proxy answered" };
124
+ }
125
+ if (!Number.isSafeInteger(managerPid) || managerPid <= 0) {
126
+ return { kind: "failed", reason: "the manager reports no running process for the job" };
127
+ }
128
+ if (servingPid === managerPid || parentPid(servingPid) === managerPid) {
129
+ return { kind: "supervised", managerPid, servingPid };
130
+ }
131
+ return { kind: "failed", reason: `the serving proxy (pid ${servingPid}) is not the manager's job (pid ${managerPid})` };
132
+ }
133
+
134
+ /**
135
+ * The serving PID to judge after a restart: a record still naming the pre-restart process is not
136
+ * evidence about the restarted one.
137
+ */
138
+ export function freshServingPid(recordPid, staleRecordPid) {
139
+ return Number.isSafeInteger(recordPid) && recordPid > 0 && recordPid !== staleRecordPid ? recordPid : null;
140
+ }
141
+
142
+ /** The warning printed when an update had to leave a proxy the manager does not own. */
143
+ export function unsupervisedRuntimeWarning(port) {
144
+ return [
145
+ `⚠️ The proxy on port ${port} is running UNSUPERVISED: the background service manager did not take it back after the update.`,
146
+ " A crash will not be restarted, and a later service start can race it for the port.",
147
+ " Run 'ocx service repair' to hand it back to the background service.",
148
+ ].join("\n");
149
+ }
150
+
151
+ /** The warning printed when the manager may still be restarting; no second proxy is started. */
152
+ export function unsettledManagerWarning(port) {
153
+ return [
154
+ `⚠️ The service manager is still restarting the proxy on port ${port}; no second proxy was started.`,
155
+ " Check it with 'ocx service status', and run 'ocx service repair' if it does not come back.",
156
+ ].join("\n");
157
+ }
@@ -294,11 +294,13 @@ export function appendFinalCacheDiagnostic(facts: CacheDiagnosticFinalFacts): vo
294
294
 
295
295
  const CACHE_DIAGNOSTIC_HOOK = Symbol.for("opencodex.cache-diagnostic.v1");
296
296
  interface CacheDiagnosticHooks {
297
+ enabled(): boolean;
297
298
  observeInbound(body: unknown, headers: Headers, source: PromptCacheKeySource): CacheDiagnosticDraft;
298
299
  rebind(body: unknown, draft: CacheDiagnosticDraft | undefined): void;
299
300
  finalize(facts: CacheDiagnosticFinalFacts): void;
300
301
  }
301
302
  (globalThis as Record<symbol, CacheDiagnosticHooks | undefined>)[CACHE_DIAGNOSTIC_HOOK] = {
303
+ enabled: isCacheDiagnosticEnabled,
302
304
  observeInbound,
303
305
  rebind: rebindCacheDiagnosticBodyAlias,
304
306
  finalize: appendFinalCacheDiagnostic,
package/src/usage/cost.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  resolveMetadataProvider,
16
16
  } from "../generated/model-metadata";
17
17
  import type { AttemptTierOutcome, OcxUsage } from "../types";
18
+ import type { PromptLengthPricing } from "../types/provider";
18
19
  import { canonicalFastTierMarker } from "../providers/fastwire";
19
20
  import { baseProviderLabel } from "../providers/label";
20
21
  import type { PersistedUsageAttempt, UsageStatus } from "./log";
@@ -79,6 +80,7 @@ export interface MatchedPrice {
79
80
  modelId: string;
80
81
  jawcodeProvider?: string;
81
82
  cost4: Cost4;
83
+ promptLengthPricing?: PromptLengthPricing;
82
84
  source: "jawcode" | "expected" | "user";
83
85
  sourceRef?: string;
84
86
  verifiedAt?: string;
@@ -97,6 +99,8 @@ export interface AttemptCostEstimate {
97
99
  priorityMultiplier?: number;
98
100
  /** Set when the published long-context rate was applied (#908). */
99
101
  contextTier?: ContextTierName;
102
+ /** User absolute rates selected for this attempt, separate from catalog tiers. */
103
+ customThresholdApplied?: true;
100
104
  /** The numeric estimate is a known floor because the exact Priority price is unavailable. */
101
105
  priorityLowerBound?: boolean;
102
106
  }
@@ -111,6 +115,8 @@ export interface CostEstimate {
111
115
  priorityMultiplier?: number;
112
116
  /** Set when any priced attempt used the published long-context rate (#908). */
113
117
  contextTier?: ContextTierName;
118
+ /** At least one request/attempt selected the user threshold rates. */
119
+ customThresholdApplied?: true;
114
120
  /** The aggregate is a known floor because every priced attempt is a lower bound. */
115
121
  priorityLowerBound?: boolean;
116
122
  }
@@ -343,6 +349,7 @@ function userOverlayMatch(
343
349
  modelId,
344
350
  cost4: overlay.cost4,
345
351
  source: "user",
352
+ ...(overlay.promptLengthPricing ? { promptLengthPricing: overlay.promptLengthPricing } : {}),
346
353
  sourceRef: overlay.source,
347
354
  verifiedAt: overlay.verifiedAt,
348
355
  status: "verified",
@@ -397,6 +404,15 @@ function vendorPrefixedCost(modelId: string): ReturnType<typeof findVendorCostBy
397
404
  return normalize(found.provider) === normalize(claimedVendor) ? found : undefined;
398
405
  }
399
406
 
407
+ /**
408
+ * Only measured usage is priced. An `unreported` or `unsupported` row can still carry tokens — a
409
+ * combo whose first leg failed without usage keeps the second leg's — and pricing them reports an
410
+ * understated cost as if it were complete.
411
+ */
412
+ export function isMeasuredStatus(usageStatus: UsageStatus): boolean {
413
+ return usageStatus === "reported" || usageStatus === "estimated";
414
+ }
415
+
400
416
  function isEstimated(usage: OcxUsage, usageStatus: UsageStatus, priceStatus: ExpectedPriceStatus | "verified"): boolean {
401
417
  return usage.estimated === true || usageStatus === "estimated" || priceStatus === "verified-derived";
402
418
  }
@@ -475,8 +491,8 @@ function isConfirmedFast(tier?: ServiceTierInput): boolean {
475
491
  }
476
492
 
477
493
  /**
478
- * Apply the published long-context rate when raw prompt size crosses the vendor
479
- * threshold. Returns [effectiveCost4, tierName].
494
+ * Select user prompt-length rates, or apply the published context rule in automatic mode.
495
+ * Returns [effectiveCost4, catalogTier, priorityLowerBound, customThresholdApplied].
480
496
  *
481
497
  * `rawInputTokens` MUST be `usage.inputTokens` (total prompt size), never the
482
498
  * normalized billable input — normalization subtracts cache read/write, so a
@@ -486,13 +502,27 @@ function isConfirmedFast(tier?: ServiceTierInput): boolean {
486
502
  * API rows can stack the bands. xAI publishes neither a combined rate nor an exclusion,
487
503
  * so its long-context rate remains the known lower bound instead of inventing a stacked multiplier.
488
504
  */
489
- function applyContextTier(
505
+ function applyPromptLengthPricing(
490
506
  cost4: Cost4,
491
507
  provider: string,
492
508
  modelId: string,
493
509
  rawInputTokens: number | undefined,
494
510
  tier?: ServiceTierInput,
495
- ): [Cost4, ContextTierName | undefined, boolean] {
511
+ policy?: PromptLengthPricing,
512
+ ): [Cost4, ContextTierName | undefined, boolean, true?] {
513
+ // A user policy replaces the catalog rule on BOTH sides of its threshold.
514
+ // Keep custom provenance separate while honoring the published Priority relation.
515
+ if (policy?.mode === "flat") return [cost4, undefined, false];
516
+ if (policy?.mode === "custom") {
517
+ const crossed = rawInputTokens !== undefined && Number.isFinite(rawInputTokens)
518
+ && (policy.comparison === "gte"
519
+ ? rawInputTokens >= policy.thresholdTokens : rawInputTokens > policy.thresholdTokens);
520
+ if (!crossed) return [cost4, undefined, false];
521
+ const relation = findContextTier(provider, modelId)?.confirmedPriorityRelation;
522
+ const confirmedFast = isConfirmedFast(tier);
523
+ if (confirmedFast && relation === "exclusive") return [cost4, undefined, false];
524
+ return [policy.rates, undefined, confirmedFast && relation === "lower-bound", true];
525
+ }
496
526
  if (rawInputTokens === undefined) return [cost4, undefined, false];
497
527
  const rule = findContextTier(provider, modelId);
498
528
  if (!rule || !isLongContext(rule, rawInputTokens)) return [cost4, undefined, false];
@@ -520,9 +550,12 @@ function applyPriorityMultiplier(
520
550
  modelId: string,
521
551
  serviceTier?: ServiceTierInput,
522
552
  contextTier?: ContextTierName,
553
+ customThresholdApplied?: true,
523
554
  ): [Cost4, number] {
524
555
  if (canonicalFastTierMarker(tierScalar(serviceTier)) !== "priority") return [cost4, 1];
525
556
  if (contextTier && findContextTier(provider, modelId)?.confirmedPriorityRelation !== "stack") return [cost4, 1];
557
+ if (customThresholdApplied && isConfirmedFast(serviceTier)
558
+ && findContextTier(provider, modelId)?.confirmedPriorityRelation === "lower-bound") return [cost4, 1];
526
559
  const rule = findPriorityPricingRule(provider, modelId);
527
560
  if (rule?.requiresResponseConfirmation && !isConfirmedFast(serviceTier)) return [cost4, 1];
528
561
  const multiplier = rule?.multiplier ?? 1;
@@ -568,7 +601,7 @@ export function estimateAttemptCost(
568
601
  serviceTier?: ServiceTierInput,
569
602
  userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
570
603
  ): AttemptCostEstimate | null {
571
- if (!attempt.usage) return null;
604
+ if (!isMeasuredStatus(attempt.usageStatus) || !attempt.usage) return null;
572
605
  const tokens = normalizeCostTokens(attempt.usage);
573
606
  if (!tokens) return null;
574
607
  const price = resolveMatchedPrice(attempt.provider, attempt.model, overlays, userOverlays, attempt);
@@ -576,11 +609,11 @@ export function estimateAttemptCost(
576
609
  const attemptServiceTier = attempt.tierOutcome
577
610
  ? serviceTierContextFromOutcome(attempt.tierOutcome)
578
611
  : serviceTier;
579
- const [tieredCost4, contextTier, contextPriorityLowerBound] = applyContextTier(
580
- price.cost4, price.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier,
612
+ const [tieredCost4, contextTier, contextPriorityLowerBound, customThresholdApplied] = applyPromptLengthPricing(
613
+ price.cost4, price.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier, price.promptLengthPricing,
581
614
  );
582
615
  const [effectiveCost4, multiplier] = applyPriorityMultiplier(
583
- tieredCost4, price.provider, attempt.model, attemptServiceTier, contextTier,
616
+ tieredCost4, price.provider, attempt.model, attemptServiceTier, contextTier, customThresholdApplied,
584
617
  );
585
618
  const priorityLowerBound = contextPriorityLowerBound
586
619
  || isOpenRouterPriorityLowerBound(price.provider, attempt.tierOutcome);
@@ -594,6 +627,7 @@ export function estimateAttemptCost(
594
627
  estimated: isEstimated(attempt.usage, attempt.usageStatus, price.status),
595
628
  ...(multiplier !== 1 ? { priorityMultiplier: multiplier } : {}),
596
629
  ...(contextTier ? { contextTier } : {}),
630
+ ...(customThresholdApplied ? { customThresholdApplied } : {}),
597
631
  ...(priorityLowerBound ? { priorityLowerBound: true } : {}),
598
632
  };
599
633
  }
@@ -637,6 +671,7 @@ export function estimateComboCost(
637
671
  ? { priorityMultiplier: estimates.find(est => est.priorityMultiplier)?.priorityMultiplier }
638
672
  : {}),
639
673
  ...(estimates.some(est => est.contextTier) ? { contextTier: "long" as const } : {}),
674
+ ...(estimates.some(est => est.customThresholdApplied) ? { customThresholdApplied: true as const } : {}),
640
675
  ...(estimates.every(est => est.priorityLowerBound === true)
641
676
  ? { priorityLowerBound: true as const }
642
677
  : {}),
@@ -656,16 +691,16 @@ export function estimateRequestCost(
656
691
  overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
657
692
  userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
658
693
  ): CostEstimate | null {
659
- if (!input.usage) return null;
694
+ if (!isMeasuredStatus(input.usageStatus) || !input.usage) return null;
660
695
  const tokens = normalizeCostTokens(input.usage);
661
696
  if (!tokens) return null;
662
697
  const price = resolveMatchedPrice(input.provider, input.model, overlays, userOverlays, input);
663
698
  if (!price) return null;
664
- const [tieredCost4, contextTier, contextPriorityLowerBound] = applyContextTier(
665
- price.cost4, price.provider, input.model, input.usage.inputTokens, input.serviceTier,
699
+ const [tieredCost4, contextTier, contextPriorityLowerBound, customThresholdApplied] = applyPromptLengthPricing(
700
+ price.cost4, price.provider, input.model, input.usage.inputTokens, input.serviceTier, price.promptLengthPricing,
666
701
  );
667
702
  const [effectiveCost4, multiplier] = applyPriorityMultiplier(
668
- tieredCost4, price.provider, input.model, input.serviceTier, contextTier,
703
+ tieredCost4, price.provider, input.model, input.serviceTier, contextTier, customThresholdApplied,
669
704
  );
670
705
  const priorityLowerBound = contextPriorityLowerBound || isOpenRouterPriorityLowerBound(
671
706
  price.provider,
@@ -678,6 +713,7 @@ export function estimateRequestCost(
678
713
  estimated: isEstimated(input.usage, input.usageStatus, price.status),
679
714
  ...(multiplier !== 1 ? { priorityMultiplier: multiplier } : {}),
680
715
  ...(contextTier ? { contextTier } : {}),
716
+ ...(customThresholdApplied ? { customThresholdApplied } : {}),
681
717
  ...(priorityLowerBound ? { priorityLowerBound: true } : {}),
682
718
  };
683
719
  }
@@ -10,6 +10,7 @@ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
10
10
  export function pricingFamilyProvider(provider: string): string {
11
11
  return isAnthropicInstanceId(provider) ? "anthropic" : provider;
12
12
  }
13
+ import type { PromptLengthPricing } from "../types/provider";
13
14
 
14
15
  /**
15
16
  * Expected-price overlay for models whose jawcode cost rows are missing or all-zero
@@ -46,6 +47,8 @@ export interface ExpectedPriceOverlay {
46
47
  provider: string;
47
48
  modelId: string;
48
49
  cost4: Cost4;
50
+ /** User-owned context policy; catalog rows leave this absent. */
51
+ promptLengthPricing?: PromptLengthPricing;
49
52
  source: string;
50
53
  verifiedAt: string;
51
54
  status: ExpectedPriceStatus;
@@ -88,6 +91,10 @@ const GEMINI_37_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, ca
88
91
  // rising to $1.50 / $7.50 on 2027-01-01. A SEPARATE constant on purpose: equal today, but
89
92
  // aliasing them would silently drag 3.8 along if 3.7's row is ever re-verified differently.
90
93
  const GEMINI_38_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0 };
94
+ // Gemini 4 Argon introductory paid-tier rate: $2 in / $10 out (thinking included) / $0.10 cached
95
+ // per 1M through 2026-11-30, stepping up to $4 / $20 / $0.20 on 2026-12-01. Revisit this row
96
+ // then. No free tier. Implicit caching only, so there is no per-token cache-write price.
97
+ const GEMINI_4_ARGON: Cost4 = { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 0 };
91
98
  const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
92
99
  const KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3 };
93
100
  const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0.95 };
@@ -200,6 +207,7 @@ export function cursorFastPriceSource(provider: string, modelId: string): string
200
207
 
201
208
  const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
202
209
  const GEMINI_37_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-08-14); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
210
+ const GEMINI_4_ARGON_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-10-14); introductory rate through 2026-11-30, rises to 4/20 (0.20 cached) on 2026-12-01; cacheWrite=0: implicit caching only";
203
211
  const GEMINI_38_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-09-03); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
204
212
  const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
205
213
  const OPENAI_GPT56_PRICING = "https://developers.openai.com/api/docs/pricing";
@@ -346,6 +354,12 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
346
354
  // 3.7 Flash rides CCA, whose billing equivalence to the Developer API list price is
347
355
  // not published, so this is `verified-derived` rather than `verified`: the number is
348
356
  // proven, the claim that Antigravity charges it is inferred.
357
+ // Gemini 4 Argon on Antigravity (AI Ultra / Enterprise only): the picker id plus its three
358
+ // verified CCA wire ids. Billing equivalence to the API price is inferred.
359
+ { provider: "google-antigravity", modelId: "gemini-4-argon", cost4: GEMINI_4_ARGON, source: `derived: Gemini 4 Argon introductory rate through 2026-11-30 ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
360
+ { provider: "google-antigravity", modelId: "gemini-4-argon-low", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
361
+ { provider: "google-antigravity", modelId: "gemini-4-argon-medium", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
362
+ { provider: "google-antigravity", modelId: "gemini-4-argon-high", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
349
363
  { provider: "google-antigravity", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: `derived: Gemini 3.8 Flash promotional rate through 2026-12-31 ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
350
364
  { provider: "google-antigravity", modelId: "gemini-3.8-flash-low", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
351
365
  { provider: "google-antigravity", modelId: "gemini-3.8-flash-medium", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
@@ -403,6 +417,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
403
417
  // Developer API row: the price IS published for this surface, so `verified`.
404
418
  { provider: "google", modelId: "gemini-3.7-flash", cost4: GEMINI_37_FLASH, source: GEMINI_37_PRICING, verifiedAt: "2026-08-14", status: "verified" },
405
419
  { provider: "google", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: GEMINI_38_PRICING, verifiedAt: "2026-09-03", status: "verified" },
420
+ { provider: "google", modelId: "gemini-4-argon", cost4: GEMINI_4_ARGON, source: GEMINI_4_ARGON_PRICING, verifiedAt: "2026-10-14", status: "verified" },
406
421
  { provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
407
422
  // Antigravity-bundled third-party models — derived from the underlying vendor's
408
423
  // official API price (Antigravity itself bills via subscription quota).
@@ -22,6 +22,7 @@ import { closeSync, fstatSync, openSync, readSync, writeSync } from "node:fs";
22
22
  import { atomicWriteFileStreamed } from "../config/atomic-write";
23
23
  import {
24
24
  currentUsageLogRevision,
25
+ exactFileIdentity,
25
26
  usageLogIdentityKey,
26
27
  usageLogPath,
27
28
  usageLogRevisionKey,
@@ -127,7 +128,7 @@ export function enforceUsageLedgerSizeLimit(
127
128
  let sourceClosed = false;
128
129
  try {
129
130
  const opened = fstatSync(sourceFd);
130
- if (Number(opened.size) !== captured.size || Number(opened.ino) !== captured.ino) {
131
+ if (Number(opened.size) !== captured.size || exactFileIdentity(sourceFd).ino !== captured.ino) {
131
132
  return { kind: "deferred", currentBytes: captured.size, reason: "revision-changed" };
132
133
  }
133
134
  const start = firstRowBoundary(sourceFd, Math.max(0, captured.size - target), captured.size);
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { closeSync, fstatSync, openSync, readSync } from "node:fs";
3
3
  import {
4
4
  currentUsageLogRevision,
5
+ exactFileIdentity,
5
6
  normalizePersistedUsageRow,
6
7
  usageLogIdentityKey,
7
8
  usageLogPath,
@@ -63,13 +64,13 @@ export class UsageLedgerRebuildRequiredError extends Error {
63
64
 
64
65
  function revisionFromStat(
65
66
  path: string,
67
+ fd: number,
66
68
  stat: ReturnType<typeof fstatSync>,
67
69
  ): UsageLogRevision {
68
70
  if (!stat.isFile()) throw new Error("usage log is not a regular file");
69
71
  return {
70
72
  path,
71
- dev: Number(stat.dev),
72
- ino: Number(stat.ino),
73
+ ...exactFileIdentity(fd),
73
74
  birthtimeMs: Number(stat.birthtimeMs),
74
75
  size: Number(stat.size),
75
76
  mtimeMs: Number(stat.mtimeMs),
@@ -276,7 +277,7 @@ export async function scanUsageLedgerCooperatively(
276
277
  let pendingLineBytes = 0;
277
278
 
278
279
  try {
279
- const openedRevision = revisionFromStat(path, fstatSync(fd));
280
+ const openedRevision = revisionFromStat(path, fd, fstatSync(fd));
280
281
  const scanEnd = openedRevision.size;
281
282
  const openedIdentityKey = usageLogIdentityKey(openedRevision);
282
283
  if (options.expectedIdentityKey && options.expectedIdentityKey !== openedIdentityKey) {
@@ -391,7 +392,7 @@ export async function scanUsageLedgerCooperatively(
391
392
  }
392
393
 
393
394
  throwIfAborted(options.signal);
394
- const endingRevision = revisionFromStat(path, fstatSync(fd));
395
+ const endingRevision = revisionFromStat(path, fd, fstatSync(fd));
395
396
  if (usageLogIdentityKey(endingRevision) !== openedIdentityKey) {
396
397
  throw rebuildRequired("identity_mismatch");
397
398
  }
@@ -417,7 +418,7 @@ export async function scanUsageLedgerCooperatively(
417
418
  line,
418
419
  options.signal,
419
420
  );
420
- const verifiedFdRevision = revisionFromStat(path, fstatSync(fd));
421
+ const verifiedFdRevision = revisionFromStat(path, fd, fstatSync(fd));
421
422
  const verifiedPathRevision = currentUsageLogRevision();
422
423
  if (capturedDigest !== verifiedDigest) throw rebuildRequired("content_changed");
423
424
  if (!verifiedPathRevision
package/src/usage/log.ts CHANGED
@@ -1163,8 +1163,12 @@ export function appendUsageEntry(entry: PersistedUsageEntry): void {
1163
1163
 
1164
1164
  export type UsageLogRevision = {
1165
1165
  path: string;
1166
- dev: number;
1167
- ino: number;
1166
+ /**
1167
+ * Exact decimal device and file id. NTFS file ids carry a sequence number in their
1168
+ * high bits and routinely exceed 2^53, so a Number would let two files compare equal.
1169
+ */
1170
+ dev: string;
1171
+ ino: string;
1168
1172
  birthtimeMs: number;
1169
1173
  size: number;
1170
1174
  mtimeMs: number;
@@ -1367,12 +1371,17 @@ function readExactly(fd: number, length: number, position: number): Buffer | nul
1367
1371
  return output;
1368
1372
  }
1369
1373
 
1370
- function usageLogRevision(path: string, stat: ReturnType<typeof fstatSync>): UsageLogRevision {
1374
+ /** Exact dev/ino of an open file, read through bigint stats so large ids keep every bit. */
1375
+ export function exactFileIdentity(fd: number): { dev: string; ino: string } {
1376
+ const exact = fstatSync(fd, { bigint: true });
1377
+ return { dev: exact.dev.toString(), ino: exact.ino.toString() };
1378
+ }
1379
+
1380
+ function usageLogRevision(path: string, fd: number, stat: ReturnType<typeof fstatSync>): UsageLogRevision {
1371
1381
  if (!stat.isFile()) throw new Error("usage log is not a regular file");
1372
1382
  return {
1373
1383
  path,
1374
- dev: Number(stat.dev),
1375
- ino: Number(stat.ino),
1384
+ ...exactFileIdentity(fd),
1376
1385
  birthtimeMs: Number(stat.birthtimeMs),
1377
1386
  size: Number(stat.size),
1378
1387
  mtimeMs: Number(stat.mtimeMs),
@@ -1405,7 +1414,7 @@ export function currentUsageLogRevision(): UsageLogRevision | null {
1405
1414
  let fd: number | undefined;
1406
1415
  try {
1407
1416
  fd = openSync(path, "r");
1408
- return usageLogRevision(path, fstatSync(fd));
1417
+ return usageLogRevision(path, fd, fstatSync(fd));
1409
1418
  } finally {
1410
1419
  if (fd !== undefined) closeSync(fd);
1411
1420
  }
@@ -1530,7 +1539,7 @@ async function readUsageEntriesFullCooperatively(
1530
1539
  if (prefixDigest === null) throw new Error("usage log changed while it was being read");
1531
1540
  return {
1532
1541
  entries: parsed.entries,
1533
- revision: usageLogRevision(path, stat),
1542
+ revision: usageLogRevision(path, fd, stat),
1534
1543
  truncatedPrefixBytes,
1535
1544
  entriesTruncated: parsed.entriesDropped > 0,
1536
1545
  entriesDropped: parsed.entriesDropped,
@@ -1562,7 +1571,7 @@ async function readUsageEntriesIncrementally(
1562
1571
  try {
1563
1572
  fd = openSync(path, "r");
1564
1573
  const stat = fstatSync(fd);
1565
- const revision = usageLogRevision(path, stat);
1574
+ const revision = usageLogRevision(path, fd, stat);
1566
1575
  if (usageLogIdentityKey(revision) !== retained.identityKey) return null;
1567
1576
  const size = Number(stat.size);
1568
1577
  // A shrink means truncation or replacement-in-place; the retained rows may no
@@ -10,7 +10,7 @@ import {
10
10
  type PersistedUsageEntry,
11
11
  type UsageStatus,
12
12
  } from "./log";
13
- import { type AttemptCostEstimate, type CostEstimate, estimateAttemptCost, estimateRequestCost, serviceTierContext, type ServiceTierContext, tokensPerSecond } from "./cost";
13
+ import { type AttemptCostEstimate, type CostEstimate, estimateAttemptCost, estimateRequestCost, isMeasuredStatus, serviceTierContext, type ServiceTierContext, tokensPerSecond } from "./cost";
14
14
 
15
15
  /**
16
16
  * Canonical range members. The warm-up loop in the management usage route
@@ -303,7 +303,10 @@ export function computeEntryCost(entry: PersistedUsageEntry): EntryCostInfo {
303
303
  isPriced = true;
304
304
  }
305
305
  }
306
- return { tier, estimate: null, attemptEstimates, costTotal, isPriced };
306
+ // An unmeasured leg that may have generated billed tokens leaves the total unknown, not short.
307
+ const unknownCost = entry.attempts.some((attempt, index) =>
308
+ !attemptEstimates[index] && (!attempt.usage || !isMeasuredStatus(attempt.usageStatus)) && !spentNothing(attempt));
309
+ return { tier, estimate: null, attemptEstimates, costTotal, isPriced: isPriced && !unknownCost };
307
310
  }
308
311
  const estimate = estimateRequestCost({
309
312
  ...usageModelPriceOptions(entry, entry),
@@ -428,10 +431,6 @@ function blankTotals(): UsageSummaryTotals {
428
431
  };
429
432
  }
430
433
 
431
- function isMeasuredStatus(status: UsageStatus): boolean {
432
- return status === "reported" || status === "estimated";
433
- }
434
-
435
434
  export interface UsageAttribution {
436
435
  requestId: string;
437
436
  provider: string;
@@ -814,14 +813,28 @@ function blankRequestCounts(): UsageRequestCounts {
814
813
  };
815
814
  }
816
815
 
816
+ /**
817
+ * A usage-less attempt that provably cost nothing: it never reached upstream, or upstream refused it
818
+ * with an HTTP error before any output (providers do not bill a pre-generation 429/401/5xx). One
819
+ * whose stream began, aborted, or ended without a status may have generated billed tokens, so it
820
+ * stays an unknown cost. A request made only of such zeros carries neither fact and counts unpriced.
821
+ */
822
+ function spentNothing(attempt: NonNullable<PersistedUsageEntry["attempts"]>[number] | undefined): boolean {
823
+ if (!attempt || attempt.usage) return false;
824
+ if (attempt.sendCount === 0 || attempt.locallyAnswered === true) return true;
825
+ return attempt.status >= 400 && attempt.streamAborted !== true && attempt.firstOutputMs === undefined;
826
+ }
827
+
817
828
  function bumpRequestCounts(counts: UsageRequestCounts, facts: number, amount = 1): void {
818
829
  counts.requests += amount;
819
830
  const status = statusFromRequestFacts(facts);
820
831
  if (isMeasuredStatus(status)) counts.measuredRequests += amount;
821
832
  if (status === "reported") counts.reportedRequests += amount;
822
833
  else if (status === "estimated") counts.estimatedRequests += amount;
823
- if ((facts & REQUEST_PRICED) !== 0) counts.pricedRequests += amount;
824
- if ((facts & REQUEST_UNPRICED) !== 0) counts.unpricedRequests += amount;
834
+ // Unpriced dominates: one unpriceable part makes the request's cost unknown, and counting it
835
+ // on both sides overstated price coverage.
836
+ if ((facts & REQUEST_PRICED) !== 0 && (facts & REQUEST_UNPRICED) === 0) counts.pricedRequests += amount;
837
+ else counts.unpricedRequests += amount;
825
838
  }
826
839
 
827
840
  function mergeRequestCounts(target: UsageRequestCounts, source: UsageRequestCounts): void {
@@ -1557,7 +1570,7 @@ class StreamingUsageSummaryAccumulator implements UsageSummaryAccumulator {
1557
1570
  : costInfo.estimate;
1558
1571
  const ordinal = this.nextOrdinal++;
1559
1572
  const facts = requestStatusFact(attribution.usageStatus)
1560
- | (estimate ? REQUEST_PRICED : REQUEST_UNPRICED);
1573
+ | (estimate ? REQUEST_PRICED : spentNothing(entry.attempts?.[index]) ? 0 : REQUEST_UNPRICED);
1561
1574
  const modelKey = this.addModelAttribution(partition, attribution, estimate, ordinal);
1562
1575
  modelFacts.set(modelKey, (modelFacts.get(modelKey) ?? 0) | facts);
1563
1576
  if (this.mode === "row-unique") {