@yansigit/opencodex 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 (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,1327 @@
1
+ import type { IncomingMeta, ProviderAdapter } from "./base";
2
+ import { createToolCallIdAllocator, type ToolCallIdAllocator } from "./tool-call-id";
3
+ import { debugDroppedFrame } from "../lib/debug";
4
+ import type {
5
+ AdapterEvent,
6
+ OcxAssistantMessage,
7
+ OcxContentPart,
8
+ OcxMessage,
9
+ OcxParsedRequest,
10
+ OcxProviderConfig,
11
+ OcxTextContent,
12
+ OcxThinkingContent,
13
+ OcxToolCall,
14
+ OcxToolResultMessage,
15
+ OcxUsage,
16
+ } from "../types";
17
+ import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolAllowedByChoice } from "../types";
18
+ import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION, applyClaudeToolPrefix, stripClaudeToolPrefix } from "../oauth/anthropic";
19
+ import { parseDataUrl } from "./image";
20
+ import { enforceAnthropicImageLimits } from "./anthropic-image-guard";
21
+ import { normalizeAnthropicImages } from "./anthropic-image-normalize";
22
+ import { normalizeAnthropicOutputSchema } from "./anthropic-output-schema";
23
+ import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema";
24
+ import { identifyRoutedModel } from "./identity";
25
+ import { redactSecretString } from "../lib/redact";
26
+ import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "./client-fingerprint";
27
+ import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
28
+ import { decodeServerSentEvents } from "../lib/sse-decoder";
29
+ import { isTranslatorBudgetExceededError, retainTranslatedEventBatch, type TranslatorBudget } from "../lib/translator-budget";
30
+
31
+ /** Map a user content part to an Anthropic content block (text or image source). */
32
+ function toAnthropicContentPart(p: OcxContentPart): unknown {
33
+ if (p.type === "image") {
34
+ const data = parseDataUrl(p.imageUrl);
35
+ return data
36
+ ? { type: "image", source: { type: "base64", media_type: data.mediaType, data: data.base64 } }
37
+ : { type: "image", source: { type: "url", url: p.imageUrl } };
38
+ }
39
+ return { type: "text", text: p.text };
40
+ }
41
+
42
+ /** Default `max_tokens` when Codex omits `max_output_tokens`. */
43
+ const DEFAULT_MAX_TOKENS = 8192;
44
+ /** Safe ceiling for `max_tokens` (thinking + visible output) across current Claude 4.x models. */
45
+ const REASONING_MAX_TOKENS_CEILING = 32_000;
46
+ /** Adaptive-thinking ceiling: max effort budget (32k) + OUTPUT_HEADROOM (8k).
47
+ * Must exceed REASONING_MAX_TOKENS_CEILING so effort=max actually preserves visible-output room. */
48
+ const ADAPTIVE_THINKING_CEILING = 40_192;
49
+ /** Anthropic's documented minimum `thinking.budget_tokens`. */
50
+ const MIN_THINKING_BUDGET = 1024;
51
+ /** Visible-output room added above the thinking budget when sizing `max_tokens`. */
52
+ const OUTPUT_HEADROOM = 8192;
53
+ /** Minimum visible-output room kept below `max_tokens` (so `max_tokens > budget_tokens` always holds). */
54
+ const OUTPUT_FLOOR = 4096;
55
+ const COMPAT_TOOL_PREFIX = "cx_";
56
+ type CacheControl = { type: "ephemeral"; ttl?: "1h" | "5m" };
57
+ const MAX_CACHE_BREAKPOINTS = 4;
58
+
59
+ function resolveCacheControl(retention: "none" | "short" | "long" | undefined): CacheControl | undefined {
60
+ const r = retention ?? "short";
61
+ if (r === "none") return undefined;
62
+ return r === "long" ? { type: "ephemeral", ttl: "1h" } : { type: "ephemeral" };
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // Prompt-caching breakpoint placement (ported from jawcode)
66
+ //
67
+ // Strategy: place cache_control breakpoints on up to 4 locations in order
68
+ // of stability (most stable first), so Anthropic's cumulative prefix cuts
69
+ // maximise cache hits across turns:
70
+ // 1. tools (last block) — changes rarely
71
+ // 2. system (last block) — changes rarely
72
+ // 3. penultimate user message — stable across the current turn
73
+ // 4. last user message — the new turn's content
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function applyCacheControlToLast<T extends Record<string, unknown>>(blocks: T[], cc: CacheControl): void {
77
+ if (blocks.length === 0) return;
78
+ const i = blocks.length - 1;
79
+ blocks[i] = { ...blocks[i], cache_control: cc };
80
+ }
81
+
82
+ function applyCacheControlToLastText(blocks: Array<Record<string, unknown>>, cc: CacheControl): void {
83
+ for (let i = blocks.length - 1; i >= 0; i--) {
84
+ if (blocks[i].type === "text") {
85
+ blocks[i] = { ...blocks[i], cache_control: cc };
86
+ return;
87
+ }
88
+ }
89
+ applyCacheControlToLast(blocks, cc);
90
+ }
91
+
92
+ type PromptCachingOptions = {
93
+ maxExplicitBreakpoints?: number;
94
+ skipLastUser?: boolean;
95
+ };
96
+
97
+ /** Place explicit cache_control breakpoints on the built Anthropic body. */
98
+ function applyPromptCaching(
99
+ body: Record<string, unknown>,
100
+ cc: CacheControl | undefined,
101
+ options: PromptCachingOptions = {},
102
+ ): void {
103
+ if (!cc) return;
104
+ const explicitLimit = options.maxExplicitBreakpoints ?? MAX_CACHE_BREAKPOINTS;
105
+ if (explicitLimit <= 0) return;
106
+
107
+ const messages = body.messages as Array<Record<string, unknown>> | undefined;
108
+
109
+ // Skip if external breakpoints are already present on messages.
110
+ if (messages) {
111
+ for (const msg of messages) {
112
+ if (Array.isArray(msg.content)) {
113
+ if ((msg.content as Array<Record<string, unknown>>).some(b => b.cache_control != null)) return;
114
+ }
115
+ }
116
+ }
117
+
118
+ let used = 0;
119
+
120
+ // 1. tools
121
+ const tools = body.tools as Array<Record<string, unknown>> | undefined;
122
+ if (tools && tools.length > 0) {
123
+ applyCacheControlToLast(tools, cc);
124
+ used++;
125
+ }
126
+ if (used >= explicitLimit) return;
127
+
128
+ // 2. system
129
+ const system = body.system as Array<Record<string, unknown>> | undefined;
130
+ if (system && system.length > 0) {
131
+ applyCacheControlToLast(system, cc);
132
+ used++;
133
+ }
134
+ if (used >= explicitLimit || !messages) return;
135
+
136
+ // Locate user-role message indexes.
137
+ const userIdxs: number[] = [];
138
+ for (let i = 0; i < messages.length; i++) {
139
+ if (messages[i].role === "user") userIdxs.push(i);
140
+ }
141
+
142
+ // 3. penultimate user message
143
+ if (userIdxs.length >= 2) {
144
+ const msg = messages[userIdxs[userIdxs.length - 2]];
145
+ if (typeof msg.content === "string") {
146
+ msg.content = [{ type: "text", text: msg.content, cache_control: cc }];
147
+ } else if (Array.isArray(msg.content) && msg.content.length > 0) {
148
+ applyCacheControlToLastText(msg.content as Array<Record<string, unknown>>, cc);
149
+ }
150
+ used++;
151
+ }
152
+ if (used >= explicitLimit || options.skipLastUser) return;
153
+
154
+ // 4. last user message
155
+ if (userIdxs.length >= 1) {
156
+ const msg = messages[userIdxs[userIdxs.length - 1]];
157
+ if (typeof msg.content === "string") {
158
+ msg.content = [{ type: "text", text: msg.content, cache_control: cc }];
159
+ } else if (Array.isArray(msg.content) && msg.content.length > 0) {
160
+ applyCacheControlToLastText(msg.content as Array<Record<string, unknown>>, cc);
161
+ }
162
+ }
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Breakpoint cap enforcement — strip excess beyond the 4-breakpoint limit
167
+ // ---------------------------------------------------------------------------
168
+
169
+ function countBreakpoints(body: Record<string, unknown>): number {
170
+ let total = 0;
171
+ const count = (blocks: Array<Record<string, unknown>> | undefined) => {
172
+ if (!blocks) return;
173
+ for (const b of blocks) if (b.cache_control) total++;
174
+ };
175
+ count(body.tools as Array<Record<string, unknown>> | undefined);
176
+ count(body.system as Array<Record<string, unknown>> | undefined);
177
+ const messages = body.messages as Array<Record<string, unknown>> | undefined;
178
+ if (messages) {
179
+ for (const msg of messages) {
180
+ if (Array.isArray(msg.content)) count(msg.content as Array<Record<string, unknown>>);
181
+ }
182
+ }
183
+ return total;
184
+ }
185
+
186
+ function enforceCacheControlLimit(body: Record<string, unknown>, limit = MAX_CACHE_BREAKPOINTS): void {
187
+ const total = countBreakpoints(body);
188
+ if (total <= limit) return;
189
+ let excess = total - limit;
190
+ // Strip from messages first (least stable), then system, then tools.
191
+ const messages = body.messages as Array<Record<string, unknown>> | undefined;
192
+ if (messages) {
193
+ for (const msg of messages) {
194
+ if (excess <= 0) break;
195
+ if (!Array.isArray(msg.content)) continue;
196
+ for (const block of msg.content as Array<Record<string, unknown>>) {
197
+ if (excess <= 0) break;
198
+ if (block.cache_control) { delete block.cache_control; excess--; }
199
+ }
200
+ }
201
+ }
202
+ const stripBlocks = (blocks: Array<Record<string, unknown>> | undefined) => {
203
+ if (!blocks) return;
204
+ for (const b of blocks) {
205
+ if (excess <= 0) break;
206
+ if (b.cache_control) { delete b.cache_control; excess--; }
207
+ }
208
+ };
209
+ if (excess > 0) stripBlocks(body.system as Array<Record<string, unknown>> | undefined);
210
+ if (excess > 0) stripBlocks(body.tools as Array<Record<string, unknown>> | undefined);
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // TTL ordering — Anthropic requires 1-hour breakpoints before 5-minute ones
215
+ // ---------------------------------------------------------------------------
216
+
217
+ function normalizeTtlOrdering(body: Record<string, unknown>): void {
218
+ const allBlocks: Array<Record<string, unknown>> = [];
219
+ const collect = (blocks: Array<Record<string, unknown>> | undefined) => {
220
+ if (!blocks) return;
221
+ for (const b of blocks) if (b.cache_control) allBlocks.push(b);
222
+ };
223
+ collect(body.tools as Array<Record<string, unknown>> | undefined);
224
+ collect(body.system as Array<Record<string, unknown>> | undefined);
225
+ const messages = body.messages as Array<Record<string, unknown>> | undefined;
226
+ if (messages) {
227
+ for (const msg of messages) {
228
+ if (Array.isArray(msg.content)) collect(msg.content as Array<Record<string, unknown>>);
229
+ }
230
+ }
231
+ // Walk forward: once we see a 5-min (no ttl / ttl:"5m"), any subsequent 1h must be demoted.
232
+ let seenShort = false;
233
+ for (const b of allBlocks) {
234
+ const cc = b.cache_control as CacheControl;
235
+ if (cc.ttl !== "1h") {
236
+ seenShort = true;
237
+ } else if (seenShort) {
238
+ // 1h after a short → demote to default (5m)
239
+ delete cc.ttl;
240
+ }
241
+ }
242
+ }
243
+
244
+ function isLikelyRealAnthropicThinkingSignature(signature: string | undefined): signature is string {
245
+ if (typeof signature !== "string" || signature.length < 16) return false;
246
+ if (/^(fc|call|msg|rs|resp|reasoning|item|ws|tool|func|function)[-_]/i.test(signature)) return false;
247
+ return /^[A-Za-z0-9+/_=-]+$/.test(signature);
248
+ }
249
+
250
+ /**
251
+ * Bridge error fidelity (web-search/images loops): extract a display-safe summary from an
252
+ * Anthropic JSON error envelope so `Provider error <status>` carries the upstream reason.
253
+ * JSON-only extraction — HTML/non-JSON bodies yield "" so raw markup is never echoed.
254
+ */
255
+ export function formatAnthropicErrorBody(status: number, _headers: Headers, payloadText: string): string {
256
+ let parsed: unknown;
257
+ try {
258
+ parsed = JSON.parse(payloadText);
259
+ } catch {
260
+ return "";
261
+ }
262
+ const detail = extractAnthropicErrorDetail(parsed);
263
+ if (!detail) return "";
264
+ return redactSecretString(detail).slice(0, 400);
265
+ }
266
+
267
+ function extractAnthropicErrorDetail(parsed: unknown): string | undefined {
268
+ if (typeof parsed === "string") return parsed.trim() || undefined;
269
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
270
+ const obj = parsed as Record<string, unknown>;
271
+ // Anthropic envelope: { type: "error", error: { type, message } }; tolerate a bare
272
+ // { error: { message } } and a string error field.
273
+ const err = obj.error;
274
+ if (typeof err === "string" && err.trim()) return err.trim();
275
+ if (err !== null && typeof err === "object" && !Array.isArray(err)) {
276
+ const e = err as Record<string, unknown>;
277
+ const msg = e.message;
278
+ if (typeof msg !== "string" || !msg.trim()) return undefined;
279
+ const type = e.type;
280
+ return typeof type === "string" && type.trim()
281
+ ? `${type.trim()}: ${msg.trim()}`
282
+ : msg.trim();
283
+ }
284
+ return undefined;
285
+ }
286
+
287
+ function usesNativeAnthropicEndpoint(provider: OcxProviderConfig): boolean {
288
+ try {
289
+ return new URL(provider.baseUrl).hostname === "api.anthropic.com";
290
+ } catch {
291
+ throw new Error(`anthropic provider has malformed baseUrl: ${provider.baseUrl}`);
292
+ }
293
+ }
294
+
295
+ /** Normalize provider baseUrl paths ending in `/`, `/v1`, or `/v1/messages` to `{origin}/v1/messages`. */
296
+ export function anthropicMessagesUrl(baseUrl: string): string {
297
+ try {
298
+ new URL(baseUrl);
299
+ } catch {
300
+ throw new Error(`anthropic provider has malformed baseUrl: ${baseUrl}`);
301
+ }
302
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
303
+ const root = trimmed.replace(/\/v1\/messages\/?$/i, "").replace(/\/v1\/?$/i, "").replace(/\/+$/, "");
304
+ return `${root}/v1/messages`;
305
+ }
306
+
307
+ function synthesizeToolUseId(): string {
308
+ return `toolu_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
309
+ }
310
+
311
+ /**
312
+ * A tool_use id that a client can actually echo back. `??` only catches a missing
313
+ * field, so an Anthropic-compatible relay that sends `""` or `" "` produced a
314
+ * call whose id round-trips as blank — the next turn then cannot pair the result
315
+ * with its call. Treat blank as absent and synthesize (#765).
316
+ */
317
+ function usableToolUseId(id: unknown): string {
318
+ return typeof id === "string" && id.trim() ? id : synthesizeToolUseId();
319
+ }
320
+
321
+ /**
322
+ * Bound repair for a malformed tool-arguments string under the compatibility profile (#658):
323
+ * a gateway such as AgentRouter can concatenate JSON objects (`{}{"value":42}`). Find the
324
+ * last parseable JSON object by scanning suffixes from each object-open brace and prefixes
325
+ * ending at each object-close brace. Both scans walk backwards from the end trying at most
326
+ * `maxCandidates` positions, so no offset index is ever materialized: a brace-dense hostile
327
+ * input costs at most 2 × maxCandidates bounded JSON.parse attempts and no extra storage.
328
+ * Inputs above MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES are not repaired at all.
329
+ */
330
+ const MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES = 1024 * 1024;
331
+
332
+ /**
333
+ * Whether `input` encodes to more than `max` UTF-8 bytes, with an early exit so the check
334
+ * itself never allocates a copy of a hostile string. `string.length` counts UTF-16 code
335
+ * units, which undercounts astral text by 2x against a byte budget.
336
+ */
337
+ function utf8BytesExceed(input: string, max: number): boolean {
338
+ let bytes = 0;
339
+ for (let i = 0; i < input.length; i++) {
340
+ const code = input.charCodeAt(i);
341
+ if (code < 0x80) bytes += 1;
342
+ else if (code < 0x800) bytes += 2;
343
+ else if (code >= 0xd800 && code <= 0xdbff && i + 1 < input.length
344
+ && input.charCodeAt(i + 1) >= 0xdc00 && input.charCodeAt(i + 1) <= 0xdfff) {
345
+ // A complete surrogate pair is one 4-byte scalar. Anything else — a high surrogate
346
+ // followed by another high surrogate or a non-surrogate — encodes as two separate
347
+ // U+FFFD replacements, so the next unit must NOT be skipped.
348
+ bytes += 4;
349
+ i++;
350
+ } else bytes += 3; // lone surrogates encode as U+FFFD (3 bytes)
351
+ if (bytes > max) return true;
352
+ }
353
+ return false;
354
+ }
355
+
356
+ function lastValidJsonObject(input: string, maxCandidates: number): string | undefined {
357
+ const tryParseObject = (candidate: string): string | undefined => {
358
+ try {
359
+ const parsed = JSON.parse(candidate) as unknown;
360
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return candidate;
361
+ } catch { /* keep scanning */ }
362
+ return undefined;
363
+ };
364
+ let scanFrom = input.length - 1;
365
+ for (let tried = 0; tried < maxCandidates && scanFrom >= 0; tried++) {
366
+ const open = input.lastIndexOf("{", scanFrom);
367
+ if (open === -1) break;
368
+ const repaired = tryParseObject(input.slice(open));
369
+ if (repaired !== undefined) return repaired;
370
+ scanFrom = open - 1;
371
+ }
372
+ scanFrom = input.length - 1;
373
+ for (let tried = 0; tried < maxCandidates && scanFrom >= 0; tried++) {
374
+ const close = input.lastIndexOf("}", scanFrom);
375
+ if (close === -1) break;
376
+ const repaired = tryParseObject(input.slice(0, close + 1));
377
+ if (repaired !== undefined) return repaired;
378
+ scanFrom = close - 1;
379
+ }
380
+ return undefined;
381
+ }
382
+
383
+ function toolUseArguments(input: unknown, lenient = false): string {
384
+ if (typeof input === "string") {
385
+ const trimmed = input.trim();
386
+ if (!trimmed) return "{}";
387
+ try {
388
+ JSON.parse(trimmed);
389
+ return trimmed;
390
+ } catch {
391
+ if (lenient && !utf8BytesExceed(trimmed, MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES)) {
392
+ const repaired = lastValidJsonObject(trimmed, 32);
393
+ if (repaired !== undefined) return repaired;
394
+ }
395
+ // A tool call's arguments must be a JSON object. Re-encoding an unparseable string as a
396
+ // JSON *string* is the double-encoding #765 reports: the caller then receives
397
+ // `"get weather"` where an object was required and the tool call is unusable either way.
398
+ // An empty object at least fails in the tool's own argument validation.
399
+ return "{}";
400
+ }
401
+ }
402
+ return JSON.stringify(input ?? {});
403
+ }
404
+
405
+ /**
406
+ * Whether arguments assembled from a stream's `input_json_delta` fragments are usable.
407
+ * A tool block that sent no fragments at all is fine — that is a no-argument call. Anything
408
+ * else has to parse, because unlike the non-stream path the fragments have already been
409
+ * forwarded to the client and cannot be repaired after the fact.
410
+ */
411
+ function streamedToolArgumentsParse(assembled: string): boolean {
412
+ const trimmed = assembled.trim();
413
+ if (!trimmed) return true;
414
+ try {
415
+ JSON.parse(trimmed);
416
+ return true;
417
+ } catch {
418
+ return false;
419
+ }
420
+ }
421
+
422
+ function anthropicKeyUsesBearer(provider: OcxProviderConfig): boolean {
423
+ return provider.apiKeyTransport === "bearer";
424
+ }
425
+
426
+ /** Map a Responses reasoning effort to an Anthropic extended-thinking budget (tokens, >= 1024). */
427
+ function reasoningBudget(effort: string): number {
428
+ switch (effort) {
429
+ case "minimal": return 1024;
430
+ case "low": return 4096;
431
+ case "high": return 16384;
432
+ case "xhigh": return 24576;
433
+ case "max": return 32000;
434
+ case "medium":
435
+ default: return 8192;
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Claude families that moved to adaptive thinking: they 400 on `thinking.type: "enabled"`
441
+ * ("Use \"thinking.type.adaptive\" and \"output_config.effort\" to control thinking behavior."),
442
+ * while older families (Haiku 4.5, Sonnet 4.x, Opus <= 4.6) 400 on `adaptive` — so both wire
443
+ * shapes must stay. Verified against api.anthropic.com: sonnet-5, fable-5, opus-4-7 and opus-4-8
444
+ * require adaptive; haiku-4-5 and sonnet-4-5 reject it; opus-4-6/sonnet-4-6 accept both.
445
+ */
446
+ const ADAPTIVE_THINKING_FAMILY_MINIMUMS: Record<string, readonly [major: number, minor: number]> = {
447
+ sonnet: [5, 0],
448
+ opus: [4, 7],
449
+ fable: [0, 0],
450
+ };
451
+
452
+ /**
453
+ * Family/version parse for a Claude model id, tolerant of a routing prefix.
454
+ *
455
+ * `parsed.modelId` is not always bare, and the slash can fall on either side.
456
+ * A `modelMap` entry may point at a routed destination such as
457
+ * `anthropic/claude-sonnet-5` (prefix), while a custom provider may expose a
458
+ * native id such as `claude-sonnet-5/variant` (suffix); both survive routing's
459
+ * known-id decoding. So this matches the segment that actually begins with
460
+ * `claude-` rather than assuming it is the first or the last one. A capability
461
+ * predicate that quietly returns false is worse than one that throws — the
462
+ * request just goes out wrong.
463
+ *
464
+ * Minor is 1-2 digits with a non-digit lookahead so date-pinned ids
465
+ * ("claude-opus-4-20250514") parse as minor 0 instead of minor 20250514;
466
+ * suffixed ids ("claude-opus-4-8[1m]") still match.
467
+ */
468
+ function claudeFamilyVersion(modelId: string): { family: string; major: number; minor: number } | undefined {
469
+ // Find the segment that actually starts with `claude-`, rather than assuming it is either
470
+ // the first (breaks `anthropic/claude-sonnet-5`) or the last (breaks `claude-sonnet-5/variant`,
471
+ // where the slash carries a vendor suffix rather than a routing prefix).
472
+ const match = /(?:^|\/)claude-([a-z]+)-(\d+)(?:-(\d{1,2}))?(?!\d)/.exec(modelId);
473
+ if (!match) return undefined;
474
+ return {
475
+ family: match[1]!,
476
+ major: Number(match[2]),
477
+ minor: match[3] === undefined ? 0 : Number(match[3]),
478
+ };
479
+ }
480
+
481
+ function meetsFamilyMinimum(
482
+ modelId: string,
483
+ minimums: Record<string, readonly [major: number, minor: number]>,
484
+ ): boolean {
485
+ const parsed = claudeFamilyVersion(modelId);
486
+ if (!parsed) return false;
487
+ const minimum = minimums[parsed.family];
488
+ if (!minimum) return false;
489
+ return parsed.major > minimum[0] || (parsed.major === minimum[0] && parsed.minor >= minimum[1]);
490
+ }
491
+
492
+ function usesAdaptiveThinking(modelId: string): boolean {
493
+ return meetsFamilyMinimum(modelId, ADAPTIVE_THINKING_FAMILY_MINIMUMS);
494
+ }
495
+
496
+ /**
497
+ * Claude families that (a) think by DEFAULT when the request omits `thinking`,
498
+ * and (b) accept an explicit `thinking: {type: "disabled"}` to turn it off.
499
+ *
500
+ * Deliberately NOT `usesAdaptiveThinking()`, which answers a different question
501
+ * (which wire shape a family accepts). The two sets differ in both directions:
502
+ * Fable always thinks and REJECTS an explicit disable, while Opus 4.7/4.8 use
503
+ * the adaptive wire but leave thinking off when the field is omitted, so they
504
+ * need no disable at all. Seeded with the family where the defect reproduces
505
+ * (#545); widen only with vendor evidence, since a wrong entry here turns a
506
+ * silent truncation into a 400.
507
+ */
508
+ const EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS: Record<string, readonly [major: number, minor: number]> = {
509
+ sonnet: [5, 0],
510
+ };
511
+
512
+ function supportsExplicitThinkingDisable(modelId: string): boolean {
513
+ return meetsFamilyMinimum(modelId, EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS);
514
+ }
515
+
516
+ /** `output_config.effort` accepts low|medium|high|xhigh|max — "minimal" is rejected with a 400. */
517
+ function adaptiveEffort(effort: string): string {
518
+ return effort === "minimal" ? "low" : effort;
519
+ }
520
+
521
+ function usageFromAnthropic(usage: Record<string, number> | undefined): OcxUsage | undefined {
522
+ if (!usage) return undefined;
523
+ const hasCache = usage.cache_read_input_tokens !== undefined || usage.cache_creation_input_tokens !== undefined;
524
+ const read = usage.cache_read_input_tokens ?? 0;
525
+ const write = usage.cache_creation_input_tokens ?? 0;
526
+ // Anthropic reports input_tokens EXCLUSIVE of cache read/write; normalize to the
527
+ // canonical inclusive convention (types.ts OcxUsage / devlog 070).
528
+ return {
529
+ inputTokens: (usage.input_tokens ?? 0) + read + write,
530
+ outputTokens: usage.output_tokens ?? 0,
531
+ ...(hasCache ? {
532
+ cachedInputTokens: read,
533
+ cacheReadInputTokens: read,
534
+ cacheCreationInputTokens: write,
535
+ } : {}),
536
+ };
537
+ }
538
+
539
+ function mergeAnthropicUsage(
540
+ base: Record<string, number> | undefined,
541
+ next: Record<string, number> | undefined,
542
+ ): Record<string, number> | undefined {
543
+ if (!next) return base;
544
+ if (!base) return { ...next };
545
+ // Anthropic `message_delta.usage` values are CUMULATIVE; adding them to the
546
+ // message_start snapshot double-counted output tokens. Later frames win per key.
547
+ return { ...base, ...next };
548
+ }
549
+
550
+ function buildToolNameTransforms(provider: OcxProviderConfig): { toWire: (name: string) => string; fromWire: (name: string) => string } {
551
+ if (provider.authMode === "oauth") {
552
+ return { toWire: applyClaudeToolPrefix, fromWire: stripClaudeToolPrefix };
553
+ }
554
+ if (provider.escapeBuiltinToolNames === true) {
555
+ return {
556
+ toWire: (name) => name.startsWith(COMPAT_TOOL_PREFIX) ? name : COMPAT_TOOL_PREFIX + name,
557
+ fromWire: (name) => name.startsWith(COMPAT_TOOL_PREFIX) ? name.slice(COMPAT_TOOL_PREFIX.length) : name,
558
+ };
559
+ }
560
+ return { toWire: (name) => name, fromWire: (name) => name };
561
+ }
562
+
563
+ function toAnthropicToolResult(msg: OcxToolResultMessage, wireCallId: string): Record<string, unknown> {
564
+ // Anthropic tool_result accepts a string OR content blocks — render images natively
565
+ // (e.g. Codex view_image output) instead of dropping them.
566
+ let content: string | unknown[];
567
+ if (typeof msg.content === "string") {
568
+ // Anthropic rejects tool_result with empty text content blocks.
569
+ content = msg.content || "(empty tool output)";
570
+ } else {
571
+ const parts = (msg.content as OcxContentPart[])
572
+ .map(toAnthropicContentPart)
573
+ .filter(p => !((p as { type?: string }).type === "text" && !(p as { text?: string }).text));
574
+ content = parts.length > 0 ? parts : "(empty tool output)";
575
+ }
576
+ return {
577
+ type: "tool_result",
578
+ tool_use_id: wireCallId,
579
+ content,
580
+ ...(msg.isError ? { is_error: true } : {}),
581
+ };
582
+ }
583
+
584
+ function unrepresentableToolCallText(tc: OcxToolCall, wireName: string): string {
585
+ const args = typeof tc.arguments === "string" ? tc.arguments : JSON.stringify(tc.arguments);
586
+ return `[tool_use without a usable id: ${wireName}]\n${args}`;
587
+ }
588
+
589
+ function orphanToolResultText(msg: OcxToolResultMessage): string {
590
+ const label = msg.toolName ? `${msg.toolName} (${msg.toolCallId})` : msg.toolCallId;
591
+ const content = typeof msg.content === "string"
592
+ ? msg.content
593
+ : JSON.stringify(msg.content);
594
+ return `[tool_result without adjacent tool_use: ${label}]\n${content}`;
595
+ }
596
+
597
+ /**
598
+ * AgentRouter answers 400 `content-blocked` when the first user message is not in English
599
+ * (#2074), while the same request in English returns 200. The gateway is inspecting the opening
600
+ * user content, so an Anthropic `system` string cannot reach it — the framing has to sit in the
601
+ * first user turn.
602
+ */
603
+ const AGENTROUTER_LANGUAGE_PREAMBLE =
604
+ "[Instruction: Process the user request below and respond in the appropriate language.]";
605
+
606
+ /**
607
+ * Exact host match, not a substring.
608
+ *
609
+ * A `hostname.includes("agentrouter")` test also matches `notagentrouter.example` and
610
+ * `agentrouter.org.attacker.example`, which would let an unrelated destination silently
611
+ * receive an injected instruction block. A prompt mutation keyed on a provider's identity
612
+ * must be keyed on that identity exactly.
613
+ */
614
+ function isAgentRouterEndpoint(baseUrl: string): boolean {
615
+ try {
616
+ const { hostname } = new URL(baseUrl);
617
+ return hostname === "agentrouter.org" || hostname.endsWith(".agentrouter.org");
618
+ } catch {
619
+ return false;
620
+ }
621
+ }
622
+
623
+ /**
624
+ * Prepend the framing as its OWN text block instead of splicing it into the user's string.
625
+ *
626
+ * The distinction matters: rewriting `content` to `${marker}\n\n${original}` edits what the
627
+ * user wrote, and every downstream consumer — logs, retries, an upstream that echoes the turn —
628
+ * then sees a sentence the user never typed as if they had. A separate leading block carries the
629
+ * same signal to the filter while the original text survives byte-for-byte.
630
+ *
631
+ * Only the first user turn is framed, because only the first is what the gateway rejects.
632
+ */
633
+ function applyAgentRouterLanguageFraming(messages: unknown[]): void {
634
+ const firstUser = messages.find(
635
+ (m): m is { role: string; content: unknown } =>
636
+ typeof m === "object" && m !== null && (m as { role?: unknown }).role === "user",
637
+ );
638
+ if (!firstUser) return;
639
+ const preamble = { type: "text", text: AGENTROUTER_LANGUAGE_PREAMBLE };
640
+ if (typeof firstUser.content === "string") {
641
+ firstUser.content = firstUser.content === ""
642
+ ? [preamble]
643
+ : [preamble, { type: "text", text: firstUser.content }];
644
+ return;
645
+ }
646
+ if (!Array.isArray(firstUser.content)) return;
647
+ // Idempotence is keyed on the LEADING block being exactly the marker. A substring test would
648
+ // let a user who quotes the marker later in their own prompt suppress the framing entirely.
649
+ const [head] = firstUser.content as { type?: unknown; text?: unknown }[];
650
+ if (head?.type === "text" && head.text === AGENTROUTER_LANGUAGE_PREAMBLE) return;
651
+ (firstUser.content as unknown[]).unshift(preamble);
652
+ }
653
+
654
+ function messagesToAnthropicFormat(
655
+ parsed: OcxParsedRequest,
656
+ toolNames: { toWire: (name: string) => string },
657
+ ): { system: string | undefined; messages: unknown[] } {
658
+ // One allocator for the whole request: a tool_result must resolve to the SAME wire id its
659
+ // call got, and two distinct raw ids must never collapse into one. Conforming ids are claimed
660
+ // first so a rewritten id can never squat on an id another call legitimately owns.
661
+ const callIds = createToolCallIdAllocator();
662
+ for (const message of parsed.context.messages) {
663
+ if (message.role === "assistant") {
664
+ for (const part of (message as OcxAssistantMessage).content) {
665
+ if (part.type === "toolCall") callIds.reserve((part as OcxToolCall).id);
666
+ }
667
+ } else if (message.role === "toolResult") {
668
+ callIds.reserve((message as OcxToolResultMessage).toolCallId);
669
+ }
670
+ }
671
+ const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(
672
+ parsed.context.tools,
673
+ parsed.options.toolChoice,
674
+ tool => toolNames.toWire(namespacedToolName(tool.namespace, tool.name)),
675
+ );
676
+ const systemParts = [...(parsed.context.systemPrompt ?? []), ...(toolCatalogNudge ? [toolCatalogNudge] : [])];
677
+ const system = systemParts.length
678
+ ? identifyRoutedModel(systemParts.join("\n\n"), parsed.modelId) || undefined
679
+ : undefined;
680
+ const messages: unknown[] = [];
681
+
682
+ for (let i = 0; i < parsed.context.messages.length; i++) {
683
+ const msg = parsed.context.messages[i];
684
+ switch (msg.role) {
685
+ case "user":
686
+ case "developer": {
687
+ let content: string | unknown[];
688
+ if (typeof msg.content === "string") {
689
+ // Anthropic rejects empty string text content blocks.
690
+ content = msg.content || "(empty)";
691
+ } else {
692
+ const parts = (msg.content as OcxContentPart[])
693
+ .map(toAnthropicContentPart)
694
+ .filter(p => !((p as { type?: string }).type === "text" && !(p as { text?: string }).text));
695
+ content = parts.length > 0 ? parts : "(empty)";
696
+ }
697
+ messages.push({ role: "user", content });
698
+ break;
699
+ }
700
+ case "assistant": {
701
+ const aMsg = msg as OcxAssistantMessage;
702
+ const preface: unknown[] = [];
703
+ const toolUses: unknown[] = [];
704
+ const toolUseIds: string[] = [];
705
+ for (const part of aMsg.content) {
706
+ if (part.type === "text") {
707
+ const text = (part as OcxTextContent).text;
708
+ if (text) preface.push({ type: "text", text });
709
+ } else if (part.type === "thinking") {
710
+ const t = part as OcxThinkingContent;
711
+ // Redacted blocks replay verbatim FIRST (they preceded the visible thinking block
712
+ // in the original stream order preserved by the bridge envelope).
713
+ for (const data of t.redacted ?? []) {
714
+ preface.push({ type: "redacted_thinking", data });
715
+ }
716
+ if (isLikelyRealAnthropicThinkingSignature(t.signature)) {
717
+ preface.push({ type: "thinking", thinking: t.thinking, signature: t.signature });
718
+ }
719
+ } else if (part.type === "toolCall") {
720
+ const tc = part as OcxToolCall;
721
+ const flatName = namespacedToolName(tc.namespace, tc.name);
722
+ // Normalized here, and identically for the matching tool_result above, so a history
723
+ // replayed from another provider path keeps its call/result pairing (#1767).
724
+ // No raw fallback: restoring an empty/unusable id puts a value on the wire Anthropic
725
+ // rejects. An unrepresentable call becomes text instead, and its result follows it there.
726
+ const wireCallId = callIds.allocate(tc.id);
727
+ if (wireCallId === undefined) {
728
+ preface.push({ type: "text", text: unrepresentableToolCallText(tc, toolNames.toWire(flatName)) });
729
+ continue;
730
+ }
731
+ toolUseIds.push(wireCallId);
732
+ toolUses.push({ type: "tool_use", id: wireCallId, name: toolNames.toWire(flatName), input: tc.arguments });
733
+ }
734
+ }
735
+ // Anthropic treats text/thinking after tool_use as ending the tool turn, which makes
736
+ // earlier tool_use ids look unpaired (#620 / common multi-step history shape).
737
+ const content = [...preface, ...toolUses];
738
+ if (content.length === 0) break;
739
+ messages.push({ role: "assistant", content });
740
+ if (toolUseIds.length > 0) {
741
+ const requiredIds = new Set(toolUseIds);
742
+ const resultBlocks: Record<string, unknown>[] = [];
743
+ const orphanBlocks: Record<string, unknown>[] = [];
744
+ const seen = new Set<string>();
745
+ let j = i + 1;
746
+ while (j < parsed.context.messages.length && parsed.context.messages[j].role === "toolResult") {
747
+ const tr = parsed.context.messages[j] as OcxToolResultMessage;
748
+ // Match on the WIRE id. requiredIds holds normalized ids, so comparing the raw result id
749
+ // made every rewritten pair lose its result to orphan text and gain a synthetic
750
+ // missing-result block. lookup() never mints an id: a result with no call stays orphan.
751
+ const wireResultId = callIds.lookup(tr.toolCallId);
752
+ if (wireResultId !== undefined && requiredIds.has(wireResultId) && !seen.has(wireResultId)) {
753
+ resultBlocks.push(toAnthropicToolResult(tr, wireResultId));
754
+ seen.add(wireResultId);
755
+ } else {
756
+ orphanBlocks.push({ type: "text", text: orphanToolResultText(tr) });
757
+ }
758
+ j++;
759
+ }
760
+ for (const id of toolUseIds) {
761
+ if (!seen.has(id)) {
762
+ resultBlocks.push({
763
+ type: "tool_result",
764
+ tool_use_id: id,
765
+ content: "[missing tool_result for this tool_use in history]",
766
+ is_error: true,
767
+ });
768
+ }
769
+ }
770
+ messages.push({ role: "user", content: [...resultBlocks, ...orphanBlocks] });
771
+ i = j - 1;
772
+ }
773
+ break;
774
+ }
775
+ case "toolResult": {
776
+ // A standalone Anthropic tool_result is invalid unless it immediately follows an
777
+ // assistant tool_use. Preserve the information as text instead of sending a 400-prone block.
778
+ messages.push({ role: "user", content: orphanToolResultText(msg as OcxToolResultMessage) });
779
+ break;
780
+ }
781
+ }
782
+ }
783
+
784
+ // Newer Anthropic models reject assistant-tail histories as prefill:
785
+ // "This model does not support assistant message prefill. The conversation must end with a user message."
786
+ // previous_response_id expansion with empty new input, interrupted-turn replay, and web-search sidecar
787
+ // first iterations can all reach this; Kiro uses the same "(continue)" nudge precedent (src/adapters/kiro.ts:283).
788
+ if (messages.length === 0) {
789
+ messages.push({ role: "user", content: "(continue)" });
790
+ } else if ((messages[messages.length - 1] as { role?: string }).role === "assistant") {
791
+ messages.push({ role: "user", content: "(continue)" });
792
+ }
793
+
794
+ return { system, messages };
795
+ }
796
+
797
+ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (name: string) => string }): unknown[] | undefined {
798
+ if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
799
+ const allowed = isAllowedToolChoice(parsed.options.toolChoice)
800
+ ? new Set(parsed.options.toolChoice.allowedTools)
801
+ : undefined;
802
+ const tools = allowed
803
+ ? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed, parsed.context.tools))
804
+ : parsed.context.tools;
805
+ if (tools.length === 0) return undefined;
806
+ const converted = tools.map(t => ({
807
+ name: toolNames.toWire(namespacedToolName(t.namespace, t.name)),
808
+ description: t.description,
809
+ input_schema: normalizeAnthropicInputSchema(t.parameters),
810
+ }));
811
+ return converted;
812
+ }
813
+
814
+ function normalizeAnthropicInputSchema(schema: unknown): Record<string, unknown> {
815
+ const stripped = stripResponsesOnlyEncryptedMarker(schema);
816
+ const obj = stripped && typeof stripped === "object" && !Array.isArray(stripped)
817
+ ? stripped as Record<string, unknown>
818
+ : {};
819
+ // Anthropic rejects root-level missing type and oneOf/anyOf/allOf in input_schema.
820
+ // Normalize the root only: ensure type:"object" + properties, flatten root composition
821
+ // while preserving nested schemas. Mirrors kiro-tools.ts ensureRootObjectType.
822
+ // Known limitation: Object.assign on branch properties means later branches overwrite
823
+ // earlier ones when the same property name appears with different schemas.
824
+ const compositionKeys = ["oneOf", "anyOf", "allOf"] as const;
825
+ const hasRootComposition = compositionKeys.some(key => Array.isArray(obj[key]));
826
+ const type = obj.type;
827
+ const rootObjectType = type === "object" || (Array.isArray(type) && type.includes("object"));
828
+
829
+ if (!hasRootComposition) {
830
+ const normalized: Record<string, unknown> = rootObjectType && type === "object"
831
+ ? { ...obj }
832
+ : { ...obj, type: "object" };
833
+ if (normalized.properties === undefined || normalized.properties === null) {
834
+ normalized.properties = {};
835
+ }
836
+ return normalized;
837
+ }
838
+
839
+ const properties: Record<string, unknown> = {};
840
+ const required = new Set<string>();
841
+ if (obj.properties && typeof obj.properties === "object" && !Array.isArray(obj.properties)) {
842
+ Object.assign(properties, obj.properties as Record<string, unknown>);
843
+ }
844
+ if (Array.isArray(obj.required)) {
845
+ for (const item of obj.required) if (typeof item === "string") required.add(item);
846
+ }
847
+
848
+ for (const key of compositionKeys) {
849
+ const variants = obj[key];
850
+ if (!Array.isArray(variants)) continue;
851
+ const mergeRequired = key === "allOf";
852
+ for (const variant of variants) {
853
+ if (!variant || typeof variant !== "object" || Array.isArray(variant)) continue;
854
+ const v = variant as Record<string, unknown>;
855
+ if (v.properties && typeof v.properties === "object" && !Array.isArray(v.properties)) {
856
+ Object.assign(properties, v.properties as Record<string, unknown>);
857
+ }
858
+ if (mergeRequired && Array.isArray(v.required)) {
859
+ for (const item of v.required) if (typeof item === "string") required.add(item);
860
+ }
861
+ }
862
+ }
863
+
864
+ const normalized: Record<string, unknown> = {};
865
+ for (const [key, value] of Object.entries(obj)) {
866
+ if (key === "oneOf" || key === "anyOf" || key === "allOf") continue;
867
+ if (key === "type" || key === "properties" || key === "required") continue;
868
+ normalized[key] = value;
869
+ }
870
+ normalized.type = "object";
871
+ normalized.properties = properties;
872
+ if (required.size > 0) normalized.required = [...required];
873
+ return normalized;
874
+ }
875
+
876
+ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetention?: "none" | "short" | "long"): ProviderAdapter {
877
+ const isOAuth = provider.authMode === "oauth";
878
+ const toolNames = buildToolNameTransforms(provider);
879
+ return {
880
+ name: "anthropic",
881
+
882
+ formatErrorBody: formatAnthropicErrorBody,
883
+
884
+ async buildRequest(parsed: OcxParsedRequest, incoming?: IncomingMeta) {
885
+ if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
886
+ if (isOAuth) {
887
+ throw new Error("anthropic oauth token missing — run ocx login anthropic");
888
+ }
889
+ throw new Error("anthropic provider requires a non-empty apiKey (authMode: key)");
890
+ }
891
+
892
+ const { system, messages } = messagesToAnthropicFormat(parsed, toolNames);
893
+ // Before image normalization, so the framing block is present for every downstream pass.
894
+ if (isAgentRouterEndpoint(provider.baseUrl)) applyAgentRouterLanguageFraming(messages);
895
+ // Primary image layer: resize/re-encode to fit Anthropic limits without dropping
896
+ // (anthropic-image-normalize.ts); the guard below remains the deterministic backstop.
897
+ // imageTierBias > 0 = upstream-413 tightened retry (030): start every image one tier lower.
898
+ await normalizeAnthropicImages(messages, { tierBias: incoming?.imageTierBias ?? 0 });
899
+ // Anthropic rejects many-image requests (>20 images) carrying any image over
900
+ // 2000px per side; see anthropic-image-guard.ts for the full limit policy.
901
+ enforceAnthropicImageLimits(messages);
902
+ const tools = toolsToAnthropicFormat(parsed, toolNames);
903
+
904
+ const body: Record<string, unknown> = {
905
+ model: parsed.modelId,
906
+ messages,
907
+ stream: parsed.stream,
908
+ max_tokens: parsed.options.maxOutputTokens ?? DEFAULT_MAX_TOKENS,
909
+ };
910
+ if (isOAuth) {
911
+ // Claude OAuth (Pro/Max) requires the first system block to be the Claude Code identity.
912
+ body.system = [
913
+ { type: "text", text: CLAUDE_CODE_SYSTEM_INSTRUCTION },
914
+ ...(system ? [{ type: "text", text: system }] : []),
915
+ ];
916
+ } else if (system) {
917
+ body.system = [{ type: "text", text: system }];
918
+ }
919
+ if (tools) body.tools = tools;
920
+ if (parsed.options.temperature !== undefined) body.temperature = parsed.options.temperature;
921
+ if (parsed.options.topP !== undefined) body.top_p = parsed.options.topP;
922
+ if (parsed.options.stopSequences) body.stop_sequences = parsed.options.stopSequences;
923
+
924
+ // `reasoning` is a Codex effort string; "none" is the disable sentinel (see parser.ts
925
+ // REASONING_EFFORTS). A bare truthy check would treat "none" as truthy and wrongly enable
926
+ // extended thinking (and strip temperature/top_p), so gate on a real, non-disable effort.
927
+ //
928
+ // "none" is not the same as absent. Omitting `thinking` lets a default-on model think
929
+ // anyway, and thinking shares the caller's `max_tokens` — which truncates a small-budget
930
+ // request before it can emit its stop sequence (#545). Say "disabled" out loud where the
931
+ // model both defaults to thinking and accepts being told not to.
932
+ if (parsed.options.reasoning === "none" && supportsExplicitThinkingDisable(parsed.modelId)) {
933
+ body.thinking = { type: "disabled" };
934
+ } else if (typeof parsed.options.reasoning === "string" && parsed.options.reasoning !== "none") {
935
+ if (usesAdaptiveThinking(parsed.modelId)) {
936
+ // Adaptive-thinking models replace the token budget with an effort knob and reject
937
+ // `thinking.type: "enabled"` outright. `max_tokens` still caps thinking plus visible
938
+ // output, so high effort needs the same total-token headroom as budget thinking or a
939
+ // default 8192-token request can spend everything on thought and return empty text.
940
+ body.thinking = { type: "adaptive" };
941
+ body.output_config = { effort: adaptiveEffort(parsed.options.reasoning) };
942
+ const explicitMaxOut = parsed.options.maxOutputTokens;
943
+ const wantBudget = reasoningBudget(parsed.options.reasoning);
944
+ const floor = wantBudget + OUTPUT_HEADROOM;
945
+ // Preserve explicit caller limits as-is; for omitted limits use the adaptive ceiling
946
+ // so effort=max (budget=32k) still leaves OUTPUT_HEADROOM tokens for visible output.
947
+ body.max_tokens = explicitMaxOut !== undefined
948
+ ? explicitMaxOut
949
+ : Math.min(ADAPTIVE_THINKING_CEILING, Math.max(DEFAULT_MAX_TOKENS, floor));
950
+ } else {
951
+ // Anthropic requires max_tokens > thinking.budget_tokens (max_tokens caps thinking +
952
+ // visible output) and budget_tokens >= 1024. Codex sends the SAME value for both, which
953
+ // 400s ("max_tokens must be greater than thinking.budget_tokens"). Size them so max_tokens
954
+ // always exceeds the budget within a model-safe ceiling, reserving room for visible output.
955
+ const maxOut = parsed.options.maxOutputTokens ?? DEFAULT_MAX_TOKENS;
956
+ const wantBudget = reasoningBudget(parsed.options.reasoning);
957
+ const maxTokens = Math.min(REASONING_MAX_TOKENS_CEILING, Math.max(maxOut, wantBudget + OUTPUT_HEADROOM));
958
+ const budget = Math.max(MIN_THINKING_BUDGET, Math.min(wantBudget, maxTokens - OUTPUT_FLOOR));
959
+ body.max_tokens = maxTokens;
960
+ body.thinking = { type: "enabled", budget_tokens: budget };
961
+ }
962
+ // Extended thinking disallows temperature != 1 and top_p — drop both or the API 400s.
963
+ delete body.temperature;
964
+ delete body.top_p;
965
+ }
966
+
967
+ const textFormat = parsed.options.textFormat;
968
+ if (textFormat?.type === "json_schema" && textFormat.schema) {
969
+ const outputConfig = body.output_config;
970
+ body.output_config = {
971
+ ...(outputConfig && typeof outputConfig === "object" && !Array.isArray(outputConfig)
972
+ ? outputConfig
973
+ : {}),
974
+ format: {
975
+ type: "json_schema",
976
+ schema: normalizeAnthropicOutputSchema(textFormat.schema),
977
+ },
978
+ };
979
+ }
980
+
981
+ if (parsed.options.toolChoice && (tools || parsed.options.toolChoice === "none")) {
982
+ const tc = parsed.options.toolChoice;
983
+ if (tc === "auto") body.tool_choice = { type: "auto" };
984
+ else if (tc === "none") body.tool_choice = { type: "none" };
985
+ else if (tc === "required") body.tool_choice = { type: "any" };
986
+ else if (isAllowedToolChoice(tc)) body.tool_choice = { type: tc.mode === "required" ? "any" : "auto" };
987
+ else if (typeof tc === "object" && "name" in tc) body.tool_choice = { type: "tool", name: toolNames.toWire(resolveToolChoiceWireName(parsed.context.tools, tc.name)) };
988
+ }
989
+
990
+ const url = anthropicMessagesUrl(provider.baseUrl);
991
+ const unresolvedPlaceholder = url.match(/\{[^}]*\}/)?.[0];
992
+ if (unresolvedPlaceholder) {
993
+ throw new Error(`anthropic baseUrl contains unresolved ${unresolvedPlaceholder}`);
994
+ }
995
+ const headers: Record<string, string> = {
996
+ "Content-Type": "application/json",
997
+ "anthropic-version": "2023-06-01",
998
+ "Accept": parsed.stream ? "text/event-stream" : "application/json",
999
+ "User-Agent": "@anthropic-ai/sdk/0.74.0",
1000
+ };
1001
+ if (isOAuth) {
1002
+ headers["Authorization"] = `Bearer ${provider.apiKey}`;
1003
+ headers["anthropic-beta"] = ANTHROPIC_OAUTH_BETA;
1004
+ // Match the real Claude Code CLI request fingerprint: a valid OAuth token with an empty
1005
+ // header set is a non-first-party signature. (cch billing-header signing is intentionally
1006
+ // out of scope — brittle and version-coupled.)
1007
+ Object.assign(headers, CLAUDE_CODE_HEADERS);
1008
+ headers["X-Claude-Code-Session-Id"] = claudeCodeSessionId(provider.apiKey);
1009
+ headers["x-client-request-id"] = crypto.randomUUID();
1010
+ } else {
1011
+ if (anthropicKeyUsesBearer(provider)) headers["Authorization"] = `Bearer ${provider.apiKey}`;
1012
+ else headers["x-api-key"] = provider.apiKey;
1013
+ }
1014
+ if (provider.headers) Object.assign(headers, provider.headers);
1015
+
1016
+ // Prompt caching: native Anthropic supports top-level automatic caching, which
1017
+ // follows the moving final block across turns. Keep one breakpoint slot free for it.
1018
+ const cc = resolveCacheControl(cacheRetention);
1019
+ const automaticPromptCaching = cc && usesNativeAnthropicEndpoint(provider);
1020
+ if (automaticPromptCaching) body.cache_control = cc;
1021
+ const explicitLimit = automaticPromptCaching ? MAX_CACHE_BREAKPOINTS - 1 : MAX_CACHE_BREAKPOINTS;
1022
+ applyPromptCaching(body, cc, {
1023
+ maxExplicitBreakpoints: explicitLimit,
1024
+ skipLastUser: !!automaticPromptCaching,
1025
+ });
1026
+ enforceCacheControlLimit(body, explicitLimit);
1027
+ normalizeTtlOrdering(body);
1028
+
1029
+ return { url, method: "POST", headers, body: JSON.stringify(body) };
1030
+ },
1031
+
1032
+ async *parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
1033
+ if (!response.body) {
1034
+ yield { type: "error", message: "No response body" };
1035
+ return;
1036
+ }
1037
+
1038
+ const budgetEncoder = new TextEncoder();
1039
+ let currentBlockType = "";
1040
+ let currentToolCallId = "";
1041
+ let currentToolCallName = "";
1042
+ let currentToolCallJson = "";
1043
+ let pendingUsage: Record<string, number> | undefined;
1044
+ let pendingStopReason: string | undefined;
1045
+ let emittedDone = false;
1046
+ let sawVisibleText = false;
1047
+
1048
+ const emitDone = function* (): Generator<AdapterEvent> {
1049
+ if (emittedDone) return;
1050
+ emittedDone = true;
1051
+ // An `error` stop reason is a failed generation, not a stop. Forwarding it as `done`
1052
+ // lets the turn report success and install replacement history on a compaction turn.
1053
+ if (pendingStopReason === "error") {
1054
+ yield {
1055
+ type: "error",
1056
+ message: "upstream ended the turn with stop_reason \"error\"",
1057
+ status: 502,
1058
+ errorType: "upstream_error",
1059
+ usage: usageFromAnthropic(pendingUsage),
1060
+ };
1061
+ return;
1062
+ }
1063
+ yield {
1064
+ type: "done",
1065
+ usage: usageFromAnthropic(pendingUsage),
1066
+ ...(pendingStopReason ? { stopReason: pendingStopReason } : {}),
1067
+ };
1068
+ };
1069
+
1070
+ try {
1071
+ for await (const record of decodeServerSentEvents(response.body, { includeComments: true, translatorBudget: budget })) {
1072
+ if (record.kind === "comment") {
1073
+ yield { type: "heartbeat" };
1074
+ continue;
1075
+ }
1076
+ const payload = record.data.trim();
1077
+ if (!payload) continue;
1078
+
1079
+ let parsed: unknown;
1080
+ try {
1081
+ parsed = JSON.parse(payload);
1082
+ } catch {
1083
+ debugDroppedFrame("anthropic", payload);
1084
+ continue;
1085
+ }
1086
+ // `JSON.parse("null")` returns null instead of throwing, so the catch above cannot cover
1087
+ // it and the `data.type` read below crashed the stream. Drop a non-record frame the same
1088
+ // way an unparseable one is dropped, so the message_stop check still governs the outcome.
1089
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1090
+ debugDroppedFrame("anthropic", payload);
1091
+ continue;
1092
+ }
1093
+ const data = parsed as Record<string, unknown>;
1094
+
1095
+ switch (record.event || data.type) {
1096
+ case "message_start": {
1097
+ const message = data.message as { usage?: Record<string, number> } | undefined;
1098
+ pendingUsage = mergeAnthropicUsage(pendingUsage, message?.usage);
1099
+ break;
1100
+ }
1101
+ case "content_block_start": {
1102
+ const block = data.content_block as { type: string; id?: string; name?: string; data?: string } | undefined;
1103
+ if (!block) break;
1104
+ currentBlockType = block.type;
1105
+ if (block.type === "tool_use") {
1106
+ currentToolCallId = usableToolUseId(block.id);
1107
+ currentToolCallName = toolNames.fromWire(block.name ?? "");
1108
+ currentToolCallJson = "";
1109
+ budget.openCall(currentToolCallId);
1110
+ yield { type: "tool_call_start", id: currentToolCallId, name: currentToolCallName };
1111
+ }
1112
+ if (block.type === "redacted_thinking" && typeof block.data === "string") {
1113
+ // Opaque redacted block: replay verbatim later or tool-use turns 400.
1114
+ yield { type: "redacted_thinking", data: block.data };
1115
+ }
1116
+ break;
1117
+ }
1118
+ case "content_block_delta": {
1119
+ const delta = data.delta as Record<string, unknown> | undefined;
1120
+ if (!delta) break;
1121
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
1122
+ // Only non-empty text proves the upstream produced usable output; an empty
1123
+ // delta followed by EOF must stay a truncation error even on the tolerant
1124
+ // profile, or a cut-off turn would surface as a successful empty answer.
1125
+ if (delta.text.length > 0) sawVisibleText = true;
1126
+ yield { type: "text_delta", text: delta.text };
1127
+ } else if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
1128
+ yield { type: "thinking_delta", thinking: delta.thinking };
1129
+ } else if (delta.type === "reasoning_delta" && typeof delta.reasoning === "string") {
1130
+ // Some Anthropic-compatible reasoning models use `reasoning` names for the
1131
+ // otherwise equivalent thinking block. Preserve it as raw reasoning and keep
1132
+ // later text blocks independent.
1133
+ yield { type: "thinking_delta", thinking: delta.reasoning };
1134
+ } else if (delta.type === "signature_delta" && typeof delta.signature === "string" && (currentBlockType === "thinking" || currentBlockType === "reasoning")) {
1135
+ // Arrives once, just before the thinking block's content_block_stop; block-scoped
1136
+ // so a stray signature on a non-thinking block can never be captured.
1137
+ yield { type: "thinking_signature", signature: delta.signature };
1138
+ } else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string" && currentBlockType === "tool_use") {
1139
+ // Forwarded immediately: the bridge maps each delta to a client-visible
1140
+ // response.function_call_arguments.delta frame, so withholding fragments until
1141
+ // block close would leave a started call showing empty arguments. A copy is kept
1142
+ // to validate the assembled payload at content_block_stop.
1143
+ const previousBytes = budgetEncoder.encode(currentToolCallJson).byteLength;
1144
+ const nextBytes = previousBytes + budgetEncoder.encode(delta.partial_json).byteLength;
1145
+ const reservation = budget.reserveTransient(nextBytes, { kind: "tool_args", callId: currentToolCallId });
1146
+ try {
1147
+ currentToolCallJson += delta.partial_json;
1148
+ reservation.commitRetained();
1149
+ budget.releaseRetained(previousBytes, { kind: "tool_args", callId: currentToolCallId });
1150
+ } catch (error) {
1151
+ reservation.release();
1152
+ throw error;
1153
+ }
1154
+ yield { type: "tool_call_delta", arguments: delta.partial_json };
1155
+ }
1156
+ break;
1157
+ }
1158
+ case "content_block_stop": {
1159
+ if (currentBlockType === "tool_use") {
1160
+ // The non-stream path repairs an unparseable payload in toolUseArguments(); the
1161
+ // stream cannot, because the fragments are already downstream. Fail the turn
1162
+ // instead of ending a tool call whose arguments will not parse — the bridge's
1163
+ // terminal-error path cancels the open call (status incomplete) rather than
1164
+ // completing it before response.failed (#765).
1165
+ if (!streamedToolArgumentsParse(currentToolCallJson)) {
1166
+ yield {
1167
+ type: "error",
1168
+ message: "Anthropic stream sent malformed tool_use arguments (invalid JSON)",
1169
+ };
1170
+ return;
1171
+ }
1172
+ yield { type: "tool_call_end" };
1173
+ budget.closeCall(currentToolCallId);
1174
+ currentToolCallId = "";
1175
+ currentToolCallJson = "";
1176
+ }
1177
+ currentBlockType = "";
1178
+ break;
1179
+ }
1180
+ case "message_delta": {
1181
+ const usage = data.usage as Record<string, number> | undefined;
1182
+ pendingUsage = mergeAnthropicUsage(pendingUsage, usage);
1183
+ const delta = data.delta as { stop_reason?: unknown } | undefined;
1184
+ if (typeof delta?.stop_reason === "string") pendingStopReason = delta.stop_reason;
1185
+ break;
1186
+ }
1187
+ case "message_stop": {
1188
+ yield* emitDone();
1189
+ break;
1190
+ }
1191
+ case "error": {
1192
+ const err = data.error as { message?: string } | undefined;
1193
+ yield { type: "error", message: err?.message ?? "Anthropic error" };
1194
+ return;
1195
+ }
1196
+ }
1197
+ }
1198
+ } catch (error) {
1199
+ if (!isTranslatorBudgetExceededError(error)) throw error;
1200
+ yield {
1201
+ type: "error",
1202
+ status: 502,
1203
+ errorType: "upstream_error",
1204
+ code: "translation_buffer_limit",
1205
+ message: "upstream translation buffer exceeded the safe limit",
1206
+ };
1207
+ // The budget error IS the terminal event for this stream. Falling through to the
1208
+ // EOF handling below could append tool_call_end/done after it, violating the
1209
+ // one-terminal-event contract for consumers that keep draining the generator.
1210
+ return;
1211
+ } finally {
1212
+ if (currentToolCallId) budget.closeCall(currentToolCallId);
1213
+ }
1214
+ if (!emittedDone) {
1215
+ // Fail closed on transport EOF. Compatible providers may omit message_stop after message_delta.stop_reason.
1216
+ if (pendingStopReason !== undefined) {
1217
+ // Same rule as emitDone: an `error` stop reason is a failed generation, not a stop.
1218
+ // This branch bypasses emitDone entirely (it exists for providers that close after
1219
+ // message_delta without message_stop), so the check has to be repeated here or the
1220
+ // EOF route silently reports success.
1221
+ if (pendingStopReason === "error") {
1222
+ emittedDone = true;
1223
+ yield {
1224
+ type: "error",
1225
+ message: "upstream ended the turn with stop_reason \"error\"",
1226
+ status: 502,
1227
+ errorType: "upstream_error",
1228
+ usage: usageFromAnthropic(pendingUsage),
1229
+ };
1230
+ return;
1231
+ }
1232
+ const stopReason = pendingStopReason === "max_tokens"
1233
+ ? "max_tokens"
1234
+ : pendingStopReason === "refusal" || pendingStopReason === "content_filter"
1235
+ ? "content_filter"
1236
+ : pendingStopReason;
1237
+ emittedDone = true;
1238
+ yield {
1239
+ type: "done",
1240
+ usage: usageFromAnthropic(pendingUsage),
1241
+ ...(stopReason ? { stopReason } : {}),
1242
+ };
1243
+ } else if (provider.anthropicEofTolerance === true) {
1244
+ // AgentRouter-style compatibility profile (#658): the upstream can close the stream
1245
+ // after valid content without terminal frames. Complete only when visible text was
1246
+ // received or an open tool call has complete JSON-object arguments; everything else
1247
+ // (incomplete tool JSON, no usable content, transport failure) stays a truncation
1248
+ // error, matching the strict default.
1249
+ if (currentToolCallId) {
1250
+ if (streamedToolArgumentsParse(currentToolCallJson)) {
1251
+ budget.closeCall(currentToolCallId);
1252
+ currentToolCallId = "";
1253
+ yield { type: "tool_call_end" };
1254
+ yield* emitDone();
1255
+ } else {
1256
+ yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
1257
+ }
1258
+ } else if (sawVisibleText) {
1259
+ yield* emitDone();
1260
+ } else {
1261
+ yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
1262
+ }
1263
+ } else {
1264
+ yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
1265
+ }
1266
+ }
1267
+ },
1268
+
1269
+ async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
1270
+ const json = await response.json() as Record<string, unknown>;
1271
+ const responseBytes = new TextEncoder().encode(JSON.stringify(json)).byteLength;
1272
+ budget.chargeRetained(responseBytes, { kind: "retained_collectors" });
1273
+ try {
1274
+ const events: AdapterEvent[] = [];
1275
+ const content = json.content as { type: string; text?: string; id?: string; name?: string; input?: unknown; thinking?: string; reasoning?: string; signature?: string; data?: string }[] | undefined;
1276
+ if (content) {
1277
+ for (const block of content) {
1278
+ if (block.type === "text" && block.text) {
1279
+ events.push({ type: "text_delta", text: block.text });
1280
+ } else if (block.type === "thinking" && typeof block.thinking === "string") {
1281
+ events.push({ type: "thinking_delta", thinking: block.thinking });
1282
+ if (typeof block.signature === "string" && block.signature) {
1283
+ events.push({ type: "thinking_signature", signature: block.signature });
1284
+ }
1285
+ } else if (block.type === "reasoning" && typeof block.reasoning === "string") {
1286
+ events.push({ type: "thinking_delta", thinking: block.reasoning });
1287
+ } else if (block.type === "redacted_thinking" && typeof block.data === "string") {
1288
+ events.push({ type: "redacted_thinking", data: block.data });
1289
+ } else if (block.type === "tool_use") {
1290
+ const id = usableToolUseId(block.id);
1291
+ events.push({ type: "tool_call_start", id, name: toolNames.fromWire(block.name ?? "") });
1292
+ events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input, provider.anthropicEofTolerance === true) });
1293
+ events.push({ type: "tool_call_end" });
1294
+ }
1295
+ }
1296
+ }
1297
+ const usage = json.usage as Record<string, number> | undefined;
1298
+ const stopReason = typeof json.stop_reason === "string" ? json.stop_reason : undefined;
1299
+ // An Anthropic-compatible upstream can forward an `error` stop reason verbatim. As a
1300
+ // `done` it reads as a clean completion, so the turn reports success and — on a compaction
1301
+ // turn — installs its partial summary as replacement history (#422). Usage is preserved:
1302
+ // a failed turn still consumed tokens.
1303
+ if (stopReason === "error") {
1304
+ events.push({
1305
+ type: "error",
1306
+ message: "upstream ended the turn with stop_reason \"error\"",
1307
+ status: 502,
1308
+ errorType: "upstream_error",
1309
+ usage: usageFromAnthropic(usage),
1310
+ });
1311
+ retainTranslatedEventBatch(events, budget);
1312
+ return events;
1313
+ }
1314
+ events.push({
1315
+ type: "done",
1316
+ usage: usageFromAnthropic(usage),
1317
+ ...(stopReason ? { stopReason } : {}),
1318
+ });
1319
+ retainTranslatedEventBatch(events, budget);
1320
+ return events;
1321
+ } finally {
1322
+ budget.releaseRetained(responseBytes, { kind: "retained_collectors" });
1323
+ }
1324
+ },
1325
+
1326
+ };
1327
+ }