@bitkyc08/opencodex 2.63.0 → 2.64.0-preview.20260923

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 (262) hide show
  1. package/AGENTS_INSTALL.md +4 -3
  2. package/README.md +46 -33
  3. package/assets/download-linux.svg +10 -0
  4. package/assets/download-macos.svg +10 -0
  5. package/assets/download-windows.svg +10 -0
  6. package/bin/ocx.mjs +12 -4
  7. package/gui/dist/assets/App-D5eN1TID.js +50 -0
  8. package/gui/dist/assets/App-I5AnaSLh.css +1 -0
  9. package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-Bh_ErQDh.js} +1 -1
  10. package/gui/dist/assets/index-BEq4OCOz.js +86 -0
  11. package/gui/dist/assets/index-DiBRuK-d.css +1 -0
  12. package/gui/dist/assets/usage-companion-chart-DBG_37kQ.js +1 -0
  13. package/gui/dist/index.html +2 -2
  14. package/native/remote-workspace-helper/src/protocol.rs +5 -0
  15. package/package.json +4 -1
  16. package/src/adapters/anthropic.ts +66 -7
  17. package/src/adapters/codebuddy/adapter.ts +112 -16
  18. package/src/adapters/codebuddy/mcp-server.ts +180 -0
  19. package/src/adapters/codebuddy/scaffold-guard.ts +25 -8
  20. package/src/adapters/codebuddy/tool-bridge.ts +597 -0
  21. package/src/adapters/coding-agent/protocol.ts +123 -10
  22. package/src/adapters/coding-agent/turn.ts +324 -2
  23. package/src/adapters/command-code-restored-schema.ts +112 -0
  24. package/src/adapters/command-code-tool-text.ts +589 -0
  25. package/src/adapters/command-code.ts +80 -6
  26. package/src/adapters/cursor/catalog.ts +12 -0
  27. package/src/adapters/cursor/current-request.ts +46 -0
  28. package/src/adapters/cursor/discovery.ts +1 -1
  29. package/src/adapters/cursor/effort-map.ts +4 -0
  30. package/src/adapters/cursor/native-exec.ts +15 -0
  31. package/src/adapters/cursor/protobuf-events.ts +40 -12
  32. package/src/adapters/cursor/protobuf-request.ts +83 -18
  33. package/src/adapters/cursor/request-builder.ts +5 -4
  34. package/src/adapters/cursor/tool-guidance.ts +1 -1
  35. package/src/adapters/devin/live-models.ts +7 -0
  36. package/src/adapters/inline-think-tags.ts +251 -0
  37. package/src/adapters/kiro/adapter.ts +1 -0
  38. package/src/adapters/kiro/stream.ts +5 -3
  39. package/src/adapters/kiro/usage.ts +4 -3
  40. package/src/adapters/mimo-free.ts +37 -18
  41. package/src/adapters/openai-chat/messages.ts +5 -5
  42. package/src/adapters/openai-chat/tool-schema.ts +124 -2
  43. package/src/adapters/openai-chat.ts +13 -3
  44. package/src/adapters/openai-responses/passthrough.ts +15 -2
  45. package/src/adapters/openai-responses/reasoning.ts +14 -1
  46. package/src/adapters/openai-responses/request-strips.ts +29 -3
  47. package/src/adapters/openai-responses/tool-output-recovery.ts +9 -3
  48. package/src/adapters/qoder/adapter.ts +15 -12
  49. package/src/adapters/qoder/scaffold-guard.ts +45 -13
  50. package/src/bridge/internal.ts +20 -0
  51. package/src/bridge/sse.ts +14 -5
  52. package/src/claude/agents-inject.ts +2 -1
  53. package/src/claude/desktop-applied-marker.ts +44 -0
  54. package/src/claude/desktop-profile.ts +22 -12
  55. package/src/claude/inbound.ts +8 -3
  56. package/src/cli/access.ts +22 -5
  57. package/src/cli/account-api.ts +6 -0
  58. package/src/cli/account-auth.ts +5 -2
  59. package/src/cli/account.ts +5 -6
  60. package/src/cli/aside-profiles.ts +61 -1
  61. package/src/cli/claude-desktop.ts +3 -2
  62. package/src/cli/claude.ts +3 -1
  63. package/src/cli/codex-cli-update.ts +2 -2
  64. package/src/cli/codex-shim-autorestore.ts +3 -0
  65. package/src/cli/dispatch.ts +12 -4
  66. package/src/cli/index.ts +98 -10
  67. package/src/cli/registry.ts +1 -1
  68. package/src/cli/resolve.ts +113 -1
  69. package/src/cli/root.ts +5 -0
  70. package/src/cli/stop-approval.ts +186 -0
  71. package/src/cli/stop-report.ts +1 -1
  72. package/src/cli/system-restart-client.ts +19 -4
  73. package/src/client/hub-client.ts +25 -18
  74. package/src/clients/config-export.ts +12 -4
  75. package/src/codex/account-auto-switch.ts +59 -0
  76. package/src/codex/account-lifecycle.ts +7 -0
  77. package/src/codex/account-priority.ts +10 -0
  78. package/src/codex/account-usability.ts +9 -0
  79. package/src/codex/auth-api/account-list.ts +5 -0
  80. package/src/codex/auth-api/login-flow.ts +15 -5
  81. package/src/codex/auth-api/login-state.ts +5 -16
  82. package/src/codex/auth-api/pool-mode-gate.ts +30 -7
  83. package/src/codex/auth-api/routes.ts +39 -3
  84. package/src/codex/auth-context.ts +145 -17
  85. package/src/codex/catalog/build-entries.ts +2 -1
  86. package/src/codex/catalog/effort.ts +8 -2
  87. package/src/codex/catalog/metadata.ts +33 -7
  88. package/src/codex/catalog/native-models.ts +102 -5
  89. package/src/codex/catalog/parsing.ts +2 -0
  90. package/src/codex/catalog/provider-models.ts +19 -6
  91. package/src/codex/cli-installation-targets.ts +44 -40
  92. package/src/codex/codex-write-lock.ts +32 -2
  93. package/src/codex/inject/multi-agent-v2.ts +90 -0
  94. package/src/codex/inject/plan.ts +365 -0
  95. package/src/codex/inject.ts +237 -362
  96. package/src/codex/log-guard/maintenance.ts +30 -13
  97. package/src/codex/project-config-warnings.ts +47 -7
  98. package/src/codex/prompt-layers/encoding.ts +41 -0
  99. package/src/codex/prompt-layers/toml-read.ts +1 -1
  100. package/src/codex/prompt-layers.ts +17 -13
  101. package/src/codex/prompt-text-probe.ts +20 -10
  102. package/src/codex/quota-observation-freshness.ts +34 -0
  103. package/src/codex/quota-rejection.ts +8 -4
  104. package/src/codex/quota.ts +4 -2
  105. package/src/codex/routing/selection.ts +32 -9
  106. package/src/codex/routing.ts +53 -11
  107. package/src/codex/subagent-model-fallback.ts +4 -3
  108. package/src/codex/sync.ts +11 -0
  109. package/src/codex/windows-installation-files.ts +33 -11
  110. package/src/codex/write-coordination.ts +4 -0
  111. package/src/combos/request.ts +6 -2
  112. package/src/companion/settings.ts +6 -1
  113. package/src/config/atomic-write.ts +12 -1
  114. package/src/config/derived-registries.ts +29 -0
  115. package/src/config/diagnostics.ts +17 -0
  116. package/src/config/live-reconcile.ts +11 -2
  117. package/src/config/load-degrade.ts +9 -2
  118. package/src/config/persist-unlocked.ts +4 -3
  119. package/src/config/persisted-mutation.ts +94 -0
  120. package/src/config/provider-validation.ts +1 -1
  121. package/src/config/proxy-env.ts +62 -12
  122. package/src/config/rebase-provenance.ts +80 -1
  123. package/src/config/schema/config-schema.ts +5 -0
  124. package/src/config/schema/leaf-validators.ts +50 -1
  125. package/src/config/subagent-models.ts +41 -15
  126. package/src/config.ts +18 -95
  127. package/src/generated/compatibility-version.json +330 -218
  128. package/src/generated/model-metadata.ts +8 -5
  129. package/src/github/star-state.ts +46 -6
  130. package/src/grok/inject.ts +74 -40
  131. package/src/grok/status.ts +11 -4
  132. package/src/integrations/cursor-effort-table.ts +50 -13
  133. package/src/integrations/raycast-detect.ts +19 -4
  134. package/src/integrations/serialize.ts +11 -1
  135. package/src/lib/bounded-body.ts +30 -0
  136. package/src/lib/crash-guard.ts +52 -4
  137. package/src/lib/local-aside-sync-contract.ts +41 -0
  138. package/src/lib/package-tree-integrity.ts +181 -9
  139. package/src/lib/proxy-env.ts +18 -1
  140. package/src/lib/request-failure-attribution.ts +1 -0
  141. package/src/lib/request-failure-model.ts +3 -0
  142. package/src/lib/service-secrets.ts +99 -2
  143. package/src/lib/socks5-fetch.ts +43 -14
  144. package/src/lib/token-estimate.ts +17 -2
  145. package/src/oauth/command-code.ts +3 -2
  146. package/src/oauth/generic-account-failover.ts +29 -1
  147. package/src/oauth/index.ts +24 -16
  148. package/src/oauth/login-flow-state.ts +5 -5
  149. package/src/oauth/meta-muse-device.ts +49 -7
  150. package/src/oauth/store.ts +74 -9
  151. package/src/providers/alibaba-region-backup.ts +16 -1
  152. package/src/providers/anthropic-fast.ts +89 -0
  153. package/src/providers/anthropic-reset-grant-ledger.ts +288 -0
  154. package/src/providers/anthropic-reset-grants.ts +329 -0
  155. package/src/providers/claude-cli-identity.ts +12 -0
  156. package/src/providers/command-code-efforts.ts +184 -102
  157. package/src/providers/derive.ts +11 -3
  158. package/src/providers/fastwire.ts +16 -3
  159. package/src/providers/label.ts +8 -3
  160. package/src/providers/model-rename-fields.ts +1 -0
  161. package/src/providers/openai-virtual-models.ts +1 -0
  162. package/src/providers/quota/vendor-probes-oauth.ts +2 -1
  163. package/src/providers/reasoning-metadata.ts +38 -15
  164. package/src/providers/registry/entries-core.ts +95 -7
  165. package/src/providers/registry/entries-extended.ts +21 -10
  166. package/src/providers/registry/model-ids.ts +1 -0
  167. package/src/providers/registry/model-seeds.ts +31 -3
  168. package/src/providers/registry/types.ts +3 -1
  169. package/src/providers/resolved-model-policy-merge.ts +38 -8
  170. package/src/providers/resolved-model-policy.ts +4 -2
  171. package/src/providers/service-tier.ts +3 -1
  172. package/src/reasoning-effort.ts +6 -5
  173. package/src/responses/code-mode-helper-compat.ts +14 -4
  174. package/src/responses/code-mode-shell-input.ts +54 -0
  175. package/src/responses/custom-tool-compat.ts +173 -5
  176. package/src/responses/parser.ts +38 -2
  177. package/src/responses/reasoning-replay-cache.ts +26 -0
  178. package/src/router.ts +11 -1
  179. package/src/routing/history/indexer.ts +7 -2
  180. package/src/routing/history/schema.ts +3 -1
  181. package/src/server/adapter-resolve.ts +2 -2
  182. package/src/server/auth-cors.ts +3 -0
  183. package/src/server/chat-completions.ts +14 -1
  184. package/src/server/chat-native.ts +17 -0
  185. package/src/server/claude-messages.ts +4 -3
  186. package/src/server/direct-local-http.ts +45 -19
  187. package/src/server/gui-session.ts +6 -15
  188. package/src/server/index/package-tree-guard.ts +53 -0
  189. package/src/server/index/serve-options.ts +17 -3
  190. package/src/server/index/startup-warnings.ts +17 -0
  191. package/src/server/index.ts +15 -17
  192. package/src/server/lifecycle.ts +8 -0
  193. package/src/server/live.ts +54 -4
  194. package/src/server/management/agent-settings-routes.ts +44 -8
  195. package/src/server/management/anthropic-reset-grant-routes.ts +252 -0
  196. package/src/server/management/codex-prompt-routes.ts +5 -1
  197. package/src/server/management/config-routes.ts +14 -4
  198. package/src/server/management/oauth-account-routes.ts +17 -1
  199. package/src/server/management/provider-overwrite-carry.ts +164 -0
  200. package/src/server/management/provider-routes.ts +36 -6
  201. package/src/server/management/remote-workspace-routes.ts +2 -2
  202. package/src/server/management/route-registry.ts +2 -0
  203. package/src/server/management/shadow-call-validation.ts +39 -0
  204. package/src/server/management/system-restart.ts +72 -6
  205. package/src/server/management-api.ts +16 -2
  206. package/src/server/management-auth.ts +52 -8
  207. package/src/server/proxy-liveness.ts +110 -4
  208. package/src/server/request-log.ts +26 -9
  209. package/src/server/request-metrics.ts +5 -0
  210. package/src/server/responses/adapter-continuation.ts +6 -3
  211. package/src/server/responses/adapter-dispatch.ts +65 -1
  212. package/src/server/responses/compact.ts +31 -1
  213. package/src/server/responses/compaction-routing.ts +28 -3
  214. package/src/server/responses/core-codex-account.ts +85 -26
  215. package/src/server/responses/core-combo-failure.ts +16 -15
  216. package/src/server/responses/core-normalize.ts +5 -1
  217. package/src/server/responses/core-opaque-recovery.ts +50 -2
  218. package/src/server/responses/core-options.ts +2 -0
  219. package/src/server/responses/core-replay.ts +7 -0
  220. package/src/server/responses/core.ts +1 -1
  221. package/src/server/responses/encrypted-payload.ts +23 -9
  222. package/src/server/responses/passthrough-delivery.ts +55 -35
  223. package/src/server/responses/passthrough-dispatch.ts +12 -13
  224. package/src/server/responses/policy-fallback.ts +12 -1
  225. package/src/server/responses/request-prepare.ts +46 -7
  226. package/src/server/responses/request-spend.ts +31 -15
  227. package/src/server/responses/run-turn-execution.ts +6 -5
  228. package/src/server/responses/shadow-target-availability.ts +61 -0
  229. package/src/server/responses-custom-tool-repair.ts +2 -0
  230. package/src/service/claim.ts +185 -0
  231. package/src/service/cli.ts +10 -1
  232. package/src/service/guarded-manager-target.ts +151 -0
  233. package/src/service/guards.ts +47 -48
  234. package/src/service/managing-cli.ts +170 -0
  235. package/src/service/orchestration.ts +3 -1
  236. package/src/service/state.ts +4 -0
  237. package/src/service/systemd.ts +16 -1
  238. package/src/service.ts +1 -1
  239. package/src/types/config.ts +8 -0
  240. package/src/types/provider.ts +16 -0
  241. package/src/types/request.ts +10 -0
  242. package/src/types/tools.ts +9 -1
  243. package/src/types/wire.ts +68 -4
  244. package/src/types.ts +1 -0
  245. package/src/update/job.ts +10 -16
  246. package/src/update/npm-invocation.mjs +17 -16
  247. package/src/update/transactional-install.d.mts +22 -1
  248. package/src/update/transactional-install.mjs +176 -19
  249. package/src/update/update-failure-guidance.d.mts +8 -0
  250. package/src/update/update-failure-guidance.mjs +47 -0
  251. package/src/usage/expected-prices.ts +54 -12
  252. package/src/usage/log.ts +117 -3
  253. package/src/usage/telemetry-contract.ts +1 -0
  254. package/src/usage/timeline.ts +34 -6
  255. package/src/usage/user-cost-overlay-reconciler.ts +3 -3
  256. package/src/vision/reasoning.ts +2 -4
  257. package/src/web-search/xai-executor.ts +14 -4
  258. package/gui/dist/assets/App-BqrsSrIR.js +0 -50
  259. package/gui/dist/assets/index-C6SJrh0N.js +0 -86
  260. package/gui/dist/assets/index-DdDunwDb.css +0 -1
  261. package/gui/dist/assets/usage-companion-chart-CzAAAB1o.js +0 -1
  262. package/src/adapters/kiro-thinking.ts +0 -112
@@ -146,9 +146,12 @@ const DEEPSEEK_PRICING = "https://api-docs.deepseek.com/quick_start/pricing-deta
146
146
  * cache-write charge.
147
147
  */
148
148
  const DEEPSEEK_V41_FLASH: Cost4 = { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0 };
149
- // Kimi official tables publish input/output/cache-hit only; cacheWrite is mapped to the
150
- // cache-miss input price (Kimi auto-caches with no separate write billing). 2026-07-20 re-verified.
151
- const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (official table; cacheWrite derived = input, Kimi auto-cache has no write billing)";
149
+ // Historical K2 rows retain their 2026-07-20 verification; they do not price retargeted aliases.
150
+ const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (historical official table; cacheWrite derived from cache-miss input)";
151
+ // K3 publishes a separate cache-write price: $3/1M for the default 5-minute TTL.
152
+ // Coding presets share an endpoint/product, but these API reference estimates are not plan
153
+ // billing or quota: K3's 1M model consumes about twice the quota of k3-256k.
154
+ const KIMI_K3_PRICING = "https://platform.kimi.ai/docs/pricing/chat (API reference estimate; default 5-minute cache-write rate, not Code Plan billing/quota); https://www.kimi.com/code/docs/en/kimi-code/models.html";
152
155
  // Z.AI publishes one USD table for the international surface; the Coding Plan
153
156
  // subscription and the domestic bigmodel.cn endpoints bill differently
154
157
  // (subscription quota / CNY tiers), so every GLM row below is verified-derived:
@@ -306,25 +309,31 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
306
309
  // Kimi / Moonshot — official price tables are now published (2026-07-20 re-check;
307
310
  // previously empty). kimi = Kimi Code OAuth surface, moonshot = CN key surface,
308
311
  // kimi-code = API key surface (expected list price, not actual billing).
309
- { provider: "kimi", modelId: "k3", cost4: KIMI_K3, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
310
- { provider: "kimi", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: k3 (official docs: k3[1m] is the 1M-context compat notation for k3) ${KIMI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
312
+ { provider: "kimi", modelId: "k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
313
+ { provider: "kimi", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: local alias sent as k3; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
314
+ { provider: "kimi", modelId: "k3-256k", cost4: KIMI_K3, source: `derived: K3 with a fixed 256K ceiling; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
311
315
  { provider: "kimi", modelId: "kimi-k2.7-code", cost4: KIMI_K27_CODE, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
312
316
  { provider: "kimi", modelId: "kimi-k2.7-code-highspeed", cost4: KIMI_K27_CODE_HIGHSPEED, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
313
317
  { provider: "kimi", modelId: "kimi-k2.6", cost4: KIMI_K26, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
314
318
  { provider: "kimi", modelId: "kimi-k2.5", cost4: KIMI_K25, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
315
- { provider: "kimi", modelId: "kimi-for-coding", cost4: KIMI_K27_CODE, source: `derived: kimi-k2.7-code (Kimi Code maps to K2.7 Code per official model docs) ${KIMI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
316
- { provider: "moonshot", modelId: "kimi-k3", cost4: KIMI_K3, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
319
+ // kimi-for-coding now serves K2.8 Preview. No verified K2.8 price: keep all three
320
+ // Coding presets unknown rather than reusing the alias's retired K2.7 price.
321
+ { provider: "moonshot", modelId: "kimi-k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
317
322
  { provider: "moonshot", modelId: "kimi-k2.7-code", cost4: KIMI_K27_CODE, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
318
323
  { provider: "moonshot", modelId: "kimi-k2.7-code-highspeed", cost4: KIMI_K27_CODE_HIGHSPEED, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
319
324
  { provider: "moonshot", modelId: "kimi-k2.6", cost4: KIMI_K26, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
320
325
  { provider: "moonshot", modelId: "kimi-k2.5", cost4: KIMI_K25, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
321
- { provider: "kimi-code", modelId: "k3", cost4: KIMI_K3, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
322
- { provider: "kimi-code", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: k3 ${KIMI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
326
+ { provider: "kimi-code", modelId: "k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
327
+ { provider: "kimi-code", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: local alias sent as k3; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
328
+ { provider: "kimi-code", modelId: "k3-256k", cost4: KIMI_K3, source: `derived: K3 with a fixed 256K ceiling; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
323
329
  { provider: "kimi-code", modelId: "kimi-k2.7-code", cost4: KIMI_K27_CODE, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
324
330
  { provider: "kimi-code", modelId: "kimi-k2.7-code-highspeed", cost4: KIMI_K27_CODE_HIGHSPEED, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
325
331
  { provider: "kimi-code", modelId: "kimi-k2.6", cost4: KIMI_K26, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
326
332
  { provider: "kimi-code", modelId: "kimi-k2.5", cost4: KIMI_K25, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
327
- { provider: "kimi-code", modelId: "kimi-for-coding", cost4: KIMI_K27_CODE, source: `derived: kimi-k2.7-code ${KIMI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
333
+ // Responses reuses the kimi OAuth account and the same Coding endpoint/model seeds.
334
+ { provider: "kimi-responses", modelId: "k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
335
+ { provider: "kimi-responses", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: local alias sent as k3; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
336
+ { provider: "kimi-responses", modelId: "k3-256k", cost4: KIMI_K3, source: `derived: K3 with a fixed 256K ceiling; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
328
337
  // Qwen3.8-Max: vendor-published input/output rate (verified). See QWEN38_MAX_PRICING
329
338
  // for what that source does and does not cover.
330
339
  { provider: "alibaba-token-plan", modelId: "qwen3.8-max", cost4: QWEN38_MAX, source: QWEN38_MAX_PRICING, verifiedAt: "2026-08-04", status: "verified" },
@@ -440,6 +449,14 @@ export const VERIFIED_PRICE_OVERRIDES: readonly ExpectedPriceOverlay[] = [
440
449
  verifiedAt: "2026-08-18",
441
450
  status: "verified",
442
451
  },
452
+ {
453
+ provider: "xai",
454
+ modelId: "grok-4.7",
455
+ cost4: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
456
+ source: "https://docs.x.ai/developers/models/grok-4.7",
457
+ verifiedAt: "2026-09-23",
458
+ status: "verified",
459
+ },
443
460
  ];
444
461
 
445
462
  export function findVerifiedPriceOverride(
@@ -503,6 +520,7 @@ export interface PriorityPricingRule {
503
520
  }
504
521
 
505
522
  const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/priority-processing";
523
+ const ANTHROPIC_FAST_PRICING = "https://platform.claude.com/docs/en/about-claude/pricing";
506
524
 
507
525
  /**
508
526
  * Exact provider/model priority premiums. Routed resellers never inherit a vendor rule merely
@@ -529,14 +547,25 @@ export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
529
547
  source: "https://developers.openai.com/api/docs/pricing (derived from the virtual selection's base wire model)",
530
548
  verifiedAt: "2026-09-05",
531
549
  })),
532
- ...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
550
+ ...["grok-4.5", "grok-4.6", "grok-4.7"].map((modelId): PriorityPricingRule => ({
533
551
  provider: "xai",
534
552
  modelId,
535
553
  multiplier: 2,
536
554
  requiresResponseConfirmation: true,
537
555
  source: XAI_PRIORITY_PRICING,
538
- verifiedAt: "2026-08-18",
556
+ verifiedAt: modelId === "grok-4.7" ? "2026-09-23" : "2026-08-18",
539
557
  })),
558
+ // Claude Code subscription fast draws usage credits at API fast rates, so OAuth is included.
559
+ ...["anthropic", "anthropic-apikey"].flatMap(provider =>
560
+ ["claude-opus-5-5", "claude-opus-5", "claude-opus-4-8"].map((modelId): PriorityPricingRule => ({
561
+ provider,
562
+ modelId,
563
+ multiplier: 2,
564
+ requiresResponseConfirmation: true,
565
+ source: ANTHROPIC_FAST_PRICING,
566
+ verifiedAt: "2026-09-23",
567
+ })),
568
+ ),
540
569
  ];
541
570
 
542
571
  /** Exact provider/model priority-pricing lookup. */
@@ -662,6 +691,19 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
662
691
  source: "https://docs.x.ai/developers/pricing",
663
692
  verifiedAt: "2026-08-18",
664
693
  },
694
+ {
695
+ // xAI documents the same whole-request >=200k band for grok-4.7;
696
+ // priority stacking remains a lower bound. See
697
+ // devlog/_plan/260923_grok47_parity/010_probe-evidence.md.
698
+ provider: "xai",
699
+ modelId: "grok-4.7",
700
+ thresholdInputTokens: 200_000,
701
+ inclusive: true,
702
+ multiplier: UNIFORM_DOUBLE,
703
+ confirmedPriorityRelation: "lower-bound",
704
+ source: "https://docs.x.ai/developers/models/grok-4.7",
705
+ verifiedAt: "2026-09-23",
706
+ },
665
707
  ...["minimax", "minimax-cn"].map((provider): ContextTier => ({
666
708
  provider,
667
709
  modelId: "MiniMax-M3",
package/src/usage/log.ts CHANGED
@@ -5,7 +5,7 @@ import { getConfigDir } from "../config";
5
5
  import type { CodexAffinityMove, CodexAffinityReason } from "../codex/routing";
6
6
  import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory";
7
7
  import { recordOwnedConfigPath } from "../lib/config-ownership";
8
- import { sanitizeLogMetadataString } from "../lib/redact";
8
+ import { redactSecretString, sanitizeLogMetadataString } from "../lib/redact";
9
9
  import { usageDisplayTotalTokens } from "./totals";
10
10
  import { normalizeAttemptDeliverySummary } from "./attempt-delivery";
11
11
  import {
@@ -222,8 +222,39 @@ export interface PersistedRequestSpend extends RequestSpendTotals {
222
222
  }
223
223
 
224
224
  const MAX_PERSISTED_MOVE_REASONS = 8;
225
+ // Model selectors are NOT length-bound at admission: configured and discovered
226
+ // model ids reach MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH, and the wire `model`
227
+ // field is raw client input. Persisting a plain prefix would merge selectors
228
+ // that share it, so over-long selectors persist as prefix + a digest of the
229
+ // FULL selector — bounded, deterministic, and still exact-matchable.
230
+ const MAX_PERSISTED_REQUESTED_MODEL_LEN = 130;
231
+ const REQUESTED_MODEL_DIGEST_HEX_LEN = 16;
225
232
  const LOGICAL_REQUEST_ID_RE = /^[A-Za-z0-9_.:-]{1,64}$/;
226
233
 
234
+ /**
235
+ * Persisted form of the wire model selector. Selectors within the bound persist
236
+ * verbatim; longer selectors persist as a prefix plus a short digest of the full
237
+ * value, so two distinct selectors that share the prefix never collapse into one
238
+ * persisted identity. Exact-match readers (`requested_model = ?`) must encode
239
+ * lookup input through this same function. Idempotent — encoded forms fit the
240
+ * bound — which matters because rows are normalized again on read.
241
+ *
242
+ * Idempotence has one cost: a literal selector that equals another selector's
243
+ * persisted form is indistinguishable from it, so both rows share one display
244
+ * value and one exact-match filter. Reaching that needs the caller to send the
245
+ * exact prefix-and-digest string; keeping them apart would need a separate
246
+ * full-selector digest column.
247
+ */
248
+ export function encodePersistedRequestedModel(selector: string): string {
249
+ if (selector.length <= MAX_PERSISTED_REQUESTED_MODEL_LEN) return selector;
250
+ const digest = createHash("sha256")
251
+ .update(selector)
252
+ .digest("hex")
253
+ .slice(0, REQUESTED_MODEL_DIGEST_HEX_LEN);
254
+ const prefixLen = MAX_PERSISTED_REQUESTED_MODEL_LEN - REQUESTED_MODEL_DIGEST_HEX_LEN - 1;
255
+ return `${selector.slice(0, prefixLen)}~${digest}`;
256
+ }
257
+
227
258
  export function isLogicalRequestId(value: unknown): value is string {
228
259
  return typeof value === "string" && LOGICAL_REQUEST_ID_RE.test(value);
229
260
  }
@@ -284,6 +315,10 @@ export interface PersistedUsageEntry {
284
315
  /** Best-effort chat/session correlation for Logs grouping (#330). */
285
316
  conversationId?: string;
286
317
  resolvedModel?: string;
318
+ /** Model the upstream actually served (openai-model header or response body). */
319
+ servedModel?: string;
320
+ /** The exact model id sent upstream when it differs from the client-facing `model`. */
321
+ wireModel?: string;
287
322
  requestedModel?: string;
288
323
  /** Original bare helper model when the opt-in shadow-call route rewrote this request. */
289
324
  shadowCallRewrittenFrom?: string;
@@ -806,6 +841,80 @@ function capMetadataString(s: string): string {
806
841
  return s.length > MAX_METADATA_STRING_LEN ? s.slice(0, MAX_METADATA_STRING_LEN) : s;
807
842
  }
808
843
 
844
+ const MAX_SERVED_MODEL_LENGTH = 200;
845
+ /**
846
+ * An upstream model is an identifier, never free-form text to truncate into one. A value the
847
+ * secret redactor would change is dropped rather than logged, because credential-shaped text can
848
+ * fit the identifier alphabet.
849
+ */
850
+ export function sanitizeServedModel(value: unknown): string | undefined {
851
+ return typeof value === "string"
852
+ && value.length <= MAX_SERVED_MODEL_LENGTH
853
+ && /^[A-Za-z0-9._:/@+-]+$/.test(value)
854
+ && redactSecretString(value) === value
855
+ ? value
856
+ : undefined;
857
+ }
858
+
859
+ /** The identity fields that decide whether a response model is ocx's own echo. */
860
+ export interface ServedModelEchoSource {
861
+ provider?: string;
862
+ model?: string;
863
+ wireModel?: string;
864
+ requestedAlias?: string;
865
+ requestedModel?: string;
866
+ /** Client-facing selector this proxy wrote into `response.model` (Anthropic routes keep it). */
867
+ responseModelEcho?: string;
868
+ }
869
+
870
+ /**
871
+ * True when `served` is the client's own selector echoed back rather than a model the upstream
872
+ * reported. Anthropic routes answer with the Codex-facing selector (`anthropic/claude-opus-5-5`),
873
+ * and recording that as the served model painted every such row as rerouted. A value equal to
874
+ * the wire model is never an echo. On adapter paths the upstream's real model is not observable
875
+ * at all, so this only removes a false signal; passthrough `openai-model` observations are kept.
876
+ */
877
+ export function isClientSelectorEcho(source: ServedModelEchoSource, served: string | undefined): boolean {
878
+ if (served === undefined) return false;
879
+ const wire = source.wireModel ?? source.model;
880
+ if (served === wire) return false;
881
+ return served === source.responseModelEcho
882
+ || served === source.requestedAlias
883
+ || served === source.requestedModel
884
+ || (source.provider !== undefined && wire !== undefined && served === `${source.provider}/${wire}`);
885
+ }
886
+
887
+ /** Record a response-body model as the served model when it is a real upstream observation. */
888
+ export function recordObservedServedModel(
889
+ target: ServedModelEchoSource & { servedModel?: string; resolvedModel?: string; preserveResolvedModelFromRoute?: boolean },
890
+ value: unknown,
891
+ ): void {
892
+ const servedModel = sanitizeServedModel(value);
893
+ if (!servedModel || isClientSelectorEcho(target, servedModel)) return;
894
+ target.servedModel = servedModel;
895
+ if (!target.preserveResolvedModelFromRoute) target.resolvedModel = servedModel;
896
+ }
897
+
898
+ /**
899
+ * Model identity fields for a log row. The served model is sanitized, and a resolvedModel that
900
+ * only echoed a dropped served model is dropped with it, so the rejected value cannot survive
901
+ * under the other name. A client-selector echo is dropped the same way, which also repairs
902
+ * rows persisted before the echo was filtered at capture.
903
+ */
904
+ export function modelIdentityLogFields(source: ServedModelEchoSource & { resolvedModel?: string; servedModel?: unknown }): {
905
+ resolvedModel?: string; servedModel?: string; wireModel?: string;
906
+ } {
907
+ const sanitized = sanitizeServedModel(source.servedModel);
908
+ const servedModel = isClientSelectorEcho(source, sanitized) ? undefined : sanitized;
909
+ const resolvedModel = source.servedModel !== undefined && source.resolvedModel === source.servedModel && !servedModel
910
+ ? undefined : source.resolvedModel;
911
+ return {
912
+ ...(resolvedModel ? { resolvedModel } : {}),
913
+ ...(servedModel ? { servedModel } : {}),
914
+ ...(source.wireModel ? { wireModel: source.wireModel } : {}),
915
+ };
916
+ }
917
+
809
918
  /** Test seam: the normalization branch old rows take is worth asserting directly. */
810
919
  export function normalizeUsageEntryForTest(entry: PersistedUsageEntry): PersistedUsageEntry {
811
920
  return normalizeUsageEntry(entry);
@@ -817,6 +926,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
817
926
  const callerServiceTier = sanitizeLogMetadataString(entry.callerServiceTier);
818
927
  const responseServiceTier = sanitizeLogMetadataString(entry.responseServiceTier);
819
928
  const shadowCallRewrittenFrom = sanitizeLogMetadataString(entry.shadowCallRewrittenFrom);
929
+ const { servedModel, resolvedModel } = modelIdentityLogFields(entry);
820
930
  const claudeCompatibility = normalizeClaudeCompatibilityUsageLog(entry.claudeCompatibility);
821
931
  const transportPhase = isKnownTransportPhase(entry.transportPhase) ? entry.transportPhase : undefined;
822
932
  const terminalSource = isKnownTerminalSource(entry.terminalSource) ? entry.terminalSource : undefined;
@@ -855,8 +965,12 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
855
965
  ...(typeof entry.conversationId === "string" && entry.conversationId.trim()
856
966
  ? { conversationId: entry.conversationId.trim().slice(0, 128) }
857
967
  : {}),
858
- ...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
859
- ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
968
+ ...(resolvedModel ? { resolvedModel } : {}),
969
+ ...(servedModel ? { servedModel } : {}),
970
+ ...(entry.wireModel ? { wireModel: entry.wireModel } : {}),
971
+ ...(typeof entry.requestedModel === "string" && entry.requestedModel
972
+ ? { requestedModel: encodePersistedRequestedModel(entry.requestedModel) }
973
+ : {}),
860
974
  ...(shadowCallRewrittenFrom ? { shadowCallRewrittenFrom } : {}),
861
975
  ...(typeof entry.requestedEffort === "string" && entry.requestedEffort
862
976
  ? { requestedEffort: capMetadataString(entry.requestedEffort) }
@@ -36,6 +36,7 @@ export const ATTEMPT_RECOVERY_KIND_ROSTER = Object.freeze([
36
36
  "opaque-blob-rejection",
37
37
  "empty-completion",
38
38
  "reasoning-effort-downgrade",
39
+ "anthropic-fast-downgrade",
39
40
  ] as const);
40
41
 
41
42
  export type AttemptRecoveryKind = typeof ATTEMPT_RECOVERY_KIND_ROSTER[number];
@@ -1,3 +1,4 @@
1
+ import { baseProviderLabel } from "../providers/label";
1
2
  import { cacheTokensFromUsage, usageAttributions } from "./summary";
2
3
  import type { PersistedUsageEntry } from "./log";
3
4
  import { usageDisplayTotalTokens } from "./totals";
@@ -55,6 +56,30 @@ export function isTimelineModelId(value: unknown): value is string {
55
56
  return typeof value === "string" && /^[^/\s]+\/\S+$/.test(value);
56
57
  }
57
58
 
59
+ /**
60
+ * Pool accounts log as `openai-p<hex6>` (and older rows as `openai-main`/`chatgpt`), so the raw
61
+ * provider would draw one line per account for the same model. The usage summary already folds these
62
+ * through `baseProviderLabel`; the timeline keys on the same label so both views agree.
63
+ */
64
+ function timelineModelId(provider: string, model: string): string {
65
+ return `${baseProviderLabel(provider)}/${model}`;
66
+ }
67
+
68
+ /** A saved selection may still name a pool account (`openai-p6bc633/gpt-5`); it selects the merged row. */
69
+ export function normalizeTimelineModelId(id: string): string {
70
+ const cut = id.indexOf("/");
71
+ return timelineModelId(id.slice(0, cut), id.slice(cut + 1));
72
+ }
73
+
74
+ /**
75
+ * In account grouping the pool suffix is the account when no explicit label was stamped. Codex pool
76
+ * accounts and Anthropic OAuth accounts (`formatAnthropicProviderForLog`) both log that way.
77
+ */
78
+ function poolAccountLabel(provider: string): string | undefined {
79
+ if (baseProviderLabel(provider) === provider) return undefined;
80
+ return provider.match(/-(main|p[a-f0-9]{6})$/)?.[1];
81
+ }
82
+
58
83
  function parseModels(raw: string | null): string[] | null | { error: string } {
59
84
  if (raw === null || raw.trim() === "") return null;
60
85
  const models = raw.split(",").map(model => model.trim());
@@ -127,6 +152,7 @@ export function createTimelineAccumulator(query: TimelineQuery): { add(entry: Pe
127
152
  const series = new Map<string, SeriesState>();
128
153
  const availableModels = new Set<string>();
129
154
  const hiddenProviders = new Set(query.hiddenProviders);
155
+ const selectedModels = query.models === null ? null : new Set(query.models.map(normalizeTimelineModelId));
130
156
  let missingMeasurements = 0;
131
157
 
132
158
  function add(entry: PersistedUsageEntry): void {
@@ -134,19 +160,21 @@ export function createTimelineAccumulator(query: TimelineQuery): { add(entry: Pe
134
160
  const bucket = Math.floor((entry.timestamp - startMs) / (bucketSeconds * 1000));
135
161
  if (bucket < 0 || bucket >= buckets) return;
136
162
  for (const attribution of usageAttributions(entry)) {
137
- if (hiddenProviders.has(attribution.provider)) continue;
138
- const modelId = `${attribution.provider}/${attribution.model}`;
163
+ const provider = baseProviderLabel(attribution.provider);
164
+ if (hiddenProviders.has(attribution.provider) || hiddenProviders.has(provider)) continue;
165
+ const modelId = timelineModelId(attribution.provider, attribution.model);
139
166
  availableModels.add(modelId);
140
- if (query.models && !query.models.includes(modelId)) continue;
167
+ if (selectedModels && !selectedModels.has(modelId)) continue;
168
+ const accountLogLabel = attribution.accountLogLabel ?? poolAccountLabel(attribution.provider) ?? "unknown";
141
169
  const id = query.grouping === "model"
142
170
  ? modelId
143
- : `${modelId} · ${attribution.accountLogLabel ?? "unknown"}`;
171
+ : `${modelId} · ${accountLogLabel}`;
144
172
  let state = series.get(id);
145
173
  if (!state) {
146
174
  state = {
147
- provider: attribution.provider,
175
+ provider,
148
176
  model: attribution.model,
149
- ...(query.grouping === "modelAccount" ? { accountLogLabel: attribution.accountLogLabel ?? "unknown" } : {}),
177
+ ...(query.grouping === "modelAccount" ? { accountLogLabel } : {}),
150
178
  points: Array<number>(buckets).fill(0),
151
179
  requests: new Map(),
152
180
  };
@@ -22,9 +22,9 @@ import { statSync } from "node:fs";
22
22
 
23
23
  import { getConfigPath, readConfigDiagnostics } from "../config";
24
24
  import type { OcxConfig, OcxProviderConfig } from "../types";
25
+ import { refreshConfigDerivedRegistries } from "../config/derived-registries";
25
26
  import {
26
27
  refreshPreservedProviderOwner,
27
- refreshUserCostOverlays,
28
28
  registerPreservedProviderOwner,
29
29
  setPreservedDiskOnlyProviders,
30
30
  unregisterPreservedProviderOwner,
@@ -169,7 +169,7 @@ export function reconcileUserCostOverlaysFromDisk(liveConfig?: OcxConfig | null)
169
169
  }
170
170
  // Refresh from the DISK config: overlays for providers only added by the
171
171
  // external edit are display-only and must still resolve for historical rows.
172
- refreshUserCostOverlays(disk);
172
+ refreshConfigDerivedRegistries(disk);
173
173
  return true;
174
174
  }
175
175
 
@@ -190,7 +190,7 @@ function reconcileForOwners(): void {
190
190
  // cache resurrect providers that were intentionally removed.
191
191
  setPreservedDiskOnlyProviders(null);
192
192
  }
193
- refreshUserCostOverlays(disk);
193
+ refreshConfigDerivedRegistries(disk);
194
194
  }
195
195
 
196
196
  /** Test-only observation that proves the timer actually read an invalid config fallback. */
@@ -1,4 +1,4 @@
1
- import { NATIVE_OPENAI_MODELS, nativeReasoningEfforts } from "../codex/catalog/metadata";
1
+ import { SUPPORTED_NATIVE_OPENAI_SLUGS, nativeReasoningEfforts } from "../codex/catalog/metadata";
2
2
  import {
3
3
  VISION_REASONING_EFFORTS,
4
4
  sanitizeVisionReasoning,
@@ -13,15 +13,13 @@ declare module "../types" {
13
13
  }
14
14
  }
15
15
 
16
- const NATIVE_VISION_MODELS = new Set(NATIVE_OPENAI_MODELS);
17
-
18
16
  /**
19
17
  * Return the known vision reasoning ladder for a native OpenAI model.
20
18
  * Unknown/custom models deliberately return undefined so callers stay permissive when reliable
21
19
  * capability metadata is unavailable.
22
20
  */
23
21
  export function nativeVisionReasoningEfforts(modelId: string): VisionReasoningEffort[] | undefined {
24
- if (!NATIVE_VISION_MODELS.has(modelId)) return undefined;
22
+ if (!SUPPORTED_NATIVE_OPENAI_SLUGS.has(modelId)) return undefined;
25
23
  const advertised = new Set(nativeReasoningEfforts(modelId));
26
24
  const supported = VISION_REASONING_EFFORTS.filter(effort => advertised.has(effort));
27
25
  return supported.length > 0 ? [...supported] : undefined;
@@ -14,6 +14,7 @@ import type { OcxProviderConfig } from "../types";
14
14
  import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
15
15
  import { applyUpstreamRecoveryInit, fetchWithResetRetry } from "../lib/upstream-retry";
16
16
  import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
17
+ import { readBoundedResponseBytes } from "../lib/bounded-body";
17
18
  import { sidecarEnter } from "../lib/sidecar-tracker";
18
19
  import { redactSecretString } from "../lib/redact";
19
20
  import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
@@ -114,10 +115,19 @@ export async function runXaiWebSearch(
114
115
  );
115
116
  const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
116
117
  if (!res.ok) {
117
- const t = await res.text().catch(() => "");
118
- detachBodyGuard();
119
- const entitlement = res.status === 401 || res.status === 403 ? " (Grok OAuth entitlement — re-run ocx login xai?)" : "";
120
- return { text: "", sources: [], error: `xai sidecar HTTP ${res.status}${entitlement}: ${redactSecretString(t.slice(0, 200))}` };
118
+ try {
119
+ const bounded = await readBoundedResponseBytes(res, {
120
+ maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
121
+ signal: linkedSignal.signal,
122
+ });
123
+ const detail = bounded.oversized
124
+ ? "response body exceeded byte bound"
125
+ : redactSecretString(new TextDecoder().decode(bounded.bytes).slice(0, 200));
126
+ const entitlement = res.status === 401 || res.status === 403 ? " (Grok OAuth entitlement — re-run ocx login xai?)" : "";
127
+ return { text: "", sources: [], error: `xai sidecar HTTP ${res.status}${entitlement}: ${detail}` };
128
+ } finally {
129
+ detachBodyGuard();
130
+ }
121
131
  }
122
132
  try {
123
133
  return await parseXaiResponsesSSE(res);