@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,411 @@
1
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { atomicWriteFile, getConfigDir } from "../config";
4
+ import { captureConfigGeneration, type GenerationContext } from "../lib/state-store-sweeper";
5
+
6
+ export type StoredAccountQuota = {
7
+ weeklyPercent?: number;
8
+ monthlyPercent?: number;
9
+ weeklyResetAt?: number;
10
+ monthlyResetAt?: number;
11
+ resetCredits?: number;
12
+ updatedAt: number;
13
+ };
14
+
15
+ /** Disk snapshot under OPENCODEX_HOME — usage percents only (no emails/tokens). */
16
+ const QUOTA_CACHE_FILENAME = "codex-quota-cache.json";
17
+ /** Keep last-known bars across restarts; WHAM still refreshes on TTL in live/prime paths. */
18
+ const QUOTA_DISK_MAX_AGE_MS = 6 * 60 * 60_000;
19
+ const QUOTA_PERSIST_DEBOUNCE_MS = 250;
20
+
21
+ type QuotaDiskFile = {
22
+ version: 1;
23
+ quotas: Record<string, StoredAccountQuota>;
24
+ };
25
+
26
+ let diskHydrated = false;
27
+ let persistTimer: ReturnType<typeof setTimeout> | null = null;
28
+
29
+ export type WhamUsageResponse = {
30
+ email?: string | null;
31
+ plan_type?: string | null;
32
+ rate_limit?: {
33
+ // Live WHAM payloads send explicit nulls for absent windows (issue #315 repro).
34
+ primary_window?: WhamUsageWindow | null;
35
+ secondary_window?: WhamUsageWindow | null;
36
+ tertiary_window?: WhamUsageWindow | null;
37
+ };
38
+ rate_limit_reset_credits?: {
39
+ available_count: number;
40
+ } | null;
41
+ };
42
+
43
+ type WhamUsageWindow = {
44
+ used_percent?: number;
45
+ reset_at?: number;
46
+ limit_window_seconds?: number;
47
+ };
48
+
49
+ const MONTHLY_WINDOW_MIN_SECONDS = 28 * 24 * 60 * 60;
50
+ const MONTHLY_WINDOW_MIN_MINUTES = MONTHLY_WINDOW_MIN_SECONDS / 60;
51
+
52
+ const accountQuota = new Map<string, StoredAccountQuota>();
53
+ let lastReconciledGeneration = 0;
54
+ let liveAccountIds = new Set<string>();
55
+
56
+ function mayCommitAccountQuota(accountId: string, writerGeneration: number): boolean {
57
+ return writerGeneration >= lastReconciledGeneration || liveAccountIds.has(accountId);
58
+ }
59
+
60
+ // Valid upstream percentages are normalized to 0..100. Keep "unknown" outside that domain so an
61
+ // actually exhausted account is still eligible for threshold rotation.
62
+ export const CODEX_UNKNOWN_USAGE_SCORE = 101;
63
+ export const CODEX_EXHAUSTED_USAGE_PERCENT = 100;
64
+
65
+ export function isCodexQuotaExhausted(
66
+ quota: Pick<StoredAccountQuota, "weeklyPercent" | "monthlyPercent"> | null,
67
+ plan?: string | null,
68
+ ): boolean {
69
+ if (!quota) return false;
70
+ const normalizedPlan = plan?.trim().toLowerCase();
71
+ const values = normalizedPlan === "go" || normalizedPlan === "free"
72
+ ? [quota.monthlyPercent]
73
+ : [quota.weeklyPercent, quota.monthlyPercent];
74
+ return values.some(value => typeof value === "number"
75
+ && Number.isFinite(value)
76
+ && value >= CODEX_EXHAUSTED_USAGE_PERCENT);
77
+ }
78
+
79
+ export function normalizeUsagePercent(value: unknown): number | undefined {
80
+ const numeric = typeof value === "number"
81
+ ? value
82
+ : typeof value === "string" && value.trim() !== ""
83
+ ? Number(value)
84
+ : undefined;
85
+ if (typeof numeric !== "number" || !Number.isFinite(numeric)) return undefined;
86
+ return Math.max(0, Math.min(100, numeric));
87
+ }
88
+
89
+ function normalizeResetAt(value: unknown): number | undefined {
90
+ const numeric = typeof value === "number"
91
+ ? value
92
+ : typeof value === "string" && value.trim() !== ""
93
+ ? Number(value)
94
+ : undefined;
95
+ if (typeof numeric !== "number" || !Number.isFinite(numeric) || numeric < 0) return undefined;
96
+ return numeric;
97
+ }
98
+
99
+ function hasKnownQuotaValue(quota: Omit<StoredAccountQuota, "updatedAt">): boolean {
100
+ return [quota.weeklyPercent, quota.monthlyPercent]
101
+ .some(value => typeof value === "number" && Number.isFinite(value));
102
+ }
103
+
104
+ function isExplicitMonthlyWindow(window: WhamUsageWindow | null | undefined): boolean {
105
+ const seconds = window?.limit_window_seconds;
106
+ return typeof seconds === "number"
107
+ && Number.isFinite(seconds)
108
+ && seconds >= MONTHLY_WINDOW_MIN_SECONDS;
109
+ }
110
+
111
+ function isExplicitMonthlyWindowMinutes(windowMinutes: unknown): boolean {
112
+ const minutes = typeof windowMinutes === "number"
113
+ ? windowMinutes
114
+ : typeof windowMinutes === "string" && windowMinutes.trim() !== ""
115
+ ? Number(windowMinutes)
116
+ : undefined;
117
+ return typeof minutes === "number"
118
+ && Number.isFinite(minutes)
119
+ && minutes >= MONTHLY_WINDOW_MIN_MINUTES;
120
+ }
121
+
122
+
123
+ function snapshotHasWeekly(quota: Omit<StoredAccountQuota, "updatedAt">): boolean {
124
+ return quota.weeklyPercent !== undefined || quota.weeklyResetAt !== undefined;
125
+ }
126
+
127
+ function snapshotHasMonthly(quota: Omit<StoredAccountQuota, "updatedAt">): boolean {
128
+ return quota.monthlyPercent !== undefined || quota.monthlyResetAt !== undefined;
129
+ }
130
+
131
+ function snapshotHasUsage(quota: Omit<StoredAccountQuota, "updatedAt">): boolean {
132
+ return snapshotHasWeekly(quota) || snapshotHasMonthly(quota);
133
+ }
134
+ export function setAccountQuotaFromParsed(
135
+ accountId: string,
136
+ quota: Omit<StoredAccountQuota, "updatedAt"> | null,
137
+ writerGeneration = captureConfigGeneration(),
138
+ ): void {
139
+ if (!quota) return;
140
+ if (!mayCommitAccountQuota(accountId, writerGeneration)) return;
141
+ const existing = accountQuota.get(accountId);
142
+ const next: StoredAccountQuota = { updatedAt: Date.now() };
143
+ const creditsOnly = quota.resetCredits !== undefined && !snapshotHasUsage(quota);
144
+
145
+ if (creditsOnly) {
146
+ if (existing?.weeklyPercent !== undefined) next.weeklyPercent = existing.weeklyPercent;
147
+ if (existing?.weeklyResetAt !== undefined) next.weeklyResetAt = existing.weeklyResetAt;
148
+ if (existing?.monthlyPercent !== undefined) next.monthlyPercent = existing.monthlyPercent;
149
+ if (existing?.monthlyResetAt !== undefined) next.monthlyResetAt = existing.monthlyResetAt;
150
+ next.resetCredits = quota.resetCredits;
151
+ accountQuota.set(accountId, next);
152
+ schedulePersistAccountQuotas();
153
+ return;
154
+ }
155
+
156
+ if (snapshotHasWeekly(quota)) {
157
+ if (quota.weeklyPercent !== undefined) next.weeklyPercent = quota.weeklyPercent;
158
+ if (quota.weeklyResetAt !== undefined) next.weeklyResetAt = quota.weeklyResetAt;
159
+ } else if (snapshotHasMonthly(quota) && !snapshotHasWeekly(quota)) {
160
+ // Monthly-only snapshots intentionally clear stale weekly values (issue #382).
161
+ } else if (existing?.weeklyPercent !== undefined) {
162
+ next.weeklyPercent = existing.weeklyPercent;
163
+ if (existing.weeklyResetAt !== undefined) next.weeklyResetAt = existing.weeklyResetAt;
164
+ }
165
+
166
+ if (snapshotHasMonthly(quota)) {
167
+ if (quota.monthlyPercent !== undefined) next.monthlyPercent = quota.monthlyPercent;
168
+ if (quota.monthlyResetAt !== undefined) next.monthlyResetAt = quota.monthlyResetAt;
169
+ } else if (snapshotHasWeekly(quota) && existing?.monthlyPercent !== undefined) {
170
+ next.monthlyPercent = existing.monthlyPercent;
171
+ if (existing.monthlyResetAt !== undefined) next.monthlyResetAt = existing.monthlyResetAt;
172
+ }
173
+
174
+ if (quota.resetCredits !== undefined) next.resetCredits = quota.resetCredits;
175
+ else if (existing?.resetCredits !== undefined) next.resetCredits = existing.resetCredits;
176
+
177
+ accountQuota.set(accountId, next);
178
+ schedulePersistAccountQuotas();
179
+ }
180
+
181
+ export function parseUpstreamQuotaHeaders(headers: Headers): Omit<StoredAccountQuota, "updatedAt"> | null {
182
+ const primaryRaw = headers.get("x-codex-primary-used-percent");
183
+ const secondaryRaw = headers.get("x-codex-secondary-used-percent");
184
+ const tertiaryRaw = headers.get("x-codex-tertiary-used-percent");
185
+ const primaryResetRaw = headers.get("x-codex-primary-reset-at");
186
+ const secondaryResetRaw = headers.get("x-codex-secondary-reset-at");
187
+ const tertiaryResetRaw = headers.get("x-codex-tertiary-reset-at");
188
+ const primaryWindowMinutes = headers.get("x-codex-primary-window-minutes");
189
+ const secondaryWindowMinutes = headers.get("x-codex-secondary-window-minutes");
190
+
191
+ const quota: Omit<StoredAccountQuota, "updatedAt"> = {};
192
+ const primaryPercent = normalizeUsagePercent(primaryRaw);
193
+ const secondaryPercent = normalizeUsagePercent(secondaryRaw);
194
+ const tertiaryPercent = normalizeUsagePercent(tertiaryRaw);
195
+ const primaryResetAt = normalizeResetAt(primaryResetRaw);
196
+ const secondaryResetAt = normalizeResetAt(secondaryResetRaw);
197
+ const tertiaryResetAt = normalizeResetAt(tertiaryResetRaw);
198
+ const primaryIsMonthly = primaryRaw !== null && isExplicitMonthlyWindowMinutes(primaryWindowMinutes);
199
+
200
+ if (primaryIsMonthly) {
201
+ if (primaryPercent !== undefined) {
202
+ quota.monthlyPercent = primaryPercent;
203
+ if (primaryResetAt !== undefined) quota.monthlyResetAt = primaryResetAt;
204
+ }
205
+ if (secondaryPercent !== undefined) {
206
+ quota.weeklyPercent = secondaryPercent;
207
+ if (secondaryResetAt !== undefined) quota.weeklyResetAt = secondaryResetAt;
208
+ }
209
+ } else {
210
+ const weeklyPercent = primaryPercent ?? secondaryPercent;
211
+ const weeklyResetAt = primaryPercent !== undefined
212
+ ? primaryResetAt
213
+ : secondaryResetAt;
214
+ if (weeklyPercent !== undefined) {
215
+ quota.weeklyPercent = weeklyPercent;
216
+ if (weeklyResetAt !== undefined) quota.weeklyResetAt = weeklyResetAt;
217
+ }
218
+ }
219
+
220
+ if (tertiaryPercent !== undefined && quota.monthlyPercent === undefined) {
221
+ quota.monthlyPercent = tertiaryPercent;
222
+ if (tertiaryResetAt !== undefined) quota.monthlyResetAt = tertiaryResetAt;
223
+ }
224
+
225
+ return hasKnownQuotaValue(quota) ? quota : null;
226
+ }
227
+
228
+ export function applyAccountQuotaFromUpstreamHeaders(
229
+ accountId: string,
230
+ headers: Headers,
231
+ writerGeneration = captureConfigGeneration(),
232
+ ): void {
233
+ const quota = parseUpstreamQuotaHeaders(headers);
234
+ if (!quota) return;
235
+ setAccountQuotaFromParsed(accountId, quota, writerGeneration);
236
+ }
237
+
238
+ export function updateAccountQuota(
239
+ accountId: string,
240
+ weekly: unknown,
241
+ weeklyResetAt?: unknown,
242
+ monthly?: unknown,
243
+ monthlyResetAt?: unknown,
244
+ resetCredits?: number,
245
+ writerGeneration = captureConfigGeneration(),
246
+ ): void {
247
+ if (!mayCommitAccountQuota(accountId, writerGeneration)) return;
248
+ const existing = accountQuota.get(accountId);
249
+ const nextWeekly = normalizeUsagePercent(weekly);
250
+ const nextMonthly = normalizeUsagePercent(monthly);
251
+ if (nextWeekly === undefined && nextMonthly === undefined && resetCredits === undefined) return;
252
+
253
+ const quota: StoredAccountQuota = {
254
+ ...(existing?.weeklyPercent !== undefined ? { weeklyPercent: existing.weeklyPercent } : {}),
255
+ ...(existing?.monthlyPercent !== undefined ? { monthlyPercent: existing.monthlyPercent } : {}),
256
+ ...(existing?.weeklyResetAt !== undefined ? { weeklyResetAt: existing.weeklyResetAt } : {}),
257
+ ...(existing?.monthlyResetAt !== undefined ? { monthlyResetAt: existing.monthlyResetAt } : {}),
258
+ ...(existing?.resetCredits !== undefined ? { resetCredits: existing.resetCredits } : {}),
259
+ updatedAt: Date.now(),
260
+ };
261
+
262
+ const nextWeeklyResetAt = normalizeResetAt(weeklyResetAt);
263
+ const nextMonthlyResetAt = normalizeResetAt(monthlyResetAt);
264
+ if (nextWeekly !== undefined) {
265
+ quota.weeklyPercent = nextWeekly;
266
+ if (nextWeeklyResetAt !== undefined) quota.weeklyResetAt = nextWeeklyResetAt;
267
+ }
268
+ if (nextMonthly !== undefined) {
269
+ quota.monthlyPercent = nextMonthly;
270
+ if (nextMonthlyResetAt !== undefined) quota.monthlyResetAt = nextMonthlyResetAt;
271
+ }
272
+ if (resetCredits !== undefined) quota.resetCredits = resetCredits;
273
+
274
+ accountQuota.set(accountId, quota);
275
+ schedulePersistAccountQuotas();
276
+ }
277
+
278
+ function hydrateAccountQuotasFromDisk(): void {
279
+ if (diskHydrated) return;
280
+ diskHydrated = true;
281
+ try {
282
+ const path = join(getConfigDir(), QUOTA_CACHE_FILENAME);
283
+ if (!existsSync(path)) return;
284
+ const raw = readFileSync(path, "utf8");
285
+ const parsed = JSON.parse(raw) as QuotaDiskFile;
286
+ if (!parsed || parsed.version !== 1 || !parsed.quotas || typeof parsed.quotas !== "object") return;
287
+ const now = Date.now();
288
+ for (const [accountId, quota] of Object.entries(parsed.quotas)) {
289
+ if (!quota || typeof quota !== "object" || typeof quota.updatedAt !== "number") continue;
290
+ if (now - quota.updatedAt > QUOTA_DISK_MAX_AGE_MS) continue;
291
+ if (!accountQuota.has(accountId)) accountQuota.set(accountId, quota);
292
+ }
293
+ } catch {
294
+ // Corrupt/missing cache must never block routing or the dashboard.
295
+ }
296
+ }
297
+
298
+ function schedulePersistAccountQuotas(): void {
299
+ if (persistTimer) clearTimeout(persistTimer);
300
+ persistTimer = setTimeout(() => {
301
+ persistTimer = null;
302
+ try {
303
+ const quotas: Record<string, StoredAccountQuota> = {};
304
+ for (const [accountId, quota] of accountQuota.entries()) {
305
+ quotas[accountId] = quota;
306
+ }
307
+ const body: QuotaDiskFile = { version: 1, quotas };
308
+ atomicWriteFile(join(getConfigDir(), QUOTA_CACHE_FILENAME), `${JSON.stringify(body)}\n`);
309
+ } catch {
310
+ // Best-effort persistence only.
311
+ }
312
+ }, QUOTA_PERSIST_DEBOUNCE_MS);
313
+ }
314
+
315
+ export function getAccountQuota(accountId: string): StoredAccountQuota | null {
316
+ hydrateAccountQuotasFromDisk();
317
+ return accountQuota.get(accountId) ?? null;
318
+ }
319
+
320
+ export function listAccountQuotas(): IterableIterator<[string, StoredAccountQuota]> {
321
+ hydrateAccountQuotasFromDisk();
322
+ return accountQuota.entries();
323
+ }
324
+
325
+ export function clearAccountQuota(accountId?: string): void {
326
+ if (accountId) {
327
+ accountQuota.delete(accountId);
328
+ schedulePersistAccountQuotas();
329
+ return;
330
+ }
331
+ accountQuota.clear();
332
+ diskHydrated = false;
333
+ if (persistTimer) {
334
+ clearTimeout(persistTimer);
335
+ persistTimer = null;
336
+ }
337
+ try {
338
+ const path = join(getConfigDir(), QUOTA_CACHE_FILENAME);
339
+ if (existsSync(path)) unlinkSync(path);
340
+ } catch {
341
+ // Best-effort; memory is already cleared.
342
+ }
343
+ }
344
+
345
+ export function reconcileCodexQuotaAccounts(context: GenerationContext): number {
346
+ if (context.generation <= lastReconciledGeneration) return 0;
347
+ hydrateAccountQuotasFromDisk();
348
+ let removed = 0;
349
+ for (const accountId of accountQuota.keys()) {
350
+ if (context.codexAccountIds.has(accountId)) continue;
351
+ accountQuota.delete(accountId);
352
+ removed += 1;
353
+ }
354
+ liveAccountIds = new Set(context.codexAccountIds);
355
+ lastReconciledGeneration = context.generation;
356
+ if (removed > 0) schedulePersistAccountQuotas();
357
+ return removed;
358
+ }
359
+
360
+ export function parseUsageQuota(data: WhamUsageResponse): Omit<StoredAccountQuota, "updatedAt"> | null {
361
+ const resetCredits = typeof data.rate_limit_reset_credits?.available_count === "number"
362
+ ? data.rate_limit_reset_credits.available_count
363
+ : undefined;
364
+
365
+ if (!data.rate_limit) {
366
+ return resetCredits !== undefined ? { resetCredits } : null;
367
+ }
368
+
369
+ const quota: Omit<StoredAccountQuota, "updatedAt"> = {};
370
+ const thirtyDayOnly = data.plan_type?.trim().toLowerCase() === "go" || data.plan_type?.trim().toLowerCase() === "free";
371
+ const primaryWindow = data.rate_limit.primary_window;
372
+ const secondaryWindow = data.rate_limit.secondary_window;
373
+ const tertiaryWindow = data.rate_limit.tertiary_window;
374
+ const primaryPercent = normalizeUsagePercent(primaryWindow?.used_percent);
375
+ const secondaryPercent = normalizeUsagePercent(secondaryWindow?.used_percent);
376
+ const tertiaryPercent = normalizeUsagePercent(tertiaryWindow?.used_percent);
377
+ const primaryResetAt = normalizeResetAt(primaryWindow?.reset_at);
378
+ const secondaryResetAt = normalizeResetAt(secondaryWindow?.reset_at);
379
+ const tertiaryResetAt = normalizeResetAt(tertiaryWindow?.reset_at);
380
+ const primaryIsMonthly = isExplicitMonthlyWindow(primaryWindow);
381
+
382
+ // [Decision Log]
383
+ // - 목적과 의도: distinguish weekly and roughly monthly WHAM primary windows without plan-name guesses.
384
+ // - 기존 구현 및 제약 조건: primary meant weekly, and older responses omit limit_window_seconds.
385
+ // - 검토한 주요 대안: exact-duration matching, plan-specific mapping, and a duration lower bound.
386
+ // - 선택한 방식: only an explicit primary duration of at least 28 days changes it to monthly.
387
+ // - 다른 대안 대신 이 방식을 선택한 이유: it accepts calendar-month variance and preserves legacy payloads.
388
+ // - 장점, 단점 및 영향: Team monthly quotas classify correctly; unknown durations remain weekly by design.
389
+ const weeklyPercent = primaryIsMonthly ? secondaryPercent : primaryPercent ?? secondaryPercent;
390
+ const weeklyResetAt = primaryIsMonthly
391
+ ? secondaryResetAt
392
+ : primaryPercent !== undefined ? primaryResetAt : secondaryResetAt;
393
+ const monthlyPercent = primaryIsMonthly ? primaryPercent ?? tertiaryPercent : tertiaryPercent;
394
+ const monthlyResetAt = primaryIsMonthly && primaryPercent !== undefined ? primaryResetAt : tertiaryResetAt;
395
+ if (thirtyDayOnly) {
396
+ if (monthlyPercent !== undefined) {
397
+ quota.monthlyPercent = monthlyPercent;
398
+ if (monthlyResetAt !== undefined) quota.monthlyResetAt = monthlyResetAt;
399
+ }
400
+ } else if (weeklyPercent !== undefined) {
401
+ quota.weeklyPercent = weeklyPercent;
402
+ if (weeklyResetAt !== undefined) quota.weeklyResetAt = weeklyResetAt;
403
+ }
404
+ if (!thirtyDayOnly && monthlyPercent !== undefined) {
405
+ quota.monthlyPercent = monthlyPercent;
406
+ if (monthlyResetAt !== undefined) quota.monthlyResetAt = monthlyResetAt;
407
+ }
408
+ if (resetCredits !== undefined) quota.resetCredits = resetCredits;
409
+
410
+ return hasKnownQuotaValue(quota) || resetCredits !== undefined ? quota : null;
411
+ }
@@ -0,0 +1,53 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { invalidateCodexModelsCache, syncCatalogModels } from "./catalog";
3
+ import type { ComboCatalogOmission } from "./catalog/aggregation";
4
+ import { CODEX_MODELS_CACHE_PATH } from "./paths";
5
+ import { atomicWriteFile } from "../config";
6
+ import type { OcxConfig } from "../types";
7
+
8
+ export interface CodexCatalogRefreshResult {
9
+ added: number;
10
+ path: string;
11
+ catalogExists: boolean;
12
+ catalogWritten: boolean;
13
+ cacheSynced: boolean;
14
+ comboOmissions: ComboCatalogOmission[];
15
+ }
16
+
17
+ interface RefreshDeps {
18
+ syncCatalogModels: typeof syncCatalogModels;
19
+ invalidateCodexModelsCache: typeof invalidateCodexModelsCache;
20
+ existsSync: typeof existsSync;
21
+ }
22
+
23
+ const defaultDeps: RefreshDeps = {
24
+ syncCatalogModels,
25
+ invalidateCodexModelsCache,
26
+ existsSync,
27
+ };
28
+
29
+ export function syncCodexModelsCacheFromCatalog(catalogPath: string): void {
30
+ const content = readFileSync(catalogPath, "utf8");
31
+ atomicWriteFile(CODEX_MODELS_CACHE_PATH, content);
32
+ }
33
+
34
+ /**
35
+ * Rebuild Codex's on-disk model catalog and force Codex's models cache stale
36
+ * when a catalog file exists. The cache must keep Codex's fetched_at/client_version
37
+ * wrapper shape; writing the raw catalog back here makes app-server/TUI refreshes
38
+ * inconsistent with the CLI models-manager cache path.
39
+ */
40
+ export async function refreshCodexModelCatalog(
41
+ config: OcxConfig,
42
+ deps: RefreshDeps = defaultDeps,
43
+ ): Promise<CodexCatalogRefreshResult> {
44
+ const result = await deps.syncCatalogModels(config);
45
+ const catalogExists = deps.existsSync(result.path);
46
+ const catalogWritten = result.catalogWritten === true;
47
+ const comboOmissions = result.comboOmissions ?? [];
48
+ if (!catalogExists) {
49
+ return { ...result, catalogExists, catalogWritten: false, cacheSynced: false, comboOmissions };
50
+ }
51
+ const cacheSynced = deps.invalidateCodexModelsCache();
52
+ return { ...result, catalogExists, catalogWritten, cacheSynced, comboOmissions };
53
+ }