@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,90 @@
1
+ /**
2
+ * Fail-closed protection for the user's REAL OpenCodex home while tests run.
3
+ *
4
+ * A management-route unit test once passed an in-memory fixture config to a handler
5
+ * that persisted it through the process-global writer, replacing a live 41KB,
6
+ * ten-provider `~/.opencodex/config.json` with an 874-byte fixture on a real machine.
7
+ * Credentials survived only because the store files are separate; the providers were
8
+ * recoverable only because an unrelated backup snapshot happened to exist.
9
+ * (devlog `_plan/260730_codex_rs_upstream_v2_live_handoff/070`.)
10
+ *
11
+ * Two properties matter more than breadth here:
12
+ *
13
+ * 1. It must be INERT in production. Guessing "am I a test?" from ecosystem variables
14
+ * like NODE_ENV would brick `NODE_ENV=test ocx ...` for a user who did nothing
15
+ * wrong — worse than the bug it prevents. Arming requires OCX_TEST_HOME_GUARD=1,
16
+ * which only this repository's test preload sets.
17
+ * 2. It must fail CLOSED for code nobody has written yet. So it denies ONE path — the
18
+ * captured production home — instead of allow-listing known-good test directories.
19
+ * An allowlist would have to be opted into, and the test that forgets is exactly
20
+ * how this incident happened.
21
+ */
22
+ import { homedir } from "node:os";
23
+ import { dirname, join, relative, resolve } from "node:path";
24
+ import { realpathSync } from "node:fs";
25
+
26
+ const GUARD_ENV = "OCX_TEST_HOME_GUARD";
27
+ /**
28
+ * Set by `scripts/test.ts` to the ORIGINAL home before it hands the child a rewritten
29
+ * HOME. On that path `homedir()` already points at the sandbox by the time this module
30
+ * loads, so the true home is only knowable from this hand-off.
31
+ */
32
+ const REAL_HOME_ENV = "OCX_REAL_HOME";
33
+
34
+ /**
35
+ * Resolve symlinks so two spellings of one location compare equal — macOS hands out
36
+ * `/var/folders/...` whose realpath is `/private/var/folders/...` — and so a path that
37
+ * merely *points* at the protected home cannot slip past a string comparison. A path
38
+ * that does not exist yet canonicalizes through its nearest existing ancestor, which is
39
+ * the common case for a config file about to be created.
40
+ */
41
+ function canonicalize(path: string): string {
42
+ let current = resolve(path);
43
+ const unresolved: string[] = [];
44
+ for (;;) {
45
+ try {
46
+ return join(realpathSync.native(current), ...unresolved.reverse());
47
+ } catch {
48
+ const parent = dirname(current);
49
+ if (parent === current) return resolve(path);
50
+ unresolved.push(current.slice(parent.length + 1));
51
+ current = parent;
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Captured ONCE at module load, before any harness replaces HOME/USERPROFILE. Reading
58
+ * `homedir()` later would return the sandbox and leave the real home unprotected — the
59
+ * guard would be perfectly inverted while its tests still looked green.
60
+ */
61
+ const PROTECTED_HOME = canonicalize(
62
+ join(process.env[REAL_HOME_ENV]?.trim() || homedir(), ".opencodex"),
63
+ );
64
+
65
+ /** The production home this process protects. Exported for the guard's own tests. */
66
+ export function protectedHomeForTests(): string {
67
+ return PROTECTED_HOME;
68
+ }
69
+
70
+ export function isTestHomeGuardArmed(): boolean {
71
+ return process.env[GUARD_ENV] === "1";
72
+ }
73
+
74
+ /**
75
+ * Throw when an armed test process is about to write the real OpenCodex home.
76
+ *
77
+ * Call FIRST inside a writer, before any mkdir/chmod/write, so a rejected write leaves
78
+ * nothing behind. Silent no-op when disarmed (production) or when `dir` is any other
79
+ * location, including a suite's own `mkdtemp` fixture — no registration required, which
80
+ * is what keeps the 54 existing suites that write config working untouched.
81
+ */
82
+ export function assertNotRealHomeUnderTest(dir: string): void {
83
+ if (!isTestHomeGuardArmed()) return;
84
+ if (canonicalize(dir) !== PROTECTED_HOME) return;
85
+ throw new Error(
86
+ `refusing to write the real OpenCodex home (${PROTECTED_HOME}) from a test process. `
87
+ + "Point OPENCODEX_HOME at a temp directory for this test, or inject persistence "
88
+ + "instead of calling the global writer (see devlog 260730_codex_rs_upstream_v2_live_handoff/070).",
89
+ );
90
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Heuristic token-estimation sidecar.
3
+ *
4
+ * Some providers (notably kiro / CodeWhisperer) return no token usage in their stream, so Codex's
5
+ * usage display and auto-compact (which read response.completed.usage) never engage. This module
6
+ * provides a cheap, dependency-free char-based estimate to fill that gap.
7
+ *
8
+ * Grounding (web): 1 token ~= 4 chars for English prose; empirical model ratios are ~Claude 3.5,
9
+ * ~GPT 3.6, ~Gemini 3.8 chars/token (within ~10%). Code / JSON / tool-args (the dominant Codex
10
+ * traffic) pack MORE tokens per char, so a lower chars-per-token ratio is used for those models.
11
+ * Over-counting fails safe (auto-compact fires earlier); under-counting risks context overflow.
12
+ */
13
+
14
+ /** Generic English-prose fallback ratio (chars per token). */
15
+ const DEFAULT_CHARS_PER_TOKEN = 4;
16
+
17
+ /**
18
+ * Kiro routes code/JSON-heavy agent traffic whose true ratio is ~3.0-3.3 chars/token. 3.5 keeps a
19
+ * small safety margin (slight over-count) without wildly inflating; tune toward 3.3 if overflow is
20
+ * ever observed. All kiro models are text LLMs, so a single ratio applies to the whole family.
21
+ */
22
+ const KIRO_CHARS_PER_TOKEN = 3.5;
23
+
24
+ const KIRO_MODEL_PREFIXES = ["kiro", "claude", "deepseek", "minimax", "glm", "qwen"];
25
+
26
+ /** Model-aware chars-per-token ratio. Unknown models fall back to the generic English ratio. */
27
+ export function charsPerToken(modelId?: string): number {
28
+ if (!modelId) return DEFAULT_CHARS_PER_TOKEN;
29
+ const id = modelId.toLowerCase();
30
+ if (KIRO_MODEL_PREFIXES.some(p => id.startsWith(p))) return KIRO_CHARS_PER_TOKEN;
31
+ return DEFAULT_CHARS_PER_TOKEN;
32
+ }
33
+
34
+ /**
35
+ * CJK-aware ratio (devlog 260712 B3, audit R2#7): Korean/Chinese/Japanese text packs
36
+ * roughly one token per 1.5-3 chars, so a CJK-heavy blob estimated at English ratios
37
+ * badly undercounts. When >30% of chars are CJK, clamp DOWN to 2.5 chars/token —
38
+ * `min(model ratio, 2.5)` so per-model ratios (Claude 3.5, Kiro family) never rise.
39
+ */
40
+ const CJK_CHARS_PER_TOKEN = 2.5;
41
+ const CJK_RATIO_THRESHOLD = 0.3;
42
+ // Hangul syllables/jamo, CJK unified ideographs (+ext A), hiragana/katakana.
43
+ const CJK_RE = /[\uAC00-\uD7A3\u1100-\u11FF\u3130-\u318F\u4E00-\u9FFF\u3400-\u4DBF\u3040-\u30FF]/;
44
+
45
+ function cjkRatio(text: string): number {
46
+ if (text.length === 0) return 0;
47
+ // Sample long blobs for O(1) cost: every char up to 2k, then a stride.
48
+ const stride = text.length > 2048 ? Math.ceil(text.length / 2048) : 1;
49
+ let cjk = 0;
50
+ let sampled = 0;
51
+ for (let i = 0; i < text.length; i += stride) {
52
+ sampled++;
53
+ if (CJK_RE.test(text[i]!)) cjk++;
54
+ }
55
+ return sampled === 0 ? 0 : cjk / sampled;
56
+ }
57
+
58
+ /**
59
+ * Estimate the token count of a text blob. Pure and deterministic.
60
+ * Returns 0 for empty/whitespace-free-empty input; otherwise ceil(length / ratio), min 1.
61
+ */
62
+ export function estimateTokens(text: string, modelId?: string): number {
63
+ if (!text) return 0;
64
+ const len = text.length;
65
+ if (len === 0) return 0;
66
+ let ratio = charsPerToken(modelId);
67
+ if (cjkRatio(text) > CJK_RATIO_THRESHOLD) ratio = Math.min(ratio, CJK_CHARS_PER_TOKEN);
68
+ return Math.max(1, Math.ceil(len / ratio));
69
+ }
@@ -0,0 +1,356 @@
1
+ export const TRANSLATOR_MAX_CALL_ARGUMENT_BYTES = 2 * 1024 * 1024;
2
+ export const TRANSLATOR_MAX_TURN_BYTES = 32 * 1024 * 1024;
3
+ export const TRANSLATOR_MAX_SSE_EVENT_BYTES = 32 * 1024 * 1024;
4
+ export const CURSOR_MAX_CONNECT_FRAME_BYTES = 32 * 1024 * 1024;
5
+ export const CURSOR_MAX_EFFECTIVE_CONNECT_PAYLOAD_BYTES = 16 * 1024 * 1024;
6
+ export const CURSOR_TRANSPORT_MAX_BUFFERED_BYTES = 32 * 1024 * 1024;
7
+ export const CURSOR_TRANSPORT_RESUME_BYTES = 16 * 1024 * 1024;
8
+ export const CURSOR_MAX_PENDING_FRAMES = 1024;
9
+ export const CURSOR_PENDING_FRAMES_RESUME = 512;
10
+ export const MAX_PENDING_IMAGE_FULFILLMENTS = 64;
11
+ export const MAX_PENDING_OAUTH_MUTATIONS = 128;
12
+
13
+ export type TranslatorBufferKind =
14
+ | "tool_args"
15
+ | "retained_collectors"
16
+ | "live_transient"
17
+ | "reasoning"
18
+ | "item_ids"
19
+ | "tool_search_sources"
20
+ | "cursor_transport"
21
+ | "cursor_kv"
22
+ | "request_copies";
23
+
24
+ export type ExternallyCappedKind = "passthrough_serialization" | "mcp_payload";
25
+
26
+ export class TranslatorBudgetExceededError extends Error {
27
+ readonly code = "translation_buffer_limit";
28
+
29
+ constructor(readonly kind: TranslatorBufferKind, readonly limitBytes: number) {
30
+ super(`translator ${kind} buffer exceeded ${limitBytes} bytes`);
31
+ this.name = "TranslatorBudgetExceededError";
32
+ }
33
+ }
34
+
35
+ export function isTranslatorBudgetExceededError(error: unknown): error is TranslatorBudgetExceededError {
36
+ return error instanceof TranslatorBudgetExceededError
37
+ || (error instanceof Error && (error as { code?: unknown }).code === "translation_buffer_limit");
38
+ }
39
+
40
+ export interface TranslatorBudgetSnapshot {
41
+ currentBytes: number;
42
+ highWaterBytes: number;
43
+ activeCalls: number;
44
+ overflows: number;
45
+ }
46
+
47
+ export interface TranslatorBudgetOptions {
48
+ maxCallArgumentBytes?: number;
49
+ maxTurnBytes?: number;
50
+ }
51
+
52
+ export interface TranslatorTransientReservation {
53
+ commitRetained(): void;
54
+ release(): void;
55
+ }
56
+
57
+ export interface TranslatorBudget {
58
+ openCall(id: string): void;
59
+ closeCall(id: string): void;
60
+ reserveTransient(
61
+ bytes: number,
62
+ scope: { kind: TranslatorBufferKind; callId?: string },
63
+ ): TranslatorTransientReservation;
64
+ chargeRetained(delta: number, scope: { kind: TranslatorBufferKind; callId?: string }): void;
65
+ releaseRetained(bytes: number, scope: { kind: TranslatorBufferKind; callId?: string }): void;
66
+ observeAcceptedRequestCopy(bytes: number): () => void;
67
+ observeExternallyCapped(kind: ExternallyCappedKind, bytes: number): () => void;
68
+ snapshot(): TranslatorBudgetSnapshot;
69
+ dispose(): void;
70
+ }
71
+
72
+ const retainedEventOwnership = new WeakMap<object, { budget: TranslatorBudget; bytes: number }>();
73
+
74
+ /**
75
+ * Charge a materialized adapter-event batch and attach its lease to the events themselves.
76
+ * A copied event array (for example terminal-guard collection) preserves the event objects, so
77
+ * the response builder can consume each source lease immediately after its replacement lands.
78
+ */
79
+ export function retainTranslatedEventBatch<T extends object>(events: T[], budget: TranslatorBudget): void {
80
+ if (events.length === 0) return;
81
+ const serialized = events.map(event => JSON.stringify(event));
82
+ const totalBytes = Buffer.byteLength(`[${serialized.join(",")}]`);
83
+ budget.chargeRetained(totalBytes, { kind: "retained_collectors" });
84
+ for (let index = 0; index < events.length; index++) {
85
+ const delimiterBytes = index === events.length - 1 ? 2 : 1;
86
+ retainedEventOwnership.set(events[index]!, {
87
+ budget,
88
+ bytes: Buffer.byteLength(serialized[index]!) + delimiterBytes,
89
+ });
90
+ }
91
+ }
92
+
93
+ /** Transfer one charged adapter event to the response builder's newly materialized owner. */
94
+ export function releaseTranslatedEvent(event: object, budget: TranslatorBudget): void {
95
+ const ownership = retainedEventOwnership.get(event);
96
+ if (!ownership || ownership.budget !== budget) return;
97
+ retainedEventOwnership.delete(event);
98
+ budget.releaseRetained(ownership.bytes, { kind: "retained_collectors" });
99
+ }
100
+
101
+ const liveBudgets = new Set<Budget>();
102
+ let aggregateCurrentBytes = 0;
103
+ let aggregateActiveCalls = 0;
104
+ let aggregateHighWaterBytes = 0;
105
+ let aggregateOverflows = 0;
106
+
107
+ function normalizeBytes(bytes: number): number {
108
+ return Number.isFinite(bytes) && bytes > 0 ? Math.floor(bytes) : 0;
109
+ }
110
+
111
+ function observeAggregate(): void {
112
+ aggregateHighWaterBytes = Math.max(aggregateHighWaterBytes, aggregateCurrentBytes);
113
+ }
114
+
115
+ class Budget implements TranslatorBudget {
116
+ private readonly calls = new Map<string, { logicalBytes: number; physicalBytes: number }>();
117
+ private readonly charged = new Map<TranslatorBufferKind, number>();
118
+ private hardChargedBytes = 0;
119
+ private observedBytes = 0;
120
+ private currentBytes = 0;
121
+ private highWaterBytes = 0;
122
+ private overflowCount = 0;
123
+ private disposed = false;
124
+
125
+ constructor(
126
+ private readonly maxCallArgumentBytes: number,
127
+ private readonly maxTurnBytes: number,
128
+ ) {}
129
+
130
+ private chargedBytes(): number {
131
+ return this.hardChargedBytes;
132
+ }
133
+
134
+ observedCurrentBytes(): number {
135
+ return this.currentBytes;
136
+ }
137
+
138
+ private changed(): void {
139
+ const nextBytes = this.hardChargedBytes + this.observedBytes;
140
+ aggregateCurrentBytes += nextBytes - this.currentBytes;
141
+ this.currentBytes = nextBytes;
142
+ this.highWaterBytes = Math.max(this.highWaterBytes, nextBytes);
143
+ observeAggregate();
144
+ }
145
+
146
+ private reject(kind: TranslatorBufferKind, limitBytes: number): never {
147
+ this.overflowCount += 1;
148
+ aggregateOverflows += 1;
149
+ throw new TranslatorBudgetExceededError(kind, limitBytes);
150
+ }
151
+
152
+ private admit(kind: TranslatorBufferKind, bytes: number): number {
153
+ const admitted = normalizeBytes(bytes);
154
+ if (this.chargedBytes() + admitted > this.maxTurnBytes) this.reject(kind, this.maxTurnBytes);
155
+ return admitted;
156
+ }
157
+
158
+ openCall(id: string): void {
159
+ if (!this.calls.has(id)) {
160
+ this.calls.set(id, { logicalBytes: 0, physicalBytes: 0 });
161
+ aggregateActiveCalls += 1;
162
+ }
163
+ }
164
+
165
+ closeCall(id: string): void {
166
+ const call = this.calls.get(id);
167
+ if (!call) return;
168
+ this.hardChargedBytes = Math.max(0, this.hardChargedBytes - call.physicalBytes);
169
+ this.calls.delete(id);
170
+ aggregateActiveCalls = Math.max(0, aggregateActiveCalls - 1);
171
+ this.changed();
172
+ }
173
+
174
+ reserveTransient(
175
+ bytes: number,
176
+ scope: { kind: TranslatorBufferKind; callId?: string },
177
+ ): TranslatorTransientReservation {
178
+ const admitted = normalizeBytes(bytes);
179
+ if (scope.callId && admitted > this.maxCallArgumentBytes) {
180
+ this.reject("tool_args", this.maxCallArgumentBytes);
181
+ }
182
+ this.admit(scope.kind, admitted);
183
+ this.charged.set(scope.kind, (this.charged.get(scope.kind) ?? 0) + admitted);
184
+ this.hardChargedBytes += admitted;
185
+ this.changed();
186
+ let state: "reserved" | "committed" | "released" = "reserved";
187
+ return {
188
+ commitRetained: () => {
189
+ if (state !== "reserved") return;
190
+ state = "committed";
191
+ if (!scope.callId) return;
192
+ const call = this.calls.get(scope.callId) ?? { logicalBytes: 0, physicalBytes: 0 };
193
+ this.charged.set(scope.kind, Math.max(0, (this.charged.get(scope.kind) ?? 0) - admitted));
194
+ call.logicalBytes = admitted;
195
+ call.physicalBytes += admitted;
196
+ this.calls.set(scope.callId, call);
197
+ this.changed();
198
+ },
199
+ release: () => {
200
+ if (state !== "reserved") return;
201
+ state = "released";
202
+ this.charged.set(scope.kind, Math.max(0, (this.charged.get(scope.kind) ?? 0) - admitted));
203
+ this.hardChargedBytes = Math.max(0, this.hardChargedBytes - admitted);
204
+ this.changed();
205
+ },
206
+ };
207
+ }
208
+
209
+ chargeRetained(delta: number, scope: { kind: TranslatorBufferKind; callId?: string }): void {
210
+ const admitted = normalizeBytes(delta);
211
+ if (scope.callId) {
212
+ const call = this.calls.get(scope.callId) ?? { logicalBytes: 0, physicalBytes: 0 };
213
+ if (call.logicalBytes + admitted > this.maxCallArgumentBytes) {
214
+ this.reject("tool_args", this.maxCallArgumentBytes);
215
+ }
216
+ this.admit(scope.kind, admitted);
217
+ call.logicalBytes += admitted;
218
+ call.physicalBytes += admitted;
219
+ this.calls.set(scope.callId, call);
220
+ } else {
221
+ this.admit(scope.kind, admitted);
222
+ this.charged.set(scope.kind, (this.charged.get(scope.kind) ?? 0) + admitted);
223
+ }
224
+ this.hardChargedBytes += admitted;
225
+ this.changed();
226
+ }
227
+
228
+ releaseRetained(bytes: number, scope: { kind: TranslatorBufferKind; callId?: string }): void {
229
+ const released = normalizeBytes(bytes);
230
+ if (scope.callId) {
231
+ const call = this.calls.get(scope.callId);
232
+ if (call) {
233
+ const actual = Math.min(released, call.physicalBytes);
234
+ call.physicalBytes -= actual;
235
+ this.hardChargedBytes = Math.max(0, this.hardChargedBytes - actual);
236
+ }
237
+ } else {
238
+ const current = this.charged.get(scope.kind) ?? 0;
239
+ const actual = Math.min(released, current);
240
+ this.charged.set(scope.kind, current - actual);
241
+ this.hardChargedBytes = Math.max(0, this.hardChargedBytes - actual);
242
+ }
243
+ this.changed();
244
+ }
245
+
246
+ private observe(bytes: number): () => void {
247
+ const observed = normalizeBytes(bytes);
248
+ this.observedBytes += observed;
249
+ this.changed();
250
+ let released = false;
251
+ return () => {
252
+ if (released) return;
253
+ released = true;
254
+ this.observedBytes = Math.max(0, this.observedBytes - observed);
255
+ this.changed();
256
+ };
257
+ }
258
+
259
+ observeAcceptedRequestCopy(bytes: number): () => void {
260
+ return this.observe(bytes);
261
+ }
262
+
263
+ observeExternallyCapped(_kind: ExternallyCappedKind, bytes: number): () => void {
264
+ return this.observe(bytes);
265
+ }
266
+
267
+ snapshot(): TranslatorBudgetSnapshot {
268
+ return {
269
+ currentBytes: this.observedCurrentBytes(),
270
+ highWaterBytes: this.highWaterBytes,
271
+ activeCalls: this.calls.size,
272
+ overflows: this.overflowCount,
273
+ };
274
+ }
275
+
276
+ dispose(): void {
277
+ if (this.disposed) return;
278
+ this.disposed = true;
279
+ aggregateActiveCalls = Math.max(0, aggregateActiveCalls - this.calls.size);
280
+ aggregateCurrentBytes = Math.max(0, aggregateCurrentBytes - this.currentBytes);
281
+ this.calls.clear();
282
+ this.charged.clear();
283
+ this.hardChargedBytes = 0;
284
+ this.observedBytes = 0;
285
+ this.currentBytes = 0;
286
+ liveBudgets.delete(this);
287
+ observeAggregate();
288
+ }
289
+ }
290
+
291
+ export function createTranslatorBudget(options: TranslatorBudgetOptions = {}): TranslatorBudget {
292
+ const budget = new Budget(
293
+ options.maxCallArgumentBytes ?? TRANSLATOR_MAX_CALL_ARGUMENT_BYTES,
294
+ options.maxTurnBytes ?? TRANSLATOR_MAX_TURN_BYTES,
295
+ );
296
+ liveBudgets.add(budget);
297
+ observeAggregate();
298
+ return budget;
299
+ }
300
+
301
+ export function translatorObservedBufferSnapshot(): {
302
+ currentBytes: number;
303
+ highWaterBytes: number;
304
+ active: number;
305
+ } {
306
+ return {
307
+ currentBytes: aggregateCurrentBytes,
308
+ highWaterBytes: aggregateHighWaterBytes,
309
+ active: aggregateActiveCalls,
310
+ };
311
+ }
312
+
313
+ /** Internal diagnostics used by focused tests; never registered with app-owned memory. */
314
+ export function translatorObservedOverflowCount(): number {
315
+ return aggregateOverflows;
316
+ }
317
+
318
+ /** Clears process-wide translator diagnostics and disposes leaked test budgets. */
319
+ export function resetTranslatorAggregateForTests(): void {
320
+ for (const budget of liveBudgets) budget.dispose();
321
+ aggregateCurrentBytes = 0;
322
+ aggregateActiveCalls = 0;
323
+ aggregateHighWaterBytes = 0;
324
+ aggregateOverflows = 0;
325
+ }
326
+
327
+ export function finalizeTranslatorBudgetResponse(response: Response, budget: TranslatorBudget): Response {
328
+ if (!response.body) {
329
+ budget.dispose();
330
+ return response;
331
+ }
332
+ const reader = response.body.getReader();
333
+ let finalized = false;
334
+ const finalize = () => {
335
+ if (finalized) return;
336
+ finalized = true;
337
+ budget.dispose();
338
+ };
339
+ return new Response(new ReadableStream<Uint8Array>({
340
+ async pull(controller) {
341
+ try {
342
+ const result = await reader.read();
343
+ if (result.done) {
344
+ finalize();
345
+ controller.close();
346
+ } else controller.enqueue(result.value);
347
+ } catch (error) {
348
+ finalize();
349
+ controller.error(error);
350
+ }
351
+ },
352
+ async cancel(reason) {
353
+ try { await reader.cancel(reason); } finally { finalize(); }
354
+ },
355
+ }), { status: response.status, statusText: response.statusText, headers: response.headers });
356
+ }