@iislee/opencodex 2.11.0 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +114 -19
  3. package/bin/ocx.mjs +164 -36
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-BF38heuV.js +104 -0
  6. package/gui/dist/assets/index-DMiI18Kv.css +1 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +19 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +376 -52
  44. package/src/adapters/base.ts +54 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +601 -0
  48. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  49. package/src/adapters/cursor/cursor-errors.ts +129 -6
  50. package/src/adapters/cursor/discovery.ts +61 -4
  51. package/src/adapters/cursor/effort-map.ts +27 -3
  52. package/src/adapters/cursor/framing.ts +39 -0
  53. package/src/adapters/cursor/h2-pool.ts +123 -0
  54. package/src/adapters/cursor/http1-bidi.ts +361 -0
  55. package/src/adapters/cursor/images.ts +704 -0
  56. package/src/adapters/cursor/live-models.ts +174 -58
  57. package/src/adapters/cursor/live-transport.ts +609 -170
  58. package/src/adapters/cursor/native-exec-common.ts +23 -2
  59. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  60. package/src/adapters/cursor/native-exec-network.ts +1 -1
  61. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  62. package/src/adapters/cursor/native-exec.ts +101 -14
  63. package/src/adapters/cursor/protobuf-events.ts +829 -11
  64. package/src/adapters/cursor/protobuf-request.ts +383 -65
  65. package/src/adapters/cursor/request-builder.ts +237 -31
  66. package/src/adapters/cursor/tool-definitions.ts +242 -12
  67. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  68. package/src/adapters/cursor/transport.ts +22 -0
  69. package/src/adapters/cursor/types.ts +28 -1
  70. package/src/adapters/cursor.ts +133 -7
  71. package/src/adapters/google-antigravity-replay.ts +558 -34
  72. package/src/adapters/google-antigravity-wire.ts +43 -10
  73. package/src/adapters/google-http.ts +38 -10
  74. package/src/adapters/google-tool-schema.ts +80 -15
  75. package/src/adapters/google-truncation.ts +11 -0
  76. package/src/adapters/google.ts +618 -74
  77. package/src/adapters/identity.ts +39 -6
  78. package/src/adapters/kiro-errors.ts +11 -0
  79. package/src/adapters/kiro-events.ts +19 -1
  80. package/src/adapters/kiro-thinking.ts +18 -2
  81. package/src/adapters/kiro-tools.ts +10 -1
  82. package/src/adapters/kiro.ts +92 -54
  83. package/src/adapters/mimo-free.ts +17 -0
  84. package/src/adapters/openai-chat-url.ts +11 -0
  85. package/src/adapters/openai-chat.ts +1311 -336
  86. package/src/adapters/openai-responses-url.ts +16 -0
  87. package/src/adapters/openai-responses.ts +830 -56
  88. package/src/adapters/registry.ts +175 -0
  89. package/src/adapters/responses-tool-schema.ts +67 -0
  90. package/src/adapters/tool-call-id.ts +119 -0
  91. package/src/adapters/tool-catalog-nudge.ts +104 -21
  92. package/src/adapters/xai-web-search.ts +185 -0
  93. package/src/bridge.ts +428 -61
  94. package/src/chat/inbound.ts +43 -19
  95. package/src/chat/outbound.ts +82 -26
  96. package/src/claude/agents-inject.ts +32 -9
  97. package/src/claude/context-windows.ts +21 -5
  98. package/src/claude/desktop-3p.ts +243 -9
  99. package/src/claude/gateway-cache.ts +41 -4
  100. package/src/claude/inbound.ts +72 -3
  101. package/src/claude/model-info.ts +38 -15
  102. package/src/claude/outbound.ts +70 -16
  103. package/src/cli/account-api.ts +35 -1
  104. package/src/cli/account-auth.ts +33 -6
  105. package/src/cli/account-catalog-refresh.ts +14 -0
  106. package/src/cli/account-extended.ts +389 -2
  107. package/src/cli/account-main.ts +317 -0
  108. package/src/cli/account.ts +30 -6
  109. package/src/cli/agent.ts +45 -1
  110. package/src/cli/claude-agent-startup-sync.ts +73 -0
  111. package/src/cli/claude-desktop.ts +29 -4
  112. package/src/cli/claude.ts +103 -34
  113. package/src/cli/codex-log-guard-doctor.ts +103 -0
  114. package/src/cli/codex-shim-autorestore.ts +2 -0
  115. package/src/cli/codex-shim-readiness.ts +76 -0
  116. package/src/cli/combo.ts +8 -0
  117. package/src/cli/config-command.ts +74 -10
  118. package/src/cli/dispatch.ts +593 -0
  119. package/src/cli/doctor.ts +315 -43
  120. package/src/cli/ensure-desired-integrations.ts +152 -0
  121. package/src/cli/export-command.ts +46 -20
  122. package/src/cli/help.ts +20 -271
  123. package/src/cli/index.ts +378 -519
  124. package/src/cli/init.ts +4 -17
  125. package/src/cli/integrations.ts +120 -2
  126. package/src/cli/lab.ts +607 -0
  127. package/src/cli/launcher-context.ts +77 -0
  128. package/src/cli/minimax.ts +497 -0
  129. package/src/cli/models-runtime.ts +35 -2
  130. package/src/cli/models.ts +100 -14
  131. package/src/cli/observe.ts +92 -3
  132. package/src/cli/opencode.ts +4 -2
  133. package/src/cli/provider-runtime.ts +18 -1
  134. package/src/cli/provider.ts +24 -3
  135. package/src/cli/ready.ts +301 -0
  136. package/src/cli/registry.ts +437 -0
  137. package/src/cli/root.ts +86 -0
  138. package/src/cli/route-policy.ts +92 -0
  139. package/src/cli/runtime-api.ts +6 -3
  140. package/src/cli/star-prompt.ts +71 -15
  141. package/src/cli/status.ts +10 -3
  142. package/src/cli/system-restart-client.ts +146 -0
  143. package/src/cli/tray-proxy.ts +153 -6
  144. package/src/cli/v2.ts +105 -10
  145. package/src/cli.ts +1 -1
  146. package/src/clients/config-export.ts +1358 -21
  147. package/src/codex/account-label.ts +14 -1
  148. package/src/codex/account-lifecycle.ts +130 -13
  149. package/src/codex/account-namespaces.ts +49 -3
  150. package/src/codex/account-priority.ts +83 -0
  151. package/src/codex/account-store.ts +29 -2
  152. package/src/codex/account-usability.ts +25 -2
  153. package/src/codex/admission.ts +256 -0
  154. package/src/codex/affinity-debug.ts +162 -0
  155. package/src/codex/app-server-processes.ts +493 -106
  156. package/src/codex/app-server-restart-service.ts +232 -0
  157. package/src/codex/auth-api.ts +849 -242
  158. package/src/codex/auth-collision.ts +5 -3
  159. package/src/codex/auth-context.ts +345 -32
  160. package/src/codex/autostart-health.ts +8 -1
  161. package/src/codex/catalog/account-models.ts +67 -0
  162. package/src/codex/catalog/aggregation.ts +68 -10
  163. package/src/codex/catalog/bundled.ts +331 -33
  164. package/src/codex/catalog/effort.ts +121 -30
  165. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  166. package/src/codex/catalog/kinds.ts +2 -0
  167. package/src/codex/catalog/metadata.ts +529 -45
  168. package/src/codex/catalog/native-models.ts +72 -0
  169. package/src/codex/catalog/parsing.ts +224 -30
  170. package/src/codex/catalog/provider-fetch.ts +1460 -134
  171. package/src/codex/catalog/sync.ts +1449 -186
  172. package/src/codex/catalog-admission.ts +199 -0
  173. package/src/codex/catalog-refresh-status.ts +105 -0
  174. package/src/codex/catalog-write-serialization.ts +242 -0
  175. package/src/codex/catalog.ts +6 -3
  176. package/src/codex/codex-write-lock.ts +384 -0
  177. package/src/codex/convergence-types.ts +614 -0
  178. package/src/codex/convergence.ts +651 -0
  179. package/src/codex/coordinator-doctor.ts +332 -0
  180. package/src/codex/custom-model-catalog-migration.ts +176 -0
  181. package/src/codex/desired-state.ts +230 -0
  182. package/src/codex/features.ts +636 -39
  183. package/src/codex/generation.ts +202 -0
  184. package/src/codex/history-job.ts +407 -0
  185. package/src/codex/history-lock.ts +242 -0
  186. package/src/codex/history-migration-guardian.ts +26 -20
  187. package/src/codex/history-provider.ts +231 -28
  188. package/src/codex/history-transition.ts +105 -0
  189. package/src/codex/history-worker.ts +220 -0
  190. package/src/codex/inject-coordination.ts +290 -0
  191. package/src/codex/inject.ts +1073 -152
  192. package/src/codex/injected-marker.ts +37 -3
  193. package/src/codex/integration-record.ts +266 -0
  194. package/src/codex/internal/catalog-writer.ts +203 -0
  195. package/src/codex/internal/history-writer.ts +80 -0
  196. package/src/codex/journal.ts +66 -4
  197. package/src/codex/log-guard/inspect.ts +506 -0
  198. package/src/codex/log-guard/lock.ts +150 -0
  199. package/src/codex/log-guard/maintenance.ts +403 -0
  200. package/src/codex/log-guard/path-safety.ts +88 -0
  201. package/src/codex/log-guard/policy.ts +44 -0
  202. package/src/codex/log-guard/processes.ts +205 -0
  203. package/src/codex/log-guard/protection.ts +489 -0
  204. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  205. package/src/codex/main-account-cache.ts +24 -0
  206. package/src/codex/main-account.ts +29 -1
  207. package/src/codex/management-convergence.ts +167 -0
  208. package/src/codex/model-cache.ts +56 -10
  209. package/src/codex/model-entitlements.ts +353 -0
  210. package/src/codex/native-main-admission.ts +47 -0
  211. package/src/codex/native-main-auth-temp.ts +187 -0
  212. package/src/codex/native-main-claim.ts +178 -0
  213. package/src/codex/native-main-lock-file.ts +162 -0
  214. package/src/codex/native-main-owner.ts +329 -0
  215. package/src/codex/native-profile-api.ts +247 -0
  216. package/src/codex/native-profile-manager.ts +1531 -0
  217. package/src/codex/native-profile-processes.ts +121 -0
  218. package/src/codex/native-profile-recovery.ts +99 -0
  219. package/src/codex/native-profile-stage-store.ts +387 -0
  220. package/src/codex/native-profile-startup.ts +492 -0
  221. package/src/codex/native-profile-store.ts +855 -0
  222. package/src/codex/native-profile-types.ts +120 -0
  223. package/src/codex/native-residue.ts +682 -0
  224. package/src/codex/paths.ts +80 -1
  225. package/src/codex/plan-from-token.ts +140 -0
  226. package/src/codex/plan.ts +40 -0
  227. package/src/codex/plugins-doctor.ts +1 -1
  228. package/src/codex/pool-rotation.ts +74 -4
  229. package/src/codex/project-config-warnings.ts +20 -6
  230. package/src/codex/prompt-journal.ts +352 -0
  231. package/src/codex/prompt-layers.ts +967 -0
  232. package/src/codex/prompt-lock.ts +143 -0
  233. package/src/codex/quota-rejection.ts +298 -0
  234. package/src/codex/quota.ts +175 -13
  235. package/src/codex/refresh.ts +11 -2
  236. package/src/codex/reset-credit-recovery.ts +1044 -0
  237. package/src/codex/routing.ts +505 -94
  238. package/src/codex/runtime.ts +159 -38
  239. package/src/codex/shim.ts +1009 -28
  240. package/src/codex/subagent-model-fallback.ts +350 -35
  241. package/src/codex/sync.ts +191 -2
  242. package/src/codex/transition-state.ts +612 -0
  243. package/src/codex/upstream-host-health.ts +368 -0
  244. package/src/codex/user-identity.ts +557 -0
  245. package/src/codex/warmup.ts +187 -81
  246. package/src/codex/write-coordination.ts +114 -0
  247. package/src/combos/failover.ts +20 -0
  248. package/src/combos/index.ts +4 -0
  249. package/src/combos/request.ts +32 -0
  250. package/src/combos/types.ts +81 -9
  251. package/src/config/provider-name.ts +24 -0
  252. package/src/config.ts +1762 -140
  253. package/src/generated/compatibility-version.json +3164 -0
  254. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  255. package/src/grok/inject.ts +16 -5
  256. package/src/grok/inspect.ts +45 -0
  257. package/src/grok/sync.ts +2 -2
  258. package/src/images/loop.ts +152 -29
  259. package/src/images/plan.ts +23 -13
  260. package/src/integrations/config-io.ts +269 -0
  261. package/src/integrations/journal.ts +315 -0
  262. package/src/integrations/merge.ts +135 -0
  263. package/src/integrations/mutation-flight.ts +71 -0
  264. package/src/integrations/native/ownership-preflight.ts +202 -0
  265. package/src/integrations/omp-yaml-source.ts +358 -0
  266. package/src/integrations/owned-refresh.ts +74 -0
  267. package/src/integrations/ownership.ts +111 -0
  268. package/src/integrations/registry.ts +159 -0
  269. package/src/integrations/serialize.ts +314 -0
  270. package/src/integrations/state.ts +361 -0
  271. package/src/integrations/store.ts +103 -0
  272. package/src/integrations/writer-lock.ts +98 -0
  273. package/src/integrations/writer.ts +691 -0
  274. package/src/lab/artifacts/sanitize.ts +586 -0
  275. package/src/lab/artifacts/secure-fs.ts +475 -0
  276. package/src/lab/artifacts/store.ts +310 -0
  277. package/src/lab/automation/budgets.ts +78 -0
  278. package/src/lab/automation/config-persistence.ts +256 -0
  279. package/src/lab/automation/constants.ts +39 -0
  280. package/src/lab/automation/cooldown.ts +103 -0
  281. package/src/lab/automation/dispatch.ts +211 -0
  282. package/src/lab/automation/index.ts +13 -0
  283. package/src/lab/automation/orchestrator.ts +499 -0
  284. package/src/lab/automation/persistence.ts +512 -0
  285. package/src/lab/automation/planner.ts +371 -0
  286. package/src/lab/automation/policy.ts +136 -0
  287. package/src/lab/automation/queue.ts +191 -0
  288. package/src/lab/automation/recovery.ts +24 -0
  289. package/src/lab/automation/route-context.ts +21 -0
  290. package/src/lab/automation/run-key.ts +44 -0
  291. package/src/lab/automation/runs-query.ts +34 -0
  292. package/src/lab/automation/types.ts +160 -0
  293. package/src/lab/conformance/assertion.ts +325 -0
  294. package/src/lab/conformance/digest.ts +22 -0
  295. package/src/lab/conformance/executor.ts +741 -0
  296. package/src/lab/conformance/fixture-provider.ts +27 -0
  297. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  298. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  299. package/src/lab/conformance/harness-budget.ts +47 -0
  300. package/src/lab/conformance/index.ts +5 -0
  301. package/src/lab/conformance/jcs.ts +64 -0
  302. package/src/lab/conformance/json-pointer.ts +39 -0
  303. package/src/lab/conformance/manifest.ts +180 -0
  304. package/src/lab/conformance/mcp-stub.ts +179 -0
  305. package/src/lab/conformance/negative-controls.ts +164 -0
  306. package/src/lab/conformance/observation.ts +355 -0
  307. package/src/lab/conformance/runner.ts +68 -0
  308. package/src/lab/conformance/sse-normalize.ts +59 -0
  309. package/src/lab/conformance/suite-manifest.ts +78 -0
  310. package/src/lab/conformance/types.ts +214 -0
  311. package/src/lab/constants.ts +126 -0
  312. package/src/lab/digest.ts +64 -0
  313. package/src/lab/events/errors.ts +9 -0
  314. package/src/lab/events/limits.ts +117 -0
  315. package/src/lab/events/types.ts +229 -0
  316. package/src/lab/events/validate.ts +781 -0
  317. package/src/lab/fabric/constants.ts +40 -0
  318. package/src/lab/fabric/executor.ts +492 -0
  319. package/src/lab/fabric/index.ts +80 -0
  320. package/src/lab/fabric/manifest.ts +222 -0
  321. package/src/lab/fabric/observe.ts +489 -0
  322. package/src/lab/fabric/patch.ts +79 -0
  323. package/src/lab/fabric/producer-child.ts +139 -0
  324. package/src/lab/fabric/producer-isolate.ts +276 -0
  325. package/src/lab/fabric/producer-protocol.ts +61 -0
  326. package/src/lab/fabric/scratch.ts +439 -0
  327. package/src/lab/fabric/subject.ts +106 -0
  328. package/src/lab/fabric/types.ts +134 -0
  329. package/src/lab/fabric/verifier.ts +98 -0
  330. package/src/lab/index.ts +54 -0
  331. package/src/lab/ledger/artifact-refs.ts +127 -0
  332. package/src/lab/ledger/invalidation.ts +136 -0
  333. package/src/lab/ledger/purge.ts +310 -0
  334. package/src/lab/ledger/store.ts +532 -0
  335. package/src/lab/live/credential-lease.ts +53 -0
  336. package/src/lab/live/destination.ts +155 -0
  337. package/src/lab/live/executor.ts +336 -0
  338. package/src/lab/live/inert-tools.ts +56 -0
  339. package/src/lab/live/manifest.ts +85 -0
  340. package/src/lab/live/mcp-loopback.ts +57 -0
  341. package/src/lab/live/runner.ts +19 -0
  342. package/src/lab/live/sandbox.ts +61 -0
  343. package/src/lab/live/suite-manifest.ts +41 -0
  344. package/src/lab/live/transport.ts +118 -0
  345. package/src/lab/live/types.ts +197 -0
  346. package/src/lab/observe/from-conformance.ts +301 -0
  347. package/src/lab/observe/from-live.ts +117 -0
  348. package/src/lab/paths.ts +153 -0
  349. package/src/lab/projection/rebuild.ts +495 -0
  350. package/src/lab/projection/schema.ts +135 -0
  351. package/src/lab/projection/verdicts.ts +474 -0
  352. package/src/lab/projection/verification.ts +412 -0
  353. package/src/lab/public/bundle.ts +217 -0
  354. package/src/lab/public/community-authority.ts +175 -0
  355. package/src/lab/public/community-files.ts +29 -0
  356. package/src/lab/public/community.ts +479 -0
  357. package/src/lab/public/file-safety.ts +155 -0
  358. package/src/lab/public/ids.ts +26 -0
  359. package/src/lab/public/index.ts +16 -0
  360. package/src/lab/public/mutation-lock.ts +424 -0
  361. package/src/lab/public/operator.ts +353 -0
  362. package/src/lab/public/origin-purge.ts +79 -0
  363. package/src/lab/public/origin.ts +203 -0
  364. package/src/lab/public/privacy.ts +143 -0
  365. package/src/lab/public/private-file.ts +261 -0
  366. package/src/lab/public/project.ts +124 -0
  367. package/src/lab/public/purge-test-fault.ts +21 -0
  368. package/src/lab/public/purge.ts +223 -0
  369. package/src/lab/public/registry.ts +44 -0
  370. package/src/lab/public/revocation.ts +252 -0
  371. package/src/lab/public/signature.ts +243 -0
  372. package/src/lab/public/storage.ts +105 -0
  373. package/src/lab/public/strict-json.ts +206 -0
  374. package/src/lab/public/time.ts +26 -0
  375. package/src/lab/public/types.ts +172 -0
  376. package/src/lab/public/validate.ts +391 -0
  377. package/src/lab/query/catalog.ts +101 -0
  378. package/src/lab/query/connection.ts +107 -0
  379. package/src/lab/query/constants.ts +4 -0
  380. package/src/lab/query/cursor.ts +132 -0
  381. package/src/lab/query/dto-map.ts +277 -0
  382. package/src/lab/query/errors.ts +22 -0
  383. package/src/lab/query/freshness.ts +53 -0
  384. package/src/lab/query/index.ts +45 -0
  385. package/src/lab/query/latest-observation.ts +59 -0
  386. package/src/lab/query/passive-production.ts +159 -0
  387. package/src/lab/query/queries.ts +444 -0
  388. package/src/lab/query/types.ts +266 -0
  389. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  390. package/src/lab/subject/installation-salt.ts +112 -0
  391. package/src/lab/subject/protocol-subject.ts +80 -0
  392. package/src/lab/subject/route-subject.ts +74 -0
  393. package/src/lib/app-owned-memory-stores.ts +22 -0
  394. package/src/lib/bounded-body.ts +153 -9
  395. package/src/lib/bun-runtime.ts +125 -12
  396. package/src/lib/bun-stream-caps.ts +13 -9
  397. package/src/lib/codex-restart-contract.ts +120 -0
  398. package/src/lib/config-ownership.ts +6 -2
  399. package/src/lib/destination-policy.ts +65 -1
  400. package/src/lib/errors.ts +44 -2
  401. package/src/lib/fabric-task-execution-authority.ts +7 -0
  402. package/src/lib/fabric-task-host.ts +29 -0
  403. package/src/lib/lab-activation.ts +223 -0
  404. package/src/lib/lab-live-execution-authority.ts +13 -0
  405. package/src/lib/lab-live-host.ts +30 -0
  406. package/src/lib/lab-live-pinned-sender.ts +56 -0
  407. package/src/lib/lab-live-route-production.ts +130 -0
  408. package/src/lib/lab-passive-linker-registration.ts +26 -0
  409. package/src/lib/local-management-attestation.ts +51 -0
  410. package/src/lib/local-management-capability.ts +100 -0
  411. package/src/lib/local-provider-reload-contract.ts +100 -0
  412. package/src/lib/optional-shutdown-hooks.ts +57 -0
  413. package/src/lib/pinned-http.ts +145 -26
  414. package/src/lib/process-control.ts +4 -1
  415. package/src/lib/provider-outbound.ts +49 -9
  416. package/src/lib/redact.ts +419 -3
  417. package/src/lib/self-launch-argv.ts +15 -0
  418. package/src/lib/server-resource-ownership.ts +71 -0
  419. package/src/lib/shadow-call.ts +35 -4
  420. package/src/lib/sse-decoder.ts +41 -0
  421. package/src/lib/state-store-registrations.ts +10 -2
  422. package/src/lib/system-restart-contract.ts +73 -0
  423. package/src/lib/token-estimate.ts +19 -2
  424. package/src/lib/tool-argument-integers.ts +202 -0
  425. package/src/lib/translator-budget.ts +44 -0
  426. package/src/lib/upstream-http-version.ts +57 -0
  427. package/src/lib/upstream-reachability.ts +95 -0
  428. package/src/lib/upstream-retry.ts +156 -3
  429. package/src/lib/windows-atomic-replace.ts +155 -0
  430. package/src/lib/windows-elevation.ts +70 -2
  431. package/src/lib/windows-secret-acl.ts +409 -69
  432. package/src/lib/windows-service-wrappers.ts +72 -0
  433. package/src/lib/windows-text.ts +106 -0
  434. package/src/lib/windows-user-principal.ts +341 -0
  435. package/src/lib/winsw.ts +33 -5
  436. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  437. package/src/oauth/account-import/index.ts +15 -0
  438. package/src/oauth/account-import/parser.ts +83 -0
  439. package/src/oauth/account-import/registry.ts +18 -0
  440. package/src/oauth/account-import/service.ts +75 -0
  441. package/src/oauth/account-import/types.ts +91 -0
  442. package/src/oauth/anthropic.ts +12 -1
  443. package/src/oauth/callback-server.ts +8 -2
  444. package/src/oauth/chatgpt.ts +12 -1
  445. package/src/oauth/command-code.ts +239 -0
  446. package/src/oauth/cursor.ts +46 -5
  447. package/src/oauth/google-antigravity.ts +35 -3
  448. package/src/oauth/health.ts +20 -12
  449. package/src/oauth/index.ts +398 -66
  450. package/src/oauth/key-providers.ts +16 -0
  451. package/src/oauth/kimi.ts +16 -2
  452. package/src/oauth/kiro.ts +50 -6
  453. package/src/oauth/local-token-detect.ts +11 -2
  454. package/src/oauth/log.ts +3 -1
  455. package/src/oauth/login-cli.ts +88 -28
  456. package/src/oauth/nous.ts +798 -0
  457. package/src/oauth/store.ts +119 -21
  458. package/src/oauth/token-guardian.ts +9 -3
  459. package/src/pi/models.ts +2 -2
  460. package/src/providers/alibaba-region-migration.ts +1 -1
  461. package/src/providers/antigravity-models.ts +521 -31
  462. package/src/providers/base-url-choices.ts +10 -0
  463. package/src/providers/codex-capacity.ts +292 -0
  464. package/src/providers/command-code-efforts.ts +144 -0
  465. package/src/providers/context-cap.ts +22 -5
  466. package/src/providers/cursor-pool.ts +72 -0
  467. package/src/providers/derive.ts +253 -6
  468. package/src/providers/fastwire.ts +501 -0
  469. package/src/providers/free-directory.ts +10 -7
  470. package/src/providers/google-vertex-location.ts +14 -0
  471. package/src/providers/key-failover.ts +71 -3
  472. package/src/providers/label.ts +1 -1
  473. package/src/providers/model-discovery-limits.ts +16 -0
  474. package/src/providers/model-discovery.ts +115 -22
  475. package/src/providers/model-rename-migration.ts +255 -0
  476. package/src/providers/model-rename-startup.ts +28 -0
  477. package/src/providers/openai-sidecar.ts +72 -4
  478. package/src/providers/openai-tier-startup.ts +31 -2
  479. package/src/providers/openai-tiers.ts +119 -4
  480. package/src/providers/openai-virtual-models.ts +1 -0
  481. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  482. package/src/providers/provider-id-rewrite.ts +29 -0
  483. package/src/providers/quota.ts +1319 -38
  484. package/src/providers/registry.ts +1429 -111
  485. package/src/providers/request-pacing.ts +310 -0
  486. package/src/providers/service-tier.ts +277 -0
  487. package/src/providers/slug-codec.ts +42 -6
  488. package/src/providers/static-model-discovery.ts +86 -0
  489. package/src/providers/xai-responses-opt-in.ts +15 -0
  490. package/src/providers/xai-transport.ts +11 -4
  491. package/src/reasoning-effort.ts +49 -1
  492. package/src/responses/compaction.ts +26 -1
  493. package/src/responses/custom-tool-compat.ts +266 -0
  494. package/src/responses/hosted-tool-policy.ts +9 -0
  495. package/src/responses/namespace-tool-compat.ts +355 -0
  496. package/src/responses/parser.ts +220 -38
  497. package/src/responses/provider-continuation.ts +98 -0
  498. package/src/responses/provider-opaque-metadata.ts +73 -0
  499. package/src/responses/reasoning-envelope.ts +9 -1
  500. package/src/responses/reasoning-replay-cache.ts +426 -0
  501. package/src/responses/schema.ts +7 -1
  502. package/src/responses/spill-store.ts +75 -10
  503. package/src/responses/state.ts +565 -27
  504. package/src/responses/thought-signature-replay.ts +347 -0
  505. package/src/responses/tool-search-compat.ts +301 -0
  506. package/src/responses/truncated-stop-reason.ts +60 -0
  507. package/src/router.ts +366 -30
  508. package/src/routing/analytics.ts +378 -0
  509. package/src/routing/capability.ts +244 -0
  510. package/src/routing/compatibility/assemble.ts +73 -0
  511. package/src/routing/compatibility/behavior.ts +278 -0
  512. package/src/routing/compatibility/catalog.ts +99 -0
  513. package/src/routing/compatibility/endpoint.ts +52 -0
  514. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  515. package/src/routing/compatibility/policy.ts +181 -0
  516. package/src/routing/compatibility/provider-slot.ts +56 -0
  517. package/src/routing/compatibility/reader.ts +110 -0
  518. package/src/routing/compatibility/subject.ts +191 -0
  519. package/src/routing/compatibility/types.ts +64 -0
  520. package/src/routing/compatibility/version.ts +104 -0
  521. package/src/routing/cost.ts +77 -0
  522. package/src/routing/evaluator.ts +495 -0
  523. package/src/routing/health.ts +412 -0
  524. package/src/routing/history/cursor.ts +43 -0
  525. package/src/routing/history/indexer.ts +605 -0
  526. package/src/routing/history/schema.ts +72 -0
  527. package/src/routing/profile-namespace.ts +15 -0
  528. package/src/routing/profile.ts +547 -0
  529. package/src/routing/quota.ts +145 -0
  530. package/src/routing/request-evidence.ts +45 -0
  531. package/src/routing/trace.ts +776 -0
  532. package/src/server/adapter-resolve.ts +2 -29
  533. package/src/server/auth-cors.ts +267 -46
  534. package/src/server/background-lifecycle.ts +182 -0
  535. package/src/server/chat-completions.ts +130 -56
  536. package/src/server/chat-native-sse.ts +331 -0
  537. package/src/server/chat-native.ts +426 -0
  538. package/src/server/claude-messages.ts +159 -43
  539. package/src/server/direct-local-http.ts +347 -0
  540. package/src/server/effort-policy.ts +18 -0
  541. package/src/server/github-copilot-responses-repair.ts +338 -0
  542. package/src/server/gui-static.ts +39 -10
  543. package/src/server/images.ts +94 -12
  544. package/src/server/index.ts +865 -181
  545. package/src/server/lifecycle.ts +284 -13
  546. package/src/server/live.ts +136 -17
  547. package/src/server/local-management-read-client.ts +90 -0
  548. package/src/server/local-provider-reload-client.ts +137 -0
  549. package/src/server/management/agent-settings-routes.ts +398 -103
  550. package/src/server/management/api-key-usage.ts +31 -5
  551. package/src/server/management/body.ts +6 -0
  552. package/src/server/management/combo-routes.ts +62 -24
  553. package/src/server/management/config-routes.ts +464 -51
  554. package/src/server/management/context.ts +80 -2
  555. package/src/server/management/integration-routes.ts +498 -0
  556. package/src/server/management/lab-automation-routes.ts +206 -0
  557. package/src/server/management/lab-routes.ts +563 -0
  558. package/src/server/management/logs-usage-routes.ts +101 -32
  559. package/src/server/management/model-routes.ts +189 -131
  560. package/src/server/management/model-rows.ts +163 -0
  561. package/src/server/management/native-integration-routes.ts +769 -0
  562. package/src/server/management/oauth-account-routes.ts +80 -4
  563. package/src/server/management/provider-capability-config.ts +48 -0
  564. package/src/server/management/provider-routes.ts +764 -157
  565. package/src/server/management/request-history-routes.ts +191 -0
  566. package/src/server/management/routing-analytics-routes.ts +74 -0
  567. package/src/server/management/routing-profile-routes.ts +380 -0
  568. package/src/server/management/shared.ts +27 -11
  569. package/src/server/management/sidebar-routes.ts +47 -31
  570. package/src/server/management/storage-log-guard-routes.ts +186 -0
  571. package/src/server/management/sync-response.ts +69 -0
  572. package/src/server/management/system-restart.ts +288 -32
  573. package/src/server/management/system-routes.ts +77 -0
  574. package/src/server/management/usage-summary-cache.ts +9 -1
  575. package/src/server/management/vision-sidecar-options.ts +167 -0
  576. package/src/server/management/web-search-sidecar-options.ts +120 -0
  577. package/src/server/management-api.ts +115 -14
  578. package/src/server/management-auth.ts +220 -5
  579. package/src/server/passive-route-linker.ts +66 -0
  580. package/src/server/ports.ts +41 -1
  581. package/src/server/proxy-liveness.ts +132 -5
  582. package/src/server/readiness.ts +99 -0
  583. package/src/server/relay-eager.ts +82 -42
  584. package/src/server/relay.ts +236 -76
  585. package/src/server/request-decompress.ts +113 -6
  586. package/src/server/request-log.ts +235 -22
  587. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  588. package/src/server/responses/agent-task-recovery.ts +465 -0
  589. package/src/server/responses/collaboration.ts +204 -35
  590. package/src/server/responses/compact.ts +442 -55
  591. package/src/server/responses/core.ts +2872 -331
  592. package/src/server/responses/empty-completion-guard.ts +276 -0
  593. package/src/server/responses/encrypted-payload.ts +62 -39
  594. package/src/server/responses/fetch-helpers.ts +79 -4
  595. package/src/server/responses/input-admission.ts +185 -0
  596. package/src/server/responses/pacing-overload.ts +13 -0
  597. package/src/server/responses/policy-fallback.ts +178 -0
  598. package/src/server/responses/responses-field-backfill.ts +251 -0
  599. package/src/server/responses/terminal-guard.ts +26 -5
  600. package/src/server/responses/upstream-error.ts +5 -0
  601. package/src/server/responses/ws-upstream.ts +308 -0
  602. package/src/server/responses-custom-tool-repair.ts +282 -0
  603. package/src/server/responses-item-id-repair.ts +54 -6
  604. package/src/server/responses-json-events.ts +90 -0
  605. package/src/server/responses-model-rewrite.ts +29 -0
  606. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  607. package/src/server/responses-snapshot-repair.ts +621 -0
  608. package/src/server/responses-terminal-repair.ts +342 -0
  609. package/src/server/responses-tool-search-repair.ts +267 -0
  610. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  611. package/src/server/responses.ts +18 -2
  612. package/src/server/search.ts +78 -13
  613. package/src/server/sse-frame-buffer.ts +292 -0
  614. package/src/server/sse-payload-rewrite.ts +110 -22
  615. package/src/server/startup-action-control.ts +8 -1
  616. package/src/server/startup-health-cache.ts +19 -1
  617. package/src/server/system-env.ts +80 -9
  618. package/src/server/ws-bridge.ts +39 -38
  619. package/src/service-manager-probe.ts +892 -0
  620. package/src/service.ts +1111 -90
  621. package/src/sidecar/auth.ts +92 -0
  622. package/src/sidecar/candidates.ts +83 -0
  623. package/src/storage/cleanup.ts +2 -2
  624. package/src/storage/scanner.ts +1 -1
  625. package/src/storage/worker-lifecycle.ts +14 -14
  626. package/src/tray/windows-tray.ps1 +83 -9
  627. package/src/tray/windows.ts +43 -16
  628. package/src/types/accounts.ts +37 -0
  629. package/src/types/config.ts +845 -0
  630. package/src/types/provider.ts +545 -0
  631. package/src/types/request.ts +384 -0
  632. package/src/types/tools.ts +131 -0
  633. package/src/types/wire.ts +80 -0
  634. package/src/types.ts +104 -1236
  635. package/src/update/index.ts +32 -19
  636. package/src/update/job.ts +442 -67
  637. package/src/update/notify.ts +12 -6
  638. package/src/update/npm-cache-preflight.d.mts +47 -0
  639. package/src/update/npm-cache-preflight.mjs +201 -0
  640. package/src/update/transactional-install.d.mts +22 -0
  641. package/src/update/transactional-install.mjs +259 -0
  642. package/src/usage/cost.ts +0 -0
  643. package/src/usage/expected-prices.ts +268 -16
  644. package/src/usage/log.ts +606 -41
  645. package/src/usage/summary.ts +177 -9
  646. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  647. package/src/usage/user-cost-overlays.ts +314 -0
  648. package/src/vision/anthropic-describe.ts +10 -6
  649. package/src/vision/backends.ts +97 -0
  650. package/src/vision/describe.ts +9 -3
  651. package/src/vision/eligibility.ts +250 -0
  652. package/src/vision/index.ts +238 -24
  653. package/src/vision/reasoning.ts +55 -0
  654. package/src/vision/routed-describe.ts +175 -0
  655. package/src/vision/timeout-bounds.ts +9 -0
  656. package/src/web-search/anthropic-executor.ts +13 -7
  657. package/src/web-search/backends.ts +108 -0
  658. package/src/web-search/exa-executor.ts +88 -0
  659. package/src/web-search/executor.ts +11 -3
  660. package/src/web-search/gemini-executor.ts +141 -0
  661. package/src/web-search/index.ts +150 -15
  662. package/src/web-search/loop.ts +279 -50
  663. package/src/web-search/parse.ts +125 -30
  664. package/src/web-search/sources.ts +60 -0
  665. package/src/web-search/xai-executor.ts +219 -0
  666. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  667. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  668. package/gui/dist/provider-icons/antigravity.svg +0 -1
  669. package/gui/dist/provider-icons/claude.svg +0 -1
  670. package/gui/dist/provider-icons/copilot.svg +0 -1
  671. package/gui/dist/provider-icons/cursor.svg +0 -2
  672. package/gui/dist/provider-icons/gemini.svg +0 -1
  673. package/gui/dist/provider-icons/grok-color.svg +0 -1
  674. package/gui/dist/provider-icons/kiro.svg +0 -14
  675. package/src/cli/internal-dispatch.ts +0 -20
@@ -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,
@@ -18,7 +19,10 @@ import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION, applyClaudeToolPr
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";
@@ -243,6 +247,43 @@ function isLikelyRealAnthropicThinkingSignature(signature: string | undefined):
243
247
  return /^[A-Za-z0-9+/_=-]+$/.test(signature);
244
248
  }
245
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
+
246
287
  function usesNativeAnthropicEndpoint(provider: OcxProviderConfig): boolean {
247
288
  try {
248
289
  return new URL(provider.baseUrl).hostname === "api.anthropic.com";
@@ -277,7 +318,69 @@ function usableToolUseId(id: unknown): string {
277
318
  return typeof id === "string" && id.trim() ? id : synthesizeToolUseId();
278
319
  }
279
320
 
280
- function toolUseArguments(input: unknown): string {
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 {
281
384
  if (typeof input === "string") {
282
385
  const trimmed = input.trim();
283
386
  if (!trimmed) return "{}";
@@ -285,6 +388,10 @@ function toolUseArguments(input: unknown): string {
285
388
  JSON.parse(trimmed);
286
389
  return trimmed;
287
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
+ }
288
395
  // A tool call's arguments must be a JSON object. Re-encoding an unparseable string as a
289
396
  // JSON *string* is the double-encoding #765 reports: the caller then receives
290
397
  // `"get weather"` where an object was required and the tool call is unusable either way.
@@ -342,16 +449,68 @@ const ADAPTIVE_THINKING_FAMILY_MINIMUMS: Record<string, readonly [major: number,
342
449
  fable: [0, 0],
343
450
  };
344
451
 
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]];
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];
351
488
  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]);
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);
355
514
  }
356
515
 
357
516
  /** `output_config.effort` accepts low|medium|high|xhigh|max — "minimal" is rejected with a 400. */
@@ -401,7 +560,7 @@ function buildToolNameTransforms(provider: OcxProviderConfig): { toWire: (name:
401
560
  return { toWire: (name) => name, fromWire: (name) => name };
402
561
  }
403
562
 
404
- function toAnthropicToolResult(msg: OcxToolResultMessage): Record<string, unknown> {
563
+ function toAnthropicToolResult(msg: OcxToolResultMessage, wireCallId: string): Record<string, unknown> {
405
564
  // Anthropic tool_result accepts a string OR content blocks — render images natively
406
565
  // (e.g. Codex view_image output) instead of dropping them.
407
566
  let content: string | unknown[];
@@ -416,12 +575,17 @@ function toAnthropicToolResult(msg: OcxToolResultMessage): Record<string, unknow
416
575
  }
417
576
  return {
418
577
  type: "tool_result",
419
- tool_use_id: msg.toolCallId,
578
+ tool_use_id: wireCallId,
420
579
  content,
421
580
  ...(msg.isError ? { is_error: true } : {}),
422
581
  };
423
582
  }
424
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
+
425
589
  function orphanToolResultText(msg: OcxToolResultMessage): string {
426
590
  const label = msg.toolName ? `${msg.toolName} (${msg.toolCallId})` : msg.toolCallId;
427
591
  const content = typeof msg.content === "string"
@@ -430,10 +594,80 @@ function orphanToolResultText(msg: OcxToolResultMessage): string {
430
594
  return `[tool_result without adjacent tool_use: ${label}]\n${content}`;
431
595
  }
432
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
+
433
654
  function messagesToAnthropicFormat(
434
655
  parsed: OcxParsedRequest,
435
656
  toolNames: { toWire: (name: string) => string },
436
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
+ }
437
671
  const toolCatalogNudge = buildNonOpenAIToolCatalogNudgeForTools(
438
672
  parsed.context.tools,
439
673
  parsed.options.toolChoice,
@@ -441,7 +675,7 @@ function messagesToAnthropicFormat(
441
675
  );
442
676
  const systemParts = [...(parsed.context.systemPrompt ?? []), ...(toolCatalogNudge ? [toolCatalogNudge] : [])];
443
677
  const system = systemParts.length
444
- ? neutralizeIdentity(systemParts.join("\n\n")) || undefined
678
+ ? identifyRoutedModel(systemParts.join("\n\n"), parsed.modelId) || undefined
445
679
  : undefined;
446
680
  const messages: unknown[] = [];
447
681
 
@@ -485,8 +719,17 @@ function messagesToAnthropicFormat(
485
719
  } else if (part.type === "toolCall") {
486
720
  const tc = part as OcxToolCall;
487
721
  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 });
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 });
490
733
  }
491
734
  }
492
735
  // Anthropic treats text/thinking after tool_use as ending the tool turn, which makes
@@ -502,9 +745,13 @@ function messagesToAnthropicFormat(
502
745
  let j = i + 1;
503
746
  while (j < parsed.context.messages.length && parsed.context.messages[j].role === "toolResult") {
504
747
  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);
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);
508
755
  } else {
509
756
  orphanBlocks.push({ type: "text", text: orphanToolResultText(tr) });
510
757
  }
@@ -553,7 +800,7 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
553
800
  ? new Set(parsed.options.toolChoice.allowedTools)
554
801
  : undefined;
555
802
  const tools = allowed
556
- ? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
803
+ ? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed, parsed.context.tools))
557
804
  : parsed.context.tools;
558
805
  if (tools.length === 0) return undefined;
559
806
  const converted = tools.map(t => ({
@@ -564,35 +811,8 @@ function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (
564
811
  return converted;
565
812
  }
566
813
 
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
814
  function normalizeAnthropicInputSchema(schema: unknown): Record<string, unknown> {
595
- const stripped = stripEncryptedMarker(schema);
815
+ const stripped = stripResponsesOnlyEncryptedMarker(schema);
596
816
  const obj = stripped && typeof stripped === "object" && !Array.isArray(stripped)
597
817
  ? stripped as Record<string, unknown>
598
818
  : {};
@@ -659,6 +879,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
659
879
  return {
660
880
  name: "anthropic",
661
881
 
882
+ formatErrorBody: formatAnthropicErrorBody,
883
+
662
884
  async buildRequest(parsed: OcxParsedRequest, incoming?: IncomingMeta) {
663
885
  if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
664
886
  if (isOAuth) {
@@ -668,6 +890,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
668
890
  }
669
891
 
670
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);
671
895
  // Primary image layer: resize/re-encode to fit Anthropic limits without dropping
672
896
  // (anthropic-image-normalize.ts); the guard below remains the deterministic backstop.
673
897
  // imageTierBias > 0 = upstream-413 tightened retry (030): start every image one tier lower.
@@ -700,7 +924,14 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
700
924
  // `reasoning` is a Codex effort string; "none" is the disable sentinel (see parser.ts
701
925
  // REASONING_EFFORTS). A bare truthy check would treat "none" as truthy and wrongly enable
702
926
  // 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") {
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") {
704
935
  if (usesAdaptiveThinking(parsed.modelId)) {
705
936
  // Adaptive-thinking models replace the token budget with an effort knob and reject
706
937
  // `thinking.type: "enabled"` outright. `max_tokens` still caps thinking plus visible
@@ -733,6 +964,20 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
733
964
  delete body.top_p;
734
965
  }
735
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
+
736
981
  if (parsed.options.toolChoice && (tools || parsed.options.toolChoice === "none")) {
737
982
  const tc = parsed.options.toolChoice;
738
983
  if (tc === "auto") body.tool_choice = { type: "auto" };
@@ -798,10 +1043,23 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
798
1043
  let pendingUsage: Record<string, number> | undefined;
799
1044
  let pendingStopReason: string | undefined;
800
1045
  let emittedDone = false;
1046
+ let sawVisibleText = false;
801
1047
 
802
1048
  const emitDone = function* (): Generator<AdapterEvent> {
803
1049
  if (emittedDone) return;
804
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
+ }
805
1063
  yield {
806
1064
  type: "done",
807
1065
  usage: usageFromAnthropic(pendingUsage),
@@ -818,13 +1076,21 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
818
1076
  const payload = record.data.trim();
819
1077
  if (!payload) continue;
820
1078
 
821
- let data: Record<string, unknown>;
1079
+ let parsed: unknown;
822
1080
  try {
823
- data = JSON.parse(payload) as Record<string, unknown>;
1081
+ parsed = JSON.parse(payload);
824
1082
  } catch {
825
1083
  debugDroppedFrame("anthropic", payload);
826
1084
  continue;
827
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>;
828
1094
 
829
1095
  switch (record.event || data.type) {
830
1096
  case "message_start": {
@@ -853,6 +1119,10 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
853
1119
  const delta = data.delta as Record<string, unknown> | undefined;
854
1120
  if (!delta) break;
855
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;
856
1126
  yield { type: "text_delta", text: delta.text };
857
1127
  } else if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
858
1128
  yield { type: "thinking_delta", thinking: delta.thinking };
@@ -934,12 +1204,31 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
934
1204
  code: "translation_buffer_limit",
935
1205
  message: "upstream translation buffer exceeded the safe limit",
936
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;
937
1211
  } finally {
938
1212
  if (currentToolCallId) budget.closeCall(currentToolCallId);
939
1213
  }
940
1214
  if (!emittedDone) {
941
1215
  // Fail closed on transport EOF. Compatible providers may omit message_stop after message_delta.stop_reason.
942
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
+ }
943
1232
  const stopReason = pendingStopReason === "max_tokens"
944
1233
  ? "max_tokens"
945
1234
  : pendingStopReason === "refusal" || pendingStopReason === "content_filter"
@@ -951,6 +1240,26 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
951
1240
  usage: usageFromAnthropic(pendingUsage),
952
1241
  ...(stopReason ? { stopReason } : {}),
953
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
+ }
954
1263
  } else {
955
1264
  yield { type: "error", message: "upstream stream ended before message_stop — possible truncation" };
956
1265
  }
@@ -980,13 +1289,28 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
980
1289
  } else if (block.type === "tool_use") {
981
1290
  const id = usableToolUseId(block.id);
982
1291
  events.push({ type: "tool_call_start", id, name: toolNames.fromWire(block.name ?? "") });
983
- events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input) });
1292
+ events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input, provider.anthropicEofTolerance === true) });
984
1293
  events.push({ type: "tool_call_end" });
985
1294
  }
986
1295
  }
987
1296
  }
988
1297
  const usage = json.usage as Record<string, number> | undefined;
989
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
+ }
990
1314
  events.push({
991
1315
  type: "done",
992
1316
  usage: usageFromAnthropic(usage),