@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,323 @@
1
+ import {
2
+ TRANSLATOR_MAX_SSE_EVENT_BYTES,
3
+ TranslatorBudgetExceededError,
4
+ type TranslatorBudget,
5
+ } from "./translator-budget";
6
+
7
+ export interface ServerSentEvent {
8
+ event?: string;
9
+ data: string;
10
+ }
11
+
12
+ export type SseRecord =
13
+ | { kind: "event"; event?: string; data: string }
14
+ | { kind: "comment"; comment: string };
15
+
16
+ /**
17
+ * Decode text/event-stream records across arbitrary fetch chunk boundaries.
18
+ *
19
+ * The final record is dispatched at EOF even when the upstream omits the trailing blank line or
20
+ * final newline. That matters for compatible APIs that place a terminal event in the last bytes of
21
+ * the body: dropping that record turns a successful response into an adapter_eof failure.
22
+ */
23
+ export function decodeServerSentEvents(
24
+ source: ReadableStream<Uint8Array>,
25
+ options: { includeComments: true; signal?: AbortSignal; translatorBudget: TranslatorBudget },
26
+ ): AsyncGenerator<SseRecord>;
27
+ export function decodeServerSentEvents(
28
+ source: ReadableStream<Uint8Array>,
29
+ options: { includeComments?: false; signal?: AbortSignal; translatorBudget: TranslatorBudget },
30
+ ): AsyncGenerator<ServerSentEvent>;
31
+ export async function* decodeServerSentEvents(
32
+ source: ReadableStream<Uint8Array>,
33
+ options: { includeComments?: boolean; signal?: AbortSignal; translatorBudget: TranslatorBudget },
34
+ ): AsyncGenerator<ServerSentEvent | SseRecord> {
35
+ const translatorBudget = options.translatorBudget;
36
+ const reader = source.getReader();
37
+ const decoder = new TextDecoder();
38
+ let lineBuffer = "";
39
+ let lineRawBytes = 0;
40
+ let lineRetainedBytes = 0;
41
+ let event: string | undefined;
42
+ let eventBytes = 0;
43
+ let dataLines: string[] = [];
44
+ let dataLinesBytes = 0;
45
+ const scope = { kind: "live_transient" as const };
46
+
47
+ const utf8SliceBytes = (value: string, start: number, end: number): number => {
48
+ let bytes = 0;
49
+ for (let index = start; index < end; index++) {
50
+ const codePoint = value.codePointAt(index)!;
51
+ if (codePoint <= 0x7f) bytes += 1;
52
+ else if (codePoint <= 0x7ff) bytes += 2;
53
+ else if (codePoint <= 0xffff) bytes += 3;
54
+ else {
55
+ bytes += 4;
56
+ index += 1;
57
+ }
58
+ }
59
+ return bytes;
60
+ };
61
+
62
+ const linePrefixAfter = (sourceValue: string, start: number, end: number): string => {
63
+ if (lineBuffer.length >= 6) return lineBuffer.slice(0, 6);
64
+ return (lineBuffer + sourceValue.slice(start, Math.min(end, start + 6 - lineBuffer.length))).slice(0, 6);
65
+ };
66
+
67
+ const accountedLineBytes = (rawBytes: number, prefix: string): number => {
68
+ if (prefix.length < 5 && "data:".startsWith(prefix)) return 0;
69
+ if (!prefix.startsWith("data:")) return rawBytes;
70
+ return Math.max(0, rawBytes - (prefix[5] === " " ? 6 : 5));
71
+ };
72
+
73
+ const appendLine = (sourceValue: string, start: number, end: number): void => {
74
+ if (start >= end) return;
75
+ const nextRawBytes = lineRawBytes + utf8SliceBytes(sourceValue, start, end);
76
+ const prefix = linePrefixAfter(sourceValue, start, end);
77
+ const nextRetainedBytes = accountedLineBytes(nextRawBytes, prefix);
78
+ if (prefix.startsWith("data:")) {
79
+ const logicalEventBytes = dataLinesBytes + dataLines.length + nextRetainedBytes;
80
+ if (logicalEventBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
81
+ throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
82
+ }
83
+ }
84
+ const reservation = translatorBudget.reserveTransient(nextRawBytes, scope);
85
+ try {
86
+ const fragment = sourceValue.slice(start, end);
87
+ lineBuffer += fragment;
88
+ reservation.commitRetained();
89
+ translatorBudget.releaseRetained(lineRetainedBytes, scope);
90
+ lineRawBytes = nextRawBytes;
91
+ lineRetainedBytes = nextRawBytes;
92
+ } catch (error) {
93
+ reservation.release();
94
+ throw error;
95
+ }
96
+ };
97
+ // Prompt cancellation channel: an abort cancels the underlying reader directly, which
98
+ // settles any in-flight read() so a consumer's iterator.return() cannot hang behind an
99
+ // idle upstream (a plain generator return waits for the pending await first).
100
+ const signal = options?.signal;
101
+ const onAbort = () => { reader.cancel(signal?.reason).catch(() => { /* already closed */ }); };
102
+ if (signal?.aborted) onAbort();
103
+ else signal?.addEventListener("abort", onAbort, { once: true });
104
+
105
+ const includeComments = options?.includeComments === true;
106
+
107
+ const dispatch = (): { record: ServerSentEvent | SseRecord; bytes: number } | undefined => {
108
+ if (dataLines.length === 0) {
109
+ translatorBudget.releaseRetained(eventBytes, scope);
110
+ event = undefined;
111
+ eventBytes = 0;
112
+ return undefined;
113
+ }
114
+ const payloadBytes = dataLinesBytes + Math.max(0, dataLines.length - 1);
115
+ if (payloadBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
116
+ throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
117
+ }
118
+ let data: string;
119
+ if (dataLines.length === 1) {
120
+ data = dataLines[0]!;
121
+ } else {
122
+ const reservation = translatorBudget.reserveTransient(payloadBytes, scope);
123
+ try {
124
+ data = dataLines.join("\n");
125
+ reservation.commitRetained();
126
+ translatorBudget.releaseRetained(dataLinesBytes, scope);
127
+ } catch (error) {
128
+ reservation.release();
129
+ throw error;
130
+ }
131
+ }
132
+ const record = { ...(event ? { event } : {}), data };
133
+ const retainedRecordBytes = payloadBytes + eventBytes;
134
+ event = undefined;
135
+ eventBytes = 0;
136
+ dataLines = [];
137
+ dataLinesBytes = 0;
138
+ return {
139
+ record: includeComments ? { kind: "event", ...record } : record,
140
+ bytes: retainedRecordBytes,
141
+ };
142
+ };
143
+
144
+ const acceptLine = (): { record: ServerSentEvent | SseRecord; bytes: number } | undefined => {
145
+ let line = lineBuffer;
146
+ let retainedLineBytes = lineRetainedBytes;
147
+ let lineOwned = true;
148
+ const releaseLine = (): void => {
149
+ if (!lineOwned) return;
150
+ lineOwned = false;
151
+ translatorBudget.releaseRetained(retainedLineBytes, scope);
152
+ };
153
+ lineBuffer = "";
154
+ lineRawBytes = 0;
155
+ lineRetainedBytes = 0;
156
+ try {
157
+ if (line.endsWith("\r")) {
158
+ const nextLineBytes = retainedLineBytes - 1;
159
+ const reservation = translatorBudget.reserveTransient(nextLineBytes, scope);
160
+ try {
161
+ const nextLine = line.slice(0, -1);
162
+ reservation.commitRetained();
163
+ translatorBudget.releaseRetained(retainedLineBytes, scope);
164
+ line = nextLine;
165
+ retainedLineBytes = nextLineBytes;
166
+ } catch (error) {
167
+ reservation.release();
168
+ throw error;
169
+ }
170
+ }
171
+ if (line === "") {
172
+ releaseLine();
173
+ return dispatch();
174
+ }
175
+ if (line.startsWith(":")) {
176
+ if (!includeComments) {
177
+ releaseLine();
178
+ return undefined;
179
+ }
180
+ const commentStart = line[1] === " " ? 2 : 1;
181
+ const commentBytes = utf8SliceBytes(line, commentStart, line.length);
182
+ translatorBudget.chargeRetained(commentBytes, scope);
183
+ try {
184
+ const comment = line.slice(commentStart);
185
+ releaseLine();
186
+ return { record: { kind: "comment", comment }, bytes: commentBytes };
187
+ } catch (error) {
188
+ translatorBudget.releaseRetained(commentBytes, scope);
189
+ throw error;
190
+ }
191
+ }
192
+
193
+ const colon = line.indexOf(":");
194
+ const fieldEnd = colon < 0 ? line.length : colon;
195
+ const fieldBytes = utf8SliceBytes(line, 0, fieldEnd);
196
+ const fieldReservation = translatorBudget.reserveTransient(fieldBytes, scope);
197
+ let field = "";
198
+ try {
199
+ field = line.slice(0, fieldEnd);
200
+ fieldReservation.commitRetained();
201
+ } catch (error) {
202
+ fieldReservation.release();
203
+ throw error;
204
+ }
205
+ try {
206
+ let valueStart = colon < 0 ? line.length : colon + 1;
207
+ if (line[valueStart] === " ") valueStart += 1;
208
+ const valueBytes = utf8SliceBytes(line, valueStart, line.length);
209
+
210
+ if (field === "event") {
211
+ const reservation = translatorBudget.reserveTransient(valueBytes, scope);
212
+ try {
213
+ const value = line.slice(valueStart);
214
+ reservation.commitRetained();
215
+ translatorBudget.releaseRetained(eventBytes, scope);
216
+ event = value;
217
+ eventBytes = valueBytes;
218
+ releaseLine();
219
+ } catch (error) {
220
+ reservation.release();
221
+ throw error;
222
+ }
223
+ }
224
+ else if (field === "data") {
225
+ const nextEventBytes = dataLinesBytes + valueBytes + dataLines.length;
226
+ if (nextEventBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
227
+ throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
228
+ }
229
+ // The extracted value is a distinct retained allocation while the source line is still
230
+ // live. Admit that insertion before slicing/pushing, then release the source owner.
231
+ translatorBudget.chargeRetained(valueBytes, scope);
232
+ try {
233
+ const value = line.slice(valueStart);
234
+ dataLines.push(value);
235
+ dataLinesBytes += valueBytes;
236
+ releaseLine();
237
+ } catch (error) {
238
+ translatorBudget.releaseRetained(valueBytes, scope);
239
+ throw error;
240
+ }
241
+ } else {
242
+ releaseLine();
243
+ }
244
+ } finally {
245
+ translatorBudget.releaseRetained(fieldBytes, scope);
246
+ }
247
+ return undefined;
248
+ } catch (error) {
249
+ releaseLine();
250
+ throw error;
251
+ }
252
+ };
253
+
254
+ const consumeDecoded = async function* (
255
+ decoded: string,
256
+ ): AsyncGenerator<ServerSentEvent | SseRecord> {
257
+ let offset = 0;
258
+ while (offset < decoded.length) {
259
+ const newline = decoded.indexOf("\n", offset);
260
+ if (newline < 0) {
261
+ appendLine(decoded, offset, decoded.length);
262
+ return;
263
+ }
264
+ appendLine(decoded, offset, newline);
265
+ const accepted = acceptLine();
266
+ if (accepted) {
267
+ try {
268
+ yield accepted.record;
269
+ } finally {
270
+ translatorBudget.releaseRetained(accepted.bytes, scope);
271
+ }
272
+ }
273
+ offset = newline + 1;
274
+ }
275
+ };
276
+
277
+ try {
278
+ while (true) {
279
+ const { done, value } = await reader.read();
280
+ if (value) {
281
+ const sourceBytes = value.byteLength;
282
+ const sourceReservation = translatorBudget.reserveTransient(sourceBytes, scope);
283
+ let sourceCommitted = false;
284
+ try {
285
+ const decoded = decoder.decode(value, { stream: !done });
286
+ sourceReservation.commitRetained();
287
+ sourceCommitted = true;
288
+ yield* consumeDecoded(decoded);
289
+ } finally {
290
+ if (sourceCommitted) translatorBudget.releaseRetained(sourceBytes, scope);
291
+ else sourceReservation.release();
292
+ }
293
+ }
294
+ if (!done) continue;
295
+ const finalDecoded = decoder.decode();
296
+ yield* consumeDecoded(finalDecoded);
297
+ if (lineBuffer.length > 0) {
298
+ const accepted = acceptLine();
299
+ if (accepted) {
300
+ try {
301
+ yield accepted.record;
302
+ } finally {
303
+ translatorBudget.releaseRetained(accepted.bytes, scope);
304
+ }
305
+ }
306
+ }
307
+ const finalRecord = dispatch();
308
+ if (finalRecord) {
309
+ try {
310
+ yield finalRecord.record;
311
+ } finally {
312
+ translatorBudget.releaseRetained(finalRecord.bytes, scope);
313
+ }
314
+ }
315
+ break;
316
+ }
317
+ } finally {
318
+ translatorBudget.releaseRetained(lineRetainedBytes + dataLinesBytes + eventBytes, scope);
319
+ signal?.removeEventListener("abort", onAbort);
320
+ try { await reader.cancel(); } catch { /* already closed/errored */ }
321
+ try { reader.releaseLock(); } catch { /* already released */ }
322
+ }
323
+ }
@@ -0,0 +1,109 @@
1
+ import {
2
+ getConfigDir,
3
+ reconcileConfigWarningMemos,
4
+ sweepDeadOcxStartProcessCache,
5
+ } from "../config";
6
+ import { reconcileCodexReauthState } from "../codex/account-runtime-state";
7
+ import { reconcileCatalogWarningMemos } from "../codex/catalog/aggregation";
8
+ import { reconcileProviderFetchWarnings } from "../codex/catalog/provider-fetch";
9
+ import { reconcileModelCacheGeneration } from "../codex/model-cache";
10
+ import { reconcilePoolRotationState } from "../codex/pool-rotation";
11
+ import { reconcileCodexQuotaAccounts } from "../codex/quota";
12
+ import {
13
+ listLiveCodexAccountIds,
14
+ reconcileCodexRoutingHealth,
15
+ } from "../codex/routing";
16
+ import { sweepExpiredSubagentModelHealth } from "../codex/subagent-model-fallback";
17
+ import {
18
+ reconcileComboTargetCooldowns,
19
+ sweepExpiredComboTargetCooldowns,
20
+ } from "../combos/failover";
21
+ import { reconcileComboWarningMemos } from "../combos/request";
22
+ import { reconcileComboRotationState } from "../combos/resolve";
23
+ import { listLiveComboTargetKeys } from "../combos/types";
24
+ import {
25
+ listLiveConfigOwnershipRoots,
26
+ reconcileConfigOwnershipRoots,
27
+ } from "./config-ownership";
28
+ import { reconcileGcpAdcTokens, sweepExpiredGcpAdcTokens } from "./gcp-adc";
29
+ import {
30
+ reconcileOAuthFlowState,
31
+ sweepExpiredXaiPermanentFailureVerdicts,
32
+ } from "../oauth";
33
+ import { sweepExpiredAnthropicRoutingHealth } from "../oauth/anthropic-routing";
34
+ import { listLiveOAuthAccountKeys, reconcileOAuthReauthState } from "../oauth/store";
35
+ import { reconcileGuardianBackoff } from "../oauth/token-guardian";
36
+ import { sweepExpiredApiKeyCooldowns } from "../providers/key-failover";
37
+ import { sweepExpiredResponseStates } from "../responses/state";
38
+ import { sweepExpiredAntigravityReplay } from "../adapters/google-antigravity-replay";
39
+ import { reconcileProviderAccountQuotaRows } from "../providers/quota";
40
+ import { reconcileRouterWarningMemos } from "../router";
41
+ import type { OcxConfig } from "../types";
42
+ import {
43
+ type GenerationContext,
44
+ reconcileStateGeneration,
45
+ registerStateStore,
46
+ setGenerationContextBuilder,
47
+ type StateStoreRegistration,
48
+ } from "./state-store-sweeper";
49
+
50
+ let liveServerConfig: OcxConfig | null = null;
51
+
52
+ export function setLiveStateStoreConfig(config: OcxConfig): void {
53
+ liveServerConfig = config;
54
+ }
55
+
56
+ export function reconcileLiveStateStores() {
57
+ if (!liveServerConfig) return { storesVisited: 0, rowsRemoved: 0 };
58
+ return reconcileStateGeneration(buildGenerationContext());
59
+ }
60
+
61
+ export function buildGenerationContext(): GenerationContext {
62
+ if (!liveServerConfig) throw new Error("live server config is not installed");
63
+ const providerNames = new Set(Object.keys(liveServerConfig.providers));
64
+ return {
65
+ generation: 0,
66
+ providerNames,
67
+ comboIds: new Set(Object.keys(liveServerConfig.combos ?? {})),
68
+ comboTargets: listLiveComboTargetKeys(liveServerConfig),
69
+ codexAccountIds: listLiveCodexAccountIds(liveServerConfig),
70
+ oauthAccountKeys: listLiveOAuthAccountKeys(providerNames),
71
+ configRoots: listLiveConfigOwnershipRoots(getConfigDir()),
72
+ };
73
+ }
74
+
75
+ export const STATE_STORE_REGISTRATIONS = [
76
+ { name: "subagent-model-health", sweepExpired: sweepExpiredSubagentModelHealth },
77
+ { name: "api-key-cooldowns", sweepExpired: sweepExpiredApiKeyCooldowns },
78
+ {
79
+ name: "combo-target-cooldowns",
80
+ sweepExpired: sweepExpiredComboTargetCooldowns,
81
+ reconcileGeneration: reconcileComboTargetCooldowns,
82
+ },
83
+ { name: "anthropic-routing-health", sweepExpired: sweepExpiredAnthropicRoutingHealth },
84
+ { name: "xai-refresh-verdicts", sweepExpired: sweepExpiredXaiPermanentFailureVerdicts },
85
+ { name: "responses-continuation", sweepExpired: sweepExpiredResponseStates },
86
+ { name: "antigravity-replay", sweepExpired: sweepExpiredAntigravityReplay },
87
+ { name: "config-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileConfigWarningMemos(context.generation) },
88
+ { name: "catalog-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileCatalogWarningMemos(context.generation) },
89
+ { name: "provider-fetch-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileProviderFetchWarnings(context.generation) },
90
+ { name: "combo-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileComboWarningMemos(context.generation) },
91
+ { name: "router-warning-memos", reconcileGeneration: (context: GenerationContext) => reconcileRouterWarningMemos(context.generation) },
92
+ { name: "codex-quota", reconcileGeneration: reconcileCodexQuotaAccounts },
93
+ { name: "provider-quota-history", reconcileGeneration: reconcileProviderAccountQuotaRows },
94
+ { name: "codex-routing-health", reconcileGeneration: reconcileCodexRoutingHealth },
95
+ { name: "model-cache-history", reconcileGeneration: reconcileModelCacheGeneration },
96
+ { name: "pool-rotation", reconcileGeneration: reconcilePoolRotationState },
97
+ { name: "combo-rotation", reconcileGeneration: reconcileComboRotationState },
98
+ { name: "guardian-backoff", reconcileGeneration: reconcileGuardianBackoff },
99
+ { name: "codex-reauth", reconcileGeneration: reconcileCodexReauthState },
100
+ { name: "oauth-reauth", reconcileGeneration: reconcileOAuthReauthState },
101
+ { name: "gcp-adc", sweepExpired: sweepExpiredGcpAdcTokens, reconcileGeneration: reconcileGcpAdcTokens },
102
+ { name: "config-ownership", reconcileGeneration: reconcileConfigOwnershipRoots },
103
+ { name: "oauth-flow-state", reconcileGeneration: reconcileOAuthFlowState },
104
+ { name: "ocx-start-process-cache", sweepLiveness: sweepDeadOcxStartProcessCache },
105
+ ] satisfies readonly StateStoreRegistration[];
106
+
107
+ for (const registration of STATE_STORE_REGISTRATIONS) registerStateStore(registration);
108
+
109
+ setGenerationContextBuilder(buildGenerationContext);
@@ -0,0 +1,184 @@
1
+ export const STATE_SWEEP_INTERVAL_MS = 60_000;
2
+
3
+ export interface GenerationContext {
4
+ generation: number;
5
+ providerNames: ReadonlySet<string>;
6
+ comboIds: ReadonlySet<string>;
7
+ comboTargets: ReadonlySet<string>;
8
+ codexAccountIds: ReadonlySet<string>;
9
+ oauthAccountKeys: ReadonlySet<string>;
10
+ configRoots: ReadonlySet<string>;
11
+ }
12
+
13
+ export interface StateStoreRegistration {
14
+ name: string;
15
+ sweepExpired?: (now: number) => number;
16
+ sweepLiveness?: () => number;
17
+ reconcileGeneration?: (context: GenerationContext) => number;
18
+ }
19
+
20
+ export interface StateSweepAfterTickRegistration {
21
+ name: string;
22
+ afterTick(): void;
23
+ }
24
+
25
+ export interface StateSweepResult {
26
+ storesVisited: number;
27
+ rowsRemoved: number;
28
+ }
29
+
30
+ export interface StateStoreSweeperOptions {
31
+ /** Override the 60 s interval (tests only; production uses the default). */
32
+ intervalMs?: number;
33
+ /** Injectable clock for tests. */
34
+ now?: () => number;
35
+ }
36
+
37
+ const registrations = new Map<string, StateStoreRegistration>();
38
+ const afterTickRegistrations = new Map<string, StateSweepAfterTickRegistration>();
39
+ let configGeneration = 0;
40
+ let attemptSequence = 0;
41
+ let generationContextBuilder: (() => GenerationContext) | null = null;
42
+ let reconciliationRetryPending = false;
43
+ let interval: ReturnType<typeof setInterval> | null = null;
44
+
45
+ function logCallbackFailure(name: string): void {
46
+ console.warn(`[state-store-sweeper] ${name} failed`);
47
+ }
48
+
49
+ export function registerStateStore(registration: StateStoreRegistration): () => void {
50
+ registrations.set(registration.name, registration);
51
+ return () => {
52
+ if (registrations.get(registration.name) === registration) {
53
+ registrations.delete(registration.name);
54
+ }
55
+ };
56
+ }
57
+
58
+ export function registerStateSweepAfterTick(registration: StateSweepAfterTickRegistration): () => void {
59
+ afterTickRegistrations.set(registration.name, registration);
60
+ return () => {
61
+ if (afterTickRegistrations.get(registration.name) === registration) {
62
+ afterTickRegistrations.delete(registration.name);
63
+ }
64
+ };
65
+ }
66
+
67
+ function runCallbacks(
68
+ callback: (registration: StateStoreRegistration) => (() => number) | undefined,
69
+ ): StateSweepResult {
70
+ let storesVisited = 0;
71
+ let rowsRemoved = 0;
72
+ for (const registration of registrations.values()) {
73
+ const invoke = callback(registration);
74
+ if (!invoke) continue;
75
+ storesVisited += 1;
76
+ try {
77
+ rowsRemoved += invoke();
78
+ } catch {
79
+ logCallbackFailure(registration.name);
80
+ }
81
+ }
82
+ return { storesVisited, rowsRemoved };
83
+ }
84
+
85
+ export function sweepExpired(now = Date.now()): StateSweepResult {
86
+ return runCallbacks(registration => registration.sweepExpired
87
+ ? () => registration.sweepExpired!(now)
88
+ : undefined);
89
+ }
90
+
91
+ export function sweepExpiredOnWrite(now = Date.now()): StateSweepResult {
92
+ const result = sweepExpired(now);
93
+ if (reconciliationRetryPending && generationContextBuilder) {
94
+ try {
95
+ reconcileStateGeneration(generationContextBuilder());
96
+ } catch {
97
+ // The builder reads authoritative live owners. Keep the single retry pending
98
+ // until a later successful owner write can obtain a complete context.
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+
104
+ export function sweepLiveness(): StateSweepResult {
105
+ return runCallbacks(registration => registration.sweepLiveness);
106
+ }
107
+
108
+ function runAfterTickCallbacks(): void {
109
+ for (const registration of afterTickRegistrations.values()) {
110
+ try {
111
+ registration.afterTick();
112
+ } catch {
113
+ logCallbackFailure(registration.name);
114
+ }
115
+ }
116
+ }
117
+
118
+ export function captureConfigGeneration(): number {
119
+ return configGeneration;
120
+ }
121
+
122
+ export function setGenerationContextBuilder(build: () => GenerationContext): void {
123
+ generationContextBuilder = build;
124
+ }
125
+
126
+ export function reconcileStateGeneration(context: GenerationContext): StateSweepResult {
127
+ const candidateGeneration = ++attemptSequence;
128
+ const candidateContext: GenerationContext = {
129
+ ...context,
130
+ generation: candidateGeneration,
131
+ };
132
+
133
+ let storesVisited = 0;
134
+ let rowsRemoved = 0;
135
+ let failed = false;
136
+ for (const registration of registrations.values()) {
137
+ if (!registration.reconcileGeneration) continue;
138
+ storesVisited += 1;
139
+ try {
140
+ rowsRemoved += registration.reconcileGeneration(candidateContext);
141
+ } catch {
142
+ failed = true;
143
+ logCallbackFailure(registration.name);
144
+ }
145
+ }
146
+ if (failed) {
147
+ reconciliationRetryPending = true;
148
+ } else {
149
+ configGeneration = candidateGeneration;
150
+ reconciliationRetryPending = false;
151
+ }
152
+ return { storesVisited, rowsRemoved };
153
+ }
154
+
155
+ export function startStateStoreSweeper(
156
+ options: StateStoreSweeperOptions = {},
157
+ ): { stop(): void } {
158
+ stopStateStoreSweeper();
159
+ const now = options.now ?? Date.now;
160
+ interval = setInterval(() => {
161
+ sweepExpired(now());
162
+ sweepLiveness();
163
+ runAfterTickCallbacks();
164
+ }, options.intervalMs ?? STATE_SWEEP_INTERVAL_MS);
165
+ interval.unref?.();
166
+ return { stop: stopStateStoreSweeper };
167
+ }
168
+
169
+ export function stopStateStoreSweeper(): void {
170
+ if (!interval) return;
171
+ clearInterval(interval);
172
+ interval = null;
173
+ }
174
+
175
+ /** Test-only reset for module-global registrations, generation, and lifecycle state. */
176
+ export function resetStateStoreSweeperForTests(): void {
177
+ stopStateStoreSweeper();
178
+ registrations.clear();
179
+ afterTickRegistrations.clear();
180
+ configGeneration = 0;
181
+ attemptSequence = 0;
182
+ generationContextBuilder = null;
183
+ reconciliationRetryPending = false;
184
+ }