@iislee/opencodex 2.11.0 → 2.31.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 (675) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +114 -19
  3. package/bin/ocx.mjs +164 -36
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-BF38heuV.js +104 -0
  6. package/gui/dist/assets/index-DMiI18Kv.css +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +19 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +376 -52
  44. package/src/adapters/base.ts +54 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +601 -0
  48. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  49. package/src/adapters/cursor/cursor-errors.ts +129 -6
  50. package/src/adapters/cursor/discovery.ts +61 -4
  51. package/src/adapters/cursor/effort-map.ts +27 -3
  52. package/src/adapters/cursor/framing.ts +39 -0
  53. package/src/adapters/cursor/h2-pool.ts +123 -0
  54. package/src/adapters/cursor/http1-bidi.ts +361 -0
  55. package/src/adapters/cursor/images.ts +704 -0
  56. package/src/adapters/cursor/live-models.ts +174 -58
  57. package/src/adapters/cursor/live-transport.ts +609 -170
  58. package/src/adapters/cursor/native-exec-common.ts +23 -2
  59. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  60. package/src/adapters/cursor/native-exec-network.ts +1 -1
  61. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  62. package/src/adapters/cursor/native-exec.ts +101 -14
  63. package/src/adapters/cursor/protobuf-events.ts +829 -11
  64. package/src/adapters/cursor/protobuf-request.ts +383 -65
  65. package/src/adapters/cursor/request-builder.ts +237 -31
  66. package/src/adapters/cursor/tool-definitions.ts +242 -12
  67. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  68. package/src/adapters/cursor/transport.ts +22 -0
  69. package/src/adapters/cursor/types.ts +28 -1
  70. package/src/adapters/cursor.ts +133 -7
  71. package/src/adapters/google-antigravity-replay.ts +558 -34
  72. package/src/adapters/google-antigravity-wire.ts +43 -10
  73. package/src/adapters/google-http.ts +38 -10
  74. package/src/adapters/google-tool-schema.ts +80 -15
  75. package/src/adapters/google-truncation.ts +11 -0
  76. package/src/adapters/google.ts +618 -74
  77. package/src/adapters/identity.ts +39 -6
  78. package/src/adapters/kiro-errors.ts +11 -0
  79. package/src/adapters/kiro-events.ts +19 -1
  80. package/src/adapters/kiro-thinking.ts +18 -2
  81. package/src/adapters/kiro-tools.ts +10 -1
  82. package/src/adapters/kiro.ts +92 -54
  83. package/src/adapters/mimo-free.ts +17 -0
  84. package/src/adapters/openai-chat-url.ts +11 -0
  85. package/src/adapters/openai-chat.ts +1311 -336
  86. package/src/adapters/openai-responses-url.ts +16 -0
  87. package/src/adapters/openai-responses.ts +830 -56
  88. package/src/adapters/registry.ts +175 -0
  89. package/src/adapters/responses-tool-schema.ts +67 -0
  90. package/src/adapters/tool-call-id.ts +119 -0
  91. package/src/adapters/tool-catalog-nudge.ts +104 -21
  92. package/src/adapters/xai-web-search.ts +185 -0
  93. package/src/bridge.ts +428 -61
  94. package/src/chat/inbound.ts +43 -19
  95. package/src/chat/outbound.ts +82 -26
  96. package/src/claude/agents-inject.ts +32 -9
  97. package/src/claude/context-windows.ts +21 -5
  98. package/src/claude/desktop-3p.ts +243 -9
  99. package/src/claude/gateway-cache.ts +41 -4
  100. package/src/claude/inbound.ts +72 -3
  101. package/src/claude/model-info.ts +38 -15
  102. package/src/claude/outbound.ts +70 -16
  103. package/src/cli/account-api.ts +35 -1
  104. package/src/cli/account-auth.ts +33 -6
  105. package/src/cli/account-catalog-refresh.ts +14 -0
  106. package/src/cli/account-extended.ts +389 -2
  107. package/src/cli/account-main.ts +317 -0
  108. package/src/cli/account.ts +30 -6
  109. package/src/cli/agent.ts +45 -1
  110. package/src/cli/claude-agent-startup-sync.ts +73 -0
  111. package/src/cli/claude-desktop.ts +29 -4
  112. package/src/cli/claude.ts +103 -34
  113. package/src/cli/codex-log-guard-doctor.ts +103 -0
  114. package/src/cli/codex-shim-autorestore.ts +2 -0
  115. package/src/cli/codex-shim-readiness.ts +76 -0
  116. package/src/cli/combo.ts +8 -0
  117. package/src/cli/config-command.ts +74 -10
  118. package/src/cli/dispatch.ts +593 -0
  119. package/src/cli/doctor.ts +315 -43
  120. package/src/cli/ensure-desired-integrations.ts +152 -0
  121. package/src/cli/export-command.ts +46 -20
  122. package/src/cli/help.ts +20 -271
  123. package/src/cli/index.ts +378 -519
  124. package/src/cli/init.ts +4 -17
  125. package/src/cli/integrations.ts +120 -2
  126. package/src/cli/lab.ts +607 -0
  127. package/src/cli/launcher-context.ts +77 -0
  128. package/src/cli/minimax.ts +497 -0
  129. package/src/cli/models-runtime.ts +35 -2
  130. package/src/cli/models.ts +100 -14
  131. package/src/cli/observe.ts +92 -3
  132. package/src/cli/opencode.ts +4 -2
  133. package/src/cli/provider-runtime.ts +18 -1
  134. package/src/cli/provider.ts +24 -3
  135. package/src/cli/ready.ts +301 -0
  136. package/src/cli/registry.ts +437 -0
  137. package/src/cli/root.ts +86 -0
  138. package/src/cli/route-policy.ts +92 -0
  139. package/src/cli/runtime-api.ts +6 -3
  140. package/src/cli/star-prompt.ts +71 -15
  141. package/src/cli/status.ts +10 -3
  142. package/src/cli/system-restart-client.ts +146 -0
  143. package/src/cli/tray-proxy.ts +153 -6
  144. package/src/cli/v2.ts +105 -10
  145. package/src/cli.ts +1 -1
  146. package/src/clients/config-export.ts +1358 -21
  147. package/src/codex/account-label.ts +14 -1
  148. package/src/codex/account-lifecycle.ts +130 -13
  149. package/src/codex/account-namespaces.ts +49 -3
  150. package/src/codex/account-priority.ts +83 -0
  151. package/src/codex/account-store.ts +29 -2
  152. package/src/codex/account-usability.ts +25 -2
  153. package/src/codex/admission.ts +256 -0
  154. package/src/codex/affinity-debug.ts +162 -0
  155. package/src/codex/app-server-processes.ts +493 -106
  156. package/src/codex/app-server-restart-service.ts +232 -0
  157. package/src/codex/auth-api.ts +849 -242
  158. package/src/codex/auth-collision.ts +5 -3
  159. package/src/codex/auth-context.ts +345 -32
  160. package/src/codex/autostart-health.ts +8 -1
  161. package/src/codex/catalog/account-models.ts +67 -0
  162. package/src/codex/catalog/aggregation.ts +68 -10
  163. package/src/codex/catalog/bundled.ts +331 -33
  164. package/src/codex/catalog/effort.ts +121 -30
  165. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  166. package/src/codex/catalog/kinds.ts +2 -0
  167. package/src/codex/catalog/metadata.ts +529 -45
  168. package/src/codex/catalog/native-models.ts +72 -0
  169. package/src/codex/catalog/parsing.ts +224 -30
  170. package/src/codex/catalog/provider-fetch.ts +1460 -134
  171. package/src/codex/catalog/sync.ts +1449 -186
  172. package/src/codex/catalog-admission.ts +199 -0
  173. package/src/codex/catalog-refresh-status.ts +105 -0
  174. package/src/codex/catalog-write-serialization.ts +242 -0
  175. package/src/codex/catalog.ts +6 -3
  176. package/src/codex/codex-write-lock.ts +384 -0
  177. package/src/codex/convergence-types.ts +614 -0
  178. package/src/codex/convergence.ts +651 -0
  179. package/src/codex/coordinator-doctor.ts +332 -0
  180. package/src/codex/custom-model-catalog-migration.ts +176 -0
  181. package/src/codex/desired-state.ts +230 -0
  182. package/src/codex/features.ts +636 -39
  183. package/src/codex/generation.ts +202 -0
  184. package/src/codex/history-job.ts +407 -0
  185. package/src/codex/history-lock.ts +242 -0
  186. package/src/codex/history-migration-guardian.ts +26 -20
  187. package/src/codex/history-provider.ts +231 -28
  188. package/src/codex/history-transition.ts +105 -0
  189. package/src/codex/history-worker.ts +220 -0
  190. package/src/codex/inject-coordination.ts +290 -0
  191. package/src/codex/inject.ts +1073 -152
  192. package/src/codex/injected-marker.ts +37 -3
  193. package/src/codex/integration-record.ts +266 -0
  194. package/src/codex/internal/catalog-writer.ts +203 -0
  195. package/src/codex/internal/history-writer.ts +80 -0
  196. package/src/codex/journal.ts +66 -4
  197. package/src/codex/log-guard/inspect.ts +506 -0
  198. package/src/codex/log-guard/lock.ts +150 -0
  199. package/src/codex/log-guard/maintenance.ts +403 -0
  200. package/src/codex/log-guard/path-safety.ts +88 -0
  201. package/src/codex/log-guard/policy.ts +44 -0
  202. package/src/codex/log-guard/processes.ts +205 -0
  203. package/src/codex/log-guard/protection.ts +489 -0
  204. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  205. package/src/codex/main-account-cache.ts +24 -0
  206. package/src/codex/main-account.ts +29 -1
  207. package/src/codex/management-convergence.ts +167 -0
  208. package/src/codex/model-cache.ts +56 -10
  209. package/src/codex/model-entitlements.ts +353 -0
  210. package/src/codex/native-main-admission.ts +47 -0
  211. package/src/codex/native-main-auth-temp.ts +187 -0
  212. package/src/codex/native-main-claim.ts +178 -0
  213. package/src/codex/native-main-lock-file.ts +162 -0
  214. package/src/codex/native-main-owner.ts +329 -0
  215. package/src/codex/native-profile-api.ts +247 -0
  216. package/src/codex/native-profile-manager.ts +1531 -0
  217. package/src/codex/native-profile-processes.ts +121 -0
  218. package/src/codex/native-profile-recovery.ts +99 -0
  219. package/src/codex/native-profile-stage-store.ts +387 -0
  220. package/src/codex/native-profile-startup.ts +492 -0
  221. package/src/codex/native-profile-store.ts +855 -0
  222. package/src/codex/native-profile-types.ts +120 -0
  223. package/src/codex/native-residue.ts +682 -0
  224. package/src/codex/paths.ts +80 -1
  225. package/src/codex/plan-from-token.ts +140 -0
  226. package/src/codex/plan.ts +40 -0
  227. package/src/codex/plugins-doctor.ts +1 -1
  228. package/src/codex/pool-rotation.ts +74 -4
  229. package/src/codex/project-config-warnings.ts +20 -6
  230. package/src/codex/prompt-journal.ts +352 -0
  231. package/src/codex/prompt-layers.ts +967 -0
  232. package/src/codex/prompt-lock.ts +143 -0
  233. package/src/codex/quota-rejection.ts +298 -0
  234. package/src/codex/quota.ts +175 -13
  235. package/src/codex/refresh.ts +11 -2
  236. package/src/codex/reset-credit-recovery.ts +1044 -0
  237. package/src/codex/routing.ts +505 -94
  238. package/src/codex/runtime.ts +159 -38
  239. package/src/codex/shim.ts +1009 -28
  240. package/src/codex/subagent-model-fallback.ts +350 -35
  241. package/src/codex/sync.ts +191 -2
  242. package/src/codex/transition-state.ts +612 -0
  243. package/src/codex/upstream-host-health.ts +368 -0
  244. package/src/codex/user-identity.ts +557 -0
  245. package/src/codex/warmup.ts +187 -81
  246. package/src/codex/write-coordination.ts +114 -0
  247. package/src/combos/failover.ts +20 -0
  248. package/src/combos/index.ts +4 -0
  249. package/src/combos/request.ts +32 -0
  250. package/src/combos/types.ts +81 -9
  251. package/src/config/provider-name.ts +24 -0
  252. package/src/config.ts +1762 -140
  253. package/src/generated/compatibility-version.json +3164 -0
  254. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  255. package/src/grok/inject.ts +16 -5
  256. package/src/grok/inspect.ts +45 -0
  257. package/src/grok/sync.ts +2 -2
  258. package/src/images/loop.ts +152 -29
  259. package/src/images/plan.ts +23 -13
  260. package/src/integrations/config-io.ts +269 -0
  261. package/src/integrations/journal.ts +315 -0
  262. package/src/integrations/merge.ts +135 -0
  263. package/src/integrations/mutation-flight.ts +71 -0
  264. package/src/integrations/native/ownership-preflight.ts +202 -0
  265. package/src/integrations/omp-yaml-source.ts +358 -0
  266. package/src/integrations/owned-refresh.ts +74 -0
  267. package/src/integrations/ownership.ts +111 -0
  268. package/src/integrations/registry.ts +159 -0
  269. package/src/integrations/serialize.ts +314 -0
  270. package/src/integrations/state.ts +361 -0
  271. package/src/integrations/store.ts +103 -0
  272. package/src/integrations/writer-lock.ts +98 -0
  273. package/src/integrations/writer.ts +691 -0
  274. package/src/lab/artifacts/sanitize.ts +586 -0
  275. package/src/lab/artifacts/secure-fs.ts +475 -0
  276. package/src/lab/artifacts/store.ts +310 -0
  277. package/src/lab/automation/budgets.ts +78 -0
  278. package/src/lab/automation/config-persistence.ts +256 -0
  279. package/src/lab/automation/constants.ts +39 -0
  280. package/src/lab/automation/cooldown.ts +103 -0
  281. package/src/lab/automation/dispatch.ts +211 -0
  282. package/src/lab/automation/index.ts +13 -0
  283. package/src/lab/automation/orchestrator.ts +499 -0
  284. package/src/lab/automation/persistence.ts +512 -0
  285. package/src/lab/automation/planner.ts +371 -0
  286. package/src/lab/automation/policy.ts +136 -0
  287. package/src/lab/automation/queue.ts +191 -0
  288. package/src/lab/automation/recovery.ts +24 -0
  289. package/src/lab/automation/route-context.ts +21 -0
  290. package/src/lab/automation/run-key.ts +44 -0
  291. package/src/lab/automation/runs-query.ts +34 -0
  292. package/src/lab/automation/types.ts +160 -0
  293. package/src/lab/conformance/assertion.ts +325 -0
  294. package/src/lab/conformance/digest.ts +22 -0
  295. package/src/lab/conformance/executor.ts +741 -0
  296. package/src/lab/conformance/fixture-provider.ts +27 -0
  297. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  298. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  299. package/src/lab/conformance/harness-budget.ts +47 -0
  300. package/src/lab/conformance/index.ts +5 -0
  301. package/src/lab/conformance/jcs.ts +64 -0
  302. package/src/lab/conformance/json-pointer.ts +39 -0
  303. package/src/lab/conformance/manifest.ts +180 -0
  304. package/src/lab/conformance/mcp-stub.ts +179 -0
  305. package/src/lab/conformance/negative-controls.ts +164 -0
  306. package/src/lab/conformance/observation.ts +355 -0
  307. package/src/lab/conformance/runner.ts +68 -0
  308. package/src/lab/conformance/sse-normalize.ts +59 -0
  309. package/src/lab/conformance/suite-manifest.ts +78 -0
  310. package/src/lab/conformance/types.ts +214 -0
  311. package/src/lab/constants.ts +126 -0
  312. package/src/lab/digest.ts +64 -0
  313. package/src/lab/events/errors.ts +9 -0
  314. package/src/lab/events/limits.ts +117 -0
  315. package/src/lab/events/types.ts +229 -0
  316. package/src/lab/events/validate.ts +781 -0
  317. package/src/lab/fabric/constants.ts +40 -0
  318. package/src/lab/fabric/executor.ts +492 -0
  319. package/src/lab/fabric/index.ts +80 -0
  320. package/src/lab/fabric/manifest.ts +222 -0
  321. package/src/lab/fabric/observe.ts +489 -0
  322. package/src/lab/fabric/patch.ts +79 -0
  323. package/src/lab/fabric/producer-child.ts +139 -0
  324. package/src/lab/fabric/producer-isolate.ts +276 -0
  325. package/src/lab/fabric/producer-protocol.ts +61 -0
  326. package/src/lab/fabric/scratch.ts +439 -0
  327. package/src/lab/fabric/subject.ts +106 -0
  328. package/src/lab/fabric/types.ts +134 -0
  329. package/src/lab/fabric/verifier.ts +98 -0
  330. package/src/lab/index.ts +54 -0
  331. package/src/lab/ledger/artifact-refs.ts +127 -0
  332. package/src/lab/ledger/invalidation.ts +136 -0
  333. package/src/lab/ledger/purge.ts +310 -0
  334. package/src/lab/ledger/store.ts +532 -0
  335. package/src/lab/live/credential-lease.ts +53 -0
  336. package/src/lab/live/destination.ts +155 -0
  337. package/src/lab/live/executor.ts +336 -0
  338. package/src/lab/live/inert-tools.ts +56 -0
  339. package/src/lab/live/manifest.ts +85 -0
  340. package/src/lab/live/mcp-loopback.ts +57 -0
  341. package/src/lab/live/runner.ts +19 -0
  342. package/src/lab/live/sandbox.ts +61 -0
  343. package/src/lab/live/suite-manifest.ts +41 -0
  344. package/src/lab/live/transport.ts +118 -0
  345. package/src/lab/live/types.ts +197 -0
  346. package/src/lab/observe/from-conformance.ts +301 -0
  347. package/src/lab/observe/from-live.ts +117 -0
  348. package/src/lab/paths.ts +153 -0
  349. package/src/lab/projection/rebuild.ts +495 -0
  350. package/src/lab/projection/schema.ts +135 -0
  351. package/src/lab/projection/verdicts.ts +474 -0
  352. package/src/lab/projection/verification.ts +412 -0
  353. package/src/lab/public/bundle.ts +217 -0
  354. package/src/lab/public/community-authority.ts +175 -0
  355. package/src/lab/public/community-files.ts +29 -0
  356. package/src/lab/public/community.ts +479 -0
  357. package/src/lab/public/file-safety.ts +155 -0
  358. package/src/lab/public/ids.ts +26 -0
  359. package/src/lab/public/index.ts +16 -0
  360. package/src/lab/public/mutation-lock.ts +424 -0
  361. package/src/lab/public/operator.ts +353 -0
  362. package/src/lab/public/origin-purge.ts +79 -0
  363. package/src/lab/public/origin.ts +203 -0
  364. package/src/lab/public/privacy.ts +143 -0
  365. package/src/lab/public/private-file.ts +261 -0
  366. package/src/lab/public/project.ts +124 -0
  367. package/src/lab/public/purge-test-fault.ts +21 -0
  368. package/src/lab/public/purge.ts +223 -0
  369. package/src/lab/public/registry.ts +44 -0
  370. package/src/lab/public/revocation.ts +252 -0
  371. package/src/lab/public/signature.ts +243 -0
  372. package/src/lab/public/storage.ts +105 -0
  373. package/src/lab/public/strict-json.ts +206 -0
  374. package/src/lab/public/time.ts +26 -0
  375. package/src/lab/public/types.ts +172 -0
  376. package/src/lab/public/validate.ts +391 -0
  377. package/src/lab/query/catalog.ts +101 -0
  378. package/src/lab/query/connection.ts +107 -0
  379. package/src/lab/query/constants.ts +4 -0
  380. package/src/lab/query/cursor.ts +132 -0
  381. package/src/lab/query/dto-map.ts +277 -0
  382. package/src/lab/query/errors.ts +22 -0
  383. package/src/lab/query/freshness.ts +53 -0
  384. package/src/lab/query/index.ts +45 -0
  385. package/src/lab/query/latest-observation.ts +59 -0
  386. package/src/lab/query/passive-production.ts +159 -0
  387. package/src/lab/query/queries.ts +444 -0
  388. package/src/lab/query/types.ts +266 -0
  389. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  390. package/src/lab/subject/installation-salt.ts +112 -0
  391. package/src/lab/subject/protocol-subject.ts +80 -0
  392. package/src/lab/subject/route-subject.ts +74 -0
  393. package/src/lib/app-owned-memory-stores.ts +22 -0
  394. package/src/lib/bounded-body.ts +153 -9
  395. package/src/lib/bun-runtime.ts +125 -12
  396. package/src/lib/bun-stream-caps.ts +13 -9
  397. package/src/lib/codex-restart-contract.ts +120 -0
  398. package/src/lib/config-ownership.ts +6 -2
  399. package/src/lib/destination-policy.ts +65 -1
  400. package/src/lib/errors.ts +44 -2
  401. package/src/lib/fabric-task-execution-authority.ts +7 -0
  402. package/src/lib/fabric-task-host.ts +29 -0
  403. package/src/lib/lab-activation.ts +223 -0
  404. package/src/lib/lab-live-execution-authority.ts +13 -0
  405. package/src/lib/lab-live-host.ts +30 -0
  406. package/src/lib/lab-live-pinned-sender.ts +56 -0
  407. package/src/lib/lab-live-route-production.ts +130 -0
  408. package/src/lib/lab-passive-linker-registration.ts +26 -0
  409. package/src/lib/local-management-attestation.ts +51 -0
  410. package/src/lib/local-management-capability.ts +100 -0
  411. package/src/lib/local-provider-reload-contract.ts +100 -0
  412. package/src/lib/optional-shutdown-hooks.ts +57 -0
  413. package/src/lib/pinned-http.ts +145 -26
  414. package/src/lib/process-control.ts +4 -1
  415. package/src/lib/provider-outbound.ts +49 -9
  416. package/src/lib/redact.ts +419 -3
  417. package/src/lib/self-launch-argv.ts +15 -0
  418. package/src/lib/server-resource-ownership.ts +71 -0
  419. package/src/lib/shadow-call.ts +35 -4
  420. package/src/lib/sse-decoder.ts +41 -0
  421. package/src/lib/state-store-registrations.ts +10 -2
  422. package/src/lib/system-restart-contract.ts +73 -0
  423. package/src/lib/token-estimate.ts +19 -2
  424. package/src/lib/tool-argument-integers.ts +202 -0
  425. package/src/lib/translator-budget.ts +44 -0
  426. package/src/lib/upstream-http-version.ts +57 -0
  427. package/src/lib/upstream-reachability.ts +95 -0
  428. package/src/lib/upstream-retry.ts +156 -3
  429. package/src/lib/windows-atomic-replace.ts +155 -0
  430. package/src/lib/windows-elevation.ts +70 -2
  431. package/src/lib/windows-secret-acl.ts +409 -69
  432. package/src/lib/windows-service-wrappers.ts +72 -0
  433. package/src/lib/windows-text.ts +106 -0
  434. package/src/lib/windows-user-principal.ts +341 -0
  435. package/src/lib/winsw.ts +33 -5
  436. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  437. package/src/oauth/account-import/index.ts +15 -0
  438. package/src/oauth/account-import/parser.ts +83 -0
  439. package/src/oauth/account-import/registry.ts +18 -0
  440. package/src/oauth/account-import/service.ts +75 -0
  441. package/src/oauth/account-import/types.ts +91 -0
  442. package/src/oauth/anthropic.ts +12 -1
  443. package/src/oauth/callback-server.ts +8 -2
  444. package/src/oauth/chatgpt.ts +12 -1
  445. package/src/oauth/command-code.ts +239 -0
  446. package/src/oauth/cursor.ts +46 -5
  447. package/src/oauth/google-antigravity.ts +35 -3
  448. package/src/oauth/health.ts +20 -12
  449. package/src/oauth/index.ts +398 -66
  450. package/src/oauth/key-providers.ts +16 -0
  451. package/src/oauth/kimi.ts +16 -2
  452. package/src/oauth/kiro.ts +50 -6
  453. package/src/oauth/local-token-detect.ts +11 -2
  454. package/src/oauth/log.ts +3 -1
  455. package/src/oauth/login-cli.ts +88 -28
  456. package/src/oauth/nous.ts +798 -0
  457. package/src/oauth/store.ts +119 -21
  458. package/src/oauth/token-guardian.ts +9 -3
  459. package/src/pi/models.ts +2 -2
  460. package/src/providers/alibaba-region-migration.ts +1 -1
  461. package/src/providers/antigravity-models.ts +521 -31
  462. package/src/providers/base-url-choices.ts +10 -0
  463. package/src/providers/codex-capacity.ts +292 -0
  464. package/src/providers/command-code-efforts.ts +144 -0
  465. package/src/providers/context-cap.ts +22 -5
  466. package/src/providers/cursor-pool.ts +72 -0
  467. package/src/providers/derive.ts +253 -6
  468. package/src/providers/fastwire.ts +501 -0
  469. package/src/providers/free-directory.ts +10 -7
  470. package/src/providers/google-vertex-location.ts +14 -0
  471. package/src/providers/key-failover.ts +71 -3
  472. package/src/providers/label.ts +1 -1
  473. package/src/providers/model-discovery-limits.ts +16 -0
  474. package/src/providers/model-discovery.ts +115 -22
  475. package/src/providers/model-rename-migration.ts +255 -0
  476. package/src/providers/model-rename-startup.ts +28 -0
  477. package/src/providers/openai-sidecar.ts +72 -4
  478. package/src/providers/openai-tier-startup.ts +31 -2
  479. package/src/providers/openai-tiers.ts +119 -4
  480. package/src/providers/openai-virtual-models.ts +1 -0
  481. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  482. package/src/providers/provider-id-rewrite.ts +29 -0
  483. package/src/providers/quota.ts +1319 -38
  484. package/src/providers/registry.ts +1429 -111
  485. package/src/providers/request-pacing.ts +310 -0
  486. package/src/providers/service-tier.ts +277 -0
  487. package/src/providers/slug-codec.ts +42 -6
  488. package/src/providers/static-model-discovery.ts +86 -0
  489. package/src/providers/xai-responses-opt-in.ts +15 -0
  490. package/src/providers/xai-transport.ts +11 -4
  491. package/src/reasoning-effort.ts +49 -1
  492. package/src/responses/compaction.ts +26 -1
  493. package/src/responses/custom-tool-compat.ts +266 -0
  494. package/src/responses/hosted-tool-policy.ts +9 -0
  495. package/src/responses/namespace-tool-compat.ts +355 -0
  496. package/src/responses/parser.ts +220 -38
  497. package/src/responses/provider-continuation.ts +98 -0
  498. package/src/responses/provider-opaque-metadata.ts +73 -0
  499. package/src/responses/reasoning-envelope.ts +9 -1
  500. package/src/responses/reasoning-replay-cache.ts +426 -0
  501. package/src/responses/schema.ts +7 -1
  502. package/src/responses/spill-store.ts +75 -10
  503. package/src/responses/state.ts +565 -27
  504. package/src/responses/thought-signature-replay.ts +347 -0
  505. package/src/responses/tool-search-compat.ts +301 -0
  506. package/src/responses/truncated-stop-reason.ts +60 -0
  507. package/src/router.ts +366 -30
  508. package/src/routing/analytics.ts +378 -0
  509. package/src/routing/capability.ts +244 -0
  510. package/src/routing/compatibility/assemble.ts +73 -0
  511. package/src/routing/compatibility/behavior.ts +278 -0
  512. package/src/routing/compatibility/catalog.ts +99 -0
  513. package/src/routing/compatibility/endpoint.ts +52 -0
  514. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  515. package/src/routing/compatibility/policy.ts +181 -0
  516. package/src/routing/compatibility/provider-slot.ts +56 -0
  517. package/src/routing/compatibility/reader.ts +110 -0
  518. package/src/routing/compatibility/subject.ts +191 -0
  519. package/src/routing/compatibility/types.ts +64 -0
  520. package/src/routing/compatibility/version.ts +104 -0
  521. package/src/routing/cost.ts +77 -0
  522. package/src/routing/evaluator.ts +495 -0
  523. package/src/routing/health.ts +412 -0
  524. package/src/routing/history/cursor.ts +43 -0
  525. package/src/routing/history/indexer.ts +605 -0
  526. package/src/routing/history/schema.ts +72 -0
  527. package/src/routing/profile-namespace.ts +15 -0
  528. package/src/routing/profile.ts +547 -0
  529. package/src/routing/quota.ts +145 -0
  530. package/src/routing/request-evidence.ts +45 -0
  531. package/src/routing/trace.ts +776 -0
  532. package/src/server/adapter-resolve.ts +2 -29
  533. package/src/server/auth-cors.ts +267 -46
  534. package/src/server/background-lifecycle.ts +182 -0
  535. package/src/server/chat-completions.ts +130 -56
  536. package/src/server/chat-native-sse.ts +331 -0
  537. package/src/server/chat-native.ts +426 -0
  538. package/src/server/claude-messages.ts +159 -43
  539. package/src/server/direct-local-http.ts +347 -0
  540. package/src/server/effort-policy.ts +18 -0
  541. package/src/server/github-copilot-responses-repair.ts +338 -0
  542. package/src/server/gui-static.ts +39 -10
  543. package/src/server/images.ts +94 -12
  544. package/src/server/index.ts +865 -181
  545. package/src/server/lifecycle.ts +284 -13
  546. package/src/server/live.ts +136 -17
  547. package/src/server/local-management-read-client.ts +90 -0
  548. package/src/server/local-provider-reload-client.ts +137 -0
  549. package/src/server/management/agent-settings-routes.ts +398 -103
  550. package/src/server/management/api-key-usage.ts +31 -5
  551. package/src/server/management/body.ts +6 -0
  552. package/src/server/management/combo-routes.ts +62 -24
  553. package/src/server/management/config-routes.ts +464 -51
  554. package/src/server/management/context.ts +80 -2
  555. package/src/server/management/integration-routes.ts +498 -0
  556. package/src/server/management/lab-automation-routes.ts +206 -0
  557. package/src/server/management/lab-routes.ts +563 -0
  558. package/src/server/management/logs-usage-routes.ts +101 -32
  559. package/src/server/management/model-routes.ts +189 -131
  560. package/src/server/management/model-rows.ts +163 -0
  561. package/src/server/management/native-integration-routes.ts +769 -0
  562. package/src/server/management/oauth-account-routes.ts +80 -4
  563. package/src/server/management/provider-capability-config.ts +48 -0
  564. package/src/server/management/provider-routes.ts +764 -157
  565. package/src/server/management/request-history-routes.ts +191 -0
  566. package/src/server/management/routing-analytics-routes.ts +74 -0
  567. package/src/server/management/routing-profile-routes.ts +380 -0
  568. package/src/server/management/shared.ts +27 -11
  569. package/src/server/management/sidebar-routes.ts +47 -31
  570. package/src/server/management/storage-log-guard-routes.ts +186 -0
  571. package/src/server/management/sync-response.ts +69 -0
  572. package/src/server/management/system-restart.ts +288 -32
  573. package/src/server/management/system-routes.ts +77 -0
  574. package/src/server/management/usage-summary-cache.ts +9 -1
  575. package/src/server/management/vision-sidecar-options.ts +167 -0
  576. package/src/server/management/web-search-sidecar-options.ts +120 -0
  577. package/src/server/management-api.ts +115 -14
  578. package/src/server/management-auth.ts +220 -5
  579. package/src/server/passive-route-linker.ts +66 -0
  580. package/src/server/ports.ts +41 -1
  581. package/src/server/proxy-liveness.ts +132 -5
  582. package/src/server/readiness.ts +99 -0
  583. package/src/server/relay-eager.ts +82 -42
  584. package/src/server/relay.ts +236 -76
  585. package/src/server/request-decompress.ts +113 -6
  586. package/src/server/request-log.ts +235 -22
  587. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  588. package/src/server/responses/agent-task-recovery.ts +465 -0
  589. package/src/server/responses/collaboration.ts +204 -35
  590. package/src/server/responses/compact.ts +442 -55
  591. package/src/server/responses/core.ts +2872 -331
  592. package/src/server/responses/empty-completion-guard.ts +276 -0
  593. package/src/server/responses/encrypted-payload.ts +62 -39
  594. package/src/server/responses/fetch-helpers.ts +79 -4
  595. package/src/server/responses/input-admission.ts +185 -0
  596. package/src/server/responses/pacing-overload.ts +13 -0
  597. package/src/server/responses/policy-fallback.ts +178 -0
  598. package/src/server/responses/responses-field-backfill.ts +251 -0
  599. package/src/server/responses/terminal-guard.ts +26 -5
  600. package/src/server/responses/upstream-error.ts +5 -0
  601. package/src/server/responses/ws-upstream.ts +308 -0
  602. package/src/server/responses-custom-tool-repair.ts +282 -0
  603. package/src/server/responses-item-id-repair.ts +54 -6
  604. package/src/server/responses-json-events.ts +90 -0
  605. package/src/server/responses-model-rewrite.ts +29 -0
  606. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  607. package/src/server/responses-snapshot-repair.ts +621 -0
  608. package/src/server/responses-terminal-repair.ts +342 -0
  609. package/src/server/responses-tool-search-repair.ts +267 -0
  610. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  611. package/src/server/responses.ts +18 -2
  612. package/src/server/search.ts +78 -13
  613. package/src/server/sse-frame-buffer.ts +292 -0
  614. package/src/server/sse-payload-rewrite.ts +110 -22
  615. package/src/server/startup-action-control.ts +8 -1
  616. package/src/server/startup-health-cache.ts +19 -1
  617. package/src/server/system-env.ts +80 -9
  618. package/src/server/ws-bridge.ts +39 -38
  619. package/src/service-manager-probe.ts +892 -0
  620. package/src/service.ts +1111 -90
  621. package/src/sidecar/auth.ts +92 -0
  622. package/src/sidecar/candidates.ts +83 -0
  623. package/src/storage/cleanup.ts +2 -2
  624. package/src/storage/scanner.ts +1 -1
  625. package/src/storage/worker-lifecycle.ts +14 -14
  626. package/src/tray/windows-tray.ps1 +83 -9
  627. package/src/tray/windows.ts +43 -16
  628. package/src/types/accounts.ts +37 -0
  629. package/src/types/config.ts +845 -0
  630. package/src/types/provider.ts +545 -0
  631. package/src/types/request.ts +384 -0
  632. package/src/types/tools.ts +131 -0
  633. package/src/types/wire.ts +80 -0
  634. package/src/types.ts +104 -1236
  635. package/src/update/index.ts +32 -19
  636. package/src/update/job.ts +442 -67
  637. package/src/update/notify.ts +12 -6
  638. package/src/update/npm-cache-preflight.d.mts +47 -0
  639. package/src/update/npm-cache-preflight.mjs +201 -0
  640. package/src/update/transactional-install.d.mts +22 -0
  641. package/src/update/transactional-install.mjs +259 -0
  642. package/src/usage/cost.ts +0 -0
  643. package/src/usage/expected-prices.ts +268 -16
  644. package/src/usage/log.ts +606 -41
  645. package/src/usage/summary.ts +177 -9
  646. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  647. package/src/usage/user-cost-overlays.ts +314 -0
  648. package/src/vision/anthropic-describe.ts +10 -6
  649. package/src/vision/backends.ts +97 -0
  650. package/src/vision/describe.ts +9 -3
  651. package/src/vision/eligibility.ts +250 -0
  652. package/src/vision/index.ts +238 -24
  653. package/src/vision/reasoning.ts +55 -0
  654. package/src/vision/routed-describe.ts +175 -0
  655. package/src/vision/timeout-bounds.ts +9 -0
  656. package/src/web-search/anthropic-executor.ts +13 -7
  657. package/src/web-search/backends.ts +108 -0
  658. package/src/web-search/exa-executor.ts +88 -0
  659. package/src/web-search/executor.ts +11 -3
  660. package/src/web-search/gemini-executor.ts +141 -0
  661. package/src/web-search/index.ts +150 -15
  662. package/src/web-search/loop.ts +279 -50
  663. package/src/web-search/parse.ts +125 -30
  664. package/src/web-search/sources.ts +60 -0
  665. package/src/web-search/xai-executor.ts +219 -0
  666. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  667. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  668. package/gui/dist/provider-icons/antigravity.svg +0 -1
  669. package/gui/dist/provider-icons/claude.svg +0 -1
  670. package/gui/dist/provider-icons/copilot.svg +0 -1
  671. package/gui/dist/provider-icons/cursor.svg +0 -2
  672. package/gui/dist/provider-icons/gemini.svg +0 -1
  673. package/gui/dist/provider-icons/grok-color.svg +0 -1
  674. package/gui/dist/provider-icons/kiro.svg +0 -14
  675. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,55 @@
1
+ import { NATIVE_OPENAI_MODELS, nativeReasoningEfforts } from "../codex/catalog/metadata";
2
+ import {
3
+ VISION_REASONING_EFFORTS,
4
+ sanitizeVisionReasoning,
5
+ type VisionReasoningEffort,
6
+ } from "../reasoning-effort";
7
+
8
+ /** Keep the public config type aligned without replacing the heavily-changed shared types file. */
9
+ declare module "../types" {
10
+ interface OcxVisionSidecarConfig {
11
+ /** OpenAI Responses reasoning effort used by the vision describer. Default: low. */
12
+ reasoning?: VisionReasoningEffort;
13
+ }
14
+ }
15
+
16
+ const NATIVE_VISION_MODELS = new Set(NATIVE_OPENAI_MODELS);
17
+
18
+ /**
19
+ * Return the known vision reasoning ladder for a native OpenAI model.
20
+ * Unknown/custom models deliberately return undefined so callers stay permissive when reliable
21
+ * capability metadata is unavailable.
22
+ */
23
+ export function nativeVisionReasoningEfforts(modelId: string): VisionReasoningEffort[] | undefined {
24
+ if (!NATIVE_VISION_MODELS.has(modelId)) return undefined;
25
+ const advertised = new Set(nativeReasoningEfforts(modelId));
26
+ const supported = VISION_REASONING_EFFORTS.filter(effort => advertised.has(effort));
27
+ return supported.length > 0 ? [...supported] : undefined;
28
+ }
29
+
30
+ /**
31
+ * Normalize one configured effort against a model's known ladder. Invalid enum values degrade to
32
+ * undefined; valid values on unknown/custom models are preserved. A known native model clamps to
33
+ * the highest supported rung at or below the requested value.
34
+ */
35
+ export function normalizeVisionReasoningForModel(
36
+ modelId: string,
37
+ value: unknown,
38
+ ): VisionReasoningEffort | undefined {
39
+ const requested = sanitizeVisionReasoning(value);
40
+ if (!requested) return undefined;
41
+ const supported = nativeVisionReasoningEfforts(modelId);
42
+ if (!supported || supported.includes(requested)) return requested;
43
+
44
+ const requestedRank = VISION_REASONING_EFFORTS.indexOf(requested);
45
+ let best = supported[0];
46
+ let bestRank = VISION_REASONING_EFFORTS.indexOf(best);
47
+ for (const effort of supported) {
48
+ const rank = VISION_REASONING_EFFORTS.indexOf(effort);
49
+ if (rank <= requestedRank && rank >= bestRank) {
50
+ best = effort;
51
+ bestRank = rank;
52
+ }
53
+ }
54
+ return best;
55
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Describe ONE image via a ROUTED model through the proxy's own
3
+ * /v1/chat/completions on loopback (#2188 roadmap 180 revised).
4
+ *
5
+ * One executor for every provider the router can reach: the chat inbound
6
+ * translates image_url parts and each adapter compiles its own wire
7
+ * (Anthropic blocks, Antigravity inlineData, xai Responses input_image, plain
8
+ * openai-chat), so provider coverage is the router's job, not this file's.
9
+ *
10
+ * Recursion fence: the request carries `x-opencodex-vision-describe: 1`.
11
+ * The Chat surface detects the raw header before its bridge rebuilds headers
12
+ * and carries it into handleResponses as `visionDescribeTerminal`; a marked
13
+ * request STRIPS images instead of planning another describe (depth cap 1,
14
+ * holds under predicate drift and combo re-resolution — audit rounds 2-4).
15
+ *
16
+ * Admission ladder (audit round 3): configuredApiAuthToken() (env token) ||
17
+ * service token file || first config.apiKeys entry, sent as
18
+ * `x-opencodex-api-key` — never Authorization (gateway-cache.ts rule: an
19
+ * admission secret in a forwardable header is a forwarding hazard). Loopback
20
+ * binds require no token at all (resolveApiAuth admits loopback).
21
+ *
22
+ * Known limitation (recorded in roadmap 170): a bindHost where 127.0.0.1
23
+ * does not answer cannot reach its own loopback — same latent limitation
24
+ * gateway-cache has.
25
+ */
26
+ import type { OcxConfig } from "../types";
27
+ import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
28
+ import { redactSecretString } from "../lib/redact";
29
+ import { sidecarEnter } from "../lib/sidecar-tracker";
30
+ import { configuredApiAuthToken, configuredPort } from "../server/auth-cors";
31
+ import { loadServiceTokenFromFile } from "../lib/service-secrets";
32
+ import type { DescribeOutcome, VisionSettings } from "./describe";
33
+
34
+ export const VISION_DESCRIBE_TERMINAL_HEADER = "x-opencodex-vision-describe";
35
+
36
+ const ALLOWED_IMAGE_MIME = new Set(["image/png", "image/jpeg", "image/jpg", "image/webp", "image/gif"]);
37
+ const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
38
+ /** Bound the loopback JSON response; descriptions are clamped to ~2k chars by the caller anyway. */
39
+ const MAX_ROUTED_RESPONSE_BYTES = 4 * 1024 * 1024;
40
+
41
+ const DESCRIBE_INSTRUCTION =
42
+ "You are a vision describer for a text-only model that cannot see the image. Describe the image "
43
+ + "thoroughly and factually so that model can fully reason about it: transcribe any visible text "
44
+ + "verbatim, and note UI/layout, colors, branding/logos, charts, and notable details. Focus on "
45
+ + "what's relevant to the user's request. Output only the description.";
46
+
47
+ function validateImageUrl(url: string): string | null {
48
+ if (url.startsWith("data:")) {
49
+ const match = /^data:([^;,]+?)(;base64)?,(.*)$/s.exec(url);
50
+ if (!match) return "malformed data URL";
51
+ const mime = match[1].toLowerCase();
52
+ if (!ALLOWED_IMAGE_MIME.has(mime)) return `unsupported image type "${mime}"`;
53
+ if (match[2]) {
54
+ const bytes = Math.floor((match[3].length * 3) / 4);
55
+ if (bytes > MAX_IMAGE_BYTES) return `image too large (~${Math.round(bytes / 1024 / 1024)}MB)`;
56
+ }
57
+ return null;
58
+ }
59
+ if (url.startsWith("https://")) return null;
60
+ return "unsupported image URL scheme (expected data: or https:)";
61
+ }
62
+
63
+ /** The admission ladder: env token, service token file, first configured API key. */
64
+ export function routedDescribeAdmissionToken(config: Pick<OcxConfig, "apiKeys">): string | undefined {
65
+ const envToken = configuredApiAuthToken();
66
+ if (envToken) return envToken;
67
+ const fileToken = loadServiceTokenFromFile(process.env);
68
+ if (fileToken) return fileToken;
69
+ const first = config.apiKeys?.[0]?.key?.trim();
70
+ return first || undefined;
71
+ }
72
+
73
+ /** Base URL seam for tests; production always self-fetches loopback. */
74
+ export function routedDescribeBaseUrl(config: Pick<OcxConfig, "port">): string {
75
+ // config.port can be 0 (ephemeral bind, tests) or stale after a live port
76
+ // override; the server records its ACTUAL bound port via setCorsOrigin at
77
+ // startup, so prefer that when config carries no positive port.
78
+ const port = config.port && config.port > 0 ? String(config.port) : configuredPort();
79
+ return `http://127.0.0.1:${port}`;
80
+ }
81
+
82
+ export async function describeImageRouted(
83
+ imageUrl: string,
84
+ _detail: string | undefined,
85
+ contextText: string,
86
+ routedModel: string,
87
+ config: Pick<OcxConfig, "port" | "apiKeys">,
88
+ settings: VisionSettings,
89
+ abortSignal?: AbortSignal,
90
+ baseUrlOverride?: string,
91
+ ): Promise<DescribeOutcome> {
92
+ const invalid = validateImageUrl(imageUrl);
93
+ if (invalid) return { text: "", error: invalid };
94
+
95
+ const headers: Record<string, string> = {
96
+ "Content-Type": "application/json",
97
+ [VISION_DESCRIBE_TERMINAL_HEADER]: "1",
98
+ };
99
+ const admission = routedDescribeAdmissionToken(config);
100
+ if (admission) headers["x-opencodex-api-key"] = admission;
101
+
102
+ const requestBody = {
103
+ model: routedModel,
104
+ stream: false,
105
+ messages: [
106
+ { role: "system", content: DESCRIBE_INSTRUCTION },
107
+ {
108
+ role: "user",
109
+ content: [
110
+ ...(contextText ? [{ type: "text", text: `User's request context: ${contextText}` }] : []),
111
+ { type: "image_url", image_url: { url: imageUrl } },
112
+ ],
113
+ },
114
+ ],
115
+ };
116
+
117
+ const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
118
+ const sidecarExit = sidecarEnter("vision");
119
+ const t0 = Date.now();
120
+ try {
121
+ const res = await fetch(`${baseUrlOverride ?? routedDescribeBaseUrl(config)}/v1/chat/completions`, {
122
+ method: "POST",
123
+ headers,
124
+ body: JSON.stringify(requestBody),
125
+ signal: linkedSignal.signal,
126
+ redirect: "manual",
127
+ });
128
+ const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
129
+ try {
130
+ const raw = await res.text();
131
+ if (raw.length > MAX_ROUTED_RESPONSE_BYTES) {
132
+ return { text: "", error: "routed describe response exceeded byte bound" };
133
+ }
134
+ if (!res.ok) {
135
+ return { text: "", error: `routed describe HTTP ${res.status}: ${redactSecretString(raw.slice(0, 200))}` };
136
+ }
137
+ let payload: unknown;
138
+ try { payload = JSON.parse(raw); } catch {
139
+ return { text: "", error: "routed describe returned non-JSON" };
140
+ }
141
+ const content = extractChatContent(payload);
142
+ if (!content) return { text: "", error: "routed describe returned no text" };
143
+ return { text: content };
144
+ } finally {
145
+ detachBodyGuard();
146
+ }
147
+ } catch (e) {
148
+ const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
149
+ console.warn(`[vision] routed describe ${kind} (${Date.now() - t0}ms)`);
150
+ return { text: "", error: redactSecretString(e instanceof Error ? e.message : String(e)) };
151
+ } finally {
152
+ sidecarExit();
153
+ linkedSignal.cleanup();
154
+ }
155
+ }
156
+
157
+ function extractChatContent(payload: unknown): string | undefined {
158
+ if (!payload || typeof payload !== "object") return undefined;
159
+ const choices = (payload as { choices?: unknown }).choices;
160
+ if (!Array.isArray(choices) || choices.length === 0) return undefined;
161
+ const message = (choices[0] as { message?: unknown })?.message;
162
+ if (!message || typeof message !== "object") return undefined;
163
+ const content = (message as { content?: unknown }).content;
164
+ if (typeof content === "string" && content.trim().length > 0) return content;
165
+ // Some adapters emit content parts; join text parts.
166
+ if (Array.isArray(content)) {
167
+ const joined = content
168
+ .map(part => (part && typeof part === "object" && typeof (part as { text?: unknown }).text === "string"
169
+ ? (part as { text: string }).text
170
+ : ""))
171
+ .join("");
172
+ if (joined.trim().length > 0) return joined;
173
+ }
174
+ return undefined;
175
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Inclusive integer bounds for `visionSidecar.timeoutMs`.
3
+ * The ceiling is the 32-bit timer delay used by `signalWithTimeout` / `setTimeout`.
4
+ * This module is the single authority: the runtime, management API, and Dashboard
5
+ * import these numbers rather than restating them.
6
+ */
7
+ export const DEFAULT_VISION_TIMEOUT_MS = 45_000;
8
+ export const MIN_VISION_TIMEOUT_MS = 1;
9
+ export const MAX_VISION_TIMEOUT_MS = 2_147_483_647;
@@ -1,9 +1,8 @@
1
1
  import type { OcxProviderConfig } from "../types";
2
- import { getValidAccessToken } from "../oauth";
2
+ import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
3
3
  import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../oauth/anthropic";
4
4
  import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "../adapters/client-fingerprint";
5
5
  import { signalWithTimeout, cancelBodyOnAbort } from "../lib/abort";
6
- import { redactSecretString } from "../lib/redact";
7
6
  import { sidecarEnter } from "../lib/sidecar-tracker";
8
7
  import { fetchWithResetRetry } from "../lib/upstream-retry";
9
8
  import type { WebSearchSource } from "./parse";
@@ -127,7 +126,7 @@ export async function runAnthropicWebSearch(
127
126
  try {
128
127
  token = await getValidAccessToken(providerName);
129
128
  } catch (e) {
130
- return { text: "", sources: [], error: `anthropic sidecar auth failed: ${e instanceof Error ? e.message : String(e)}` };
129
+ return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
131
130
  }
132
131
  const headers: Record<string, string> = {
133
132
  "Content-Type": "application/json",
@@ -166,13 +165,20 @@ export async function runAnthropicWebSearch(
166
165
  () => fetch(url, { method: "POST", headers, body: JSON.stringify(body), signal: linkedSignal.signal }),
167
166
  { abortSignal: linkedSignal.signal, label: "web-search-sidecar-anthropic" },
168
167
  );
168
+ // Guard before any branch reads the body: the failure branch's `res.text()` ran ahead of
169
+ // the success-path guard, reopening the fetch-resolution-to-reader-attach race
170
+ // (found investigating #1419).
171
+ const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
169
172
  if (!res.ok) {
170
173
  const t = await res.text().catch(() => "");
174
+ detachBodyGuard();
171
175
  console.warn(`[web-search] anthropic sidecar HTTP ${res.status} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
172
- // Redact before surfacing: the body can echo auth headers/tokens (#398 review).
173
- return { text: "", sources: [], error: `sidecar HTTP ${res.status}: ${redactSecretString(t.slice(0, 200))}` };
176
+ if (res.status === 401) {
177
+ return { text: "", sources: [], error: `anthropic sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(new Error(t))}` };
178
+ }
179
+ // Upstream bodies are untrusted and may contain credentials, paths, or provider diagnostics.
180
+ return { text: "", sources: [], error: `sidecar HTTP ${res.status}` };
174
181
  }
175
- const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
176
182
  try {
177
183
  return await parseAnthropicSidecarSSE(res);
178
184
  } finally {
@@ -181,7 +187,7 @@ export async function runAnthropicWebSearch(
181
187
  } catch (e) {
182
188
  const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
183
189
  console.warn(`[web-search] anthropic sidecar ${kind} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
184
- return { text: "", sources: [], error: e instanceof Error ? e.message : String(e) };
190
+ return { text: "", sources: [], error: `anthropic sidecar ${kind}` };
185
191
  } finally {
186
192
  sidecarExit();
187
193
  linkedSignal.cleanup();
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Which backends may RUN a hosted web_search, and which candidate rows each
3
+ * can run (#2188 rule 2 for web-search: ∩ probed backend with an executor).
4
+ *
5
+ * A backend registers here only when BOTH hold:
6
+ * - an executor exists in this repository (src/web-search/executor.ts or
7
+ * anthropic-executor.ts today), and
8
+ * - its liveness probe passes. For the two shipped backends the probe IS
9
+ * auth presence — the ChatGPT forward path and the stored Anthropic OAuth
10
+ * path fail closed without a credential, so a live credential is the
11
+ * strongest pre-flight signal short of spending a search.
12
+ *
13
+ * Future backends (Gemini google_search, Grok web_search, Zen hosted search,
14
+ * Exa-class vendors) stay OUT of this table until a live probe and an
15
+ * executor land — the research and probe contracts are recorded in
16
+ * devlog/_plan/260820_sidecar_selection_unification/002 and 031. Documenting
17
+ * a tool is not the same as being able to run it.
18
+ */
19
+ import type { OcxConfig } from "../types";
20
+ import { AUTH_SLOT_MODELS, type SidecarAuthState } from "../sidecar/auth";
21
+ import type { SidecarCandidate } from "../sidecar/candidates";
22
+ import { getAccountSet } from "../oauth/store";
23
+ import type { WebSearchBackendId } from "./index";
24
+
25
+ export interface WebSearchBackendDescriptor {
26
+ backend: WebSearchBackendId;
27
+ /** Liveness signal for this backend (auth presence for the shipped two). */
28
+ isActive(auth: SidecarAuthState, config: OcxConfig): boolean;
29
+ /** Which candidate rows this backend's executor can actually run. */
30
+ eligibleModel(candidate: SidecarCandidate, auth: SidecarAuthState): boolean;
31
+ }
32
+
33
+ export const WEB_SEARCH_BACKENDS: readonly WebSearchBackendDescriptor[] = [
34
+ {
35
+ backend: "openai",
36
+ isActive: auth => auth.isCodexAuth,
37
+ // The ChatGPT forward executor runs BARE native slugs and the Codex auth
38
+ // slot — settings.model is POSTed verbatim to the forward /responses, so
39
+ // an account-bound "selector/slug" row (model-rows emits those as
40
+ // provider "openai", native true) or a custom openai-keyed row would
41
+ // persist an id the executor cannot run (review F1). The sidecar never
42
+ // calls routeModel; there is no prefix-stripping on this path.
43
+ eligibleModel: candidate => candidate.provider === "openai"
44
+ && (candidate.native === true || candidate.authSlot === true)
45
+ && !candidate.id.includes("/"),
46
+ },
47
+ {
48
+ backend: "anthropic",
49
+ isActive: auth => auth.isAnthropicAuth,
50
+ // The stored-OAuth Messages executor dispatches through exactly ONE
51
+ // provider — the one the shared auth module resolved. Same-adapter keyed
52
+ // rows are unreachable, mirroring visionBackendForCandidate's stance.
53
+ eligibleModel: (candidate, auth) => candidate.provider === auth.anthropicProviderName,
54
+ },
55
+ {
56
+ backend: "xai",
57
+ // Probe = stored Grok OAuth usable: enabled oauth-mode "xai" provider whose
58
+ // active account is not marked for reauth — the same predicate
59
+ // findXaiSidecarProvider applies at plan time (L7).
60
+ isActive: (_auth, config) => {
61
+ const provider = config.providers["xai"];
62
+ if (!provider || provider.disabled === true || provider.authMode !== "oauth") return false;
63
+ const set = getAccountSet("xai");
64
+ const active = set?.accounts.find(account => account.id === set.activeAccountId);
65
+ return !!active && active.needsReauth !== true;
66
+ },
67
+ eligibleModel: candidate => candidate.provider === "xai",
68
+ },
69
+ {
70
+ backend: "gemini",
71
+ // Probe = usable Antigravity OAuth + discovered projectId (findGeminiSidecarProvider's predicate).
72
+ isActive: (_auth, config) => {
73
+ const provider = config.providers["google-antigravity"];
74
+ if (!provider || provider.disabled === true || provider.authMode !== "oauth") return false;
75
+ const set = getAccountSet("google-antigravity");
76
+ const active = set?.accounts.find(account => account.id === set.activeAccountId);
77
+ if (!active || active.needsReauth === true) return false;
78
+ return !!(active.credential as { projectId?: string } | undefined)?.projectId;
79
+ },
80
+ eligibleModel: candidate => candidate.provider === "google-antigravity",
81
+ },
82
+ {
83
+ backend: "exa",
84
+ // Probe = operator key present. Exa is not an LLM: no candidate models ever
85
+ // match, so the GUI's model list stays untouched by this backend.
86
+ isActive: (_auth, config) => !!config.webSearchSidecar?.exaApiKey,
87
+ eligibleModel: () => false,
88
+ },
89
+ ];
90
+
91
+ /**
92
+ * (picker-visible ∪ auth slots) ∩ (active backend able to run the row).
93
+ * The auth slots always survive their own side's activation: a logged-in
94
+ * side keeps Luna/Haiku even when the picker hides them.
95
+ */
96
+ export function webSearchSidecarCandidates(
97
+ config: OcxConfig,
98
+ auth: SidecarAuthState,
99
+ all: readonly SidecarCandidate[],
100
+ ): SidecarCandidate[] {
101
+ const active = WEB_SEARCH_BACKENDS.filter(descriptor => descriptor.isActive(auth, config));
102
+ return all.filter(candidate => active.some(descriptor => descriptor.eligibleModel(candidate, auth)));
103
+ }
104
+
105
+ /** True when the id is one of the fixed auth-slot models (#2188 write-gate exception). */
106
+ export function isWebSearchAuthSlotModel(id: string): boolean {
107
+ return id === AUTH_SLOT_MODELS.codex || id === AUTH_SLOT_MODELS.anthropic;
108
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Execute ONE web search via the Exa Search API (#2188 L9) — the non-LLM lane.
3
+ *
4
+ * Exa returns ranked result JSON, not a prose answer: the outcome's text is a
5
+ * per-result digest the routed model synthesizes from. Probe-verified 2026-08-21
6
+ * (devlog 002): POST https://api.exa.ai/search with x-api-key returns
7
+ * {requestId, results[{title,url,publishedDate,text}], costDollars}.
8
+ * redirect: "manual" because Bun forwards custom headers across redirects.
9
+ * Never throws; every error string passes redactSecretString.
10
+ */
11
+ import { fetchWithResetRetry } from "../lib/upstream-retry";
12
+ import { signalWithTimeout } from "../lib/abort";
13
+ import { sidecarEnter } from "../lib/sidecar-tracker";
14
+ import { redactSecretString } from "../lib/redact";
15
+ import type { WebSearchSource } from "./parse";
16
+ import type { SidecarOutcome, SidecarSettings } from "./executor";
17
+
18
+ const EXA_SEARCH_URL = "https://api.exa.ai/search";
19
+ const EXA_NUM_RESULTS = 5;
20
+ const EXA_SNIPPET_CHARS = 1000;
21
+
22
+ function isRec(v: unknown): v is Record<string, unknown> {
23
+ return !!v && typeof v === "object" && !Array.isArray(v);
24
+ }
25
+
26
+ export async function runExaWebSearch(
27
+ query: string,
28
+ apiKey: string,
29
+ settings: SidecarSettings,
30
+ abortSignal?: AbortSignal,
31
+ ): Promise<SidecarOutcome> {
32
+ if (!apiKey) return { text: "", sources: [], error: "exa backend selected without an exaApiKey" };
33
+ // The executor KNOWS the secret — pattern-based redaction cannot be trusted to
34
+ // recognize an arbitrary operator key, so scrub the literal value explicitly.
35
+ const scrub = (s: string) => redactSecretString(s.split(apiKey).join("[redacted-exa-key]"));
36
+ const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
37
+ const sidecarExit = sidecarEnter("web-search");
38
+ const t0 = Date.now();
39
+ try {
40
+ const res = await fetchWithResetRetry(
41
+ () => fetch(EXA_SEARCH_URL, {
42
+ method: "POST",
43
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
44
+ body: JSON.stringify({ query, numResults: EXA_NUM_RESULTS, contents: { text: { maxCharacters: EXA_SNIPPET_CHARS } } }),
45
+ signal: linkedSignal.signal,
46
+ redirect: "manual",
47
+ }),
48
+ { abortSignal: linkedSignal.signal, label: "exa-web-search-sidecar" },
49
+ );
50
+ if (!res.ok) {
51
+ const t = await res.text().catch(() => "");
52
+ // Scrub BEFORE truncating: slicing first can cut the literal key at the
53
+ // boundary, leaving an unscrubbable key prefix in the surviving text.
54
+ return { text: "", sources: [], error: `exa sidecar HTTP ${res.status}: ${scrub(t).slice(0, 200)}` };
55
+ }
56
+ const payload = await res.json().catch(() => null);
57
+ return mapExaSearchResponse(payload);
58
+ } catch (e) {
59
+ const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
60
+ console.warn(`[web-search] exa sidecar ${kind} (${Date.now() - t0}ms)`);
61
+ return { text: "", sources: [], error: scrub(e instanceof Error ? e.message : String(e)) };
62
+ } finally {
63
+ sidecarExit();
64
+ linkedSignal.cleanup();
65
+ }
66
+ }
67
+
68
+ /** Map an Exa /search payload to a digest the routed model can synthesize from. */
69
+ export function mapExaSearchResponse(payload: unknown): SidecarOutcome {
70
+ if (!isRec(payload) || !Array.isArray(payload.results)) {
71
+ return { text: "", sources: [], error: "exa sidecar returned a non-JSON or shapeless body" };
72
+ }
73
+ const sources: WebSearchSource[] = [];
74
+ const lines: string[] = [];
75
+ const seen = new Set<string>();
76
+ for (const result of payload.results) {
77
+ if (!isRec(result) || typeof result.url !== "string" || result.url.length === 0) continue;
78
+ if (seen.has(result.url)) continue;
79
+ seen.add(result.url);
80
+ const title = typeof result.title === "string" && result.title.length > 0 ? result.title : result.url;
81
+ sources.push({ url: result.url, ...(title !== result.url ? { title } : {}) });
82
+ const snippet = typeof result.text === "string" ? result.text.trim().slice(0, EXA_SNIPPET_CHARS) : "";
83
+ const dated = typeof result.publishedDate === "string" ? ` (${result.publishedDate.slice(0, 10)})` : "";
84
+ lines.push(`- ${title}${dated}: ${snippet || "(no excerpt)"} [${result.url}]`);
85
+ }
86
+ if (lines.length === 0) return { text: "", sources: [], error: "exa sidecar returned no results" };
87
+ return { text: `Search results:\n${lines.join("\n")}`, sources };
88
+ }
@@ -62,8 +62,8 @@ export async function runWebSearch(
62
62
  tool_choice: "auto",
63
63
  reasoning: { effort: settings.reasoning },
64
64
  // NOTE: the ChatGPT (codex) backend rejects `max_output_tokens` ("Unsupported parameter") and
65
- // requires `store: false` — keep this body minimal. Answer length is capped downstream
66
- // (format-result clamps the injected tool_result), so no upstream cap is needed.
65
+ // requires `store: false` — keep this body minimal. The shared SSE parser bounds raw response
66
+ // bytes before format-result applies its smaller display clamp.
67
67
  store: false,
68
68
  stream: true,
69
69
  };
@@ -78,16 +78,24 @@ export async function runWebSearch(
78
78
  headers,
79
79
  body: JSON.stringify(body),
80
80
  signal: linkedSignal.signal,
81
+ // Credential-bearing: do not follow a cross-origin 3xx. Bun strips `Authorization`
82
+ // across origins but forwards nonstandard headers such as `chatgpt-account-id`,
83
+ // `session_id`, and `x-codex-turn-metadata` to the redirect target.
84
+ redirect: "manual",
81
85
  }),
82
86
  { abortSignal: linkedSignal.signal, label: "web-search-sidecar" },
83
87
  );
84
88
  recordOutcome?.(res.status);
89
+ // Attach the body guard before ANY branch reads it. The success path guarded itself below,
90
+ // but the failure branch's `res.text()` runs first, so a cancel landing between fetch
91
+ // resolution and reader attach orphaned the internal rejection (found investigating #1419).
92
+ const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
85
93
  if (!res.ok) {
86
94
  const t = await res.text().catch(() => "");
95
+ detachBodyGuard();
87
96
  console.warn(`[web-search] sidecar HTTP ${res.status} for query "${query.slice(0, 80)}" (${Date.now() - t0}ms)`);
88
97
  return { text: "", sources: [], error: `sidecar HTTP ${res.status}: ${redactSecretString(t.slice(0, 200))}` };
89
98
  }
90
- const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
91
99
  try {
92
100
  return await parseSidecarSSE(res);
93
101
  } finally {
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Execute ONE web search via Gemini google_search grounding on the Antigravity
3
+ * Cloud Code Assist transport (#2188 L8). Live-probed 2026-08-20/21 (devlog 002):
4
+ * the CCA envelope with tools [{google_search:{}}] returns a grounded answer
5
+ * plus groundingMetadata; a non-IDE User-Agent gets 404, so the request reuses
6
+ * the adapter's fingerprint constants. The OAuth bearer only ever travels to
7
+ * the REGISTRY-pinned endpoint — a config-level baseUrl override is never
8
+ * trusted for token transmission (same rule as src/server/images.ts).
9
+ * Never throws — returns {error} so the caller injects a graceful tool result.
10
+ */
11
+ import type { OcxProviderConfig } from "../types";
12
+ import { getValidAccessTokenSnapshot, publicOAuthAuthenticationErrorMessage } from "../oauth";
13
+ import { fetchWithResetRetry } from "../lib/upstream-retry";
14
+ import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
15
+ import { readBoundedResponseBytes } from "../lib/bounded-body";
16
+ import { sidecarEnter } from "../lib/sidecar-tracker";
17
+ import { redactSecretString } from "../lib/redact";
18
+ import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
19
+ import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
20
+ import { getProviderRegistryEntry } from "../providers/registry";
21
+ import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
22
+ import { BASE_INSTRUCTION, IMAGE_INSTRUCTION, type SidecarOutcome, type SidecarSettings } from "./executor";
23
+
24
+ const CCA_FALLBACK_BASE = "https://daily-cloudcode-pa.googleapis.com";
25
+
26
+ function isRec(v: unknown): v is Record<string, unknown> {
27
+ return !!v && typeof v === "object" && !Array.isArray(v);
28
+ }
29
+
30
+ export async function runGeminiWebSearch(
31
+ query: string,
32
+ providerName: string,
33
+ _provider: OcxProviderConfig,
34
+ settings: SidecarSettings,
35
+ abortSignal?: AbortSignal,
36
+ ): Promise<SidecarOutcome> {
37
+ let token: string;
38
+ let project: string | undefined;
39
+ try {
40
+ const snapshot = await getValidAccessTokenSnapshot(providerName);
41
+ token = snapshot.accessToken;
42
+ project = snapshot.projectId;
43
+ } catch (e) {
44
+ return { text: "", sources: [], error: `gemini sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
45
+ }
46
+ if (!project) {
47
+ return { text: "", sources: [], error: "gemini sidecar missing Cloud Code Assist project id — re-run ocx login google-antigravity" };
48
+ }
49
+ // Destination pinned to the registry endpoint (see module doc).
50
+ const base = getProviderRegistryEntry("google-antigravity")?.baseUrl ?? CCA_FALLBACK_BASE;
51
+ const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(settings.model, settings.reasoning, base);
52
+ const instruction = settings.describeImages ? BASE_INSTRUCTION + IMAGE_INSTRUCTION : BASE_INSTRUCTION;
53
+ const envelope = {
54
+ model: wireModelId,
55
+ userAgent: "antigravity",
56
+ requestType: "agent",
57
+ project,
58
+ requestId: `agent-${crypto.randomUUID()}`,
59
+ request: {
60
+ systemInstruction: { role: "user", parts: [{ text: instruction }] },
61
+ contents: [{ role: "user", parts: [{ text: query }] }],
62
+ tools: [{ google_search: {} }],
63
+ sessionId: crypto.randomUUID(),
64
+ ...(thinkingLevel ? { generationConfig: { thinkingConfig: { thinkingLevel } } } : {}),
65
+ },
66
+ };
67
+ const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
68
+ const sidecarExit = sidecarEnter("web-search");
69
+ const t0 = Date.now();
70
+ try {
71
+ const res = await fetchWithResetRetry(
72
+ () => fetch(`${base}/v1internal:generateContent`, {
73
+ method: "POST",
74
+ headers: {
75
+ "Content-Type": "application/json",
76
+ "Authorization": `Bearer ${token}`,
77
+ "User-Agent": ANTIGRAVITY_REQUEST_UA,
78
+ },
79
+ body: JSON.stringify(envelope),
80
+ signal: linkedSignal.signal,
81
+ redirect: "manual",
82
+ }),
83
+ { abortSignal: linkedSignal.signal, label: "gemini-web-search-sidecar" },
84
+ );
85
+ const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
86
+ try {
87
+ const bounded = await readBoundedResponseBytes(res, {
88
+ maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
89
+ signal: linkedSignal.signal,
90
+ });
91
+ if (bounded.oversized) {
92
+ const prefix = res.ok ? "gemini sidecar response" : `gemini sidecar HTTP ${res.status} response`;
93
+ return { text: "", sources: [], error: `${prefix} exceeded byte bound` };
94
+ }
95
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bounded.bytes);
96
+ if (!res.ok) {
97
+ return { text: "", sources: [], error: `gemini sidecar HTTP ${res.status}: ${redactSecretString(text.slice(0, 200))}` };
98
+ }
99
+ let payload: unknown = null;
100
+ try {
101
+ payload = JSON.parse(text);
102
+ } catch {
103
+ // The mapper owns the stable malformed/empty JSON outcome.
104
+ }
105
+ return mapCcaGroundedResponse(payload);
106
+ } finally {
107
+ detachBodyGuard();
108
+ }
109
+ } catch (e) {
110
+ const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
111
+ console.warn(`[web-search] gemini sidecar ${kind} (${Date.now() - t0}ms)`);
112
+ return { text: "", sources: [], error: redactSecretString(e instanceof Error ? e.message : String(e)) };
113
+ } finally {
114
+ sidecarExit();
115
+ linkedSignal.cleanup();
116
+ }
117
+ }
118
+
119
+ /** Map a CCA generateContent payload (possibly wrapped in {response}) to text + grounding sources. */
120
+ export function mapCcaGroundedResponse(payload: unknown): SidecarOutcome {
121
+ const root = isRec(payload) && isRec(payload.response) ? payload.response : payload;
122
+ if (!isRec(root)) return { text: "", sources: [], error: "gemini sidecar returned a non-JSON or empty body" };
123
+ const candidate = Array.isArray(root.candidates) && isRec(root.candidates[0]) ? root.candidates[0] : undefined;
124
+ if (!candidate) return { text: "", sources: [], error: "gemini sidecar returned no candidates" };
125
+ const parts = isRec(candidate.content) && Array.isArray(candidate.content.parts) ? candidate.content.parts : [];
126
+ const text = parts.map(p => (isRec(p) && typeof p.text === "string" ? p.text : "")).join("");
127
+ const sources: WebSearchSource[] = [];
128
+ const seen = new Set<string>();
129
+ const gm = isRec(candidate.groundingMetadata) ? candidate.groundingMetadata : undefined;
130
+ if (gm && Array.isArray(gm.groundingChunks)) {
131
+ for (const chunk of gm.groundingChunks) {
132
+ const web = isRec(chunk) && isRec(chunk.web) ? chunk.web : undefined;
133
+ const uri = web && typeof web.uri === "string" ? web.uri : undefined;
134
+ if (!uri || seen.has(uri)) continue;
135
+ seen.add(uri);
136
+ sources.push({ url: uri, ...(typeof web?.title === "string" && web.title.length > 0 ? { title: web.title } : {}) });
137
+ }
138
+ }
139
+ if (text.length === 0) return { text: "", sources, error: "gemini sidecar returned no text" };
140
+ return { text, sources };
141
+ }