@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,146 @@
1
+ export interface LinkedAbortSignal {
2
+ signal: AbortSignal;
3
+ cleanup: () => void;
4
+ }
5
+
6
+ export interface ClearableDeadline {
7
+ /** Parent-linked signal passed to fetch; remains parent-linked after clear(). */
8
+ signal: AbortSignal;
9
+ /** Stable reason object used when this deadline wins the abort race. */
10
+ timeoutReason: DOMException;
11
+ /** True only when this deadline, rather than the parent, fired first. */
12
+ didExpire: () => boolean;
13
+ /** Clear only the timer. Never aborts the deadline controller or detaches the parent. */
14
+ clear: () => void;
15
+ }
16
+
17
+ export interface IdleDeadline {
18
+ /** (Re-)arm the timer for one idle window. Call when a wait for progress BEGINS. No-op after fire/cancel. */
19
+ reset: () => void;
20
+ /** Disarm the timer WITHOUT retiring the deadline (call when the awaited progress arrives). No-op after fire/cancel. */
21
+ pause: () => void;
22
+ /** Retire permanently (success/teardown paths). Idempotent. */
23
+ cancel: () => void;
24
+ }
25
+
26
+ /**
27
+ * Resettable inactivity deadline (devlog 260716_passthrough_followups/010).
28
+ *
29
+ * Fires `onIdle` at most ONCE after `idleMs` elapses with no `reset()`/`pause()`.
30
+ * Contract:
31
+ * - `idleMs <= 0` returns an inert no-op deadline — the 0-disable responsibility
32
+ * lives here so callers cannot mis-handle it.
33
+ * - The timer starts DISARMED: callers arm it with `reset()` when a wait begins and
34
+ * `pause()` it when the wait settles, so pull-based relays never count downstream
35
+ * backpressure (no pending read) as upstream inactivity.
36
+ * - First terminal wins: after `onIdle` runs or `cancel()` is called, every method
37
+ * is a no-op and `onIdle` never runs again.
38
+ * - Never linked to fetch signals — the consumer decides how to kill its stream
39
+ * (e.g. reader.cancel), keeping body-lifetime semantics unchanged.
40
+ */
41
+ export function idleDeadline(idleMs: number, onIdle: () => void): IdleDeadline {
42
+ if (idleMs <= 0) return { reset: () => {}, pause: () => {}, cancel: () => {} };
43
+ let timer: ReturnType<typeof setTimeout> | undefined;
44
+ let done = false;
45
+ const disarm = () => {
46
+ if (timer !== undefined) clearTimeout(timer);
47
+ timer = undefined;
48
+ };
49
+ return {
50
+ reset: () => {
51
+ if (done) return;
52
+ disarm();
53
+ timer = setTimeout(() => {
54
+ if (done) return;
55
+ done = true;
56
+ timer = undefined;
57
+ onIdle();
58
+ }, idleMs);
59
+ },
60
+ pause: () => {
61
+ if (done) return;
62
+ disarm();
63
+ },
64
+ cancel: () => {
65
+ if (done) return;
66
+ done = true;
67
+ disarm();
68
+ },
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Response-header deadline whose timer can be cleared without severing body-lifetime cancellation.
74
+ *
75
+ * `signalWithTimeout().cleanup()` intentionally removes its parent listener and is therefore suited
76
+ * to operations that are completely finished at cleanup. A fetch response body is different: once
77
+ * headers arrive the deadline ends, but the original parent/client signal must remain attached to
78
+ * the body. `AbortSignal.any()` supplies that direct lifetime link while `clear()` owns only the
79
+ * timer.
80
+ */
81
+ export function clearableDeadline(timeoutMs: number, parent?: AbortSignal): ClearableDeadline {
82
+ const deadline = new AbortController();
83
+ const timeoutReason = new DOMException("Timeout elapsed", "TimeoutError");
84
+ let timer: ReturnType<typeof setTimeout> | undefined = setTimeout(() => {
85
+ timer = undefined;
86
+ if (!deadline.signal.aborted) deadline.abort(timeoutReason);
87
+ }, timeoutMs);
88
+ const signal = parent ? AbortSignal.any([parent, deadline.signal]) : deadline.signal;
89
+
90
+ return {
91
+ signal,
92
+ timeoutReason,
93
+ didExpire: () => signal.aborted && signal.reason === timeoutReason,
94
+ clear: () => {
95
+ if (timer !== undefined) clearTimeout(timer);
96
+ timer = undefined;
97
+ },
98
+ };
99
+ }
100
+
101
+ export function signalWithTimeout(timeoutMs: number, parent?: AbortSignal): LinkedAbortSignal {
102
+ const controller = new AbortController();
103
+ const timeout = setTimeout(() => {
104
+ if (!controller.signal.aborted) controller.abort(new DOMException("Timeout elapsed", "TimeoutError"));
105
+ }, timeoutMs);
106
+
107
+ const abortFromParent = () => {
108
+ if (!controller.signal.aborted) controller.abort(parent?.reason);
109
+ };
110
+
111
+ if (parent?.aborted) {
112
+ abortFromParent();
113
+ } else {
114
+ parent?.addEventListener("abort", abortFromParent, { once: true });
115
+ }
116
+
117
+ return {
118
+ signal: controller.signal,
119
+ cleanup: () => {
120
+ clearTimeout(timeout);
121
+ parent?.removeEventListener("abort", abortFromParent);
122
+ },
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Bind a response body's lifetime to an abort signal.
128
+ *
129
+ * Bun's HTTP client, when a `fetch(..., { signal })` is aborted AFTER the response resolved, tears
130
+ * down the response body stream and rejects any in-flight internal read. If our code hasn't attached
131
+ * a reader yet (e.g. the abort lands between `await fetch()` and the decoder's first read), that
132
+ * rejection is orphaned off the awaited path and Bun reports it as
133
+ * `unhandledRejection: TypeError: null is not an object` (native-only stack) — uncatchable by any
134
+ * caller try/catch. Proactively cancelling the body on abort makes US the consumer that settles it,
135
+ * so the rejection is absorbed. Returns a cleanup to detach the listener on the normal path.
136
+ */
137
+ export function cancelBodyOnAbort(body: ReadableStream<Uint8Array> | null, signal?: AbortSignal): () => void {
138
+ if (!body || !signal) return () => {};
139
+ const onAbort = () => { void body.cancel().catch(() => {}); };
140
+ if (signal.aborted) {
141
+ onAbort();
142
+ return () => {};
143
+ }
144
+ signal.addEventListener("abort", onAbort, { once: true });
145
+ return () => signal.removeEventListener("abort", onAbort);
146
+ }
@@ -0,0 +1,25 @@
1
+ import { lstatSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getConfigDir } from "../config";
4
+
5
+ export const ADMIN_TOKEN_FILE = "admin-api-token";
6
+
7
+ export function adminApiTokenFilePath(configDir = getConfigDir()): string {
8
+ return join(configDir, ADMIN_TOKEN_FILE);
9
+ }
10
+
11
+ export function loadAdminTokenFromFile(configDir = getConfigDir()): string | null {
12
+ const path = adminApiTokenFilePath(configDir);
13
+ try {
14
+ const stat = lstatSync(path);
15
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 512) return null;
16
+ const token = readFileSync(path, "utf8").trim();
17
+ return /^ocx_admin_[A-Za-z0-9_-]{43}$/.test(token) ? token : null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function configuredAdminToken(configDir = getConfigDir(), env: NodeJS.ProcessEnv = process.env): string | null {
24
+ return env.OPENCODEX_ADMIN_AUTH_TOKEN?.trim() || loadAdminTokenFromFile(configDir);
25
+ }
@@ -0,0 +1,83 @@
1
+ export const RETAINED_TRUNCATION_MARKER = "\n…[truncated by opencodex]";
2
+
3
+ export class ResourceAdmissionError extends Error {
4
+ readonly code: string = "server_busy";
5
+
6
+ constructor(readonly resource: string, readonly limit: number) {
7
+ super(`${resource} capacity reached (${limit})`);
8
+ this.name = "ResourceAdmissionError";
9
+ }
10
+ }
11
+
12
+ export interface AdmissionMetrics {
13
+ active: number;
14
+ peak: number;
15
+ admitted: number;
16
+ rejected: number;
17
+ releaseMisses: number;
18
+ }
19
+
20
+ export interface AdmissionLease {
21
+ release(): void;
22
+ }
23
+
24
+ export interface AdmissionReservation<T> extends AdmissionLease {
25
+ bind(value: T): void;
26
+ }
27
+
28
+ export function createAdmissionGate(name: string, limit: number): {
29
+ tryAcquire(): AdmissionLease | null;
30
+ metrics(): Readonly<AdmissionMetrics>;
31
+ } {
32
+ if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError(`${name} admission limit must be positive`);
33
+ const state: AdmissionMetrics = { active: 0, peak: 0, admitted: 0, rejected: 0, releaseMisses: 0 };
34
+ return {
35
+ tryAcquire() {
36
+ if (state.active >= limit) {
37
+ state.rejected += 1;
38
+ return null;
39
+ }
40
+ state.active += 1;
41
+ state.admitted += 1;
42
+ state.peak = Math.max(state.peak, state.active);
43
+ let active = true;
44
+ return {
45
+ release() {
46
+ if (!active) {
47
+ return;
48
+ }
49
+ active = false;
50
+ state.active -= 1;
51
+ },
52
+ };
53
+ },
54
+ metrics() {
55
+ return { ...state };
56
+ },
57
+ };
58
+ }
59
+
60
+ export function retainedUtf8Bytes(value: string): number {
61
+ return new TextEncoder().encode(value).byteLength;
62
+ }
63
+
64
+ function utf8Prefix(value: string, maxBytes: number): string {
65
+ if (maxBytes <= 0) return "";
66
+ let bytes = 0;
67
+ let result = "";
68
+ for (const character of value) {
69
+ const size = retainedUtf8Bytes(character);
70
+ if (bytes + size > maxBytes) break;
71
+ result += character;
72
+ bytes += size;
73
+ }
74
+ return result;
75
+ }
76
+
77
+ export function truncateRetainedUtf8(value: string, maxBytes: number): string {
78
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new RangeError("maxBytes must be a non-negative integer");
79
+ if (retainedUtf8Bytes(value) <= maxBytes) return value;
80
+ const markerBytes = retainedUtf8Bytes(RETAINED_TRUNCATION_MARKER);
81
+ if (markerBytes > maxBytes) return utf8Prefix(RETAINED_TRUNCATION_MARKER, maxBytes);
82
+ return utf8Prefix(value, maxBytes - markerBytes) + RETAINED_TRUNCATION_MARKER;
83
+ }
@@ -0,0 +1,173 @@
1
+ import {
2
+ enforceAppOwnedMemoryBudget,
3
+ registerRetainedStore,
4
+ registerObservedBuffer,
5
+ type RetainedStoreRegistration,
6
+ type RetainedStoreSnapshot,
7
+ } from "./app-owned-memory";
8
+ import { registerStateSweepAfterTick } from "./state-store-sweeper";
9
+ import { debugBufferMetrics, evictOldestDebugEntryForBudget } from "./debug-log-buffer";
10
+ import { injectionBufferMetrics, evictOldestInjectionEntryForBudget } from "./injection-debug-log";
11
+ import { crashRingMetrics, evictOldestCrashTraceForBudget } from "./crash-guard";
12
+ import { claudeInboundDebugMetrics, evictOldestClaudeInboundForBudget } from "../claude/inbound-debug";
13
+ import {
14
+ evictOldestRequestLogForBudget,
15
+ requestLogRetainedStoreSnapshot,
16
+ } from "../server/request-log";
17
+ import {
18
+ anthropicImageNormalizeRetainedStoreSnapshot,
19
+ evictOldestAnthropicImageNormalizeForBudget,
20
+ } from "../adapters/anthropic-image-normalize";
21
+ import {
22
+ evictOldestVisionDescriptionForBudget,
23
+ visionDescriptionRetainedStoreSnapshot,
24
+ } from "../vision";
25
+ import {
26
+ antigravityReplayRetainedStoreSnapshot,
27
+ evictOldestAntigravityReplayForBudget,
28
+ } from "../adapters/google-antigravity-replay";
29
+ import {
30
+ evictOldestModelCacheForBudget,
31
+ modelCacheRetainedStoreSnapshot,
32
+ } from "../codex/model-cache";
33
+ import {
34
+ evictOldestUsageSummaryForBudget,
35
+ usageSummaryRetainedStoreSnapshot,
36
+ } from "../server/management/usage-summary-cache";
37
+ import {
38
+ cursorBlobRetainedStoreSnapshot,
39
+ evictOldestCursorBlobForBudget,
40
+ } from "../adapters/cursor/native-exec";
41
+ import {
42
+ evictOldestResponseContinuationForBudget,
43
+ responseContinuationRetainedStoreSnapshot,
44
+ } from "../responses/state";
45
+ import { translatorObservedBufferSnapshot } from "./translator-budget";
46
+ import { imageFulfillmentTailSnapshot } from "../images/fulfill";
47
+ import { oauthMutationTailSnapshot } from "../oauth/store";
48
+ import { grokApplyFlightSnapshot } from "../server/management/agent-settings-routes";
49
+
50
+ function ringSnapshot(metrics: { entries: number; bytes: number; oldestAt: number | null }): RetainedStoreSnapshot {
51
+ return {
52
+ count: metrics.entries,
53
+ bytes: metrics.bytes,
54
+ evictableBytes: metrics.bytes,
55
+ pinnedBytes: 0,
56
+ oldestAt: metrics.oldestAt,
57
+ };
58
+ }
59
+
60
+ function providerDebugSnapshot(): RetainedStoreSnapshot {
61
+ return ringSnapshot(debugBufferMetrics());
62
+ }
63
+
64
+ function injectionDebugSnapshot(): RetainedStoreSnapshot {
65
+ return ringSnapshot(injectionBufferMetrics());
66
+ }
67
+
68
+ function claudeDebugSnapshot(): RetainedStoreSnapshot {
69
+ return ringSnapshot(claudeInboundDebugMetrics());
70
+ }
71
+
72
+ function crashRingSnapshot(): RetainedStoreSnapshot {
73
+ return ringSnapshot(crashRingMetrics());
74
+ }
75
+
76
+ export const APP_OWNED_RETAINED_STORE_REGISTRATIONS = [
77
+ {
78
+ id: "request_log",
79
+ category: "logs",
80
+ snapshot: requestLogRetainedStoreSnapshot,
81
+ evictOldest: evictOldestRequestLogForBudget,
82
+ },
83
+ {
84
+ id: "provider_debug",
85
+ category: "logs",
86
+ snapshot: providerDebugSnapshot,
87
+ evictOldest: evictOldestDebugEntryForBudget,
88
+ },
89
+ {
90
+ id: "injection_debug",
91
+ category: "logs",
92
+ snapshot: injectionDebugSnapshot,
93
+ evictOldest: evictOldestInjectionEntryForBudget,
94
+ },
95
+ {
96
+ id: "claude_debug",
97
+ category: "logs",
98
+ snapshot: claudeDebugSnapshot,
99
+ evictOldest: evictOldestClaudeInboundForBudget,
100
+ },
101
+ {
102
+ id: "crash_ring",
103
+ category: "logs",
104
+ snapshot: crashRingSnapshot,
105
+ evictOldest: evictOldestCrashTraceForBudget,
106
+ },
107
+ {
108
+ id: "image_normalize",
109
+ category: "caches",
110
+ snapshot: anthropicImageNormalizeRetainedStoreSnapshot,
111
+ evictOldest: evictOldestAnthropicImageNormalizeForBudget,
112
+ },
113
+ {
114
+ id: "vision_descriptions",
115
+ category: "caches",
116
+ snapshot: visionDescriptionRetainedStoreSnapshot,
117
+ evictOldest: evictOldestVisionDescriptionForBudget,
118
+ },
119
+ {
120
+ id: "antigravity_replay",
121
+ category: "caches",
122
+ snapshot: antigravityReplayRetainedStoreSnapshot,
123
+ evictOldest: evictOldestAntigravityReplayForBudget,
124
+ },
125
+ {
126
+ id: "model_cache",
127
+ category: "caches",
128
+ snapshot: modelCacheRetainedStoreSnapshot,
129
+ evictOldest: evictOldestModelCacheForBudget,
130
+ },
131
+ {
132
+ id: "usage_summary",
133
+ category: "caches",
134
+ snapshot: usageSummaryRetainedStoreSnapshot,
135
+ evictOldest: evictOldestUsageSummaryForBudget,
136
+ },
137
+ {
138
+ id: "cursor_blobs",
139
+ category: "blobs",
140
+ snapshot: cursorBlobRetainedStoreSnapshot,
141
+ evictOldest: evictOldestCursorBlobForBudget,
142
+ },
143
+ {
144
+ id: "responses_continuation",
145
+ category: "continuation",
146
+ snapshot: responseContinuationRetainedStoreSnapshot,
147
+ evictOldest: evictOldestResponseContinuationForBudget,
148
+ },
149
+ ] as const satisfies readonly RetainedStoreRegistration[];
150
+
151
+ export function registerDefaultAppOwnedMemoryStores(): void {
152
+ for (const registration of APP_OWNED_RETAINED_STORE_REGISTRATIONS) {
153
+ registerRetainedStore(registration);
154
+ }
155
+ }
156
+
157
+ export const APP_OWNED_OBSERVED_BUFFER_REGISTRATIONS = [
158
+ { id: "translator_buffers", category: "translator", snapshot: translatorObservedBufferSnapshot },
159
+ { id: "image_fulfillment_tail", category: "serialized_tails", snapshot: imageFulfillmentTailSnapshot },
160
+ { id: "oauth_mutation_tail", category: "serialized_tails", snapshot: oauthMutationTailSnapshot },
161
+ { id: "grok_apply_flight", category: "serialized_tails", snapshot: grokApplyFlightSnapshot },
162
+ ] as const;
163
+
164
+ export function registerDefaultAppOwnedObservedBuffers(): void {
165
+ for (const registration of APP_OWNED_OBSERVED_BUFFER_REGISTRATIONS) registerObservedBuffer(registration);
166
+ }
167
+
168
+ export function registerAppOwnedMemorySweepFallback(): void {
169
+ registerStateSweepAfterTick({
170
+ name: "app-owned-memory-budget",
171
+ afterTick: enforceAppOwnedMemoryBudget,
172
+ });
173
+ }
@@ -0,0 +1,265 @@
1
+ export type AppOwnedRetainedCategory = "logs" | "caches" | "blobs" | "continuation";
2
+ export type AppOwnedObservedCategory = "translator" | "serialized_tails";
3
+
4
+ export interface RetainedStoreSnapshot {
5
+ count: number;
6
+ bytes: number;
7
+ evictableBytes: number;
8
+ pinnedBytes: number;
9
+ oldestAt: number | null;
10
+ }
11
+
12
+ export interface RetainedStoreRegistration {
13
+ id: string;
14
+ category: AppOwnedRetainedCategory;
15
+ snapshot(): RetainedStoreSnapshot;
16
+ evictOldest(): number;
17
+ }
18
+
19
+ export interface ObservedBufferRegistration {
20
+ id: string;
21
+ category: AppOwnedObservedCategory;
22
+ snapshot(): { currentBytes: number; highWaterBytes: number; active: number };
23
+ }
24
+
25
+ export interface AppOwnedBytesSnapshot {
26
+ budgetBytes: number;
27
+ retainedBytes: number;
28
+ evictableBytes: number;
29
+ pinnedBytes: number;
30
+ overBudgetBytes: number;
31
+ stores: Record<string, RetainedStoreSnapshot>;
32
+ observedInFlight: Record<string, { currentBytes: number; highWaterBytes: number; active: number }>;
33
+ enforcement: {
34
+ runs: number;
35
+ entriesDemoted: number;
36
+ bytesReleased: number;
37
+ noEvictableCandidate: number;
38
+ snapshotFailures: number;
39
+ oldestAtContractViolations: number;
40
+ };
41
+ }
42
+
43
+ export const DEFAULT_APP_OWNED_MEMORY_BUDGET_BYTES = 256 * 1024 * 1024;
44
+ /**
45
+ * The budget above is an eviction target, not a hard resident limit: active/pinned
46
+ * state may temporarily keep retained bytes above it. Every pin-capable owner has
47
+ * its own finite admission cap; their documented aggregate must remain below this
48
+ * process-owned hard ceiling.
49
+ */
50
+ export const APP_OWNED_WORST_CASE_PINNED_BYTES = 512 * 1024 * 1024;
51
+ export const MIN_APP_OWNED_MEMORY_BUDGET_MB = 64;
52
+ export const MAX_APP_OWNED_MEMORY_BUDGET_MB = 4_096;
53
+
54
+ const CATEGORY_ORDER: readonly AppOwnedRetainedCategory[] = [
55
+ "logs",
56
+ "caches",
57
+ "blobs",
58
+ "continuation",
59
+ ];
60
+ const WARN_INTERVAL_MS = 60_000;
61
+ const ZERO_RETAINED_SNAPSHOT: RetainedStoreSnapshot = {
62
+ count: 0,
63
+ bytes: 0,
64
+ evictableBytes: 0,
65
+ pinnedBytes: 0,
66
+ oldestAt: null,
67
+ };
68
+ const ZERO_OBSERVED_SNAPSHOT = { currentBytes: 0, highWaterBytes: 0, active: 0 };
69
+
70
+ const retainedStores = new Map<string, RetainedStoreRegistration>();
71
+ const observedBuffers = new Map<string, ObservedBufferRegistration>();
72
+ let budgetBytes = DEFAULT_APP_OWNED_MEMORY_BUDGET_BYTES;
73
+ let lastSaturationWarningAt = Number.NEGATIVE_INFINITY;
74
+ const enforcementCounters = {
75
+ runs: 0,
76
+ entriesDemoted: 0,
77
+ bytesReleased: 0,
78
+ noEvictableCandidate: 0,
79
+ snapshotFailures: 0,
80
+ oldestAtContractViolations: 0,
81
+ };
82
+ let isEnforcing = false;
83
+
84
+ export function resolveAppOwnedMemoryBudgetBytes(value: unknown): number {
85
+ return typeof value === "number"
86
+ && Number.isInteger(value)
87
+ && value >= MIN_APP_OWNED_MEMORY_BUDGET_MB
88
+ && value <= MAX_APP_OWNED_MEMORY_BUDGET_MB
89
+ ? value * 1024 * 1024
90
+ : DEFAULT_APP_OWNED_MEMORY_BUDGET_BYTES;
91
+ }
92
+
93
+ export function registerRetainedStore(registration: RetainedStoreRegistration): () => void {
94
+ retainedStores.set(registration.id, registration);
95
+ return () => {
96
+ if (retainedStores.get(registration.id) === registration) retainedStores.delete(registration.id);
97
+ };
98
+ }
99
+
100
+ export function registerObservedBuffer(registration: ObservedBufferRegistration): () => void {
101
+ observedBuffers.set(registration.id, registration);
102
+ return () => {
103
+ if (observedBuffers.get(registration.id) === registration) observedBuffers.delete(registration.id);
104
+ };
105
+ }
106
+
107
+ export function configureAppOwnedMemoryBudget(bytes: number): void {
108
+ budgetBytes = bytes;
109
+ }
110
+
111
+ function retainedSnapshot(registration: RetainedStoreRegistration): RetainedStoreSnapshot {
112
+ try {
113
+ return registration.snapshot();
114
+ } catch {
115
+ enforcementCounters.snapshotFailures += 1;
116
+ return { ...ZERO_RETAINED_SNAPSHOT };
117
+ }
118
+ }
119
+
120
+ function observedSnapshot(
121
+ registration: ObservedBufferRegistration,
122
+ ): { currentBytes: number; highWaterBytes: number; active: number } {
123
+ try {
124
+ return registration.snapshot();
125
+ } catch {
126
+ enforcementCounters.snapshotFailures += 1;
127
+ return { ...ZERO_OBSERVED_SNAPSHOT };
128
+ }
129
+ }
130
+
131
+ export function appOwnedBytesSnapshot(): AppOwnedBytesSnapshot {
132
+ return appOwnedBytesSnapshotFrom(retainedSnapshots().stores);
133
+ }
134
+
135
+ function appOwnedBytesSnapshotFrom(
136
+ retained: ReadonlyMap<string, RetainedStoreSnapshot>,
137
+ ): AppOwnedBytesSnapshot {
138
+ const stores: Record<string, RetainedStoreSnapshot> = {};
139
+ let retainedBytes = 0;
140
+ let evictableBytes = 0;
141
+ let pinnedBytes = 0;
142
+ for (const registration of retainedStores.values()) {
143
+ const snapshot = retained.get(registration.id) ?? ZERO_RETAINED_SNAPSHOT;
144
+ stores[registration.id] = snapshot;
145
+ retainedBytes += snapshot.bytes;
146
+ evictableBytes += snapshot.evictableBytes;
147
+ pinnedBytes += snapshot.pinnedBytes;
148
+ }
149
+
150
+ const observedInFlight: AppOwnedBytesSnapshot["observedInFlight"] = {};
151
+ for (const registration of observedBuffers.values()) {
152
+ observedInFlight[registration.id] = observedSnapshot(registration);
153
+ }
154
+
155
+ return {
156
+ budgetBytes,
157
+ retainedBytes,
158
+ evictableBytes,
159
+ pinnedBytes,
160
+ overBudgetBytes: Math.max(0, retainedBytes - budgetBytes),
161
+ stores,
162
+ observedInFlight,
163
+ enforcement: { ...enforcementCounters },
164
+ };
165
+ }
166
+
167
+ function nextCandidate(
168
+ snapshots: ReadonlyMap<string, RetainedStoreSnapshot>,
169
+ ineligible: Set<string>,
170
+ ): RetainedStoreRegistration | undefined {
171
+ for (const category of CATEGORY_ORDER) {
172
+ let candidate: RetainedStoreRegistration | undefined;
173
+ let candidateAt = Number.POSITIVE_INFINITY;
174
+ for (const registration of retainedStores.values()) {
175
+ if (registration.category !== category || ineligible.has(registration.id)) continue;
176
+ const snapshot = snapshots.get(registration.id);
177
+ if (!snapshot || snapshot.evictableBytes <= 0) continue;
178
+ const oldestAt = snapshot.oldestAt;
179
+ if (oldestAt === null || !Number.isFinite(oldestAt)) {
180
+ enforcementCounters.oldestAtContractViolations += 1;
181
+ ineligible.add(registration.id);
182
+ continue;
183
+ }
184
+ if (!candidate || oldestAt < candidateAt) {
185
+ candidate = registration;
186
+ candidateAt = oldestAt;
187
+ }
188
+ }
189
+ if (candidate) return candidate;
190
+ }
191
+ return undefined;
192
+ }
193
+
194
+ function retainedSnapshots(): { total: number; stores: Map<string, RetainedStoreSnapshot> } {
195
+ const stores = new Map<string, RetainedStoreSnapshot>();
196
+ let total = 0;
197
+ for (const registration of retainedStores.values()) {
198
+ const snapshot = retainedSnapshot(registration);
199
+ stores.set(registration.id, snapshot);
200
+ total += snapshot.bytes;
201
+ }
202
+ return { total, stores };
203
+ }
204
+
205
+ function warnPinnedSaturation(): void {
206
+ const at = Date.now();
207
+ if (at - lastSaturationWarningAt < WARN_INTERVAL_MS) return;
208
+ lastSaturationWarningAt = at;
209
+ console.warn("[app-owned-memory] retained state remains over budget with no evictable candidate");
210
+ }
211
+
212
+ export function enforceAppOwnedMemoryBudget(): AppOwnedBytesSnapshot {
213
+ if (isEnforcing) return appOwnedBytesSnapshot();
214
+ isEnforcing = true;
215
+ enforcementCounters.runs += 1;
216
+ try {
217
+ const ineligible = new Set<string>();
218
+ const current = retainedSnapshots();
219
+ while (current.total > budgetBytes) {
220
+ const candidate = nextCandidate(current.stores, ineligible);
221
+ if (!candidate) {
222
+ enforcementCounters.noEvictableCandidate += 1;
223
+ warnPinnedSaturation();
224
+ break;
225
+ }
226
+
227
+ let reportedReleased = 0;
228
+ try {
229
+ reportedReleased = candidate.evictOldest();
230
+ } catch {
231
+ ineligible.add(candidate.id);
232
+ continue;
233
+ }
234
+ const previousSnapshot = current.stores.get(candidate.id) ?? ZERO_RETAINED_SNAPSHOT;
235
+ const nextSnapshot = retainedSnapshot(candidate);
236
+ current.stores.set(candidate.id, nextSnapshot);
237
+ const nextTotal = Math.max(0, current.total - previousSnapshot.bytes + nextSnapshot.bytes);
238
+ const actualReleased = Math.max(0, current.total - nextTotal);
239
+ if (reportedReleased <= 0 || actualReleased <= 0) {
240
+ ineligible.add(candidate.id);
241
+ } else {
242
+ enforcementCounters.entriesDemoted += 1;
243
+ enforcementCounters.bytesReleased += actualReleased;
244
+ }
245
+ current.total = nextTotal;
246
+ }
247
+ return appOwnedBytesSnapshotFrom(current.stores);
248
+ } finally {
249
+ isEnforcing = false;
250
+ }
251
+ }
252
+
253
+ export function resetAppOwnedMemoryForTests(): void {
254
+ retainedStores.clear();
255
+ observedBuffers.clear();
256
+ budgetBytes = DEFAULT_APP_OWNED_MEMORY_BUDGET_BYTES;
257
+ lastSaturationWarningAt = Number.NEGATIVE_INFINITY;
258
+ enforcementCounters.runs = 0;
259
+ enforcementCounters.entriesDemoted = 0;
260
+ enforcementCounters.bytesReleased = 0;
261
+ enforcementCounters.noEvictableCandidate = 0;
262
+ enforcementCounters.snapshotFailures = 0;
263
+ enforcementCounters.oldestAtContractViolations = 0;
264
+ isEnforcing = false;
265
+ }