@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,765 @@
1
+ /**
2
+ * Chat Completions outbound: internal /v1/responses output -> OpenAI Chat Completions shapes.
3
+ *
4
+ * Wire contract for GitHub Copilot App / OpenAI-compatible clients:
5
+ * - Streaming: `data: {choices:[{delta:...}]}` frames ending with `data: [DONE]`
6
+ * - Non-streaming: `{ id, object:"chat.completion", choices:[{message}], usage }`
7
+ */
8
+ type Rec = Record<string, unknown>;
9
+
10
+ import { decodeServerSentEvents } from "../lib/sse-decoder";
11
+ import { isTranslatorBudgetExceededError, type TranslatorBudget } from "../lib/translator-budget";
12
+ import { classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, isCyberPolicyMessage } from "../lib/errors";
13
+
14
+ function isRec(v: unknown): v is Rec {
15
+ return !!v && typeof v === "object" && !Array.isArray(v);
16
+ }
17
+
18
+ function uuid(): string {
19
+ return crypto.randomUUID().replace(/-/g, "");
20
+ }
21
+
22
+ function completionId(): string {
23
+ return `chatcmpl-${uuid().slice(0, 24)}`;
24
+ }
25
+
26
+ /** Responses usage (inclusive input_tokens) -> Chat Completions usage. */
27
+ export function chatCompletionsUsage(usage: unknown): Rec {
28
+ const u = isRec(usage) ? usage : {};
29
+ const details = isRec(u.input_tokens_details) ? u.input_tokens_details : {};
30
+ const prompt = typeof u.input_tokens === "number" ? u.input_tokens : 0;
31
+ const completion = typeof u.output_tokens === "number" ? u.output_tokens : 0;
32
+ const cached = typeof details.cached_tokens === "number" ? details.cached_tokens : 0;
33
+ const out: Rec = {
34
+ prompt_tokens: prompt,
35
+ completion_tokens: completion,
36
+ total_tokens: prompt + completion,
37
+ };
38
+ // Detail objects are always emitted (zero defaults) so strict OpenAI-compatible
39
+ // clients that require them (see responsesUsage in src/bridge.ts) never fail on
40
+ // routed providers that report no cache/reasoning numbers.
41
+ out.prompt_tokens_details = { cached_tokens: cached };
42
+ const outDetails = isRec(u.output_tokens_details) ? u.output_tokens_details : {};
43
+ const reasoning = typeof outDetails.reasoning_tokens === "number" ? outDetails.reasoning_tokens : 0;
44
+ out.completion_tokens_details = { reasoning_tokens: reasoning };
45
+ return out;
46
+ }
47
+
48
+ export function chatCompletionsErrorBody(
49
+ status: number,
50
+ message: string,
51
+ type = "invalid_request_error",
52
+ code?: string | null,
53
+ ): Rec {
54
+ if (isCyberPolicyCode(code) || isCyberPolicyMessage(message)) {
55
+ return {
56
+ error: {
57
+ message,
58
+ type: "invalid_request_error",
59
+ param: null,
60
+ code: CYBER_POLICY_ERROR_CODE,
61
+ },
62
+ };
63
+ }
64
+ return {
65
+ error: {
66
+ message,
67
+ type,
68
+ param: null,
69
+ code: code !== undefined
70
+ ? code
71
+ : status === 401 ? "invalid_api_key"
72
+ : status === 404 ? "model_not_found"
73
+ : status === 429 ? "rate_limit_exceeded"
74
+ : null,
75
+ },
76
+ };
77
+ }
78
+
79
+ export function chatCompletionsErrorResponse(
80
+ status: number,
81
+ message: string,
82
+ type?: string,
83
+ code?: string | null,
84
+ ): Response {
85
+ const body = chatCompletionsErrorBody(status, message, type, code);
86
+ const err = body.error as { code?: string | null };
87
+ const finalStatus = err.code === CYBER_POLICY_ERROR_CODE ? 400 : status;
88
+ return new Response(JSON.stringify(body), {
89
+ status: finalStatus,
90
+ headers: { "Content-Type": "application/json" },
91
+ });
92
+ }
93
+
94
+ /** Thrown when a Chat Completions SSE stream ends in a typed failure/truncation. */
95
+ export class ChatCompletionsStreamError extends Error {
96
+ readonly status: number;
97
+ readonly type: string;
98
+ readonly code: string | null;
99
+
100
+ constructor(message: string, options: { status?: number; type?: string; code?: string | null } = {}) {
101
+ super(message);
102
+ this.name = "ChatCompletionsStreamError";
103
+ this.status = options.status ?? 502;
104
+ this.type = options.type ?? "server_error";
105
+ this.code = options.code ?? null;
106
+ }
107
+ }
108
+
109
+ export function isChatCompletionsStreamError(err: unknown): err is ChatCompletionsStreamError {
110
+ return err instanceof ChatCompletionsStreamError;
111
+ }
112
+
113
+ function streamErrorStatus(message: string): number {
114
+ const lower = message.toLowerCase();
115
+ if (isCyberPolicyMessage(lower)) return 400;
116
+ if (lower.includes("truncated")) return 502;
117
+ if (lower.includes("rate") || lower.includes("429")) return 429;
118
+ if (lower.includes("unauthor") || lower.includes("401") || lower.includes("api key")) return 401;
119
+ if (lower.includes("not found") || lower.includes("404")) return 404;
120
+ if (lower.includes("invalid") || lower.includes("400")) return 400;
121
+ return 502;
122
+ }
123
+
124
+ function dataFrame(payload: Rec | "[DONE]"): string {
125
+ if (payload === "[DONE]") return "data: [DONE]\n\n";
126
+ return `data: ${JSON.stringify(payload)}\n\n`;
127
+ }
128
+
129
+ function chunkBase(id: string, model: string, created: number): Rec {
130
+ return {
131
+ id,
132
+ object: "chat.completion.chunk",
133
+ created,
134
+ model,
135
+ choices: [],
136
+ };
137
+ }
138
+
139
+ function appendedUtf8Bytes(previous: string, previousBytes: number, fragment: string): number {
140
+ let nextBytes = previousBytes + Buffer.byteLength(fragment);
141
+ const previousLast = previous.charCodeAt(previous.length - 1);
142
+ const fragmentFirst = fragment.charCodeAt(0);
143
+ if (previousLast >= 0xd800 && previousLast <= 0xdbff
144
+ && fragmentFirst >= 0xdc00 && fragmentFirst <= 0xdfff) {
145
+ // Buffer.byteLength() replaces each isolated surrogate with three bytes, while the joined
146
+ // pair is one four-byte scalar. Preserve full-string sizing without re-encoding the prefix.
147
+ nextBytes -= 2;
148
+ }
149
+ return nextBytes;
150
+ }
151
+
152
+ /**
153
+ * Streaming: Responses SSE bytes -> Chat Completions SSE bytes.
154
+ */
155
+ export function responsesSseToChatCompletionsSse(
156
+ upstream: ReadableStream<Uint8Array>,
157
+ model: string,
158
+ opts: { translatorBudget: TranslatorBudget },
159
+ ): ReadableStream<Uint8Array> {
160
+ const translatorBudget = opts.translatorBudget;
161
+ const encoder = new TextEncoder();
162
+ let terminated = false;
163
+ let cancelled = false;
164
+ let started = false;
165
+ let sawToolUse = false;
166
+ const id = completionId();
167
+ const created = Math.floor(Date.now() / 1000);
168
+ // tool call_id -> streaming index (OpenAI requires stable indices per tool call)
169
+ const toolIndexByCallId = new Map<string, number>();
170
+ const toolIndexByItemId = new Map<string, number>();
171
+ const toolCallIdByIndex = new Map<number, string>();
172
+ const toolNameByIndex = new Map<number, string>();
173
+ const toolArgumentsByIndex = new Map<number, string>();
174
+ const toolArgumentBytesByIndex = new Map<number, number>();
175
+ const emittedToolIndexes = new Set<number>();
176
+ let nextToolIndex = 0;
177
+ let sseIterator: AsyncGenerator<{ event?: string; data: string }> | undefined;
178
+ const upstreamAbort = new AbortController();
179
+ let controller: ReadableStreamDefaultController<Uint8Array>;
180
+ let failed = false;
181
+ let emittedFrames = 0;
182
+ let stepping = false;
183
+ let decoderStarted = false;
184
+ const queuedLiveFrameBytes: number[] = [];
185
+ const enqueueLiveFrame = (frame: Uint8Array) => {
186
+ const reservation = translatorBudget.reserveTransient(frame.byteLength, { kind: "live_transient" });
187
+ controller.enqueue(frame);
188
+ reservation.commitRetained();
189
+ queuedLiveFrameBytes.push(frame.byteLength);
190
+ };
191
+ const releaseDeliveredFrame = () => {
192
+ const bytes = queuedLiveFrameBytes.shift();
193
+ if (bytes !== undefined) translatorBudget.releaseRetained(bytes, { kind: "live_transient" });
194
+ };
195
+ const closeToolCalls = () => {
196
+ for (const callId of toolIndexByCallId.keys()) translatorBudget.closeCall(callId);
197
+ };
198
+ const replaceToolArguments = (toolIndex: number, next: string) => {
199
+ const previous = toolArgumentsByIndex.get(toolIndex) ?? "";
200
+ if (previous === next && toolArgumentsByIndex.has(toolIndex)) return;
201
+ const previousBytes = toolArgumentBytesByIndex.get(toolIndex) ?? 0;
202
+ const nextBytes = Buffer.byteLength(next);
203
+ const callId = toolCallIdByIndex.get(toolIndex);
204
+ const scope = { kind: "tool_args" as const, ...(callId ? { callId } : {}) };
205
+ const reservation = translatorBudget.reserveTransient(nextBytes, scope);
206
+ toolArgumentsByIndex.set(toolIndex, next);
207
+ toolArgumentBytesByIndex.set(toolIndex, nextBytes);
208
+ reservation.commitRetained();
209
+ translatorBudget.releaseRetained(previousBytes, scope);
210
+ };
211
+ const appendToolArguments = (toolIndex: number, fragment: string) => {
212
+ const previous = toolArgumentsByIndex.get(toolIndex) ?? "";
213
+ const previousBytes = toolArgumentBytesByIndex.get(toolIndex) ?? 0;
214
+ const nextBytes = appendedUtf8Bytes(previous, previousBytes, fragment);
215
+ const callId = toolCallIdByIndex.get(toolIndex);
216
+ const scope = { kind: "tool_args" as const, ...(callId ? { callId } : {}) };
217
+ const reservation = translatorBudget.reserveTransient(nextBytes, scope);
218
+ try {
219
+ const next = previous + fragment;
220
+ toolArgumentsByIndex.set(toolIndex, next);
221
+ toolArgumentBytesByIndex.set(toolIndex, nextBytes);
222
+ reservation.commitRetained();
223
+ translatorBudget.releaseRetained(previousBytes, scope);
224
+ } catch (error) {
225
+ reservation.release();
226
+ throw error;
227
+ }
228
+ };
229
+ const emit = (payload: Rec | "[DONE]") => {
230
+ if (failed) return;
231
+ enqueueLiveFrame(encoder.encode(dataFrame(payload)));
232
+ emittedFrames++;
233
+ };
234
+ const ensureRole = () => {
235
+ if (started) return;
236
+ started = true;
237
+ const frame = chunkBase(id, model, created);
238
+ frame.choices = [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }];
239
+ emit(frame);
240
+ };
241
+ const emitContent = (text: string) => {
242
+ if (!text) return;
243
+ ensureRole();
244
+ const frame = chunkBase(id, model, created);
245
+ frame.choices = [{ index: 0, delta: { content: text }, finish_reason: null }];
246
+ emit(frame);
247
+ };
248
+ const emitReasoning = (text: string) => {
249
+ if (!text) return;
250
+ ensureRole();
251
+ // Many OpenAI-compatible clients accept reasoning_content; harmless if ignored.
252
+ const frame = chunkBase(id, model, created);
253
+ frame.choices = [{ index: 0, delta: { reasoning_content: text }, finish_reason: null }];
254
+ emit(frame);
255
+ };
256
+ const resolveFinalArguments = (candidate: unknown, streamed: string) => {
257
+ if (typeof candidate !== "string") return streamed;
258
+ // Some compatible providers send an empty final snapshot. Do not let that erase
259
+ // non-empty streamed arguments; genuinely empty calls have no buffered content.
260
+ return candidate.length > 0 || streamed.length === 0 ? candidate : streamed;
261
+ };
262
+ const emitToolCall = (toolIndex: number, callId: string, name: string, args: string) => {
263
+ if (!callId || emittedToolIndexes.has(toolIndex)) return;
264
+ emittedToolIndexes.add(toolIndex);
265
+ ensureRole();
266
+ const frame = chunkBase(id, model, created);
267
+ frame.choices = [{
268
+ index: 0,
269
+ delta: {
270
+ tool_calls: [{
271
+ index: toolIndex,
272
+ id: callId,
273
+ type: "function",
274
+ function: { name, arguments: args },
275
+ }],
276
+ },
277
+ finish_reason: null,
278
+ }];
279
+ emit(frame);
280
+ };
281
+ const flushPendingToolCalls = () => {
282
+ const pending = [...toolCallIdByIndex.entries()].sort(([a], [b]) => a - b);
283
+ for (const [toolIndex, callId] of pending) {
284
+ emitToolCall(
285
+ toolIndex,
286
+ callId,
287
+ toolNameByIndex.get(toolIndex) ?? "",
288
+ toolArgumentsByIndex.get(toolIndex) ?? "",
289
+ );
290
+ }
291
+ };
292
+ const finish = (finishReason: string, usage: unknown) => {
293
+ if (terminated) return;
294
+ // A valid completed/incomplete terminal frame may arrive without output_item.done.
295
+ // Preserve any known tool call before emitting its finish reason.
296
+ flushPendingToolCalls();
297
+ terminated = true;
298
+ ensureRole();
299
+ const frame = chunkBase(id, model, created);
300
+ frame.choices = [{ index: 0, delta: {}, finish_reason: finishReason }];
301
+ if (usage) frame.usage = chatCompletionsUsage(usage);
302
+ emit(frame);
303
+ emit("[DONE]");
304
+ };
305
+ const fail = (message: string, details?: { code?: string | null; type?: string; status?: number }) => {
306
+ if (terminated) return;
307
+ terminated = true;
308
+ failed = true;
309
+ // OpenAI-compatible clients need a real error event, not a success completion
310
+ // that embeds `[error] ...` text followed by a clean [DONE].
311
+ // Deliver the error frame then close the stream abnormally (no [DONE]).
312
+ // Do not controller.error() — that can drop already-enqueued bytes from consumers
313
+ // like response.text().
314
+ const statusHint = details?.status ?? streamErrorStatus(message);
315
+ const classified = classifyError(statusHint, details?.type ?? "upstream_error", message);
316
+ const translatorOverflow = details?.code === "translation_buffer_limit";
317
+ if (translatorOverflow) {
318
+ upstreamAbort.abort(new Error("upstream translation buffer exceeded the safe limit"));
319
+ closeToolCalls();
320
+ try { void sseIterator?.return(undefined).catch(() => {}); } catch { /* already closed */ }
321
+ classified.code = "translation_buffer_limit";
322
+ classified.type = "invalid_request_error";
323
+ } else if (isCyberPolicyCode(details?.code) || classified.code === CYBER_POLICY_ERROR_CODE) {
324
+ classified.code = CYBER_POLICY_ERROR_CODE;
325
+ classified.type = "invalid_request_error";
326
+ } else if (details?.code !== undefined && details.code !== null && !classified.code) {
327
+ classified.code = details.code;
328
+ }
329
+ try {
330
+ const frame = encoder.encode(dataFrame({
331
+ error: {
332
+ message: classified.message,
333
+ type: classified.type,
334
+ param: null,
335
+ code: classified.code,
336
+ },
337
+ }));
338
+ // The budget is already exhausted. This bounded emergency frame is the sole
339
+ // typed overflow closure and therefore cannot reserve from that budget again.
340
+ if (translatorOverflow) controller.enqueue(frame);
341
+ else enqueueLiveFrame(frame);
342
+ emittedFrames++;
343
+ } catch {
344
+ /* controller may already be closed */
345
+ }
346
+ try { controller.close(); } catch { /* already closed */ }
347
+ };
348
+
349
+ const handleFrame = (eventName: string, data: Rec) => {
350
+ switch (eventName) {
351
+ case "response.created":
352
+ case "response.heartbeat":
353
+ ensureRole();
354
+ break;
355
+ case "response.output_text.delta": {
356
+ if (typeof data.delta === "string") emitContent(data.delta);
357
+ break;
358
+ }
359
+ case "response.reasoning_summary_text.delta":
360
+ case "response.reasoning_text.delta": {
361
+ if (typeof data.delta === "string") emitReasoning(data.delta);
362
+ break;
363
+ }
364
+ case "response.output_item.added": {
365
+ const item = isRec(data.item) ? data.item : null;
366
+ if (!item || item.type !== "function_call") break;
367
+ ensureRole();
368
+ sawToolUse = true;
369
+ const callId = typeof item.call_id === "string" ? item.call_id : `call_${uuid().slice(0, 16)}`;
370
+ const name = typeof item.name === "string" ? item.name : "";
371
+ let toolIndex = toolIndexByCallId.get(callId);
372
+ if (toolIndex === undefined) {
373
+ toolIndex = nextToolIndex++;
374
+ toolIndexByCallId.set(callId, toolIndex);
375
+ }
376
+ toolCallIdByIndex.set(toolIndex, callId);
377
+ if (typeof item.id === "string") toolIndexByItemId.set(item.id, toolIndex);
378
+ if (name) toolNameByIndex.set(toolIndex, name);
379
+ translatorBudget.openCall(callId);
380
+ if (!toolArgumentsByIndex.has(toolIndex)) replaceToolArguments(
381
+ toolIndex,
382
+ typeof item.arguments === "string" ? item.arguments : "",
383
+ );
384
+ break;
385
+ }
386
+ case "response.function_call_arguments.delta": {
387
+ if (typeof data.delta !== "string" || data.delta.length === 0) break;
388
+ const itemId = typeof data.item_id === "string" ? data.item_id : undefined;
389
+ const toolIndex = (itemId ? toolIndexByItemId.get(itemId) : undefined)
390
+ ?? (nextToolIndex > 0 ? nextToolIndex - 1 : 0);
391
+ ensureRole();
392
+ appendToolArguments(toolIndex, data.delta);
393
+ break;
394
+ }
395
+ case "response.function_call_arguments.done": {
396
+ const itemId = typeof data.item_id === "string" ? data.item_id : undefined;
397
+ const toolIndex = itemId ? toolIndexByItemId.get(itemId) : undefined;
398
+ if (toolIndex === undefined) break;
399
+ sawToolUse = true;
400
+ const name = typeof data.name === "string" ? data.name : "";
401
+ if (name) toolNameByIndex.set(toolIndex, name);
402
+ const streamedArgs = toolArgumentsByIndex.get(toolIndex) ?? "";
403
+ replaceToolArguments(toolIndex, resolveFinalArguments(data.arguments, streamedArgs));
404
+ break;
405
+ }
406
+ case "response.output_item.done": {
407
+ const item = isRec(data.item) ? data.item : null;
408
+ if (!item) break;
409
+ if (item.type === "function_call") {
410
+ sawToolUse = true;
411
+ const callId = typeof item.call_id === "string" ? item.call_id : "";
412
+ const name = typeof item.name === "string" ? item.name : "";
413
+ if (!callId) break;
414
+ const itemId = typeof item.id === "string" ? item.id : undefined;
415
+ const existingIndex = toolIndexByCallId.get(callId)
416
+ ?? (itemId ? toolIndexByItemId.get(itemId) : undefined);
417
+ const toolIndex = existingIndex ?? nextToolIndex++;
418
+ toolIndexByCallId.set(callId, toolIndex);
419
+ toolCallIdByIndex.set(toolIndex, callId);
420
+ translatorBudget.openCall(callId);
421
+ if (itemId) toolIndexByItemId.set(itemId, toolIndex);
422
+ if (name) toolNameByIndex.set(toolIndex, name);
423
+ const finalName = name || toolNameByIndex.get(toolIndex) || "";
424
+ const streamedArgs = toolArgumentsByIndex.get(toolIndex) ?? "";
425
+ // A Responses stream can carry incremental/finalized argument events plus an
426
+ // authoritative output_item.done snapshot. Chat Completions tool-call fields are
427
+ // append-only deltas, so forwarding multiple representations corrupts clients that
428
+ // accumulate them. Emit one complete call here; finish() flushes any item whose
429
+ // done event was omitted, and replace-style clients still get a complete object.
430
+ const args = resolveFinalArguments(item.arguments, streamedArgs);
431
+ replaceToolArguments(toolIndex, args);
432
+ emitToolCall(toolIndex, callId, finalName, args);
433
+ }
434
+ break;
435
+ }
436
+ case "response.completed": {
437
+ const response = isRec(data.response) ? data.response : {};
438
+ finish(sawToolUse ? "tool_calls" : "stop", response.usage);
439
+ break;
440
+ }
441
+ case "response.incomplete": {
442
+ const response = isRec(data.response) ? data.response : {};
443
+ const details = isRec(response.incomplete_details) ? response.incomplete_details : {};
444
+ const reason = details.reason === "max_output_tokens" ? "length"
445
+ : details.reason === "content_filter" ? "content_filter"
446
+ : undefined;
447
+ if (reason !== undefined) {
448
+ // Truthful OpenAI-compatible finish reasons: the turn ended, just early.
449
+ finish(reason, response.usage);
450
+ } else {
451
+ // upstream_stall_timeout / adapter_eof / proxy-synthesized incompletes are
452
+ // failures, not early finishes: emit an error frame and close WITHOUT
453
+ // [DONE] instead of a success-looking stop/tool_calls + [DONE].
454
+ const why = typeof details.reason === "string" ? details.reason : "unknown";
455
+ const message = typeof details.message === "string" && details.message.length > 0
456
+ ? details.message
457
+ : `upstream stream ended early (${why})`;
458
+ fail(message);
459
+ }
460
+ break;
461
+ }
462
+ case "response.failed": {
463
+ const response = isRec(data.response) ? data.response : {};
464
+ const error = isRec(response.error) ? response.error : {};
465
+ const message = typeof error.message === "string" ? error.message : "upstream request failed";
466
+ const code = typeof error.code === "string" ? error.code : null;
467
+ const type = typeof error.type === "string" ? error.type : undefined;
468
+ fail(message, {
469
+ code,
470
+ ...(code === "translation_buffer_limit"
471
+ ? { status: 413, type: "invalid_request_error" }
472
+ : { type, ...(code === CYBER_POLICY_ERROR_CODE ? { status: 400 } : {}) }),
473
+ });
474
+ break;
475
+ }
476
+ default:
477
+ break;
478
+ }
479
+ };
480
+
481
+ // Shared spec-shaped SSE decoder: handles CRLF framing, arbitrary chunk boundaries,
482
+ // multi-line data, and a terminal event without a trailing blank line (Sol audit
483
+ // blocker 3 — the hand-rolled "\n\n" splitter misreported those as truncation).
484
+ sseIterator = decodeServerSentEvents(upstream, { signal: upstreamAbort.signal, translatorBudget });
485
+ const step = async () => {
486
+ if (stepping || cancelled) return;
487
+ stepping = true;
488
+ const emittedAtStart = emittedFrames;
489
+ try {
490
+ while (!cancelled && emittedFrames === emittedAtStart) {
491
+ decoderStarted = true;
492
+ const next = await sseIterator!.next();
493
+ if (next.done) {
494
+ if (!cancelled && !terminated) {
495
+ fail("upstream stream ended before a terminal frame (truncated response)");
496
+ }
497
+ // Success path: close after [DONE]. Failure path closes inside fail().
498
+ if (!cancelled && terminated && !failed) {
499
+ try { controller.close(); } catch { /* already closed */ }
500
+ }
501
+ break;
502
+ }
503
+ const record = next.value;
504
+ const eventName = record.event ?? "";
505
+ const dataLine = record.data.trim();
506
+ if (!eventName || !dataLine) continue;
507
+ let data: unknown;
508
+ try { data = JSON.parse(dataLine); } catch { continue; }
509
+ if (!isRec(data)) continue;
510
+ if (terminated) continue;
511
+ handleFrame(eventName, data);
512
+ }
513
+ } catch (err) {
514
+ if (isTranslatorBudgetExceededError(err)) {
515
+ upstreamAbort.abort(err);
516
+ closeToolCalls();
517
+ fail(err.message, { status: 413, type: "invalid_request_error", code: err.code });
518
+ } else {
519
+ fail(err instanceof Error ? err.message : String(err));
520
+ }
521
+ } finally {
522
+ stepping = false;
523
+ }
524
+ };
525
+
526
+ return new ReadableStream<Uint8Array>({
527
+ start(streamController) {
528
+ controller = streamController;
529
+ // Default HWM=1: one Responses event is translated atomically, then upstream
530
+ // decoding pauses until the chat consumer creates demand again.
531
+ },
532
+ pull() {
533
+ releaseDeliveredFrame();
534
+ return step();
535
+ },
536
+ cancel(reason) {
537
+ cancelled = true;
538
+ while (queuedLiveFrameBytes.length > 0) releaseDeliveredFrame();
539
+ closeToolCalls();
540
+ // Abort first: it cancels the decoder's underlying reader, settling any in-flight
541
+ // read() so the generator's return() below resolves promptly instead of hanging
542
+ // behind an idle upstream (Sol re-verification blocker).
543
+ upstreamAbort.abort(reason);
544
+ if (!decoderStarted) {
545
+ return upstream.cancel(reason).then(() => undefined, () => undefined);
546
+ }
547
+ return sseIterator?.return(undefined).then(() => undefined, () => undefined) ?? Promise.resolve(undefined);
548
+ },
549
+ });
550
+ }
551
+
552
+ /** Non-streaming: /v1/responses JSON -> Chat Completions message JSON. */
553
+ export function responsesJsonToChatCompletion(json: unknown, model: string): Rec {
554
+ const body = isRec(json) ? json : {};
555
+ const output = Array.isArray(body.output) ? body.output : [];
556
+ let content = "";
557
+ let reasoning = "";
558
+ const toolCalls: Rec[] = [];
559
+
560
+ for (const raw of output) {
561
+ if (!isRec(raw)) continue;
562
+ if (raw.type === "message" && Array.isArray(raw.content)) {
563
+ for (const part of raw.content) {
564
+ if (isRec(part) && part.type === "output_text" && typeof part.text === "string") {
565
+ content += part.text;
566
+ }
567
+ }
568
+ } else if (raw.type === "reasoning") {
569
+ if (Array.isArray(raw.summary)) {
570
+ for (const part of raw.summary) {
571
+ if (isRec(part) && part.type === "summary_text" && typeof part.text === "string") {
572
+ reasoning += part.text;
573
+ }
574
+ }
575
+ }
576
+ if (Array.isArray(raw.content)) {
577
+ for (const part of raw.content) {
578
+ if (isRec(part) && part.type === "reasoning_text" && typeof part.text === "string") {
579
+ reasoning += part.text;
580
+ }
581
+ }
582
+ }
583
+ } else if (raw.type === "function_call") {
584
+ toolCalls.push({
585
+ id: typeof raw.call_id === "string" ? raw.call_id : `call_${uuid().slice(0, 16)}`,
586
+ type: "function",
587
+ function: {
588
+ name: typeof raw.name === "string" ? raw.name : "",
589
+ arguments: typeof raw.arguments === "string" ? raw.arguments : "{}",
590
+ },
591
+ });
592
+ }
593
+ }
594
+
595
+ const finishReason = toolCalls.length > 0 ? "tool_calls"
596
+ : body.status === "incomplete" ? "length"
597
+ : "stop";
598
+
599
+ const message: Rec = {
600
+ role: "assistant",
601
+ content: content || null,
602
+ };
603
+ if (reasoning) message.reasoning_content = reasoning;
604
+ if (toolCalls.length > 0) message.tool_calls = toolCalls;
605
+
606
+ return {
607
+ id: completionId(),
608
+ object: "chat.completion",
609
+ created: Math.floor(Date.now() / 1000),
610
+ model,
611
+ choices: [{
612
+ index: 0,
613
+ message,
614
+ finish_reason: finishReason,
615
+ logprobs: null,
616
+ }],
617
+ usage: chatCompletionsUsage(body.usage),
618
+ };
619
+ }
620
+
621
+ /** Fold a Chat Completions SSE stream into a final completion JSON. */
622
+ export async function collectChatCompletion(
623
+ stream: ReadableStream<Uint8Array>,
624
+ model: string,
625
+ translatorBudget: TranslatorBudget,
626
+ ): Promise<Rec> {
627
+ const decoder = new TextDecoder();
628
+ let buffer = "";
629
+ let content = "";
630
+ let reasoning = "";
631
+ const toolCalls = new Map<number, { id: string; name: string; arguments: string; argumentBytes: number }>();
632
+ let finishReason = "stop";
633
+ let usage: unknown;
634
+ let streamError: ChatCompletionsStreamError | null = null;
635
+ const replaceRetained = (previous: string, next: string, kind: "live_transient" | "retained_collectors") => {
636
+ const reservation = translatorBudget.reserveTransient(Buffer.byteLength(next), { kind });
637
+ reservation.commitRetained();
638
+ translatorBudget.releaseRetained(Buffer.byteLength(previous), { kind });
639
+ return next;
640
+ };
641
+ const reader = stream.getReader();
642
+ try {
643
+ for (;;) {
644
+ let done = false;
645
+ let value: Uint8Array | undefined;
646
+ try {
647
+ ({ done, value } = await reader.read());
648
+ } catch (err) {
649
+ if (isChatCompletionsStreamError(err)) throw err;
650
+ if (isTranslatorBudgetExceededError(err)) {
651
+ throw new ChatCompletionsStreamError(err.message, {
652
+ status: 413,
653
+ type: "invalid_request_error",
654
+ code: err.code,
655
+ });
656
+ }
657
+ throw new ChatCompletionsStreamError(err instanceof Error ? err.message : String(err));
658
+ }
659
+ if (done) break;
660
+ if (!value) continue;
661
+ buffer = replaceRetained(buffer, buffer + decoder.decode(value, { stream: true }), "live_transient");
662
+ let sep: number;
663
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
664
+ const rawFrame = buffer.slice(0, sep);
665
+ buffer = replaceRetained(buffer, buffer.slice(sep + 2), "live_transient");
666
+ for (const line of rawFrame.split("\n")) {
667
+ if (!line.startsWith("data: ")) continue;
668
+ const data = line.slice(6).trim();
669
+ if (!data || data === "[DONE]") continue;
670
+ let parsed: unknown;
671
+ try { parsed = JSON.parse(data); } catch { continue; }
672
+ if (!isRec(parsed)) continue;
673
+ if (isRec(parsed.error)) {
674
+ const message = typeof parsed.error.message === "string"
675
+ ? parsed.error.message
676
+ : "upstream request failed";
677
+ const type = typeof parsed.error.type === "string" ? parsed.error.type : "server_error";
678
+ const code = typeof parsed.error.code === "string" ? parsed.error.code : null;
679
+ const status = code === "translation_buffer_limit"
680
+ ? 413
681
+ : code === CYBER_POLICY_ERROR_CODE || isCyberPolicyMessage(message)
682
+ ? 400
683
+ : streamErrorStatus(message);
684
+ streamError = new ChatCompletionsStreamError(message, { status, type, code });
685
+ continue;
686
+ }
687
+ if (parsed.usage) usage = parsed.usage;
688
+ const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
689
+ const choice = isRec(choices[0]) ? choices[0] : null;
690
+ if (!choice) continue;
691
+ if (typeof choice.finish_reason === "string") finishReason = choice.finish_reason;
692
+ const delta = isRec(choice.delta) ? choice.delta : null;
693
+ if (!delta) continue;
694
+ if (typeof delta.content === "string") content = replaceRetained(content, content + delta.content, "retained_collectors");
695
+ if (typeof delta.reasoning_content === "string") reasoning = replaceRetained(reasoning, reasoning + delta.reasoning_content, "retained_collectors");
696
+ if (Array.isArray(delta.tool_calls)) {
697
+ for (const tc of delta.tool_calls) {
698
+ if (!isRec(tc)) continue;
699
+ const index = typeof tc.index === "number" ? tc.index : 0;
700
+ const current = toolCalls.get(index) ?? { id: "", name: "", arguments: "", argumentBytes: 0 };
701
+ if (typeof tc.id === "string") current.id = tc.id;
702
+ const fn = isRec(tc.function) ? tc.function : {};
703
+ // Done-frame final arguments are authoritative last-write-wins snapshots.
704
+ if (typeof fn.name === "string" && fn.name.length > 0) current.name = fn.name;
705
+ if (typeof fn.arguments === "string") {
706
+ const replace = fn.arguments.startsWith("{") || fn.arguments.startsWith("[") || current.arguments.length === 0;
707
+ const nextBytes = replace
708
+ ? Buffer.byteLength(fn.arguments)
709
+ : appendedUtf8Bytes(current.arguments, current.argumentBytes, fn.arguments);
710
+ const reservation = translatorBudget.reserveTransient(nextBytes, { kind: "retained_collectors" });
711
+ try {
712
+ current.arguments = replace ? fn.arguments : current.arguments + fn.arguments;
713
+ reservation.commitRetained();
714
+ translatorBudget.releaseRetained(current.argumentBytes, { kind: "retained_collectors" });
715
+ current.argumentBytes = nextBytes;
716
+ } catch (error) {
717
+ reservation.release();
718
+ throw error;
719
+ }
720
+ }
721
+ toolCalls.set(index, current);
722
+ }
723
+ }
724
+ }
725
+ }
726
+ }
727
+ } finally {
728
+ reader.releaseLock();
729
+ }
730
+ if (streamError) throw streamError;
731
+
732
+ const message: Rec = {
733
+ role: "assistant",
734
+ content: content || null,
735
+ };
736
+ if (reasoning) message.reasoning_content = reasoning;
737
+ if (toolCalls.size > 0) {
738
+ message.tool_calls = [...toolCalls.entries()]
739
+ .sort((a, b) => a[0] - b[0])
740
+ .map(([, tc]) => {
741
+ const copy = {
742
+ id: tc.id || `call_${uuid().slice(0, 16)}`,
743
+ type: "function",
744
+ function: { name: tc.name, arguments: tc.arguments },
745
+ };
746
+ translatorBudget.chargeRetained(Buffer.byteLength(JSON.stringify(copy)), { kind: "retained_collectors" });
747
+ return copy;
748
+ });
749
+ if (finishReason === "stop") finishReason = "tool_calls";
750
+ }
751
+
752
+ return {
753
+ id: completionId(),
754
+ object: "chat.completion",
755
+ created: Math.floor(Date.now() / 1000),
756
+ model,
757
+ choices: [{
758
+ index: 0,
759
+ message,
760
+ finish_reason: finishReason,
761
+ logprobs: null,
762
+ }],
763
+ usage: usage && isRec(usage) ? usage : chatCompletionsUsage(undefined),
764
+ };
765
+ }