@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,364 @@
1
+ export interface OcxErrorPayload {
2
+ message: string;
3
+ type: string;
4
+ code: string | null;
5
+ }
6
+
7
+ /** OpenAI / Codex hard block for high-risk cybersecurity activity (HTTP 400 or mid-stream). */
8
+ export const CYBER_POLICY_ERROR_CODE = "cyber_policy";
9
+
10
+ export function isCyberPolicyCode(code: string | null | undefined): boolean {
11
+ return code === CYBER_POLICY_ERROR_CODE;
12
+ }
13
+
14
+ /**
15
+ * Detect OpenAI cyber-policy refusals from message text when structured `code` was stripped.
16
+ * Matches Codex fallback copy and Cursor/API agent wording (session evidence 2026-07-24).
17
+ * Does not treat a bare `cyber_policy` token as conclusive — model ids / routing errors can
18
+ * include that substring without being a policy refusal.
19
+ */
20
+ export function isCyberPolicyMessage(text: string): boolean {
21
+ const lower = text.toLowerCase();
22
+ // Serialized error-code signatures only (not bare model-id collisions).
23
+ if (/"code"\s*:\s*"cyber_policy"/.test(lower)) return true;
24
+ if (/\bcode\s*[:=]\s*["']?cyber_policy\b/.test(lower)) return true;
25
+ if (lower.includes("high-risk cybersecurity")) return true;
26
+ if (lower.includes("high-risk cyber activity") || lower.includes("high-risk cyber ")) return true;
27
+ if (lower.includes("possible cybersecurity risk")) return true;
28
+ if (lower.includes("flagged") && lower.includes("cybersecurity")) return true;
29
+ if (lower.includes("flagged") && lower.includes("cyber activity")) return true;
30
+ return false;
31
+ }
32
+
33
+ function isSubscriptionGateMessage(text: string): boolean {
34
+ return (
35
+ text.includes("requires a subscription") ||
36
+ text.includes("requires subscription") ||
37
+ text.includes("subscription required") ||
38
+ text.includes("upgrade for access") ||
39
+ text.includes("upgrade to pro") ||
40
+ text.includes("pro subscription") ||
41
+ text.includes("ollama.com/upgrade") ||
42
+ (text.includes("upgrade") && text.includes("subscription"))
43
+ );
44
+ }
45
+
46
+ function isAuthenticationMessage(text: string): boolean {
47
+ const accessDeniedWithCredentialCue = (
48
+ text.includes("access denied") ||
49
+ text.includes("accessdeniedexception")
50
+ ) && (
51
+ text.includes("authentication") ||
52
+ text.includes("credential") ||
53
+ text.includes("api key") ||
54
+ text.includes("token") ||
55
+ text.includes("signature")
56
+ );
57
+ return (
58
+ text.includes("authentication failed") ||
59
+ text.includes("authentication") ||
60
+ text.includes("invalid_api_key") ||
61
+ text.includes("invalid api key") ||
62
+ text.includes("invalid token") ||
63
+ text.includes("unauthorizedexception") ||
64
+ text.includes("unrecognizedclientexception") ||
65
+ text.includes("unrecognizedclient") ||
66
+ text.includes("expired token") ||
67
+ text.includes("expiredtoken") ||
68
+ text.includes("unauthenticated") ||
69
+ text.includes("unauthorized") ||
70
+ accessDeniedWithCredentialCue
71
+ );
72
+ }
73
+
74
+ function isPermissionMessage(text: string): boolean {
75
+ return (
76
+ text.includes("permission_denied") ||
77
+ text.includes("permission denied") ||
78
+ text.includes("forbidden") ||
79
+ text.includes("access denied") ||
80
+ text.includes("accessdeniedexception") ||
81
+ text.includes("not allowed to use") ||
82
+ text.includes("model access")
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Client cancelled / closed the turn. Matches ONLY abort phrases this codebase
88
+ * produces — "client closed request during web-search" (src/web-search/loop.ts),
89
+ * "Client cancelled request" (src/server/responses.ts) — plus the explicit
90
+ * "request cancel(l)ed by client" forms. Deliberately narrow: bare "client closed"
91
+ * would also swallow legitimate upstream failures like "upstream HTTP client
92
+ * closed idle connection" and turn a real 502 into a 499.
93
+ */
94
+ export function isClientClosedMessage(text: string): boolean {
95
+ const lower = text.toLowerCase();
96
+ return (
97
+ lower.includes("client closed request") ||
98
+ lower.includes("client cancelled request") ||
99
+ lower.includes("client canceled request") ||
100
+ lower.includes("request canceled by client") ||
101
+ lower.includes("request cancelled by client")
102
+ );
103
+ }
104
+
105
+ export function classifyError(status: number, type: string, message: string): OcxErrorPayload {
106
+ const text = message.toLowerCase();
107
+ if (type === "previous_response_not_found") {
108
+ return { message, type: "invalid_request_error", code: "previous_response_not_found" };
109
+ }
110
+ // Preserve explicit cancel types used by compact/combo JSON errors; unify message-inferred
111
+ // client closes (web-search abort text) onto client_closed_request for /api/logs.
112
+ if (type === "client_cancelled") {
113
+ return { message, type: "client_cancelled", code: "client_cancelled" };
114
+ }
115
+ if (
116
+ status === 499 ||
117
+ type === "client_closed_request" ||
118
+ isClientClosedMessage(text)
119
+ ) {
120
+ return { message, type: "invalid_request_error", code: "client_closed_request" };
121
+ }
122
+ // Codex only shows the dedicated cyber UI when error.code === "cyber_policy".
123
+ // Prefer that code (and invalid_request_error) over generic remaps / 502 upstream_server_error.
124
+ if (type === CYBER_POLICY_ERROR_CODE || isCyberPolicyMessage(text)) {
125
+ return { message, type: "invalid_request_error", code: CYBER_POLICY_ERROR_CODE };
126
+ }
127
+ if (
128
+ text.includes("context_length_exceeded") ||
129
+ text.includes("context window") ||
130
+ text.includes("context length") ||
131
+ text.includes("maximum context") ||
132
+ text.includes("too many tokens")
133
+ ) {
134
+ return { message, type: "invalid_request_error", code: "context_length_exceeded" };
135
+ }
136
+ // "Cursor resource limit exceeded" is emitted only for explicit request-size overflow
137
+ // details (isCursorRequestTooLargeDetail in cursor-errors.ts); quota-style resource
138
+ // exhaustion arrives as "Cursor rate limit exceeded" and falls through to 429 below.
139
+ if (text.includes("cursor resource limit exceeded")) {
140
+ return { message, type: "invalid_request_error", code: "tool_catalog_too_large" };
141
+ }
142
+ // The Cursor adapter's classified rate-limit prefix is authoritative: its DETAIL may echo
143
+ // quota wording ("... quota exhausted") that would otherwise hit the insufficient_quota
144
+ // branch below and break the planned retry-with-backoff contract (WP3 review blocker 1).
145
+ if (text.includes("cursor rate limit exceeded")) {
146
+ return { message, type: "rate_limit_error", code: "rate_limit_exceeded" };
147
+ }
148
+ if (
149
+ text.includes("insufficient_quota") ||
150
+ text.includes("exceeded your current quota") ||
151
+ text.includes("quota exhausted") ||
152
+ text.includes("account quota exceeded") ||
153
+ text.includes("monthly quota exceeded") ||
154
+ text.includes("daily quota exceeded")
155
+ ) {
156
+ return { message, type: "insufficient_quota", code: "insufficient_quota" };
157
+ }
158
+ if (
159
+ status === 429 ||
160
+ text.includes("rate limit") ||
161
+ text.includes("rate limited") ||
162
+ text.includes("too many requests") ||
163
+ text.includes("resource_exhausted") ||
164
+ text.includes("resource exhausted") ||
165
+ text.includes("throttlingexception") ||
166
+ text.includes("throttling")
167
+ ) {
168
+ return { message, type: "rate_limit_error", code: "rate_limit_exceeded" };
169
+ }
170
+ if (type === "origin_rejected") {
171
+ return { message, type: "invalid_request_error", code: "origin_rejected" };
172
+ }
173
+ // HTTP 401 and explicit auth failures are authoritative even when provider text
174
+ // also advertises an upgrade or subscription.
175
+ if (
176
+ status === 401 ||
177
+ type === "authentication_error" ||
178
+ isAuthenticationMessage(text)
179
+ ) {
180
+ return { message, type: "authentication_error", code: "invalid_api_key" };
181
+ }
182
+ // Subscription labels are valid only in a known permission context.
183
+ if (
184
+ (status === 403 || type === "permission_error") &&
185
+ isSubscriptionGateMessage(text)
186
+ ) {
187
+ return { message, type: "permission_error", code: "subscription_required" };
188
+ }
189
+ if (
190
+ status === 403 ||
191
+ type === "permission_error" ||
192
+ isPermissionMessage(text)
193
+ ) {
194
+ return { message, type: "permission_error", code: "permission_denied" };
195
+ }
196
+ if (
197
+ status === 503 ||
198
+ text.includes("overloaded") ||
199
+ text.includes("server is busy") ||
200
+ text.includes("temporarily unavailable")
201
+ ) {
202
+ // Codex recognizes "server_is_overloaded" and applies retry-after backoff
203
+ // (responses.rs is_server_overloaded_error); generic "upstream_server_error" is not recognized.
204
+ return { message, type: "server_error", code: "server_is_overloaded" };
205
+ }
206
+ if (
207
+ text.includes("validationexception") ||
208
+ text.includes("invalid request") ||
209
+ text.includes("model unavailable") ||
210
+ text.includes("model not found") ||
211
+ text.includes("unsupported model") ||
212
+ text.includes("profile arn") ||
213
+ text.includes("wrong region") ||
214
+ text.includes("invalid region")
215
+ ) {
216
+ return { message, type: "invalid_request_error", code: "invalid_request_error" };
217
+ }
218
+ if (status >= 500) {
219
+ return { message, type: "server_error", code: "upstream_server_error" };
220
+ }
221
+ if (status === 400 || type === "invalid_request_error") {
222
+ return { message, type: "invalid_request_error", code: "invalid_request_error" };
223
+ }
224
+ return { message, type, code: type || null };
225
+ }
226
+
227
+ /**
228
+ * True when a provider failure should participate in rate-limit / quota health blocking.
229
+ * Reuses {@link classifyError} so generic 429 wording and quota phrases stay aligned.
230
+ */
231
+ export function isRateLimitOrQuotaFailureMessage(message: string): boolean {
232
+ const normalized = String(message ?? "").trim();
233
+ if (!normalized) return false;
234
+ const numericStatus = Number(normalized);
235
+ if (numericStatus === 429 || numericStatus === 402) return true;
236
+ const statusHint = Number.isInteger(numericStatus) && numericStatus > 0 ? numericStatus : 0;
237
+ const classified = classifyError(statusHint, "", normalized);
238
+ if (
239
+ classified.type === "rate_limit_error"
240
+ || classified.code === "rate_limit_exceeded"
241
+ || classified.type === "insufficient_quota"
242
+ || classified.code === "insufficient_quota"
243
+ ) {
244
+ return true;
245
+ }
246
+ // Retained quota cue used by subagent health before classifyError covered it.
247
+ return normalized.toLowerCase().includes("usage limit");
248
+ }
249
+
250
+ /** Best-effort parse of a retry delay embedded in an upstream error message. */
251
+ export function parseRetryAfterFromMessage(message: string): number | undefined {
252
+ const patterns = [
253
+ /try again in (\d+(?:\.\d+)?)\s*s(?:ec(?:ond)?s?)?/i,
254
+ /retry after (\d+(?:\.\d+)?)\s*s(?:ec(?:ond)?s?)?/i,
255
+ /retry[- ]after[:\s]+(\d+)/i,
256
+ ];
257
+ for (const pattern of patterns) {
258
+ const match = message.match(pattern);
259
+ if (!match?.[1]) continue;
260
+ const seconds = Number.parseFloat(match[1]);
261
+ if (Number.isFinite(seconds) && seconds > 0) return Math.ceil(seconds);
262
+ }
263
+ return undefined;
264
+ }
265
+
266
+ /** Infer HTTP status from adapter terminal error text (provider-agnostic keyword matching). */
267
+ export function inferHttpStatusFromAdapterMessage(message: string): number {
268
+ const lower = message.toLowerCase();
269
+ // Client aborts (e.g. mid web-search loop) must not look like upstream 502s in /api/logs.
270
+ if (isClientClosedMessage(lower)) return 499;
271
+ // Codex Transport maps cyber_policy only on HTTP 400 (SSE is code-based).
272
+ if (isCyberPolicyMessage(lower)) return 400;
273
+ // See classifyError: this prefix now only means explicit request-size overflow (400);
274
+ // quota-style Cursor resource exhaustion carries the rate-limit prefix and maps to 429.
275
+ if (lower.includes("cursor resource limit exceeded")) return 400;
276
+ if (
277
+ lower.includes("resource_exhausted") ||
278
+ lower.includes("resource exhausted") ||
279
+ lower.includes("rate limit") ||
280
+ lower.includes("too many requests") ||
281
+ lower.includes("throttling")
282
+ ) return 429;
283
+ // Strong authentication signals win when a message contains mixed auth and
284
+ // subscription/permission wording.
285
+ if (isAuthenticationMessage(lower)) return 401;
286
+ if (isSubscriptionGateMessage(lower) || isPermissionMessage(lower)) return 403;
287
+ if (
288
+ lower.includes("unavailable") ||
289
+ lower.includes("overloaded") ||
290
+ lower.includes("temporarily") ||
291
+ lower.includes("server is busy")
292
+ ) return 503;
293
+ if (
294
+ lower.includes("invalid") ||
295
+ lower.includes("not found") ||
296
+ lower.includes("unsupported") ||
297
+ lower.includes("malformed") ||
298
+ lower.includes("unimplemented")
299
+ ) return 400;
300
+ if (
301
+ lower.includes("timed out") ||
302
+ lower.includes("timeout") ||
303
+ lower.includes("etimedout") ||
304
+ lower.includes("deadline")
305
+ ) return 504;
306
+ return 502;
307
+ }
308
+
309
+ /** Map an adapter terminal error message to HTTP status + classified Codex error payload. */
310
+ export function adapterFailureFromMessage(message: string): { httpStatus: number; error: OcxErrorPayload } {
311
+ const httpStatus = inferHttpStatusFromAdapterMessage(message);
312
+ let finalMessage = message;
313
+ const retryAfterSeconds = parseRetryAfterFromMessage(message);
314
+ if (retryAfterSeconds && !/please try again in /i.test(message)) {
315
+ finalMessage = `${message} Please try again in ${retryAfterSeconds}s.`;
316
+ }
317
+ const errorType = httpStatus === 499
318
+ ? "client_closed_request"
319
+ : httpStatus === 429
320
+ ? "rate_limit_error"
321
+ : httpStatus === 401
322
+ ? "authentication_error"
323
+ : httpStatus === 403
324
+ ? "permission_error"
325
+ : httpStatus === 503 || httpStatus === 504
326
+ ? "server_error"
327
+ : httpStatus === 400
328
+ ? "invalid_request_error"
329
+ : "upstream_error";
330
+ return {
331
+ httpStatus,
332
+ error: classifyError(httpStatus, errorType, finalMessage),
333
+ };
334
+ }
335
+
336
+ /** Map a terminal Responses error object to the HTTP status we record in /api/logs. */
337
+ export function httpStatusFromTerminalError(error: {
338
+ type?: string;
339
+ code?: string | null;
340
+ message?: string;
341
+ } | undefined): number {
342
+ if (!error) return 502;
343
+ if (error.code === "client_closed_request" || error.code === "client_cancelled") return 499;
344
+ if (isCyberPolicyCode(error.code) || (error.message ? isCyberPolicyMessage(error.message) : false)) {
345
+ return 400;
346
+ }
347
+ if (error.type === "rate_limit_error" || error.code === "rate_limit_exceeded") return 429;
348
+ if (error.type === "authentication_error" || error.code === "invalid_api_key") return 401;
349
+ if (
350
+ error.type === "permission_error" ||
351
+ error.code === "permission_denied" ||
352
+ error.code === "subscription_required"
353
+ ) return 403;
354
+ if (error.type === "insufficient_quota" || error.code === "insufficient_quota") return 429;
355
+ if (error.type === "server_error" && error.code === "server_is_overloaded") return 503;
356
+ // Client-closed messages often arrive as invalid_request_error after classifyError; check message
357
+ // before treating every invalid_request_error as HTTP 400.
358
+ const message = error.message ?? "";
359
+ if (message && isClientClosedMessage(message)) return 499;
360
+ if (error.type === "invalid_request_error") return 400;
361
+ if (error.type === "proxy_error") return 500;
362
+ if (message) return inferHttpStatusFromAdapterMessage(message);
363
+ return 502;
364
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * `application/vnd.amazon.eventstream` decoder.
3
+ *
4
+ * Ported from jawcode `packages/ai/src/providers/aws-eventstream.ts` (verbatim logic).
5
+ * Foundational dependency for AWS-eventstream providers: kiro (CodeWhisperer
6
+ * GenerateAssistantResponse) and amazon-bedrock (Converse). Self-contained — only
7
+ * uses Buffer / DataView / TextDecoder (Bun + Node compatible).
8
+ *
9
+ * Wire format (all integers big-endian):
10
+ *
11
+ * [total length u32]
12
+ * [headers length u32]
13
+ * [prelude CRC32 u32] <- CRC over the first 8 bytes
14
+ * [headers headers_length]
15
+ * [payload total_length - headers_length - 16]
16
+ * [message CRC32 u32] <- CRC over the entire message minus the trailing 4 bytes
17
+ *
18
+ * Headers: a sequence of `[name_len u8][name utf8][value_type u8][value …]`.
19
+ */
20
+
21
+ const PRELUDE_LEN = 8;
22
+ const PRELUDE_CRC_LEN = 4;
23
+ const MESSAGE_CRC_LEN = 4;
24
+ const HEADER_BLOCK_OFFSET = PRELUDE_LEN + PRELUDE_CRC_LEN;
25
+ const MIN_MESSAGE_LEN = HEADER_BLOCK_OFFSET + MESSAGE_CRC_LEN;
26
+ const MAX_MESSAGE_LEN = 16 * 1024 * 1024;
27
+ const MAX_HEADERS_LEN = 128 * 1024;
28
+
29
+ export interface EventStreamMessage {
30
+ /** Header casing is preserved verbatim (e.g. `:event-type`, `:message-type`). */
31
+ headers: Record<string, string>;
32
+ payload: Uint8Array;
33
+ }
34
+
35
+ /** CRC32 (IEEE / zlib polynomial 0xEDB88320), matches `@aws-crypto/crc32`. */
36
+ const CRC_TABLE = (() => {
37
+ const t = new Uint32Array(256);
38
+ for (let i = 0; i < 256; i++) {
39
+ let c = i;
40
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
41
+ t[i] = c >>> 0;
42
+ }
43
+ return t;
44
+ })();
45
+
46
+ export function crc32(bytes: Uint8Array, seed = 0): number {
47
+ let c = (seed ^ 0xffffffff) >>> 0;
48
+ for (let i = 0; i < bytes.length; i++) c = (CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8)) >>> 0;
49
+ return (c ^ 0xffffffff) >>> 0;
50
+ }
51
+
52
+ /**
53
+ * Decode a single, fully buffered eventstream message. Throws if the framing is
54
+ * malformed or either CRC mismatches.
55
+ */
56
+ export function decodeMessage(frame: Uint8Array): EventStreamMessage {
57
+ if (frame.length < MIN_MESSAGE_LEN) throw new Error("eventstream: frame too short");
58
+ const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
59
+ const total = view.getUint32(0, false);
60
+ if (total !== frame.length) throw new Error(`eventstream: framed length ${total} != buffer ${frame.length}`);
61
+ if (total > MAX_MESSAGE_LEN) throw new Error(`eventstream: total length ${total} exceeds maximum`);
62
+ const headersLen = view.getUint32(4, false);
63
+ const preludeCrc = view.getUint32(8, false);
64
+ const computedPreludeCrc = crc32(frame.subarray(0, PRELUDE_LEN));
65
+ if (computedPreludeCrc !== preludeCrc) throw new Error("eventstream: prelude CRC mismatch");
66
+ if (headersLen > MAX_HEADERS_LEN) throw new Error(`eventstream: headers length ${headersLen} exceeds maximum`);
67
+ if (headersLen > total - MIN_MESSAGE_LEN) throw new Error("eventstream: headers length exceeds frame payload");
68
+ const msgCrc = view.getUint32(total - MESSAGE_CRC_LEN, false);
69
+ const computedMsgCrc = crc32(frame.subarray(0, total - MESSAGE_CRC_LEN));
70
+ if (computedMsgCrc !== msgCrc) throw new Error("eventstream: message CRC mismatch");
71
+
72
+ const headersBytes = frame.subarray(HEADER_BLOCK_OFFSET, HEADER_BLOCK_OFFSET + headersLen);
73
+ const payload = frame.subarray(HEADER_BLOCK_OFFSET + headersLen, total - MESSAGE_CRC_LEN);
74
+ return { headers: parseHeaders(headersBytes), payload };
75
+ }
76
+
77
+ function parseHeaders(buf: Uint8Array): Record<string, string> {
78
+ const out: Record<string, string> = {};
79
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
80
+ const decoder = new TextDecoder();
81
+ let p = 0;
82
+ const need = (n: number, label: string) => {
83
+ if (p + n > buf.length) throw new Error(`eventstream: truncated header ${label}`);
84
+ };
85
+ while (p < buf.length) {
86
+ need(1, "name length");
87
+ const nameLen = view.getUint8(p);
88
+ p += 1;
89
+ need(nameLen, "name");
90
+ const name = decoder.decode(buf.subarray(p, p + nameLen));
91
+ p += nameLen;
92
+ need(1, "type");
93
+ const type = view.getUint8(p);
94
+ p += 1;
95
+ switch (type) {
96
+ case 0: // bool true
97
+ out[name] = "true";
98
+ break;
99
+ case 1: // bool false
100
+ out[name] = "false";
101
+ break;
102
+ case 2: // byte
103
+ need(1, "byte value");
104
+ out[name] = String(view.getInt8(p));
105
+ p += 1;
106
+ break;
107
+ case 3: // short
108
+ need(2, "short value");
109
+ out[name] = String(view.getInt16(p, false));
110
+ p += 2;
111
+ break;
112
+ case 4: // integer
113
+ need(4, "integer value");
114
+ out[name] = String(view.getInt32(p, false));
115
+ p += 4;
116
+ break;
117
+ case 5: // long — decimal string to avoid precision loss
118
+ need(8, "long value");
119
+ out[name] = bigIntFromBytes(buf.subarray(p, p + 8)).toString();
120
+ p += 8;
121
+ break;
122
+ case 6: {
123
+ // byte array — base64 for safe transport
124
+ need(2, "byte-array length");
125
+ const len = view.getUint16(p, false);
126
+ p += 2;
127
+ need(len, "byte-array value");
128
+ out[name] = Buffer.from(buf.buffer, buf.byteOffset + p, len).toString("base64");
129
+ p += len;
130
+ break;
131
+ }
132
+ case 7: {
133
+ // string
134
+ need(2, "string length");
135
+ const len = view.getUint16(p, false);
136
+ p += 2;
137
+ need(len, "string value");
138
+ out[name] = decoder.decode(buf.subarray(p, p + len));
139
+ p += len;
140
+ break;
141
+ }
142
+ case 8: // timestamp (ms since epoch as i64)
143
+ need(8, "timestamp value");
144
+ out[name] = new Date(Number(bigIntFromBytes(buf.subarray(p, p + 8)))).toISOString();
145
+ p += 8;
146
+ break;
147
+ case 9: {
148
+ // uuid
149
+ need(16, "uuid value");
150
+ const u = buf.subarray(p, p + 16);
151
+ const hex: string[] = [];
152
+ for (let i = 0; i < 16; i++) hex.push(u[i].toString(16).padStart(2, "0"));
153
+ out[name] =
154
+ `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
155
+ p += 16;
156
+ break;
157
+ }
158
+ default:
159
+ throw new Error(`eventstream: unknown header value type ${type}`);
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+
165
+ function bigIntFromBytes(b: Uint8Array): bigint {
166
+ let v = 0n;
167
+ for (let i = 0; i < b.length; i++) v = (v << 8n) | BigInt(b[i]);
168
+ // sign-extend (two's complement)
169
+ if (b.length === 8 && b[0] & 0x80) v -= 1n << 64n;
170
+ return v;
171
+ }
172
+
173
+ /**
174
+ * Async generator that consumes a `ReadableStream<Uint8Array>` (a fetch response
175
+ * body) and yields fully-framed messages, handling arbitrary chunk boundaries.
176
+ */
177
+ export async function* decodeEventStream(source: ReadableStream<Uint8Array>): AsyncGenerator<EventStreamMessage> {
178
+ const reader = source.getReader();
179
+ let buf: Uint8Array = new Uint8Array(0);
180
+ try {
181
+ while (true) {
182
+ const { value, done } = await reader.read();
183
+ if (value && value.length > 0) buf = buf.length === 0 ? value : Buffer.concat([buf, value]);
184
+ let offset = 0;
185
+ while (buf.length - offset >= 4) {
186
+ const dv = new DataView(buf.buffer, buf.byteOffset + offset, buf.length - offset);
187
+ const total = dv.getUint32(0, false);
188
+ if (total < MIN_MESSAGE_LEN) throw new Error(`eventstream: total length ${total} below minimum`);
189
+ if (total > MAX_MESSAGE_LEN) throw new Error(`eventstream: total length ${total} exceeds maximum`);
190
+ if (buf.length - offset >= HEADER_BLOCK_OFFSET) {
191
+ const headersLen = dv.getUint32(4, false);
192
+ const preludeCrc = dv.getUint32(8, false);
193
+ const computedPreludeCrc = crc32(buf.subarray(offset, offset + PRELUDE_LEN));
194
+ if (computedPreludeCrc !== preludeCrc) throw new Error("eventstream: prelude CRC mismatch");
195
+ if (headersLen > MAX_HEADERS_LEN) throw new Error(`eventstream: headers length ${headersLen} exceeds maximum`);
196
+ }
197
+ if (buf.length - offset < total) break;
198
+ const frame = buf.subarray(offset, offset + total);
199
+ yield decodeMessage(frame);
200
+ offset += total;
201
+ }
202
+ if (offset > 0) buf = buf.slice(offset);
203
+ if (buf.length > MAX_MESSAGE_LEN) throw new Error(`eventstream: buffered frame exceeds maximum ${MAX_MESSAGE_LEN}`);
204
+ if (done) break;
205
+ }
206
+ if (buf.length > 0) throw new Error("eventstream: truncated message at end of stream");
207
+ } finally {
208
+ // Early termination (consumer break/return, turn abort, or an HTTP/2 mid-body reset) can leave
209
+ // an in-flight `reader.read()` pending when this generator's `finally` runs. Releasing the lock
210
+ // does NOT settle that orphaned read — Bun then surfaces it as an off-path
211
+ // `unhandledRejection: TypeError: null is not an object` that no caller try/catch can intercept.
212
+ // Cancel first (settles the pending read + closes the body), then release. On a clean `done`
213
+ // finish the read is already settled, so cancel() is a harmless no-op.
214
+ try {
215
+ await reader.cancel();
216
+ } catch {
217
+ /* body already errored/closed — nothing to cancel */
218
+ }
219
+ try {
220
+ reader.releaseLock();
221
+ } catch {
222
+ /* lock already released by cancel() on some runtimes */
223
+ }
224
+ }
225
+ }
226
+
227
+ /** Build a single eventstream frame (string headers only) — used by tests and fixtures. */
228
+ export function encodeMessage(headers: Record<string, string>, payload: Uint8Array): Uint8Array {
229
+ const enc = new TextEncoder();
230
+ const headerParts: Uint8Array[] = [];
231
+ for (const [name, value] of Object.entries(headers)) {
232
+ const nameBytes = enc.encode(name);
233
+ const valueBytes = enc.encode(value);
234
+ const head = new Uint8Array(1 + nameBytes.length + 1 + 2);
235
+ const hv = new DataView(head.buffer);
236
+ hv.setUint8(0, nameBytes.length);
237
+ head.set(nameBytes, 1);
238
+ hv.setUint8(1 + nameBytes.length, 7); // string type
239
+ hv.setUint16(1 + nameBytes.length + 1, valueBytes.length, false);
240
+ headerParts.push(head, valueBytes);
241
+ }
242
+ const headersBytes = Buffer.concat(headerParts.map(p => Buffer.from(p)));
243
+ const total = HEADER_BLOCK_OFFSET + headersBytes.length + payload.length + MESSAGE_CRC_LEN;
244
+ const frame = new Uint8Array(total);
245
+ const dv = new DataView(frame.buffer);
246
+ dv.setUint32(0, total, false);
247
+ dv.setUint32(4, headersBytes.length, false);
248
+ dv.setUint32(8, crc32(frame.subarray(0, PRELUDE_LEN)), false);
249
+ frame.set(headersBytes, HEADER_BLOCK_OFFSET);
250
+ frame.set(payload, HEADER_BLOCK_OFFSET + headersBytes.length);
251
+ dv.setUint32(total - MESSAGE_CRC_LEN, crc32(frame.subarray(0, total - MESSAGE_CRC_LEN)), false);
252
+ return frame;
253
+ }