@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
@@ -14,6 +14,8 @@ export interface ProviderApiKeySelection {
14
14
  entryId?: string;
15
15
  reference?: string;
16
16
  revision?: string;
17
+ /** Request-local allocation; does not replace the saved active key. */
18
+ distributed?: boolean;
17
19
  }
18
20
 
19
21
  export interface OpenRouterProviderRouting {
@@ -164,16 +166,25 @@ export interface ProviderWebSearchBridgeConfig {
164
166
  endpoint?: string;
165
167
  }
166
168
 
167
- /**
168
- * User-configured display price for one model (USD per 1M tokens).
169
- * Mirrors the `Cost4` shape used by the usage cost estimator; structurally
170
- * compatible so config rows can be lifted directly into price overlays.
171
- */
169
+ /** Optional user-owned prompt-length policy; rates are USD per 1M tokens. */
170
+ export type PromptLengthPricing =
171
+ | { mode: "automatic" }
172
+ | { mode: "flat" }
173
+ | {
174
+ mode: "custom";
175
+ thresholdTokens: number;
176
+ comparison: "gt" | "gte";
177
+ rates: { input: number; output: number; cacheRead: number; cacheWrite: number };
178
+ };
179
+
180
+ /** Complete base rates for one model, with automatic pricing when policy is absent. */
172
181
  export interface ProviderCostOverlay {
173
182
  input: number;
174
183
  output: number;
175
184
  cacheRead: number;
176
185
  cacheWrite: number;
186
+ /** Absent means automatic vendor prompt-length pricing. */
187
+ promptLengthPricing?: PromptLengthPricing;
177
188
  }
178
189
 
179
190
  export interface RequestPacingRule {
@@ -190,6 +201,10 @@ export interface ProviderRequestPacingConfig extends RequestPacingRule {
190
201
  enabled: boolean;
191
202
  /** Exact upstream model-id overrides; other models inherit the provider rule. */
192
203
  models?: Record<string, RequestPacingRule>;
204
+ /** Normal request allocation, separate from cooldown recovery ordering. */
205
+ keySelection?: "active" | "round-robin";
206
+ /** Additional uniform limits per pool credential, across all models. */
207
+ keys?: RequestPacingRule;
193
208
  }
194
209
 
195
210
  export interface FastWire {
@@ -432,6 +447,11 @@ export interface OcxProviderConfig {
432
447
  * to extract turn provenance (#6220).
433
448
  */
434
449
  preserveResponsesMessageMetadata?: boolean;
450
+ /**
451
+ * Opt in to lossy hosted-search history notes on ordinary canonical Codex Lite requests.
452
+ * Default off; only literal true enables it, and non-Lite/native compact stay unchanged.
453
+ */
454
+ projectResponsesLiteHostedSearchHistory?: boolean;
435
455
  /**
436
456
  * Treat this provider's `modelReasoningEfforts` as authoritative at the wire, not only in the
437
457
  * catalog. Adapters that ship their own per-model effort table (currently `command-code`)
@@ -453,6 +473,10 @@ export interface OcxProviderConfig {
453
473
  * compaction blobs. Absent or false degrades foreign blobs to an opaque note.
454
474
  */
455
475
  decodesNativeCompactionBlobs?: boolean;
476
+ /** Accept native Responses v2 `input` compaction_trigger items. Responses adapter only. */
477
+ supportsNativeCompactionTrigger?: boolean;
478
+ /** Serve native Responses v1 /responses/compact. Responses adapter only; no auth authority. */
479
+ supportsNativeCompactEndpoint?: boolean;
456
480
  /**
457
481
  * Trust this direct key-auth Responses provider to consume or relay opaque encrypted
458
482
  * V2 agent tasks. OpenCodex does not decrypt, translate, or recover an eligible task.
@@ -712,12 +736,14 @@ export interface OcxProviderConfig {
712
736
  /** Kiro OAuth only: active serving requests per account, 1..100. */
713
737
  maxConcurrentPerAccount?: number;
714
738
  enabled?: boolean;
739
+ /** Opt-in process-local conversation ownership; requires proactive enablement. */
740
+ sessionAffinity?: "conversation" | "none";
715
741
  /**
716
742
  * Generic OAuth pool selection strategy (#695). Persisted through the pool-settings
717
743
  * contract. Consumed by the selector only while `pool.kernel` is on; with the flag off
718
744
  * it is still merely persisted, so omitted and set behave the same.
719
745
  */
720
- strategy?: "quota" | "round-robin" | "fill-first" | "least-loaded";
746
+ strategy?: "quota" | "round-robin" | "fill-first" | "reset-first" | "least-loaded";
721
747
  /**
722
748
  * 0-100 usage percent at which fill-first advances off the active account (#695).
723
749
  * Read only under `pool.kernel` with `strategy: "fill-first"`; 80 when unset, matching
@@ -2,6 +2,7 @@ import type { KiroOAuthMetadata } from "../oauth/types";
2
2
  import type { NativeReasoningOwner } from "../responses/reasoning-replay-cache";
3
3
  import type { OcxTool, OcxToolChoice } from "./tools";
4
4
  import type { TierDecision, TierObservationContext } from "./provider";
5
+ import type { TranslatorBudget } from "../lib/translator-budget";
5
6
 
6
7
  /** Exact provider/credential namespace for process-local reasoning replay. */
7
8
  export interface OcxReasoningReplayIdentity {
@@ -62,6 +63,16 @@ export interface OcxParsedRequest {
62
63
  _nativeReasoningReplay?: ReadonlyMap<string, string>;
63
64
  _nativeReasoningBoundOwner?: NativeReasoningOwner;
64
65
  _nativeReasoningMint?: { owner?: NativeReasoningOwner };
66
+ /**
67
+ * The Claude client's typed Anthropic web tools, as declared. Only the anthropic adapter reads
68
+ * them, and only for api.anthropic.com; every other destination never sees them.
69
+ */
70
+ _anthropicServerTools?: readonly Record<string, unknown>[];
71
+ /**
72
+ * Completed server-tool exchanges from the Claude client's history, keyed by the block that
73
+ * followed them (`call:<tool_use id>` / `text:<text>`). Same audience as `_anthropicServerTools`.
74
+ */
75
+ _anthropicServerReplay?: ReadonlyMap<string, readonly Record<string, unknown>[]>;
65
76
  /**
66
77
  * Boundary between replayed history and this turn's newly appended input. Usually the
67
78
  * items the proxy restored from local previous_response_id state; also set when the
@@ -92,11 +103,17 @@ export interface OcxParsedRequest {
92
103
  _cursorClientThreadId?: string;
93
104
  /** Conversation/provider/account/model-bound namespace for reasoning replay state. */
94
105
  _reasoningReplayScope?: OcxReasoningReplayScopeRef;
106
+ /** Trusted configuration snapshot; never read from the client's request body. */
107
+ _nativeReasoningRetention?: Readonly<{ modelSwitch: boolean; accountSwitch: boolean }>;
95
108
  /**
96
109
  * Set by bindRouteReasoningReplayScope after a proven serving-identity change, or by
97
110
  * prepareOpaqueBlobRecovery after an authoritative rejection; consumers strip replayed blobs.
98
111
  */
99
112
  _stripReasoningEncryptedContent?: boolean;
113
+ /** Native compaction remains nonportable even when reasoning-only retention is enabled. */
114
+ _stripNativeCompactionEncryptedContent?: boolean;
115
+ /** Retention kept reasoning ciphertext across a model/account change; arms streamed rejection recovery. */
116
+ _nativeReasoningRetainedAcrossRoute?: boolean;
100
117
  /**
101
118
  * Set when replayed reasoning item ids name items in a store this destination cannot read: by
102
119
  * prepareOpaqueBlobRecovery before the one recovery rebuild, and by bindRouteReasoningReplayScope
@@ -150,6 +167,8 @@ export interface OcxParsedRequest {
150
167
  * (see src/responses/compaction.ts).
151
168
  */
152
169
  _compactionRequest?: boolean;
170
+ /** Request-owned encoding of readable routed v2 retention; never used for native ciphertext. */
171
+ _compactionRetention?: { encode(summary: string): string; commit(): void };
153
172
  /** Manual compaction moved to another provider: summarize portably even on a canonical ChatGPT target. */
154
173
  _portableCompaction?: boolean;
155
174
  /**
@@ -173,6 +192,7 @@ export interface OcxParsedRequest {
173
192
  */
174
193
  _advisorGuard?: (options: {
175
194
  parsed: OcxParsedRequest;
195
+ translatorBudget?: TranslatorBudget;
176
196
  firstEvents: AsyncIterable<AdapterEvent>;
177
197
  /** One bounded worker continuation re-dispatch (same machinery as the terminal guard). */
178
198
  continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
@@ -399,9 +419,14 @@ export interface OcxProviderContinuationState {
399
419
  [provider: string]: Record<string, unknown> | undefined;
400
420
  }
401
421
 
422
+ /** One Anthropic server-side call as the API returned it: `server_tool_use` + its `*_tool_result`. */
423
+ export interface AnthropicServerBlocks { use: Record<string, unknown>; result: Record<string, unknown> }
424
+
402
425
  export type AdapterEvent =
403
426
  | { type: "heartbeat"; replayUnsafe?: true; preflightReady?: true }
404
- | { type: "text_delta"; text: string; phase?: OcxMessagePhase }
427
+ | { type: "text_delta"; text: string; phase?: OcxMessagePhase;
428
+ /** Complete documented Anthropic progress block: text remains signed thinking on replay. */
429
+ anthropicThinkingSignature?: string }
405
430
  | { type: "thinking_delta"; thinking: string }
406
431
  // Anthropic extended-thinking round-trip: signature_delta for the current thinking block, and
407
432
  // opaque redacted_thinking blocks. Both must be replayed verbatim or tool-use turns 400.
@@ -424,7 +449,11 @@ export type AdapterEvent =
424
449
  // output_item.added(in_progress) and end → the matching output_item.done(completed|failed) under
425
450
  // the SAME output index, so the activity animates instead of flashing completed instantly.
426
451
  | { type: "web_search_call_begin"; id: string }
427
- | { type: "web_search_call_end"; id: string; queries: string[]; status?: "completed" | "failed"; sources?: OcxUrlCitation[] }
452
+ | {
453
+ type: "web_search_call_end"; id: string; queries: string[]; status?: "completed" | "failed"; sources?: OcxUrlCitation[];
454
+ /** Anthropic's server-side call (adapters/anthropic.ts): its `server_tool_use` and result block, verbatim. */
455
+ anthropic?: AnthropicServerBlocks;
456
+ }
428
457
  | {
429
458
  type: "done";
430
459
  usage?: OcxUsage;
package/src/types/wire.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { isOpenCodeGoGeminiDestination, OPENCODE_GO_BASE_URL, OPENCODE_GO_MODEL_WIRES, OPENCODE_GO_LEGACY_NAME_PINS, type OpenCodeGoDestination } from "./opencode-go";
2
+ import { openCodeZenBaseUrl, openCodeZenKeyBaseUrl, type OpenCodeZenDestination } from "./opencode-zen";
3
+
1
4
  /**
2
5
  * Accepted values for the per-provider upstream HTTP-version pin (#1668). Shared by the
3
6
  * zod load schema, the management write boundary (POST/PATCH), and the fetch runtime, so
@@ -40,35 +43,16 @@ export const MODEL_ADAPTER_OVERRIDE_ALLOWED: ReadonlySet<string> = new Set([
40
43
  "openai-responses",
41
44
  ]);
42
45
 
43
- /**
44
- * Providers whose listed model ids must be driven over the Anthropic wire even when
45
- * the provider's configured adapter says otherwise — the upstream only speaks
46
- * Anthropic for these models.
47
- */
48
- const ANTHROPIC_WIRE_MODELS: Record<string, ReadonlySet<string>> = {
49
- "opencode-go": new Set([
50
- "claude-haiku-5-5",
51
- "minimax-m2.5",
52
- "minimax-m2.7",
53
- "minimax-m3",
54
- // OpenCode's catalog identifies Union Alpha as @ai-sdk/anthropic while the
55
- // provider defaults to OpenAI-compatible; direct Chat returns 500 and direct
56
- // Messages reaches the session check (#4847).
57
- "union-alpha",
58
- ]),
59
- };
60
-
61
- function anthropicWireModelsForProvider(providerName: string): ReadonlySet<string> | undefined {
62
- return Object.hasOwn(ANTHROPIC_WIRE_MODELS, providerName)
63
- ? ANTHROPIC_WIRE_MODELS[providerName]
64
- : undefined;
65
- }
66
-
67
- /** Detached provider-local hard-pin table for pure wire-policy resolution. */
68
- export function captureWireAdapterHardPins(providerName: string): Readonly<Record<string, string>> {
69
- const models = anthropicWireModelsForProvider(providerName);
70
- if (!models) return Object.freeze({});
71
- return Object.freeze(Object.fromEntries([...models].map(modelId => [modelId, "anthropic"])));
46
+ /** Native Go wire pins share the exact-model table with registry Responses defaults. */
47
+ export function captureWireAdapterHardPins(providerName: string, provider?: WirePinProvider): Readonly<Record<string, string>> {
48
+ if (providerName !== "opencode-go") return Object.freeze({});
49
+ // The existing Go preset pins saved URLs to this endpoint before dispatch. Pure policy
50
+ // captures must use that same destination even when handed an unnormalized config row.
51
+ const destination = provider && { ...provider, baseUrl: OPENCODE_GO_BASE_URL };
52
+ return Object.freeze(Object.fromEntries(Object.entries(OPENCODE_GO_MODEL_WIRES).filter(([modelId, wire]) =>
53
+ (wire === "anthropic" && OPENCODE_GO_LEGACY_NAME_PINS.has(modelId))
54
+ || (wire !== "openai-responses" && isOpenCodeGoGeminiDestination(destination)),
55
+ )));
72
56
  }
73
57
 
74
58
  interface WirePinPrefixRule {
@@ -98,9 +82,7 @@ const WIRE_ADAPTER_PIN_PREFIXES: Readonly<Record<string, WirePinPrefixRule>> = O
98
82
  });
99
83
 
100
84
  /** Just enough of a provider config to tell whether it still points at the pinned endpoint. */
101
- export interface WirePinProvider {
102
- readonly baseUrl?: unknown;
103
- }
85
+ export interface WirePinProvider extends OpenCodeGoDestination, OpenCodeZenDestination {}
104
86
 
105
87
  function normalizedEndpoint(value: unknown): string | undefined {
106
88
  if (typeof value !== "string") return undefined;
@@ -112,7 +94,21 @@ function normalizedEndpoint(value: unknown): string | undefined {
112
94
  }
113
95
  }
114
96
 
97
+ /**
98
+ * Providers whose adapter is their credential boundary: the adapter alone pins the destination and
99
+ * the auth header for an account token. No model may switch wires, whatever `baseUrl` or the row's
100
+ * own `adapter` says, because a generic wire would send that token as a Bearer key to the configured
101
+ * URL. The empty prefix covers every model id.
102
+ */
103
+ const CREDENTIAL_PINNED_ADAPTERS: Readonly<Record<string, Readonly<Record<string, string>>>> = Object.freeze({
104
+ "dsh-account": Object.freeze({ "": "dsh-account" }),
105
+ });
106
+
115
107
  function prefixPinsFor(providerName: string, provider: WirePinProvider | undefined): Readonly<Record<string, string>> | undefined {
108
+ if (Object.hasOwn(CREDENTIAL_PINNED_ADAPTERS, providerName)) return CREDENTIAL_PINNED_ADAPTERS[providerName];
109
+ if (providerName === "opencode-zen" && openCodeZenKeyBaseUrl(provider)) {
110
+ return openCodeZenBaseUrl(provider) ? { "gemini-": "google", "claude-": "anthropic" } : { "claude-": "anthropic" };
111
+ }
116
112
  if (!provider || !Object.hasOwn(WIRE_ADAPTER_PIN_PREFIXES, providerName)) return undefined;
117
113
  const rule = WIRE_ADAPTER_PIN_PREFIXES[providerName]!;
118
114
  return normalizedEndpoint(provider.baseUrl) === rule.endpoint ? rule.prefixes : undefined;
@@ -148,13 +144,11 @@ function prefixedWireAdapter(providerName: string, modelId: string, provider: Wi
148
144
  * adapter" would pass on the first pass and then let the override win on the second.
149
145
  */
150
146
  export function isWirePinnedModel(providerName: string, modelId: string, provider?: WirePinProvider): boolean {
151
- return (anthropicWireModelsForProvider(providerName)?.has(modelId) ?? false)
152
- || prefixedWireAdapter(providerName, modelId, provider) !== undefined;
147
+ return pinnedWireAdapter(providerName, modelId, provider) !== undefined;
153
148
  }
154
149
 
155
150
  /** The wire a pinned model must use, or undefined when the model is not pinned. */
156
151
  export function pinnedWireAdapter(providerName: string, modelId: string, provider?: WirePinProvider): string | undefined {
157
- return anthropicWireModelsForProvider(providerName)?.has(modelId)
158
- ? "anthropic"
159
- : prefixedWireAdapter(providerName, modelId, provider);
152
+ const pins = captureWireAdapterHardPins(providerName, provider);
153
+ return Object.hasOwn(pins, modelId) ? pins[modelId] : prefixedWireAdapter(providerName, modelId, provider);
160
154
  }
package/src/types.ts CHANGED
@@ -61,6 +61,7 @@ export type {
61
61
  OcxProviderContinuationOwner,
62
62
  OcxProviderContinuationState,
63
63
  AdapterEvent,
64
+ AnthropicServerBlocks,
64
65
  OcxUrlCitation,
65
66
  OcxUsage,
66
67
  } from "./types/request";
@@ -122,6 +123,7 @@ export type {
122
123
  ProviderWebSearchBridgeBackend,
123
124
  ProviderWebSearchBridgeConfig,
124
125
  ProviderCostOverlay,
126
+ PromptLengthPricing,
125
127
  RequestPacingRule,
126
128
  ProviderRequestPacingConfig,
127
129
  FastWire,
@@ -95,12 +95,22 @@ export function resolveNpmCommand(
95
95
  return null;
96
96
  }
97
97
 
98
+ function shellEnvValue(env, name) {
99
+ // Copying process.env loses Windows' case-insensitive property access. Keep
100
+ // the existing exact-key precedence; do not choose between conflicting aliases.
101
+ if (env[name] !== undefined) return env[name];
102
+ const values = new Set(Object.entries(env)
103
+ .filter(([key, value]) => key.toLowerCase() === name.toLowerCase() && value !== undefined)
104
+ .map(([, value]) => value));
105
+ return values.size === 1 ? values.values().next().value : undefined;
106
+ }
107
+
98
108
  function systemCommandProcessor(env) {
99
- const systemRoot = env.SystemRoot ?? env.windir;
109
+ const systemRoot = shellEnvValue(env, "SystemRoot") ?? shellEnvValue(env, "windir");
100
110
  if (systemRoot && win32.isAbsolute(systemRoot)) {
101
111
  return win32.join(systemRoot, "System32", "cmd.exe");
102
112
  }
103
- const comSpec = env.ComSpec;
113
+ const comSpec = shellEnvValue(env, "ComSpec");
104
114
  return comSpec && win32.isAbsolute(comSpec) ? win32.resolve(comSpec) : null;
105
115
  }
106
116
 
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;
@@ -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