@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -1,4 +1,5 @@
1
1
  import type { IncomingMeta, ProviderAdapter } from "./base";
2
+ import { createToolCallIdAllocator, type ToolCallIdAllocator } from "./tool-call-id";
2
3
  import { debugDroppedFrame } from "../lib/debug";
3
4
  import type {
4
5
  AdapterEvent,
@@ -13,16 +14,20 @@ import type {
13
14
  OcxToolResultMessage,
14
15
  OcxUsage,
15
16
  } from "../types";
16
- import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolAllowedByChoice } from "../types";
17
+ import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
17
18
  import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION, applyClaudeToolPrefix, stripClaudeToolPrefix } from "../oauth/anthropic";
18
19
  import { parseDataUrl } from "./image";
19
20
  import { enforceAnthropicImageLimits } from "./anthropic-image-guard";
20
21
  import { normalizeAnthropicImages } from "./anthropic-image-normalize";
21
- import { neutralizeIdentity } from "./identity";
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";
22
26
  import { CLAUDE_CODE_HEADERS, claudeCodeSessionId } from "./client-fingerprint";
23
27
  import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
24
28
  import { decodeServerSentEvents } from "../lib/sse-decoder";
25
29
  import { isTranslatorBudgetExceededError, retainTranslatedEventBatch, type TranslatorBudget } from "../lib/translator-budget";
30
+ import { isReasoningEffortOmitted, modelRecordValue } from "../reasoning-effort";
26
31
 
27
32
  /** Map a user content part to an Anthropic content block (text or image source). */
28
33
  function toAnthropicContentPart(p: OcxContentPart): unknown {
@@ -32,6 +37,7 @@ function toAnthropicContentPart(p: OcxContentPart): unknown {
32
37
  ? { type: "image", source: { type: "base64", media_type: data.mediaType, data: data.base64 } }
33
38
  : { type: "image", source: { type: "url", url: p.imageUrl } };
34
39
  }
40
+ if (p.type === "video") return { type: "text", text: "[video]" };
35
41
  return { type: "text", text: p.text };
36
42
  }
37
43
 
@@ -243,6 +249,76 @@ function isLikelyRealAnthropicThinkingSignature(signature: string | undefined):
243
249
  return /^[A-Za-z0-9+/_=-]+$/.test(signature);
244
250
  }
245
251
 
252
+ /**
253
+ * Bridge error fidelity (web-search/images loops): extract a display-safe summary from an
254
+ * Anthropic JSON error envelope so `Provider error <status>` carries the upstream reason.
255
+ * JSON-only extraction — HTML/non-JSON bodies yield "" so raw markup is never echoed.
256
+ */
257
+ export function formatAnthropicErrorBody(status: number, _headers: Headers, payloadText: string): string {
258
+ let parsed: unknown;
259
+ try {
260
+ parsed = JSON.parse(payloadText);
261
+ } catch {
262
+ return "";
263
+ }
264
+ const detail = extractAnthropicErrorDetail(parsed);
265
+ if (!detail) return "";
266
+ return redactSecretString(detail).slice(0, 400);
267
+ }
268
+
269
+ function isAnthropicRecord(value: unknown): value is Record<string, unknown> {
270
+ return value !== null && typeof value === "object" && !Array.isArray(value);
271
+ }
272
+
273
+ function anthropicStructuralValueType(value: unknown): string {
274
+ if (value === null) return "null";
275
+ return Array.isArray(value) ? "array" : typeof value;
276
+ }
277
+
278
+ interface InvalidAnthropicShapeDiagnostic {
279
+ reason: "content_not_array" | "content_block_not_object";
280
+ blockIndex?: number;
281
+ valueType: string;
282
+ }
283
+
284
+ /**
285
+ * Structured refusal for a malformed buffered response body, shaped like the google adapter's
286
+ * `invalidGoogleShapeEvent` so an operator reading a log can tell which rung failed: the content
287
+ * container, or one block inside an otherwise well-formed container.
288
+ */
289
+ function invalidAnthropicShapeEvent(
290
+ diagnostic: InvalidAnthropicShapeDiagnostic,
291
+ ): Extract<AdapterEvent, { type: "error" }> {
292
+ const at = diagnostic.blockIndex !== undefined ? `; blockIndex=${diagnostic.blockIndex}` : "";
293
+ const subject = diagnostic.reason === "content_not_array" ? "content" : "content block";
294
+ return {
295
+ type: "error",
296
+ message: `anthropic response contained invalid ${subject} (${diagnostic.reason}${at}; valueType=${diagnostic.valueType})`,
297
+ status: 502,
298
+ errorType: "upstream_error",
299
+ };
300
+ }
301
+
302
+ function extractAnthropicErrorDetail(parsed: unknown): string | undefined {
303
+ if (typeof parsed === "string") return parsed.trim() || undefined;
304
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
305
+ const obj = parsed as Record<string, unknown>;
306
+ // Anthropic envelope: { type: "error", error: { type, message } }; tolerate a bare
307
+ // { error: { message } } and a string error field.
308
+ const err = obj.error;
309
+ if (typeof err === "string" && err.trim()) return err.trim();
310
+ if (err !== null && typeof err === "object" && !Array.isArray(err)) {
311
+ const e = err as Record<string, unknown>;
312
+ const msg = e.message;
313
+ if (typeof msg !== "string" || !msg.trim()) return undefined;
314
+ const type = e.type;
315
+ return typeof type === "string" && type.trim()
316
+ ? `${type.trim()}: ${msg.trim()}`
317
+ : msg.trim();
318
+ }
319
+ return undefined;
320
+ }
321
+
246
322
  function usesNativeAnthropicEndpoint(provider: OcxProviderConfig): boolean {
247
323
  try {
248
324
  return new URL(provider.baseUrl).hostname === "api.anthropic.com";
@@ -277,7 +353,69 @@ function usableToolUseId(id: unknown): string {
277
353
  return typeof id === "string" && id.trim() ? id : synthesizeToolUseId();
278
354
  }
279
355
 
280
- function toolUseArguments(input: unknown): string {
356
+ /**
357
+ * Bound repair for a malformed tool-arguments string under the compatibility profile (#658):
358
+ * a gateway such as AgentRouter can concatenate JSON objects (`{}{"value":42}`). Find the
359
+ * last parseable JSON object by scanning suffixes from each object-open brace and prefixes
360
+ * ending at each object-close brace. Both scans walk backwards from the end trying at most
361
+ * `maxCandidates` positions, so no offset index is ever materialized: a brace-dense hostile
362
+ * input costs at most 2 × maxCandidates bounded JSON.parse attempts and no extra storage.
363
+ * Inputs above MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES are not repaired at all.
364
+ */
365
+ const MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES = 1024 * 1024;
366
+
367
+ /**
368
+ * Whether `input` encodes to more than `max` UTF-8 bytes, with an early exit so the check
369
+ * itself never allocates a copy of a hostile string. `string.length` counts UTF-16 code
370
+ * units, which undercounts astral text by 2x against a byte budget.
371
+ */
372
+ function utf8BytesExceed(input: string, max: number): boolean {
373
+ let bytes = 0;
374
+ for (let i = 0; i < input.length; i++) {
375
+ const code = input.charCodeAt(i);
376
+ if (code < 0x80) bytes += 1;
377
+ else if (code < 0x800) bytes += 2;
378
+ else if (code >= 0xd800 && code <= 0xdbff && i + 1 < input.length
379
+ && input.charCodeAt(i + 1) >= 0xdc00 && input.charCodeAt(i + 1) <= 0xdfff) {
380
+ // A complete surrogate pair is one 4-byte scalar. Anything else — a high surrogate
381
+ // followed by another high surrogate or a non-surrogate — encodes as two separate
382
+ // U+FFFD replacements, so the next unit must NOT be skipped.
383
+ bytes += 4;
384
+ i++;
385
+ } else bytes += 3; // lone surrogates encode as U+FFFD (3 bytes)
386
+ if (bytes > max) return true;
387
+ }
388
+ return false;
389
+ }
390
+
391
+ function lastValidJsonObject(input: string, maxCandidates: number): string | undefined {
392
+ const tryParseObject = (candidate: string): string | undefined => {
393
+ try {
394
+ const parsed = JSON.parse(candidate) as unknown;
395
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return candidate;
396
+ } catch { /* keep scanning */ }
397
+ return undefined;
398
+ };
399
+ let scanFrom = input.length - 1;
400
+ for (let tried = 0; tried < maxCandidates && scanFrom >= 0; tried++) {
401
+ const open = input.lastIndexOf("{", scanFrom);
402
+ if (open === -1) break;
403
+ const repaired = tryParseObject(input.slice(open));
404
+ if (repaired !== undefined) return repaired;
405
+ scanFrom = open - 1;
406
+ }
407
+ scanFrom = input.length - 1;
408
+ for (let tried = 0; tried < maxCandidates && scanFrom >= 0; tried++) {
409
+ const close = input.lastIndexOf("}", scanFrom);
410
+ if (close === -1) break;
411
+ const repaired = tryParseObject(input.slice(0, close + 1));
412
+ if (repaired !== undefined) return repaired;
413
+ scanFrom = close - 1;
414
+ }
415
+ return undefined;
416
+ }
417
+
418
+ function toolUseArguments(input: unknown, lenient = false): string {
281
419
  if (typeof input === "string") {
282
420
  const trimmed = input.trim();
283
421
  if (!trimmed) return "{}";
@@ -285,6 +423,10 @@ function toolUseArguments(input: unknown): string {
285
423
  JSON.parse(trimmed);
286
424
  return trimmed;
287
425
  } catch {
426
+ if (lenient && !utf8BytesExceed(trimmed, MAX_REPAIRABLE_TOOL_ARGUMENT_BYTES)) {
427
+ const repaired = lastValidJsonObject(trimmed, 32);
428
+ if (repaired !== undefined) return repaired;
429
+ }
288
430
  // A tool call's arguments must be a JSON object. Re-encoding an unparseable string as a
289
431
  // JSON *string* is the double-encoding #765 reports: the caller then receives
290
432
  // `"get weather"` where an object was required and the tool call is unusable either way.
@@ -342,16 +484,68 @@ const ADAPTIVE_THINKING_FAMILY_MINIMUMS: Record<string, readonly [major: number,
342
484
  fable: [0, 0],
343
485
  };
344
486
 
345
- function usesAdaptiveThinking(modelId: string): boolean {
346
- // Minor is 1-2 digits with a non-digit lookahead so date-pinned ids ("claude-opus-4-20250514")
347
- // parse as minor 0 instead of minor 20250514; suffixed ids ("claude-opus-4-8[1m]") still match.
348
- const match = /^claude-([a-z]+)-(\d+)(?:-(\d{1,2}))?(?!\d)/.exec(modelId);
349
- if (!match) return false;
350
- const minimum = ADAPTIVE_THINKING_FAMILY_MINIMUMS[match[1]];
487
+ /**
488
+ * Family/version parse for a Claude model id, tolerant of a routing prefix.
489
+ *
490
+ * `parsed.modelId` is not always bare, and the slash can fall on either side.
491
+ * A `modelMap` entry may point at a routed destination such as
492
+ * `anthropic/claude-sonnet-5` (prefix), while a custom provider may expose a
493
+ * native id such as `claude-sonnet-5/variant` (suffix); both survive routing's
494
+ * known-id decoding. So this matches the segment that actually begins with
495
+ * `claude-` rather than assuming it is the first or the last one. A capability
496
+ * predicate that quietly returns false is worse than one that throws — the
497
+ * request just goes out wrong.
498
+ *
499
+ * Minor is 1-2 digits with a non-digit lookahead so date-pinned ids
500
+ * ("claude-opus-4-20250514") parse as minor 0 instead of minor 20250514;
501
+ * suffixed ids ("claude-opus-4-8[1m]") still match.
502
+ */
503
+ function claudeFamilyVersion(modelId: string): { family: string; major: number; minor: number } | undefined {
504
+ // Find the segment that actually starts with `claude-`, rather than assuming it is either
505
+ // the first (breaks `anthropic/claude-sonnet-5`) or the last (breaks `claude-sonnet-5/variant`,
506
+ // where the slash carries a vendor suffix rather than a routing prefix).
507
+ const match = /(?:^|\/)claude-([a-z]+)-(\d+)(?:[.-](\d{1,2}))?(?!\d)/i.exec(modelId);
508
+ if (!match) return undefined;
509
+ return {
510
+ family: match[1]!.toLowerCase(),
511
+ major: Number(match[2]),
512
+ minor: match[3] === undefined ? 0 : Number(match[3]),
513
+ };
514
+ }
515
+
516
+ function meetsFamilyMinimum(
517
+ modelId: string,
518
+ minimums: Record<string, readonly [major: number, minor: number]>,
519
+ ): boolean {
520
+ const parsed = claudeFamilyVersion(modelId);
521
+ if (!parsed) return false;
522
+ const minimum = minimums[parsed.family];
351
523
  if (!minimum) return false;
352
- const major = Number(match[2]);
353
- const minor = match[3] === undefined ? 0 : Number(match[3]);
354
- return major > minimum[0] || (major === minimum[0] && minor >= minimum[1]);
524
+ return parsed.major > minimum[0] || (parsed.major === minimum[0] && parsed.minor >= minimum[1]);
525
+ }
526
+
527
+ function usesAdaptiveThinking(modelId: string): boolean {
528
+ return meetsFamilyMinimum(modelId, ADAPTIVE_THINKING_FAMILY_MINIMUMS);
529
+ }
530
+
531
+ /**
532
+ * Claude families that (a) think by DEFAULT when the request omits `thinking`,
533
+ * and (b) accept an explicit `thinking: {type: "disabled"}` to turn it off.
534
+ *
535
+ * Deliberately NOT `usesAdaptiveThinking()`, which answers a different question
536
+ * (which wire shape a family accepts). The two sets differ in both directions:
537
+ * Fable always thinks and REJECTS an explicit disable, while Opus 4.7/4.8 use
538
+ * the adaptive wire but leave thinking off when the field is omitted, so they
539
+ * need no disable at all. Seeded with the family where the defect reproduces
540
+ * (#545); widen only with vendor evidence, since a wrong entry here turns a
541
+ * silent truncation into a 400.
542
+ */
543
+ const EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS: Record<string, readonly [major: number, minor: number]> = {
544
+ sonnet: [5, 0],
545
+ };
546
+
547
+ function supportsExplicitThinkingDisable(modelId: string): boolean {
548
+ return meetsFamilyMinimum(modelId, EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS);
355
549
  }
356
550
 
357
551
  /** `output_config.effort` accepts low|medium|high|xhigh|max — "minimal" is rejected with a 400. */
@@ -359,6 +553,18 @@ function adaptiveEffort(effort: string): string {
359
553
  return effort === "minimal" ? "low" : effort;
360
554
  }
361
555
 
556
+ function defaultReasoningEffort(provider: OcxProviderConfig, modelId: string): string | undefined {
557
+ const value = modelRecordValue(provider.modelDefaultReasoningEfforts, modelId);
558
+ if (typeof value !== "string") return undefined;
559
+ const trimmed = value.trim();
560
+ // `__omit__` means "send no reasoning field", not "an effort literally named
561
+ // __omit__". Without this the sentinel reached the wire as
562
+ // `output_config.effort: "__omit__"` on adaptive models, and enabled budget
563
+ // thinking on the rest — the opposite of what it asks for (#2432).
564
+ if (!trimmed || isReasoningEffortOmitted(trimmed)) return undefined;
565
+ return trimmed;
566
+ }
567
+
362
568
  function usageFromAnthropic(usage: Record<string, number> | undefined): OcxUsage | undefined {
363
569
  if (!usage) return undefined;
364
570
  const hasCache = usage.cache_read_input_tokens !== undefined || usage.cache_creation_input_tokens !== undefined;
@@ -401,7 +607,7 @@ function buildToolNameTransforms(provider: OcxProviderConfig): { toWire: (name:
401
607
  return { toWire: (name) => name, fromWire: (name) => name };
402
608
  }
403
609
 
404
- function toAnthropicToolResult(msg: OcxToolResultMessage): Record<string, unknown> {
610
+ function toAnthropicToolResult(msg: OcxToolResultMessage, wireCallId: string): Record<string, unknown> {
405
611
  // Anthropic tool_result accepts a string OR content blocks — render images natively
406
612
  // (e.g. Codex view_image output) instead of dropping them.
407
613
  let content: string | unknown[];
@@ -416,12 +622,17 @@ function toAnthropicToolResult(msg: OcxToolResultMessage): Record<string, unknow
416
622
  }
417
623
  return {
418
624
  type: "tool_result",
419
- tool_use_id: msg.toolCallId,
625
+ tool_use_id: wireCallId,
420
626
  content,
421
627
  ...(msg.isError ? { is_error: true } : {}),
422
628
  };
423
629
  }
424
630
 
631
+ function unrepresentableToolCallText(tc: OcxToolCall, wireName: string): string {
632
+ const args = typeof tc.arguments === "string" ? tc.arguments : JSON.stringify(tc.arguments);
633
+ return `[tool_use without a usable id: ${wireName}]\n${args}`;
634
+ }
635
+
425
636
  function orphanToolResultText(msg: OcxToolResultMessage): string {
426
637
  const label = msg.toolName ? `${msg.toolName} (${msg.toolCallId})` : msg.toolCallId;
427
638
  const content = typeof msg.content === "string"
@@ -430,10 +641,80 @@ function orphanToolResultText(msg: OcxToolResultMessage): string {
430
641
  return `[tool_result without adjacent tool_use: ${label}]\n${content}`;
431
642
  }
432
643
 
644
+ /**
645
+ * AgentRouter answers 400 `content-blocked` when the first user message is not in English
646
+ * (#2074), while the same request in English returns 200. The gateway is inspecting the opening
647
+ * user content, so an Anthropic `system` string cannot reach it — the framing has to sit in the
648
+ * first user turn.
649
+ */
650
+ const AGENTROUTER_LANGUAGE_PREAMBLE =
651
+ "[Instruction: Process the user request below and respond in the appropriate language.]";
652
+
653
+ /**
654
+ * Exact host match, not a substring.
655
+ *
656
+ * A `hostname.includes("agentrouter")` test also matches `notagentrouter.example` and
657
+ * `agentrouter.org.attacker.example`, which would let an unrelated destination silently
658
+ * receive an injected instruction block. A prompt mutation keyed on a provider's identity
659
+ * must be keyed on that identity exactly.
660
+ */
661
+ function isAgentRouterEndpoint(baseUrl: string): boolean {
662
+ try {
663
+ const { hostname } = new URL(baseUrl);
664
+ return hostname === "agentrouter.org" || hostname.endsWith(".agentrouter.org");
665
+ } catch {
666
+ return false;
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Prepend the framing as its OWN text block instead of splicing it into the user's string.
672
+ *
673
+ * The distinction matters: rewriting `content` to `${marker}\n\n${original}` edits what the
674
+ * user wrote, and every downstream consumer — logs, retries, an upstream that echoes the turn —
675
+ * then sees a sentence the user never typed as if they had. A separate leading block carries the
676
+ * same signal to the filter while the original text survives byte-for-byte.
677
+ *
678
+ * Only the first user turn is framed, because only the first is what the gateway rejects.
679
+ */
680
+ function applyAgentRouterLanguageFraming(messages: unknown[]): void {
681
+ const firstUser = messages.find(
682
+ (m): m is { role: string; content: unknown } =>
683
+ typeof m === "object" && m !== null && (m as { role?: unknown }).role === "user",
684
+ );
685
+ if (!firstUser) return;
686
+ const preamble = { type: "text", text: AGENTROUTER_LANGUAGE_PREAMBLE };
687
+ if (typeof firstUser.content === "string") {
688
+ firstUser.content = firstUser.content === ""
689
+ ? [preamble]
690
+ : [preamble, { type: "text", text: firstUser.content }];
691
+ return;
692
+ }
693
+ if (!Array.isArray(firstUser.content)) return;
694
+ // Idempotence is keyed on the LEADING block being exactly the marker. A substring test would
695
+ // let a user who quotes the marker later in their own prompt suppress the framing entirely.
696
+ const [head] = firstUser.content as { type?: unknown; text?: unknown }[];
697
+ if (head?.type === "text" && head.text === AGENTROUTER_LANGUAGE_PREAMBLE) return;
698
+ (firstUser.content as unknown[]).unshift(preamble);
699
+ }
700
+
433
701
  function messagesToAnthropicFormat(
434
702
  parsed: OcxParsedRequest,
435
703
  toolNames: { toWire: (name: string) => string },
436
704
  ): { system: string | undefined; messages: unknown[] } {
705
+ // One allocator for the whole request: a tool_result must resolve to the SAME wire id its
706
+ // call got, and two distinct raw ids must never collapse into one. Conforming ids are claimed
707
+ // first so a rewritten id can never squat on an id another call legitimately owns.
708
+ const callIds = createToolCallIdAllocator();
709
+ for (const message of parsed.context.messages) {
710
+ if (message.role === "assistant") {
711
+ for (const part of (message as OcxAssistantMessage).content) {
712
+ if (part.type === "toolCall") callIds.reserve((part as OcxToolCall).id);
713
+ }
714
+ } else if (message.role === "toolResult") {
715
+ callIds.reserve((message as OcxToolResultMessage).toolCallId);
716
+ }
717
+ }
437
718
  const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(
438
719
  parsed.context.tools,
439
720
  parsed.options.toolChoice,
@@ -441,7 +722,7 @@ function messagesToAnthropicFormat(
441
722
  );
442
723
  const systemParts = [...(parsed.context.systemPrompt ?? []), ...(toolCatalogNudge ? [toolCatalogNudge] : [])];
443
724
  const system = systemParts.length
444
- ? neutralizeIdentity(systemParts.join("\n\n")) || undefined
725
+ ? identifyRoutedModel(systemParts.join("\n\n"), parsed.modelId) || undefined
445
726
  : undefined;
446
727
  const messages: unknown[] = [];
447
728
 
@@ -485,8 +766,17 @@ function messagesToAnthropicFormat(
485
766
  } else if (part.type === "toolCall") {
486
767
  const tc = part as OcxToolCall;
487
768
  const flatName = namespacedToolName(tc.namespace, tc.name);
488
- toolUseIds.push(tc.id);
489
- toolUses.push({ type: "tool_use", id: tc.id, name: toolNames.toWire(flatName), input: tc.arguments });
769
+ // Normalized here, and identically for the matching tool_result above, so a history
770
+ // replayed from another provider path keeps its call/result pairing (#1767).
771
+ // No raw fallback: restoring an empty/unusable id puts a value on the wire Anthropic
772
+ // rejects. An unrepresentable call becomes text instead, and its result follows it there.
773
+ const wireCallId = callIds.allocate(tc.id);
774
+ if (wireCallId === undefined) {
775
+ preface.push({ type: "text", text: unrepresentableToolCallText(tc, toolNames.toWire(flatName)) });
776
+ continue;
777
+ }
778
+ toolUseIds.push(wireCallId);
779
+ toolUses.push({ type: "tool_use", id: wireCallId, name: toolNames.toWire(flatName), input: tc.arguments });
490
780
  }
491
781
  }
492
782
  // Anthropic treats text/thinking after tool_use as ending the tool turn, which makes
@@ -502,9 +792,13 @@ function messagesToAnthropicFormat(
502
792
  let j = i + 1;
503
793
  while (j < parsed.context.messages.length && parsed.context.messages[j].role === "toolResult") {
504
794
  const tr = parsed.context.messages[j] as OcxToolResultMessage;
505
- if (requiredIds.has(tr.toolCallId) && !seen.has(tr.toolCallId)) {
506
- resultBlocks.push(toAnthropicToolResult(tr));
507
- seen.add(tr.toolCallId);
795
+ // Match on the WIRE id. requiredIds holds normalized ids, so comparing the raw result id
796
+ // made every rewritten pair lose its result to orphan text and gain a synthetic
797
+ // missing-result block. lookup() never mints an id: a result with no call stays orphan.
798
+ const wireResultId = callIds.lookup(tr.toolCallId);
799
+ if (wireResultId !== undefined && requiredIds.has(wireResultId) && !seen.has(wireResultId)) {
800
+ resultBlocks.push(toAnthropicToolResult(tr, wireResultId));
801
+ seen.add(wireResultId);
508
802
  } else {
509
803
  orphanBlocks.push({ type: "text", text: orphanToolResultText(tr) });
510
804
  }
@@ -549,11 +843,8 @@ function messagesToAnthropicFormat(
549
843
 
550
844
  function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (name: string) => string }): unknown[] | undefined {
551
845
  if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
552
- const allowed = isAllowedToolChoice(parsed.options.toolChoice)
553
- ? new Set(parsed.options.toolChoice.allowedTools)
554
- : undefined;
555
- const tools = allowed
556
- ? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
846
+ const tools = isAllowedToolChoice(parsed.options.toolChoice)
847
+ ? parsed.context.tools.filter(toolChoiceToolPredicate(parsed.options.toolChoice, parsed.context.tools))
557
848
  : parsed.context.tools;
558
849
  if (tools.length === 0) return undefined;
559
850
  const converted = tools.map(t => ({
@@ -564,35 +855,8 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
564
855
  return converted;
565
856
  }
566
857
 
567
- // Codex multi-agent v2 stamps a Responses-only `encrypted: true` marker on
568
- // collaboration tool schemas (openai/codex 5f4d06ef; issue #85). It is an
569
- // annotation for the ChatGPT backend only. Anthropic input_schema is strict
570
- // JSON Schema; strip the marker defensively everywhere it can appear as a
571
- // schema keyword, while preserving properties literally named "encrypted".
572
- const ENCRYPTED_MARKER_NAME_BAG_KEYS = new Set(["properties", "patternProperties", "$defs", "definitions"]);
573
- const ENCRYPTED_MARKER_LITERAL_VALUE_KEYS = new Set(["const", "default", "enum", "examples"]);
574
-
575
- function stripEncryptedMarker(node: unknown, inNameBag = false): unknown {
576
- if (Array.isArray(node)) return node.map(item => stripEncryptedMarker(item));
577
- if (!node || typeof node !== "object") return node;
578
-
579
- const out: Record<string, unknown> = {};
580
-
581
- for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
582
- if (inNameBag) {
583
- out[key] = stripEncryptedMarker(value);
584
- } else if (key !== "encrypted") {
585
- out[key] = ENCRYPTED_MARKER_LITERAL_VALUE_KEYS.has(key)
586
- ? value
587
- : stripEncryptedMarker(value, ENCRYPTED_MARKER_NAME_BAG_KEYS.has(key));
588
- }
589
- }
590
-
591
- return out;
592
- }
593
-
594
858
  function normalizeAnthropicInputSchema(schema: unknown): Record<string, unknown> {
595
- const stripped = stripEncryptedMarker(schema);
859
+ const stripped = stripResponsesOnlyEncryptedMarker(schema);
596
860
  const obj = stripped && typeof stripped === "object" && !Array.isArray(stripped)
597
861
  ? stripped as Record<string, unknown>
598
862
  : {};
@@ -659,6 +923,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
659
923
  return {
660
924
  name: "anthropic",
661
925
 
926
+ formatErrorBody: formatAnthropicErrorBody,
927
+
662
928
  async buildRequest(parsed: OcxParsedRequest, incoming?: IncomingMeta) {
663
929
  if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
664
930
  if (isOAuth) {
@@ -668,6 +934,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
668
934
  }
669
935
 
670
936
  const { system, messages } = messagesToAnthropicFormat(parsed, toolNames);
937
+ // Before image normalization, so the framing block is present for every downstream pass.
938
+ if (isAgentRouterEndpoint(provider.baseUrl)) applyAgentRouterLanguageFraming(messages);
671
939
  // Primary image layer: resize/re-encode to fit Anthropic limits without dropping
672
940
  // (anthropic-image-normalize.ts); the guard below remains the deterministic backstop.
673
941
  // imageTierBias > 0 = upstream-413 tightened retry (030): start every image one tier lower.
@@ -700,16 +968,25 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
700
968
  // `reasoning` is a Codex effort string; "none" is the disable sentinel (see parser.ts
701
969
  // REASONING_EFFORTS). A bare truthy check would treat "none" as truthy and wrongly enable
702
970
  // extended thinking (and strip temperature/top_p), so gate on a real, non-disable effort.
703
- if (typeof parsed.options.reasoning === "string" && parsed.options.reasoning !== "none") {
971
+ //
972
+ // "none" is not the same as absent. Omitting `thinking` lets a default-on model think
973
+ // anyway, and thinking shares the caller's `max_tokens` — which truncates a small-budget
974
+ // request before it can emit its stop sequence (#545). Say "disabled" out loud where the
975
+ // model both defaults to thinking and accepts being told not to.
976
+ const effectiveReasoning = parsed.options.reasoning ?? defaultReasoningEffort(provider, parsed.modelId);
977
+ if (effectiveReasoning === "none" && supportsExplicitThinkingDisable(parsed.modelId)) {
978
+ body.thinking = { type: "disabled" };
979
+ } else if (typeof effectiveReasoning === "string" && effectiveReasoning !== "none") {
704
980
  if (usesAdaptiveThinking(parsed.modelId)) {
705
981
  // Adaptive-thinking models replace the token budget with an effort knob and reject
706
982
  // `thinking.type: "enabled"` outright. `max_tokens` still caps thinking plus visible
707
983
  // output, so high effort needs the same total-token headroom as budget thinking or a
708
984
  // default 8192-token request can spend everything on thought and return empty text.
709
985
  body.thinking = { type: "adaptive" };
710
- body.output_config = { effort: adaptiveEffort(parsed.options.reasoning) };
986
+ const effort = adaptiveEffort(effectiveReasoning);
987
+ body.output_config = { effort };
711
988
  const explicitMaxOut = parsed.options.maxOutputTokens;
712
- const wantBudget = reasoningBudget(parsed.options.reasoning);
989
+ const wantBudget = reasoningBudget(effort);
713
990
  const floor = wantBudget + OUTPUT_HEADROOM;
714
991
  // Preserve explicit caller limits as-is; for omitted limits use the adaptive ceiling
715
992
  // so effort=max (budget=32k) still leaves OUTPUT_HEADROOM tokens for visible output.
@@ -722,7 +999,7 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
722
999
  // 400s ("max_tokens must be greater than thinking.budget_tokens"). Size them so max_tokens
723
1000
  // always exceeds the budget within a model-safe ceiling, reserving room for visible output.
724
1001
  const maxOut = parsed.options.maxOutputTokens ?? DEFAULT_MAX_TOKENS;
725
- const wantBudget = reasoningBudget(parsed.options.reasoning);
1002
+ const wantBudget = reasoningBudget(effectiveReasoning);
726
1003
  const maxTokens = Math.min(REASONING_MAX_TOKENS_CEILING, Math.max(maxOut, wantBudget + OUTPUT_HEADROOM));
727
1004
  const budget = Math.max(MIN_THINKING_BUDGET, Math.min(wantBudget, maxTokens - OUTPUT_FLOOR));
728
1005
  body.max_tokens = maxTokens;
@@ -733,6 +1010,20 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
733
1010
  delete body.top_p;
734
1011
  }
735
1012
 
1013
+ const textFormat = parsed.options.textFormat;
1014
+ if (textFormat?.type === "json_schema" && textFormat.schema) {
1015
+ const outputConfig = body.output_config;
1016
+ body.output_config = {
1017
+ ...(outputConfig && typeof outputConfig === "object" && !Array.isArray(outputConfig)
1018
+ ? outputConfig
1019
+ : {}),
1020
+ format: {
1021
+ type: "json_schema",
1022
+ schema: normalizeAnthropicOutputSchema(textFormat.schema),
1023
+ },
1024
+ };
1025
+ }
1026
+
736
1027
  if (parsed.options.toolChoice && (tools || parsed.options.toolChoice === "none")) {
737
1028
  const tc = parsed.options.toolChoice;
738
1029
  if (tc === "auto") body.tool_choice = { type: "auto" };
@@ -798,10 +1089,23 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
798
1089
  let pendingUsage: Record<string, number> | undefined;
799
1090
  let pendingStopReason: string | undefined;
800
1091
  let emittedDone = false;
1092
+ let sawVisibleText = false;
801
1093
 
802
1094
  const emitDone = function* (): Generator<AdapterEvent> {
803
1095
  if (emittedDone) return;
804
1096
  emittedDone = true;
1097
+ // An `error` stop reason is a failed generation, not a stop. Forwarding it as `done`
1098
+ // lets the turn report success and install replacement history on a compaction turn.
1099
+ if (pendingStopReason === "error") {
1100
+ yield {
1101
+ type: "error",
1102
+ message: "upstream ended the turn with stop_reason \"error\"",
1103
+ status: 502,
1104
+ errorType: "upstream_error",
1105
+ usage: usageFromAnthropic(pendingUsage),
1106
+ };
1107
+ return;
1108
+ }
805
1109
  yield {
806
1110
  type: "done",
807
1111
  usage: usageFromAnthropic(pendingUsage),
@@ -818,13 +1122,21 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
818
1122
  const payload = record.data.trim();
819
1123
  if (!payload) continue;
820
1124
 
821
- let data: Record<string, unknown>;
1125
+ let parsed: unknown;
822
1126
  try {
823
- data = JSON.parse(payload) as Record<string, unknown>;
1127
+ parsed = JSON.parse(payload);
824
1128
  } catch {
825
1129
  debugDroppedFrame("anthropic", payload);
826
1130
  continue;
827
1131
  }
1132
+ // `JSON.parse("null")` returns null instead of throwing, so the catch above cannot cover
1133
+ // it and the `data.type` read below crashed the stream. Drop a non-record frame the same
1134
+ // way an unparseable one is dropped, so the message_stop check still governs the outcome.
1135
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1136
+ debugDroppedFrame("anthropic", payload);
1137
+ continue;
1138
+ }
1139
+ const data = parsed as Record<string, unknown>;
828
1140
 
829
1141
  switch (record.event || data.type) {
830
1142
  case "message_start": {
@@ -853,6 +1165,10 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
853
1165
  const delta = data.delta as Record<string, unknown> | undefined;
854
1166
  if (!delta) break;
855
1167
  if (delta.type === "text_delta" && typeof delta.text === "string") {
1168
+ // Only non-empty text proves the upstream produced usable output; an empty
1169
+ // delta followed by EOF must stay a truncation error even on the tolerant
1170
+ // profile, or a cut-off turn would surface as a successful empty answer.
1171
+ if (delta.text.length > 0) sawVisibleText = true;
856
1172
  yield { type: "text_delta", text: delta.text };
857
1173
  } else if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
858
1174
  yield { type: "thinking_delta", thinking: delta.thinking };
@@ -934,12 +1250,31 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
934
1250
  code: "translation_buffer_limit",
935
1251
  message: "upstream translation buffer exceeded the safe limit",
936
1252
  };
1253
+ // The budget error IS the terminal event for this stream. Falling through to the
1254
+ // EOF handling below could append tool_call_end/done after it, violating the
1255
+ // one-terminal-event contract for consumers that keep draining the generator.
1256
+ return;
937
1257
  } finally {
938
1258
  if (currentToolCallId) budget.closeCall(currentToolCallId);
939
1259
  }
940
1260
  if (!emittedDone) {
941
1261
  // Fail closed on transport EOF. Compatible providers may omit message_stop after message_delta.stop_reason.
942
1262
  if (pendingStopReason !== undefined) {
1263
+ // Same rule as emitDone: an `error` stop reason is a failed generation, not a stop.
1264
+ // This branch bypasses emitDone entirely (it exists for providers that close after
1265
+ // message_delta without message_stop), so the check has to be repeated here or the
1266
+ // EOF route silently reports success.
1267
+ if (pendingStopReason === "error") {
1268
+ emittedDone = true;
1269
+ yield {
1270
+ type: "error",
1271
+ message: "upstream ended the turn with stop_reason \"error\"",
1272
+ status: 502,
1273
+ errorType: "upstream_error",
1274
+ usage: usageFromAnthropic(pendingUsage),
1275
+ };
1276
+ return;
1277
+ }
943
1278
  const stopReason = pendingStopReason === "max_tokens"
944
1279
  ? "max_tokens"
945
1280
  : pendingStopReason === "refusal" || pendingStopReason === "content_filter"
@@ -951,6 +1286,26 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
951
1286
  usage: usageFromAnthropic(pendingUsage),
952
1287
  ...(stopReason ? { stopReason } : {}),
953
1288
  };
1289
+ } else if (provider.anthropicEofTolerance === true) {
1290
+ // AgentRouter-style compatibility profile (#658): the upstream can close the stream
1291
+ // after valid content without terminal frames. Complete only when visible text was
1292
+ // received or an open tool call has complete JSON-object arguments; everything else
1293
+ // (incomplete tool JSON, no usable content, transport failure) stays a truncation
1294
+ // error, matching the strict default.
1295
+ if (currentToolCallId) {
1296
+ if (streamedToolArgumentsParse(currentToolCallJson)) {
1297
+ budget.closeCall(currentToolCallId);
1298
+ currentToolCallId = "";
1299
+ yield { type: "tool_call_end" };
1300
+ yield* emitDone();
1301
+ } else {
1302
+ yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
1303
+ }
1304
+ } else if (sawVisibleText) {
1305
+ yield* emitDone();
1306
+ } else {
1307
+ yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
1308
+ }
954
1309
  } else {
955
1310
  yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
956
1311
  }
@@ -958,12 +1313,55 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
958
1313
  },
959
1314
 
960
1315
  async parseResponse(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]> {
961
- const json = await response.json() as Record<string, unknown>;
1316
+ const parsed: unknown = await response.json();
1317
+ // `response.json()` resolves a body of `null` to `null` without throwing, so the cast below
1318
+ // used to reach `json.content` on it — the #1219 defect at the buffered body root. The
1319
+ // streaming parser has skipped a non-record frame since #1240, but a buffered body has no
1320
+ // next frame to recover into, so this fails closed instead.
1321
+ if (!isAnthropicRecord(parsed)) {
1322
+ return [{
1323
+ type: "error",
1324
+ message: `anthropic response was not a JSON object (${anthropicStructuralValueType(parsed)})`,
1325
+ status: 502,
1326
+ errorType: "upstream_error",
1327
+ }];
1328
+ }
1329
+ const json = parsed;
962
1330
  const responseBytes = new TextEncoder().encode(JSON.stringify(json)).byteLength;
963
1331
  budget.chargeRetained(responseBytes, { kind: "retained_collectors" });
964
1332
  try {
965
1333
  const events: AdapterEvent[] = [];
966
- const content = json.content as { type: string; text?: string; id?: string; name?: string; input?: unknown; thinking?: string; reasoning?: string; signature?: string; data?: string }[] | undefined;
1334
+ // Same retain-then-return tail every other terminal branch in this function uses; naming it
1335
+ // keeps the two shape guards below from drifting out of step with the accounting.
1336
+ const finishWithEvents = (batch: AdapterEvent[]): AdapterEvent[] => {
1337
+ retainTranslatedEventBatch(batch, budget);
1338
+ return batch;
1339
+ };
1340
+ const rawContent: unknown = json.content;
1341
+ // `content` is claimed model output inside a well-formed response, so it is governed by the
1342
+ // #1332 nested-shape rule (fail closed) rather than #1240's root-frame padding rule (skip).
1343
+ // Absence stays legal in both encodings. A present non-array was silently accepted and is
1344
+ // the dangerous case: a string is iterable, so `for (const block of "text")` walked it one
1345
+ // CHARACTER at a time, read `undefined` from every `block.type`, and completed the turn as a
1346
+ // successful empty response — the #2231 failure mode on a different adapter.
1347
+ if (rawContent !== undefined && rawContent !== null && !Array.isArray(rawContent)) {
1348
+ return finishWithEvents([invalidAnthropicShapeEvent({
1349
+ reason: "content_not_array",
1350
+ valueType: anthropicStructuralValueType(rawContent),
1351
+ })]);
1352
+ }
1353
+ if (Array.isArray(rawContent)) {
1354
+ for (let blockIndex = 0; blockIndex < rawContent.length; blockIndex++) {
1355
+ if (!isAnthropicRecord(rawContent[blockIndex])) {
1356
+ return finishWithEvents([invalidAnthropicShapeEvent({
1357
+ reason: "content_block_not_object",
1358
+ blockIndex,
1359
+ valueType: anthropicStructuralValueType(rawContent[blockIndex]),
1360
+ })]);
1361
+ }
1362
+ }
1363
+ }
1364
+ const content = rawContent as { type: string; text?: string; id?: string; name?: string; input?: unknown; thinking?: string; reasoning?: string; signature?: string; data?: string }[] | undefined;
967
1365
  if (content) {
968
1366
  for (const block of content) {
969
1367
  if (block.type === "text" && block.text) {
@@ -980,13 +1378,28 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
980
1378
  } else if (block.type === "tool_use") {
981
1379
  const id = usableToolUseId(block.id);
982
1380
  events.push({ type: "tool_call_start", id, name: toolNames.fromWire(block.name ?? "") });
983
- events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input) });
1381
+ events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input, provider.anthropicEofTolerance === true) });
984
1382
  events.push({ type: "tool_call_end" });
985
1383
  }
986
1384
  }
987
1385
  }
988
1386
  const usage = json.usage as Record<string, number> | undefined;
989
1387
  const stopReason = typeof json.stop_reason === "string" ? json.stop_reason : undefined;
1388
+ // An Anthropic-compatible upstream can forward an `error` stop reason verbatim. As a
1389
+ // `done` it reads as a clean completion, so the turn reports success and — on a compaction
1390
+ // turn — installs its partial summary as replacement history (#422). Usage is preserved:
1391
+ // a failed turn still consumed tokens.
1392
+ if (stopReason === "error") {
1393
+ events.push({
1394
+ type: "error",
1395
+ message: "upstream ended the turn with stop_reason \"error\"",
1396
+ status: 502,
1397
+ errorType: "upstream_error",
1398
+ usage: usageFromAnthropic(usage),
1399
+ });
1400
+ retainTranslatedEventBatch(events, budget);
1401
+ return events;
1402
+ }
990
1403
  events.push({
991
1404
  type: "done",
992
1405
  usage: usageFromAnthropic(usage),