@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,220 @@
1
+ /** A single web source backing the sidecar's answer. */
2
+ export interface WebSearchSource {
3
+ url: string;
4
+ title?: string;
5
+ }
6
+
7
+ /** The sidecar's synthesized answer plus its sources (empty `sources` is fine). */
8
+ export interface WebSearchResult {
9
+ text: string;
10
+ sources: WebSearchSource[];
11
+ /** Set only when the stream surfaced an error AND produced no usable answer text. */
12
+ error?: string;
13
+ }
14
+
15
+ interface AnnotationLike {
16
+ type?: string;
17
+ url?: string;
18
+ title?: string;
19
+ }
20
+ interface OutputTextBlock {
21
+ type?: string;
22
+ text?: string;
23
+ annotations?: AnnotationLike[];
24
+ }
25
+ interface OutputItem {
26
+ type?: string;
27
+ content?: OutputTextBlock[];
28
+ }
29
+
30
+ /** Push a `url_citation` annotation as a source, de-duplicated by URL. */
31
+ function collectAnnotation(ann: AnnotationLike | undefined, sources: WebSearchSource[], seen: Set<string>): void {
32
+ if (!ann || ann.type !== "url_citation" || typeof ann.url !== "string" || seen.has(ann.url)) return;
33
+ seen.add(ann.url);
34
+ sources.push({ url: ann.url, ...(ann.title ? { title: ann.title } : {}) });
35
+ }
36
+
37
+ /**
38
+ * Hosted web_search (gpt-mini) rarely emits structured `url_citation` annotations; instead it ends
39
+ * its answer with a markdown `Sources:` section. Extract sources from that TRAILING section only
40
+ * (a whole-body URL scan would false-positive on URLs the model merely mentions), and return the
41
+ * answer text with that section stripped so the tool_result renderer doesn't double-print sources.
42
+ *
43
+ * Handles the per-line forms seen from the backend: `- title: url`, `- title (url)`,
44
+ * `- [title](url)`, `- <url>`, `- url`, numbered `1. ...` variants, a markdown-prefixed header
45
+ * (`### Sources:`, `**Sources**`), a title line whose URL sits on the FOLLOWING line, and trailing
46
+ * URL punctuation (`;`, `,`, `)`, `]`, `.`). Prose that follows the source list is preserved.
47
+ */
48
+ const URL_RE = /https?:\/\/[^\s<>()\[\]]+/;
49
+ // A "Sources:" / "Source:" header, allowing markdown prefixes (#, *, -, >) and bold/italic wrappers.
50
+ const SOURCES_HEADER_RE = /^\s*(?:#{1,6}\s*)?[-*>\s]*\**\s*sources?\s*\**\s*:?\s*\**\s*$/i;
51
+
52
+ /** Trim wrapping/trailing noise from a captured URL: angle brackets, then trailing punctuation. */
53
+ function cleanUrl(url: string): string {
54
+ return url.replace(/^<+/, "").replace(/[)>\].,;:]+$/, "");
55
+ }
56
+
57
+ /** Derive a human title from the list-item text preceding the URL (strip markers, md link, seps). */
58
+ function cleanTitle(prefix: string): string {
59
+ let title = prefix.replace(/^[-*>\d.)\s]+/, "").trim();
60
+ // `[title](` from a markdown link, or a leading `[`.
61
+ title = title.replace(/^\[/, "").replace(/\]\(?$/, "").replace(/[:\-—(<]\s*$/, "").trim();
62
+ return title;
63
+ }
64
+
65
+ function extractTrailingSources(text: string): { text: string; sources: WebSearchSource[] } {
66
+ const lines = text.split("\n");
67
+ // Find the LAST line that is a "Sources:" header (markdown prefixes allowed).
68
+ let headerIdx = -1;
69
+ for (let i = lines.length - 1; i >= 0; i--) {
70
+ if (SOURCES_HEADER_RE.test(lines[i])) { headerIdx = i; break; }
71
+ }
72
+ if (headerIdx === -1) return { text, sources: [] };
73
+ const sources: WebSearchSource[] = [];
74
+ const seen = new Set<string>();
75
+ // Track the last line index actually consumed as part of the source list so trailing prose after
76
+ // the list survives (we strip the header through the last consumed source line, not to EOF).
77
+ let lastConsumed = headerIdx;
78
+ // A title line whose URL is expected on a following line (multiline entry).
79
+ let pendingTitle: string | null = null;
80
+ for (let i = headerIdx + 1; i < lines.length; i++) {
81
+ const raw = lines[i].trim();
82
+ if (raw === "") {
83
+ // Blank line between header and first entry is fine; a blank AFTER entries ends the list.
84
+ if (sources.length > 0 || pendingTitle !== null) break;
85
+ continue;
86
+ }
87
+ const m = raw.match(URL_RE);
88
+ if (!m) {
89
+ // A list-ish line with no URL may be a title whose URL is on the next line. Only treat it as a
90
+ // pending title when it looks like a list item; otherwise it's prose → stop.
91
+ if (/^[-*>\d.)]/.test(raw) || pendingTitle === null) {
92
+ if (/^[-*>\d.)]/.test(raw)) { pendingTitle = raw; lastConsumed = i; continue; }
93
+ }
94
+ break;
95
+ }
96
+ const url = cleanUrl(m[0]);
97
+ if (!url) { break; }
98
+ lastConsumed = i;
99
+ // Title: text before the URL on this line, else a buffered title from a preceding line.
100
+ const inlinePrefix = raw.slice(0, m.index);
101
+ const title = cleanTitle(inlinePrefix) || (pendingTitle ? cleanTitle(pendingTitle) : "");
102
+ pendingTitle = null;
103
+ if (seen.has(url)) continue;
104
+ seen.add(url);
105
+ sources.push(title ? { url, title } : { url });
106
+ }
107
+ if (sources.length === 0) return { text, sources: [] };
108
+ // Keep text before the header AND any prose after the consumed source lines.
109
+ const before = lines.slice(0, headerIdx).join("\n").replace(/\s+$/, "");
110
+ const after = lines.slice(lastConsumed + 1).join("\n").replace(/^\s+/, "");
111
+ const body = after ? (before ? `${before}\n\n${after}` : after) : before;
112
+ return { text: body, sources };
113
+ }
114
+
115
+ /** Pull final text + url_citation sources from a completed Responses `output[]` array. */
116
+ function fromOutputArray(output: OutputItem[], seen: Set<string>): WebSearchResult {
117
+ let text = "";
118
+ const sources: WebSearchSource[] = [];
119
+ for (const item of output) {
120
+ if (item.type !== "message" || !Array.isArray(item.content)) continue;
121
+ for (const block of item.content) {
122
+ if (block.type === "output_text" && typeof block.text === "string") {
123
+ text += block.text;
124
+ for (const ann of block.annotations ?? []) collectAnnotation(ann, sources, seen);
125
+ }
126
+ }
127
+ }
128
+ return { text, sources };
129
+ }
130
+
131
+ /**
132
+ * Parse the sidecar's streamed Responses SSE into a final answer + sources. Tolerant of the full set of
133
+ * Responses streaming events: prefers the authoritative `response.completed` output[], then the
134
+ * `response.output_text.done` text; falls back to accumulated `response.output_text.delta`. Sources are
135
+ * collected from EVERY shape they arrive in — `response.output_text.annotation.added` events (the
136
+ * streaming path, which earlier testing missed → empty citations), `done`-block `annotations[]`, and
137
+ * the final output[]. `response.failed`/`error` events surface as `error` when no answer text was produced.
138
+ */
139
+ export async function parseSidecarSSE(response: Response): Promise<WebSearchResult> {
140
+ if (!response.body) return { text: "", sources: [] };
141
+ const reader = response.body.getReader();
142
+ const decoder = new TextDecoder();
143
+ let buffer = "";
144
+ const seen = new Set<string>();
145
+ // Holder object — fields are mutated inside the closure, so they can't live as narrowed locals.
146
+ const acc: {
147
+ deltaText: string;
148
+ doneText: string;
149
+ final: WebSearchResult | null;
150
+ streamSources: WebSearchSource[];
151
+ error: string | null;
152
+ } = { deltaText: "", doneText: "", final: null, streamSources: [], error: null };
153
+
154
+ const handle = (payload: string): void => {
155
+ if (!payload || payload === "[DONE]") return;
156
+ let data: Record<string, unknown>;
157
+ try { data = JSON.parse(payload) as Record<string, unknown>; } catch {
158
+ console.warn(`[web-search-parse] malformed SSE JSON (${payload.length} chars): ${payload.slice(0, 120)}`);
159
+ return;
160
+ }
161
+ const type = data.type as string | undefined;
162
+ if (type === "response.output_text.delta" && typeof data.delta === "string") {
163
+ acc.deltaText += data.delta;
164
+ } else if (type === "response.output_text.done" && typeof data.text === "string") {
165
+ // The `done` event carries the full, authoritative text for one content part.
166
+ acc.doneText += data.text;
167
+ } else if (type === "response.completed" || type === "response.done") {
168
+ const resp = data.response as { output?: OutputItem[] } | undefined;
169
+ if (resp?.output) acc.final = fromOutputArray(resp.output, seen);
170
+ } else if (type === "response.failed" || type === "response.incomplete" || type === "error") {
171
+ const resp = data.response as { error?: { message?: string } } | undefined;
172
+ const msg = resp?.error?.message
173
+ ?? (data.error as { message?: string } | undefined)?.message
174
+ ?? (typeof data.message === "string" ? data.message : undefined);
175
+ if (msg) acc.error = msg;
176
+ }
177
+ // Citations stream as a dedicated `response.output_text.annotation.added` event (singular
178
+ // `annotation`); capture it regardless of the exact event name so they aren't lost.
179
+ if (data.annotation) collectAnnotation(data.annotation as AnnotationLike, acc.streamSources, seen);
180
+ };
181
+
182
+ try {
183
+ while (true) {
184
+ const { done, value } = await reader.read();
185
+ if (done) break;
186
+ buffer += decoder.decode(value, { stream: true });
187
+ const lines = buffer.split("\n");
188
+ buffer = lines.pop() ?? "";
189
+ for (const line of lines) {
190
+ if (line.startsWith("data: ")) handle(line.slice(6).trim());
191
+ }
192
+ }
193
+ } finally {
194
+ reader.releaseLock();
195
+ }
196
+
197
+ // Prefer the authoritative completed output[], then the done text, then accumulated deltas.
198
+ const text = (acc.final?.text.trim() ? acc.final.text : "")
199
+ || acc.doneText.trim() && acc.doneText
200
+ || acc.deltaText;
201
+ // Merge sources from the final output[] and the streaming annotation events.
202
+ const sources = [...(acc.final?.sources ?? [])];
203
+ const seenMerge = new Set(sources.map(s => s.url));
204
+ for (const s of acc.streamSources) {
205
+ if (!seenMerge.has(s.url)) { seenMerge.add(s.url); sources.push(s); }
206
+ }
207
+ // Hosted web_search usually omits url_citation annotations and lists sources in a trailing
208
+ // `Sources:` markdown block instead. Pull those out (and strip the block from the answer so the
209
+ // tool_result renderer doesn't print sources twice). Annotation titles win; text-block titles
210
+ // only fill a gap. URL-deduped against annotation sources.
211
+ const { text: body, sources: textSources } = extractTrailingSources(typeof text === "string" ? text : "");
212
+ for (const s of textSources) {
213
+ if (seenMerge.has(s.url)) continue;
214
+ seenMerge.add(s.url);
215
+ sources.push(s);
216
+ }
217
+ const finalText = textSources.length > 0 ? body : (typeof text === "string" ? text : "");
218
+ if (!finalText.trim() && acc.error) return { text: "", sources, error: acc.error };
219
+ return { text: finalText, sources };
220
+ }
@@ -0,0 +1,342 @@
1
+ import type { ProviderAdapter } from "../adapters/base";
2
+ import type { AdapterEvent } from "../types";
3
+ import {
4
+ isTranslatorBudgetExceededError,
5
+ TRANSLATOR_MAX_TURN_BYTES,
6
+ TranslatorBudgetExceededError,
7
+ type TranslatorBudget,
8
+ } from "../lib/translator-budget";
9
+
10
+ const DEFAULT_POST_TERMINAL_DRAIN_TIMEOUT_MS = 5_000;
11
+
12
+ export class RoutedModelInactivityError extends Error {
13
+ readonly timeoutMs: number;
14
+
15
+ constructor(timeoutMs: number) {
16
+ super(`Routed model generation timeout after ${timeoutMs}ms without response bytes during web-search`);
17
+ this.name = "RoutedModelInactivityError";
18
+ this.timeoutMs = timeoutMs;
19
+ }
20
+ }
21
+
22
+ export class WebSearchStreamProtocolError extends Error {
23
+ constructor(message: string) {
24
+ super(`Web-search adapter stream protocol error: ${message}`);
25
+ this.name = "WebSearchStreamProtocolError";
26
+ }
27
+ }
28
+
29
+ export interface ParseStreamWithProgressOptions {
30
+ inactivityTimeoutMs: number;
31
+ translatorBudget: TranslatorBudget;
32
+ signal?: AbortSignal;
33
+ /** Kept configurable for focused tests; production callers should use the 5 second default. */
34
+ postTerminalDrainTimeoutMs?: number;
35
+ }
36
+
37
+ type ParseStream = ProviderAdapter["parseStream"];
38
+ type Resolver<T> = (value: T | PromiseLike<T>) => void;
39
+
40
+ interface PendingDelivery {
41
+ event: AdapterEvent;
42
+ resolve: () => void;
43
+ reject: (reason: unknown) => void;
44
+ }
45
+
46
+ /**
47
+ * Capacity-one handoff. Semantic events are lossless and acknowledged when received; raw-byte
48
+ * progress is only a coalesced liveness bit and never displaces a semantic event.
49
+ */
50
+ class ProgressHandoff {
51
+ private semantic: PendingDelivery | undefined;
52
+ private progress = false;
53
+ private receiver: { resolve: Resolver<IteratorResult<AdapterEvent>>; reject: (reason: unknown) => void } | undefined;
54
+ private ended = false;
55
+ private failed = false;
56
+ private failure: unknown | undefined;
57
+
58
+ async deliver(event: AdapterEvent): Promise<void> {
59
+ if (this.failed) throw this.failure;
60
+ if (this.ended) throw new WebSearchStreamProtocolError("event delivered after collector closed");
61
+
62
+ if (this.receiver) {
63
+ const receiver = this.receiver;
64
+ this.receiver = undefined;
65
+ receiver.resolve({ done: false, value: event });
66
+ return;
67
+ }
68
+
69
+ await new Promise<void>((resolve, reject) => {
70
+ this.semantic = { event, resolve, reject };
71
+ });
72
+ }
73
+
74
+ offerProgress(): void {
75
+ if (this.ended || this.failed) return;
76
+ if (this.receiver && !this.semantic) {
77
+ const receiver = this.receiver;
78
+ this.receiver = undefined;
79
+ receiver.resolve({ done: false, value: { type: "heartbeat" } });
80
+ return;
81
+ }
82
+ this.progress = true;
83
+ }
84
+
85
+ receive(): Promise<IteratorResult<AdapterEvent>> {
86
+ if (this.failed) return Promise.reject(this.failure);
87
+ if (this.semantic) {
88
+ const delivery = this.semantic;
89
+ this.semantic = undefined;
90
+ delivery.resolve();
91
+ return Promise.resolve({ done: false, value: delivery.event });
92
+ }
93
+ if (this.progress) {
94
+ this.progress = false;
95
+ return Promise.resolve({ done: false, value: { type: "heartbeat" } });
96
+ }
97
+ if (this.ended) return Promise.resolve({ done: true, value: undefined });
98
+
99
+ return new Promise<IteratorResult<AdapterEvent>>((resolve, reject) => {
100
+ this.receiver = { resolve, reject };
101
+ });
102
+ }
103
+
104
+ fail(reason: unknown): void {
105
+ if (this.ended || this.failed) return;
106
+ this.failed = true;
107
+ this.failure = reason;
108
+ this.progress = false;
109
+ this.semantic?.reject(reason);
110
+ this.semantic = undefined;
111
+ this.receiver?.reject(reason);
112
+ this.receiver = undefined;
113
+ }
114
+
115
+ close(): void {
116
+ if (this.ended || this.failed) return;
117
+ this.ended = true;
118
+ this.progress = false;
119
+ this.semantic?.reject(new WebSearchStreamProtocolError("collector closed with a pending event"));
120
+ this.semantic = undefined;
121
+ this.receiver?.resolve({ done: true, value: undefined });
122
+ this.receiver = undefined;
123
+ }
124
+ }
125
+
126
+ function normalizeTimeout(value: number, label: string): number {
127
+ if (!Number.isFinite(value) || value <= 0) {
128
+ throw new RangeError(`${label} must be a positive finite number`);
129
+ }
130
+ return value;
131
+ }
132
+
133
+ /**
134
+ * Parse an adapter stream while exposing invisible raw-byte liveness heartbeats.
135
+ *
136
+ * The original body is never cloned or tee'd. This function owns its sole reader and supplies the
137
+ * adapter with a demand-driven, highWaterMark-zero response body.
138
+ */
139
+ export async function* parseStreamWithProgress(
140
+ response: Response,
141
+ parseStream: ParseStream,
142
+ options: ParseStreamWithProgressOptions,
143
+ ): AsyncGenerator<AdapterEvent> {
144
+ const inactivityTimeoutMs = normalizeTimeout(options.inactivityTimeoutMs, "inactivityTimeoutMs");
145
+ const postTerminalDrainTimeoutMs = normalizeTimeout(
146
+ options.postTerminalDrainTimeoutMs ?? DEFAULT_POST_TERMINAL_DRAIN_TIMEOUT_MS,
147
+ "postTerminalDrainTimeoutMs",
148
+ );
149
+ const handoff = new ProgressHandoff();
150
+ const reader = response.body?.getReader();
151
+ if (!reader) throw new WebSearchStreamProtocolError("upstream response has no body");
152
+
153
+ let inactivityTimer: ReturnType<typeof setTimeout> | undefined;
154
+ let tappedController: ReadableStreamDefaultController<Uint8Array> | undefined;
155
+ let settled = false;
156
+ let iterator: AsyncGenerator<AdapterEvent> | undefined;
157
+ let iteratorReturnStarted = false;
158
+ let detachAbort = (): void => {};
159
+ let stopSignalled = false;
160
+ let signalStop!: (reason: unknown) => void;
161
+ const stopped = new Promise<unknown>(resolve => { signalStop = resolve; });
162
+
163
+ const clearInactivity = (): void => {
164
+ if (inactivityTimer !== undefined) clearTimeout(inactivityTimer);
165
+ inactivityTimer = undefined;
166
+ };
167
+
168
+ const cancelReader = (reason: unknown): void => {
169
+ try {
170
+ void reader.cancel(reason).catch(() => {});
171
+ } catch { /* reader cancellation is best-effort and must never replace the foreground outcome */ }
172
+ };
173
+
174
+ const closeIterator = (): void => {
175
+ if (!iterator || iteratorReturnStarted) return;
176
+ iteratorReturnStarted = true;
177
+ try {
178
+ const returned = iterator.return(undefined);
179
+ void returned.catch(() => {});
180
+ } catch { /* iterator return is best-effort and must never delay foreground failure */ }
181
+ };
182
+
183
+ const stop = (reason: unknown): void => {
184
+ if (stopSignalled) return;
185
+ stopSignalled = true;
186
+ signalStop(reason);
187
+ };
188
+
189
+ const fail = (reason: unknown): void => {
190
+ // A second failure may arrive from the parser after reader cancellation. Iterator cleanup is
191
+ // still required even when the foreground failure has already won the settlement race.
192
+ closeIterator();
193
+ if (settled) return;
194
+ settled = true;
195
+ clearInactivity();
196
+ detachAbort();
197
+ stop(reason);
198
+ handoff.fail(reason);
199
+ try { tappedController?.error(reason); } catch { /* already closed/cancelled */ }
200
+ cancelReader(reason);
201
+ };
202
+
203
+ const resetInactivity = (): void => {
204
+ if (settled) return;
205
+ clearInactivity();
206
+ inactivityTimer = setTimeout(() => fail(new RoutedModelInactivityError(inactivityTimeoutMs)), inactivityTimeoutMs);
207
+ };
208
+
209
+ const onAbort = (): void => fail(options.signal?.reason);
210
+ detachAbort = (): void => options.signal?.removeEventListener("abort", onAbort);
211
+ if (options.signal?.aborted) fail(options.signal.reason);
212
+ else options.signal?.addEventListener("abort", onAbort, { once: true });
213
+
214
+ const tappedBody = new ReadableStream<Uint8Array>({
215
+ start(controller) {
216
+ tappedController = controller;
217
+ },
218
+ async pull(controller) {
219
+ try {
220
+ while (!settled) {
221
+ const result = await reader.read();
222
+ if (result.done) {
223
+ controller.close();
224
+ return;
225
+ }
226
+ if (result.value.byteLength === 0) continue;
227
+ resetInactivity();
228
+ handoff.offerProgress();
229
+ controller.enqueue(result.value);
230
+ return;
231
+ }
232
+ } catch (error) {
233
+ fail(error);
234
+ }
235
+ },
236
+ cancel(reason) {
237
+ cancelReader(reason);
238
+ closeIterator();
239
+ },
240
+ }, { highWaterMark: 0 });
241
+
242
+ const tappedResponse = new Response(tappedBody, {
243
+ status: response.status,
244
+ statusText: response.statusText,
245
+ headers: response.headers,
246
+ });
247
+
248
+ resetInactivity();
249
+
250
+ const parserPump = (async (): Promise<void> => {
251
+ let heldTerminal: Extract<AdapterEvent, { type: "done" | "incomplete" }> | undefined;
252
+ try {
253
+ iterator = parseStream(tappedResponse, options.translatorBudget);
254
+ if (settled) {
255
+ closeIterator();
256
+ return;
257
+ }
258
+ while (true) {
259
+ let result: IteratorResult<AdapterEvent>;
260
+ if (heldTerminal) {
261
+ let drainTimer: ReturnType<typeof setTimeout> | undefined;
262
+ const drainTimeout = new Promise<never>((_, reject) => {
263
+ drainTimer = setTimeout(() => reject(new WebSearchStreamProtocolError(
264
+ `adapter did not return within ${postTerminalDrainTimeoutMs}ms after ${heldTerminal?.type ?? "terminal event"}`,
265
+ )), postTerminalDrainTimeoutMs);
266
+ });
267
+ const stoppedDuringDrain = stopped.then(reason => { throw reason; });
268
+ try {
269
+ result = await Promise.race([iterator.next(), drainTimeout, stoppedDuringDrain]);
270
+ } finally {
271
+ if (drainTimer !== undefined) clearTimeout(drainTimer);
272
+ }
273
+ } else {
274
+ result = await iterator.next();
275
+ }
276
+
277
+ if (result.done) {
278
+ if (!heldTerminal) throw new WebSearchStreamProtocolError("adapter returned without a terminal event");
279
+ await handoff.deliver(heldTerminal);
280
+ if (!settled) {
281
+ settled = true;
282
+ clearInactivity();
283
+ detachAbort();
284
+ handoff.close();
285
+ cancelReader(undefined);
286
+ }
287
+ return;
288
+ }
289
+
290
+ const event = result.value;
291
+ if (heldTerminal) {
292
+ throw new WebSearchStreamProtocolError(
293
+ event.type === "done" || event.type === "incomplete"
294
+ ? "adapter yielded more than one terminal event"
295
+ : "adapter yielded an event after its terminal event",
296
+ );
297
+ }
298
+ if (event.type === "error") {
299
+ fail(event.code === "translation_buffer_limit"
300
+ ? new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_TURN_BYTES)
301
+ : new Error(event.message));
302
+ return;
303
+ }
304
+ if (event.type === "done" || event.type === "incomplete") {
305
+ heldTerminal = event;
306
+ continue;
307
+ }
308
+ await handoff.deliver(event);
309
+ }
310
+ } catch (error) {
311
+ fail(isTranslatorBudgetExceededError(error)
312
+ ? error
313
+ : error instanceof WebSearchStreamProtocolError
314
+ ? error
315
+ : new WebSearchStreamProtocolError(
316
+ `adapter threw${heldTerminal ? " after its terminal event" : ""}: ${error instanceof Error ? error.message : String(error)}`,
317
+ ));
318
+ }
319
+ })();
320
+ // The foreground intentionally never awaits this task during cancellation; keep every rejection observed.
321
+ void parserPump.catch(() => {});
322
+
323
+ try {
324
+ while (true) {
325
+ const result = await handoff.receive();
326
+ if (result.done) return;
327
+ yield result.value;
328
+ }
329
+ } finally {
330
+ detachAbort();
331
+ if (!settled) {
332
+ const reason = new Error("Web-search stream consumer stopped");
333
+ settled = true;
334
+ clearInactivity();
335
+ stop(reason);
336
+ handoff.close();
337
+ cancelReader(reason);
338
+ }
339
+ closeIterator();
340
+ try { reader.releaseLock(); } catch { /* an outstanding read may still own the lock */ }
341
+ }
342
+ }
@@ -0,0 +1,47 @@
1
+ import type { OcxTool } from "../types";
2
+
3
+ /** The function name the chat model sees + the name the loop intercepts. */
4
+ export const WEB_SEARCH_TOOL_NAME = "web_search";
5
+
6
+ /**
7
+ * Find the hosted `{type:"web_search", ...}` entry in a Responses request's `tools[]` and return it
8
+ * verbatim (so its config — external_web_access/filters/user_location/search_context_size — can be
9
+ * replayed into the sidecar's REAL web_search tool). Returns undefined when web search isn't enabled.
10
+ */
11
+ export function extractHostedWebSearch(tools: unknown[] | undefined): Record<string, unknown> | undefined {
12
+ if (!Array.isArray(tools)) return undefined;
13
+ for (const t of tools) {
14
+ if (t && typeof t === "object" && (t as { type?: string }).type === "web_search") {
15
+ return t as Record<string, unknown>;
16
+ }
17
+ }
18
+ return undefined;
19
+ }
20
+
21
+ /**
22
+ * The synthetic function tool exposed to a chat/anthropic model in place of the dropped hosted
23
+ * web_search. The model calls it like any function; the proxy intercepts the call and runs the real
24
+ * search via the sidecar (the call is never relayed to Codex). `webSearch:true` flags it for the loop.
25
+ */
26
+ export function buildWebSearchTool(): OcxTool {
27
+ return {
28
+ name: WEB_SEARCH_TOOL_NAME,
29
+ description:
30
+ "Search the web for current, real-world, or post-training-cutoff information. " +
31
+ "Returns a concise answer synthesized from live results, with sources. " +
32
+ "Use it whenever the user asks about recent events, versions, prices, docs, or anything you are unsure is current.",
33
+ parameters: {
34
+ type: "object",
35
+ properties: {
36
+ query: { type: "string", description: "A single search query — a focused natural-language question or keywords." },
37
+ queries: {
38
+ type: "array",
39
+ items: { type: "string" },
40
+ description: "Optional: run several related queries together in one call. Use instead of `query` to batch independent searches.",
41
+ },
42
+ },
43
+ // Either `query` or `queries` is accepted; the proxy normalizes them.
44
+ },
45
+ webSearch: true,
46
+ };
47
+ }