@iislee/opencodex 2.11.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 (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,585 @@
1
+ import { baseProviderLabel } from "../providers/label";
2
+ import { canonicalAntigravityUsageModel } from "../providers/antigravity-models";
3
+ import { usageDisplayTotalTokens } from "./totals";
4
+ import type { PersistedUsageEntry, UsageStatus } from "./log";
5
+ import { estimateComboCost, estimateRequestCost, effectiveServiceTier } from "./cost";
6
+
7
+ export type UsageRange = "7d" | "30d" | "all";
8
+ export type UsageSurface = "all" | "codex" | "claude" | "grok";
9
+
10
+ export interface UsageSummaryTotals {
11
+ requests: number;
12
+ attemptCount: number;
13
+ measuredRequests: number;
14
+ reportedRequests: number;
15
+ unreportedRequests: number;
16
+ unsupportedRequests: number;
17
+ estimatedRequests: number;
18
+ inputTokens: number;
19
+ outputTokens: number;
20
+ cachedInputTokens: number;
21
+ cacheReadInputTokens: number;
22
+ cacheCreationInputTokens: number;
23
+ reasoningOutputTokens: number;
24
+ totalTokens: number;
25
+ coverageRatio: number;
26
+ /** Display-time estimated cost in USD for the filtered window (WP6, devlog 004).
27
+ * Sums per-request estimateRequestCost / per-attempt combo costs; requests whose
28
+ * price is unmatched are excluded from the sum and counted separately. */
29
+ estimatedCostUsd: number;
30
+ pricedRequests: number;
31
+ /** Requests with usage but no matched price anywhere (excluded from the sum). */
32
+ unpricedRequests: number;
33
+ /** Requests whose usage itself is missing/unsupported, so no cost can be computed. */
34
+ unmeteredRequests: number;
35
+ }
36
+
37
+ export interface UsageDay {
38
+ date: string;
39
+ requests: number;
40
+ measuredRequests: number;
41
+ reportedRequests: number;
42
+ totalTokens: number;
43
+ models: UsageDayModel[];
44
+ }
45
+
46
+ export interface UsageDayModel {
47
+ model: string;
48
+ provider: string;
49
+ requests: number;
50
+ attemptCount: number;
51
+ totalTokens: number;
52
+ }
53
+
54
+ export interface UsageModel {
55
+ provider: string;
56
+ model: string;
57
+ resolvedModel?: string;
58
+ requests: number;
59
+ attemptCount: number;
60
+ measuredRequests: number;
61
+ reportedRequests: number;
62
+ estimatedRequests: number;
63
+ totalTokens: number;
64
+ inputTokens: number;
65
+ outputTokens: number;
66
+ shareRatio: number;
67
+ estimatedCostUsd?: number;
68
+ }
69
+
70
+ export interface UsageProvider {
71
+ provider: string;
72
+ requests: number;
73
+ attemptCount: number;
74
+ measuredRequests: number;
75
+ reportedRequests: number;
76
+ estimatedRequests: number;
77
+ totalTokens: number;
78
+ shareRatio: number;
79
+ estimatedCostUsd?: number;
80
+ }
81
+
82
+ export interface UsageSummary {
83
+ range: UsageRange;
84
+ surface: UsageSurface;
85
+ since: number | null;
86
+ generatedAt: number;
87
+ summary: UsageSummaryTotals;
88
+ days: UsageDay[];
89
+ models: UsageModel[];
90
+ providers: UsageProvider[];
91
+ }
92
+
93
+ const DAY_MS = 86_400_000;
94
+ export const MAX_USAGE_MODEL_BREAKDOWN_ROWS = 256;
95
+
96
+ function retainedBreakdownRows<T>(
97
+ rows: T[],
98
+ aggregateOverflow: (overflow: T[]) => T,
99
+ ): T[] {
100
+ if (rows.length <= MAX_USAGE_MODEL_BREAKDOWN_ROWS) return rows;
101
+ const keep = rows.slice(0, MAX_USAGE_MODEL_BREAKDOWN_ROWS - 1);
102
+ keep.push(aggregateOverflow(rows.slice(MAX_USAGE_MODEL_BREAKDOWN_ROWS - 1)));
103
+ return keep;
104
+ }
105
+
106
+ export function parseRange(input: string | null | undefined): UsageRange {
107
+ if (input === "7d" || input === "30d" || input === "all") return input;
108
+ return "30d";
109
+ }
110
+
111
+ export function parseUsageSurface(input: string | null | undefined): UsageSurface {
112
+ if (input === "codex" || input === "claude" || input === "grok") return input;
113
+ return "all";
114
+ }
115
+
116
+ function rangeWindow(range: UsageRange, now: number): { since: number | null; days: number } {
117
+ if (range === "7d") return { since: now - 7 * DAY_MS, days: 7 };
118
+ if (range === "30d") return { since: now - 30 * DAY_MS, days: 30 };
119
+ return { since: null, days: 0 };
120
+ }
121
+
122
+ function localDateKey(ts: number): string {
123
+ const d = new Date(ts);
124
+ const y = d.getFullYear();
125
+ const m = String(d.getMonth() + 1).padStart(2, "0");
126
+ const day = String(d.getDate()).padStart(2, "0");
127
+ return `${y}-${m}-${day}`;
128
+ }
129
+
130
+ function dayCountForAllRange(entries: PersistedUsageEntry[], now: number): number {
131
+ if (entries.length === 0) return 1;
132
+ const oldest = entries.reduce((min, e) => Math.min(min, e.timestamp), entries[0].timestamp);
133
+ const days = Math.ceil((now - oldest) / DAY_MS) + 1;
134
+ return Math.max(1, days);
135
+ }
136
+
137
+ function blankTotals(): UsageSummaryTotals {
138
+ return {
139
+ requests: 0,
140
+ attemptCount: 0,
141
+ measuredRequests: 0,
142
+ reportedRequests: 0,
143
+ unreportedRequests: 0,
144
+ unsupportedRequests: 0,
145
+ estimatedRequests: 0,
146
+ inputTokens: 0,
147
+ outputTokens: 0,
148
+ cachedInputTokens: 0,
149
+ cacheReadInputTokens: 0,
150
+ cacheCreationInputTokens: 0,
151
+ reasoningOutputTokens: 0,
152
+ totalTokens: 0,
153
+ coverageRatio: 0,
154
+ estimatedCostUsd: 0,
155
+ pricedRequests: 0,
156
+ unpricedRequests: 0,
157
+ unmeteredRequests: 0,
158
+ };
159
+ }
160
+
161
+ function isMeasuredStatus(status: UsageStatus): boolean {
162
+ return status === "reported" || status === "estimated";
163
+ }
164
+
165
+ interface UsageAttribution {
166
+ requestId: string;
167
+ provider: string;
168
+ model: string;
169
+ resolvedModel?: string;
170
+ usageStatus: UsageStatus;
171
+ usage?: PersistedUsageEntry["usage"];
172
+ totalTokens?: number;
173
+ }
174
+
175
+
176
+ /**
177
+ * Usage row identity for model breakdowns.
178
+ * Google Antigravity collapses wire/compat/suffix ids to picker/call base models so
179
+ * historical effort-variant logs merge with current base-model invocations.
180
+ */
181
+ function usageModelIdentity(
182
+ provider: string,
183
+ model: string,
184
+ resolvedModel?: string,
185
+ ): { model: string; resolvedModel?: string } {
186
+ if (baseProviderLabel(provider) !== "google-antigravity") {
187
+ return resolvedModel ? { model, resolvedModel } : { model };
188
+ }
189
+ const fromModel = canonicalAntigravityUsageModel(model);
190
+ const fromResolved = resolvedModel
191
+ ? canonicalAntigravityUsageModel(resolvedModel)
192
+ : undefined;
193
+ // Prefer an explicit base mapping from model; if model is unknown but resolved maps
194
+ // to a known base, use that (covers base call + upstream wire resolvedModel pairs).
195
+ const canonical = fromModel !== model
196
+ ? fromModel
197
+ : (fromResolved && fromResolved !== resolvedModel ? fromResolved : fromModel);
198
+ return { model: canonical };
199
+ }
200
+
201
+ function usageModelKey(providerKey: string, model: string): string {
202
+ return `${providerKey}/${model}`;
203
+ }
204
+
205
+ function antigravityUsageModel(provider: string, model: string): string {
206
+ if (baseProviderLabel(provider) !== "google-antigravity") return model;
207
+ return canonicalAntigravityUsageModel(model);
208
+ }
209
+
210
+ function usageAttributions(entry: PersistedUsageEntry): UsageAttribution[] {
211
+ if (!entry.attempts?.length) {
212
+ return [{
213
+ requestId: entry.requestId,
214
+ provider: entry.provider,
215
+ ...usageModelIdentity(entry.provider, entry.model, entry.resolvedModel),
216
+ usageStatus: entry.usageStatus,
217
+ ...(entry.usage ? { usage: entry.usage } : {}),
218
+ ...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
219
+ }];
220
+ }
221
+ return entry.attempts.map(attempt => ({
222
+ requestId: entry.requestId,
223
+ provider: attempt.provider,
224
+ ...usageModelIdentity(attempt.provider, attempt.model),
225
+ usageStatus: attempt.usageStatus,
226
+ ...(attempt.usage ? { usage: attempt.usage } : {}),
227
+ ...(attempt.totalTokens !== undefined ? { totalTokens: attempt.totalTokens } : {}),
228
+ }));
229
+ }
230
+
231
+ function foldAttributionStatuses(statuses: readonly UsageStatus[]): UsageStatus {
232
+ if (statuses.length > 0 && statuses.every(status => status === "unsupported")) {
233
+ return "unsupported";
234
+ }
235
+ if (statuses.some(status => status === "unreported" || status === "unsupported")) {
236
+ return "unreported";
237
+ }
238
+ if (statuses.some(status => status === "estimated")) return "estimated";
239
+ return statuses.length > 0 ? "reported" : "unreported";
240
+ }
241
+
242
+ function bumpStatus(totals: UsageSummaryTotals, status: UsageStatus): void {
243
+ totals.requests += 1;
244
+ if (isMeasuredStatus(status)) totals.measuredRequests += 1;
245
+ if (status === "reported") totals.reportedRequests += 1;
246
+ else if (status === "unreported") totals.unreportedRequests += 1;
247
+ else if (status === "unsupported") totals.unsupportedRequests += 1;
248
+ else if (status === "estimated") totals.estimatedRequests += 1;
249
+ }
250
+
251
+ function addTokens(
252
+ totals: UsageSummaryTotals,
253
+ entry: Pick<PersistedUsageEntry, "usage" | "totalTokens">,
254
+ ): void {
255
+ if (!entry.usage) return;
256
+ totals.inputTokens += entry.usage.inputTokens;
257
+ totals.outputTokens += entry.usage.outputTokens;
258
+ // Prefer the explicit read/write split; legacy claude-route rows stored read+write
259
+ // combined in cachedInputTokens with only the creation split present (devlog 070),
260
+ // so recover reads by subtracting the write share for those rows.
261
+ const creation = entry.usage.cacheCreationInputTokens;
262
+ const read = typeof entry.usage.cacheReadInputTokens === "number"
263
+ ? entry.usage.cacheReadInputTokens
264
+ : typeof entry.usage.cachedInputTokens === "number" && typeof creation === "number"
265
+ ? Math.max(0, entry.usage.cachedInputTokens - creation)
266
+ : entry.usage.cachedInputTokens;
267
+ if (typeof read === "number") {
268
+ totals.cachedInputTokens += read;
269
+ totals.cacheReadInputTokens += read;
270
+ }
271
+ if (typeof creation === "number") totals.cacheCreationInputTokens += creation;
272
+ if (typeof entry.usage.reasoningOutputTokens === "number") totals.reasoningOutputTokens += entry.usage.reasoningOutputTokens;
273
+ totals.totalTokens += usageDisplayTotalTokens(entry.usage, entry.totalTokens) ?? 0;
274
+ }
275
+
276
+ function finalizeCoverage(totals: UsageSummaryTotals): void {
277
+ totals.coverageRatio = totals.requests === 0 ? 0 : totals.measuredRequests / totals.requests;
278
+ }
279
+
280
+ function addEstimatedCost(
281
+ totals: UsageSummaryTotals,
282
+ entry: Pick<PersistedUsageEntry, "provider" | "model" | "usageStatus" | "usage" | "attempts" | "responseServiceTier" | "requestedServiceTier" | "configuredServiceTier">,
283
+ ): void {
284
+ if (entry.usageStatus === "unreported" || entry.usageStatus === "unsupported"
285
+ || (!entry.usage && !entry.attempts?.length)) {
286
+ totals.unmeteredRequests += 1;
287
+ return;
288
+ }
289
+ const tier = effectiveServiceTier(entry);
290
+ const estimate = entry.attempts?.length
291
+ ? estimateComboCost(entry.attempts, undefined, tier)
292
+ : estimateRequestCost({ provider: entry.provider, model: entry.model, usage: entry.usage, usageStatus: entry.usageStatus, serviceTier: tier });
293
+ if (!estimate) {
294
+ totals.unpricedRequests += 1;
295
+ return;
296
+ }
297
+ totals.pricedRequests += 1;
298
+ totals.estimatedCostUsd += estimate.cost.total;
299
+ }
300
+
301
+ function buildDayGrid(range: UsageRange, since: number | null, now: number, entries: PersistedUsageEntry[]): UsageDay[] {
302
+ const window = rangeWindow(range, now);
303
+ const days = range === "all" ? dayCountForAllRange(entries, now) : window.days;
304
+ const grid = new Map<string, UsageDay>();
305
+ // Per-day model breakdown accumulator, keyed by day then provider/model, so the 7d bar chart can
306
+ // render a per-model stacked bar with a hover tooltip without a second pass over the entries.
307
+ const dayModels = new Map<string, Map<string, UsageDayModel>>();
308
+ const dayModelRequests = new Map<string, Set<string>>();
309
+ const bumpDayModel = (dayKey: string, attribution: UsageAttribution): void => {
310
+ let models = dayModels.get(dayKey);
311
+ if (!models) { models = new Map(); dayModels.set(dayKey, models); }
312
+ const providerKey = baseProviderLabel(attribution.provider);
313
+ const mKey = usageModelKey(providerKey, attribution.model);
314
+ let m = models.get(mKey);
315
+ if (!m) {
316
+ m = { model: attribution.model, provider: providerKey, requests: 0, attemptCount: 0, totalTokens: 0 };
317
+ models.set(mKey, m);
318
+ }
319
+ const requestKey = `${dayKey}\0${mKey}`;
320
+ let requests = dayModelRequests.get(requestKey);
321
+ if (!requests) { requests = new Set(); dayModelRequests.set(requestKey, requests); }
322
+ requests.add(attribution.requestId);
323
+ m.requests = requests.size;
324
+ m.attemptCount += 1;
325
+ m.totalTokens += usageDisplayTotalTokens(attribution.usage, attribution.totalTokens) ?? 0;
326
+ };
327
+ for (let i = days - 1; i >= 0; i--) {
328
+ const key = localDateKey(now - i * DAY_MS);
329
+ grid.set(key, { date: key, requests: 0, measuredRequests: 0, reportedRequests: 0, totalTokens: 0, models: [] });
330
+ }
331
+ for (const entry of entries) {
332
+ const key = localDateKey(entry.timestamp);
333
+ let day = grid.get(key);
334
+ if (!day) {
335
+ day = { date: key, requests: 0, measuredRequests: 0, reportedRequests: 0, totalTokens: 0, models: [] };
336
+ grid.set(key, day);
337
+ }
338
+ day.requests += 1;
339
+ if (isMeasuredStatus(entry.usageStatus)) day.measuredRequests += 1;
340
+ if (entry.usageStatus === "reported") day.reportedRequests += 1;
341
+ day.totalTokens += usageDisplayTotalTokens(entry.usage, entry.totalTokens) ?? 0;
342
+ for (const attribution of usageAttributions(entry)) bumpDayModel(key, attribution);
343
+ }
344
+ void since;
345
+ const out = [...grid.values()].sort((a, b) => a.date.localeCompare(b.date));
346
+ for (const day of out) {
347
+ const models = dayModels.get(day.date);
348
+ if (models) {
349
+ const sorted = [...models.values()].sort((a, b) => b.requests - a.requests);
350
+ day.models = retainedBreakdownRows(sorted, overflow => {
351
+ const requests = new Set<string>();
352
+ let attemptCount = 0;
353
+ let totalTokens = 0;
354
+ for (const model of overflow) {
355
+ attemptCount += model.attemptCount;
356
+ totalTokens += model.totalTokens;
357
+ const requestKey = `${day.date}\0${usageModelKey(model.provider, model.model)}`;
358
+ for (const requestId of dayModelRequests.get(requestKey) ?? []) requests.add(requestId);
359
+ }
360
+ return { model: "other", provider: "other", requests: requests.size, attemptCount, totalTokens };
361
+ });
362
+ }
363
+ }
364
+ return out;
365
+ }
366
+
367
+ function buildModels(entries: PersistedUsageEntry[], totalTokens: number): UsageModel[] {
368
+ const byKey = new Map<string, UsageModel>();
369
+ const statusesByKey = new Map<string, Map<string, UsageStatus[]>>();
370
+ for (const entry of entries) {
371
+ for (const attribution of usageAttributions(entry)) {
372
+ const providerKey = baseProviderLabel(attribution.provider);
373
+ // resolvedModel is a routing detail, not a row identity.
374
+ const key = usageModelKey(providerKey, attribution.model);
375
+ let model = byKey.get(key);
376
+ if (!model) {
377
+ model = {
378
+ provider: providerKey,
379
+ model: attribution.model,
380
+ ...(attribution.resolvedModel ? { resolvedModel: attribution.resolvedModel } : {}),
381
+ requests: 0,
382
+ attemptCount: 0,
383
+ measuredRequests: 0,
384
+ reportedRequests: 0,
385
+ estimatedRequests: 0,
386
+ totalTokens: 0,
387
+ inputTokens: 0,
388
+ outputTokens: 0,
389
+ shareRatio: 0,
390
+ };
391
+ byKey.set(key, model);
392
+ }
393
+ model.attemptCount += 1;
394
+ let requests = statusesByKey.get(key);
395
+ if (!requests) { requests = new Map(); statusesByKey.set(key, requests); }
396
+ const statuses = requests.get(attribution.requestId) ?? [];
397
+ statuses.push(attribution.usageStatus);
398
+ requests.set(attribution.requestId, statuses);
399
+ if (attribution.usage) {
400
+ model.inputTokens += attribution.usage.inputTokens;
401
+ model.outputTokens += attribution.usage.outputTokens;
402
+ model.totalTokens += usageDisplayTotalTokens(attribution.usage, attribution.totalTokens) ?? 0;
403
+ }
404
+ }
405
+ }
406
+ for (const [key, model] of byKey) {
407
+ const groups = statusesByKey.get(key) ?? new Map();
408
+ model.requests = groups.size;
409
+ for (const statuses of groups.values()) {
410
+ const status = foldAttributionStatuses(statuses);
411
+ if (isMeasuredStatus(status)) model.measuredRequests += 1;
412
+ if (status === "reported") model.reportedRequests += 1;
413
+ else if (status === "estimated") model.estimatedRequests += 1;
414
+ }
415
+ }
416
+ // Accumulate per-model estimated cost
417
+ for (const entry of entries) {
418
+ const tier = effectiveServiceTier(entry);
419
+ const estimate = entry.attempts?.length
420
+ ? estimateComboCost(entry.attempts, undefined, tier)
421
+ : estimateRequestCost({ provider: entry.provider, model: entry.model, usage: entry.usage, usageStatus: entry.usageStatus, serviceTier: tier });
422
+ if (!estimate) continue;
423
+
424
+ if (entry.attempts?.length && estimate.attempts) {
425
+ // Combo: attribute each attempt's cost to its own model
426
+ for (const attemptEst of estimate.attempts) {
427
+ const aProviderKey = baseProviderLabel(attemptEst.provider);
428
+ const aKey = usageModelKey(aProviderKey, antigravityUsageModel(attemptEst.provider, attemptEst.model));
429
+ const m = byKey.get(aKey);
430
+ if (m) m.estimatedCostUsd = (m.estimatedCostUsd ?? 0) + attemptEst.cost.total;
431
+ }
432
+ } else {
433
+ // Single-target: attribute to the entry's model
434
+ const providerKey = baseProviderLabel(entry.provider);
435
+ const key = usageModelKey(providerKey, antigravityUsageModel(entry.provider, entry.model));
436
+ const m = byKey.get(key);
437
+ if (m) m.estimatedCostUsd = (m.estimatedCostUsd ?? 0) + estimate.cost.total;
438
+ }
439
+ }
440
+ const models = [...byKey.values()];
441
+ for (const m of models) m.shareRatio = totalTokens === 0 ? 0 : m.totalTokens / totalTokens;
442
+ const sorted = models.sort((a, b) => b.requests - a.requests);
443
+ return retainedBreakdownRows(sorted, overflow => {
444
+ const statusesByRequest = new Map<string, UsageStatus[]>();
445
+ const other: UsageModel = {
446
+ provider: "other",
447
+ model: "other",
448
+ requests: 0,
449
+ attemptCount: 0,
450
+ measuredRequests: 0,
451
+ reportedRequests: 0,
452
+ estimatedRequests: 0,
453
+ totalTokens: 0,
454
+ inputTokens: 0,
455
+ outputTokens: 0,
456
+ shareRatio: 0,
457
+ };
458
+ for (const model of overflow) {
459
+ other.attemptCount += model.attemptCount;
460
+ other.totalTokens += model.totalTokens;
461
+ other.inputTokens += model.inputTokens;
462
+ other.outputTokens += model.outputTokens;
463
+ if (model.estimatedCostUsd !== undefined) {
464
+ other.estimatedCostUsd = (other.estimatedCostUsd ?? 0) + model.estimatedCostUsd;
465
+ }
466
+ const key = usageModelKey(model.provider, model.model);
467
+ for (const [requestId, statuses] of statusesByKey.get(key) ?? []) {
468
+ const combined = statusesByRequest.get(requestId) ?? [];
469
+ combined.push(...statuses);
470
+ statusesByRequest.set(requestId, combined);
471
+ }
472
+ }
473
+ other.requests = statusesByRequest.size;
474
+ for (const statuses of statusesByRequest.values()) {
475
+ const status = foldAttributionStatuses(statuses);
476
+ if (isMeasuredStatus(status)) other.measuredRequests += 1;
477
+ if (status === "reported") other.reportedRequests += 1;
478
+ else if (status === "estimated") other.estimatedRequests += 1;
479
+ }
480
+ other.shareRatio = totalTokens === 0 ? 0 : other.totalTokens / totalTokens;
481
+ return other;
482
+ });
483
+ }
484
+
485
+ function buildProviders(entries: PersistedUsageEntry[], totalTokens: number): UsageProvider[] {
486
+ const byKey = new Map<string, UsageProvider>();
487
+ const statusesByKey = new Map<string, Map<string, UsageStatus[]>>();
488
+ for (const entry of entries) {
489
+ for (const attribution of usageAttributions(entry)) {
490
+ const providerKey = baseProviderLabel(attribution.provider);
491
+ let provider = byKey.get(providerKey);
492
+ if (!provider) {
493
+ provider = {
494
+ provider: providerKey,
495
+ requests: 0,
496
+ attemptCount: 0,
497
+ measuredRequests: 0,
498
+ reportedRequests: 0,
499
+ estimatedRequests: 0,
500
+ totalTokens: 0,
501
+ shareRatio: 0,
502
+ };
503
+ byKey.set(providerKey, provider);
504
+ }
505
+ provider.attemptCount += 1;
506
+ let requests = statusesByKey.get(providerKey);
507
+ if (!requests) { requests = new Map(); statusesByKey.set(providerKey, requests); }
508
+ const statuses = requests.get(attribution.requestId) ?? [];
509
+ statuses.push(attribution.usageStatus);
510
+ requests.set(attribution.requestId, statuses);
511
+ if (attribution.usage) {
512
+ provider.totalTokens += usageDisplayTotalTokens(attribution.usage, attribution.totalTokens) ?? 0;
513
+ }
514
+ }
515
+ }
516
+ for (const [key, provider] of byKey) {
517
+ const groups = statusesByKey.get(key) ?? new Map();
518
+ provider.requests = groups.size;
519
+ for (const statuses of groups.values()) {
520
+ const status = foldAttributionStatuses(statuses);
521
+ if (isMeasuredStatus(status)) provider.measuredRequests += 1;
522
+ if (status === "reported") provider.reportedRequests += 1;
523
+ else if (status === "estimated") provider.estimatedRequests += 1;
524
+ }
525
+ }
526
+ for (const entry of entries) {
527
+ const tier = effectiveServiceTier(entry);
528
+ const estimate = entry.attempts?.length
529
+ ? estimateComboCost(entry.attempts, undefined, tier)
530
+ : estimateRequestCost({ provider: entry.provider, model: entry.model, usage: entry.usage, usageStatus: entry.usageStatus, serviceTier: tier });
531
+ if (!estimate) continue;
532
+
533
+ if (entry.attempts?.length && estimate.attempts) {
534
+ for (const attemptEst of estimate.attempts) {
535
+ const aProviderKey = baseProviderLabel(attemptEst.provider);
536
+ const p = byKey.get(aProviderKey);
537
+ if (p) p.estimatedCostUsd = (p.estimatedCostUsd ?? 0) + attemptEst.cost.total;
538
+ }
539
+ } else {
540
+ const providerKey = baseProviderLabel(entry.provider);
541
+ const p = byKey.get(providerKey);
542
+ if (p) p.estimatedCostUsd = (p.estimatedCostUsd ?? 0) + estimate.cost.total;
543
+ }
544
+ }
545
+ const providers = [...byKey.values()];
546
+ for (const p of providers) p.shareRatio = totalTokens === 0 ? 0 : p.totalTokens / totalTokens;
547
+ return providers.sort((a, b) => b.requests - a.requests);
548
+ }
549
+
550
+ export function summarizeUsage(
551
+ entries: PersistedUsageEntry[],
552
+ range: UsageRange,
553
+ now: number,
554
+ surface: UsageSurface = "all",
555
+ ): UsageSummary {
556
+ const { since } = rangeWindow(range, now);
557
+ const filteredEntries = entries.filter(entry => {
558
+ if (since !== null && entry.timestamp < since) return false;
559
+ if (surface === "claude") return entry.surface === "claude" || entry.surface === "claude-desktop";
560
+ if (surface === "grok") return entry.surface === "grok";
561
+ // Codex = the historical unlabelled bucket. Before the grok tag existed every
562
+ // non-Claude turn landed here, and `surface !== "claude"` also swallowed
563
+ // claude-desktop — disjoint predicates fix both.
564
+ if (surface === "codex") return entry.surface === undefined;
565
+ return true;
566
+ });
567
+ const totals = blankTotals();
568
+ for (const entry of filteredEntries) {
569
+ bumpStatus(totals, entry.usageStatus);
570
+ totals.attemptCount += entry.attempts?.length ?? 1;
571
+ addTokens(totals, entry);
572
+ addEstimatedCost(totals, entry);
573
+ }
574
+ finalizeCoverage(totals);
575
+ return {
576
+ range,
577
+ surface,
578
+ since,
579
+ generatedAt: now,
580
+ summary: totals,
581
+ days: buildDayGrid(range, since, now, filteredEntries),
582
+ models: buildModels(filteredEntries, totals.totalTokens),
583
+ providers: buildProviders(filteredEntries, totals.totalTokens),
584
+ };
585
+ }
@@ -0,0 +1,14 @@
1
+ import type { OcxUsage } from "../types";
2
+
3
+ /**
4
+ * Canonical display total (devlog 070): `inputTokens` is already INCLUSIVE of cache
5
+ * read/write, so the total is simply input+output. Cache detail is never re-added —
6
+ * that was the 2x inflation bug on cache-heavy Claude rows. `Math.max` keeps legacy
7
+ * persisted rows (pre-070 exclusive input) honest via their stored explicit total.
8
+ */
9
+ export function usageDisplayTotalTokens(usage: OcxUsage | undefined, storedTotal?: number): number | undefined {
10
+ if (!usage) return storedTotal;
11
+ const baseTotal = usage.inputTokens + usage.outputTokens;
12
+ const explicitTotal = usage.totalTokens ?? storedTotal;
13
+ return typeof explicitTotal === "number" ? Math.max(explicitTotal, baseTotal) : baseTotal;
14
+ }