@linxiraos/pi-ai 1.0.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 (639) hide show
  1. package/CHANGELOG.md +5066 -0
  2. package/README.md +1195 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
  5. package/dist/types/auth-broker/client.d.ts +93 -0
  6. package/dist/types/auth-broker/discover.d.ts +39 -0
  7. package/dist/types/auth-broker/index.d.ts +7 -0
  8. package/dist/types/auth-broker/refresher.d.ts +25 -0
  9. package/dist/types/auth-broker/remote-store.d.ts +136 -0
  10. package/dist/types/auth-broker/server.d.ts +48 -0
  11. package/dist/types/auth-broker/snapshot-cache.d.ts +17 -0
  12. package/dist/types/auth-broker/types.d.ts +152 -0
  13. package/dist/types/auth-broker/wire-schema-resource.d.ts +53 -0
  14. package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
  15. package/dist/types/auth-gateway/http.d.ts +56 -0
  16. package/dist/types/auth-gateway/index.d.ts +3 -0
  17. package/dist/types/auth-gateway/server.d.ts +36 -0
  18. package/dist/types/auth-gateway/types.d.ts +127 -0
  19. package/dist/types/auth-retry.d.ts +150 -0
  20. package/dist/types/auth-storage.d.ts +1258 -0
  21. package/dist/types/dialect/anthropic.d.ts +15 -0
  22. package/dist/types/dialect/catalog.d.ts +3 -0
  23. package/dist/types/dialect/coercion.d.ts +23 -0
  24. package/dist/types/dialect/deepseek.d.ts +14 -0
  25. package/dist/types/dialect/demotion.d.ts +30 -0
  26. package/dist/types/dialect/examples.d.ts +18 -0
  27. package/dist/types/dialect/factory.d.ts +3 -0
  28. package/dist/types/dialect/fenced-thinking.d.ts +53 -0
  29. package/dist/types/dialect/gemini.d.ts +17 -0
  30. package/dist/types/dialect/gemma.d.ts +15 -0
  31. package/dist/types/dialect/glm.d.ts +9 -0
  32. package/dist/types/dialect/harmony.d.ts +8 -0
  33. package/dist/types/dialect/hermes.d.ts +9 -0
  34. package/dist/types/dialect/history.d.ts +3 -0
  35. package/dist/types/dialect/index.d.ts +11 -0
  36. package/dist/types/dialect/inventory.d.ts +9 -0
  37. package/dist/types/dialect/kimi.d.ts +14 -0
  38. package/dist/types/dialect/minimax.d.ts +3 -0
  39. package/dist/types/dialect/owned-stream.d.ts +4 -0
  40. package/dist/types/dialect/qwen3.d.ts +9 -0
  41. package/dist/types/dialect/rendering.d.ts +54 -0
  42. package/dist/types/dialect/thinking.d.ts +6 -0
  43. package/dist/types/dialect/types.d.ts +68 -0
  44. package/dist/types/dialect/xml.d.ts +9 -0
  45. package/dist/types/error/abort.d.ts +14 -0
  46. package/dist/types/error/auth-classify.d.ts +20 -0
  47. package/dist/types/error/auth.d.ts +27 -0
  48. package/dist/types/error/aws.d.ts +27 -0
  49. package/dist/types/error/classes.d.ts +106 -0
  50. package/dist/types/error/finalize.d.ts +39 -0
  51. package/dist/types/error/flags.d.ts +94 -0
  52. package/dist/types/error/format.d.ts +20 -0
  53. package/dist/types/error/gateway.d.ts +20 -0
  54. package/dist/types/error/index.d.ts +14 -0
  55. package/dist/types/error/oauth.d.ts +43 -0
  56. package/dist/types/error/provider.d.ts +42 -0
  57. package/dist/types/error/rate-limit.d.ts +80 -0
  58. package/dist/types/error/retryable.d.ts +27 -0
  59. package/dist/types/error/validation.d.ts +32 -0
  60. package/dist/types/index.d.ts +51 -0
  61. package/dist/types/provider-details.d.ts +24 -0
  62. package/dist/types/providers/amazon-bedrock.d.ts +39 -0
  63. package/dist/types/providers/anthropic-client.d.ts +106 -0
  64. package/dist/types/providers/anthropic-messages-server-schema.d.ts +937 -0
  65. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  66. package/dist/types/providers/anthropic-wire.d.ts +345 -0
  67. package/dist/types/providers/anthropic.d.ts +261 -0
  68. package/dist/types/providers/aws-credentials.d.ts +48 -0
  69. package/dist/types/providers/aws-eventstream.d.ts +39 -0
  70. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  71. package/dist/types/providers/azure-openai-responses.d.ts +16 -0
  72. package/dist/types/providers/bedrock-mantle.d.ts +13 -0
  73. package/dist/types/providers/claude-code-fingerprint.d.ts +19 -0
  74. package/dist/types/providers/cowork-fetch.d.ts +3 -0
  75. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  76. package/dist/types/providers/cursor-pi-args.d.ts +105 -0
  77. package/dist/types/providers/cursor.d.ts +221 -0
  78. package/dist/types/providers/devin.d.ts +12 -0
  79. package/dist/types/providers/error-message.d.ts +25 -0
  80. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  81. package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
  82. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  83. package/dist/types/providers/google-auth.d.ts +24 -0
  84. package/dist/types/providers/google-gemini-cli.d.ts +120 -0
  85. package/dist/types/providers/google-shared.d.ts +203 -0
  86. package/dist/types/providers/google-types.d.ts +155 -0
  87. package/dist/types/providers/google-vertex.d.ts +7 -0
  88. package/dist/types/providers/google.d.ts +4 -0
  89. package/dist/types/providers/grammar.d.ts +1 -0
  90. package/dist/types/providers/kimi.d.ts +27 -0
  91. package/dist/types/providers/mock.d.ts +179 -0
  92. package/dist/types/providers/ollama.d.ts +8 -0
  93. package/dist/types/providers/openai-anthropic-shim.d.ts +35 -0
  94. package/dist/types/providers/openai-chat-server-schema.d.ts +1311 -0
  95. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  96. package/dist/types/providers/openai-chat-wire.d.ts +669 -0
  97. package/dist/types/providers/openai-codex/request-transformer.d.ts +109 -0
  98. package/dist/types/providers/openai-codex/response-handler.d.ts +26 -0
  99. package/dist/types/providers/openai-codex-responses.d.ts +238 -0
  100. package/dist/types/providers/openai-completions.d.ts +48 -0
  101. package/dist/types/providers/openai-reasoning-fallback.d.ts +25 -0
  102. package/dist/types/providers/openai-responses-server-schema.d.ts +1314 -0
  103. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  104. package/dist/types/providers/openai-responses-wire.d.ts +6099 -0
  105. package/dist/types/providers/openai-responses.d.ts +131 -0
  106. package/dist/types/providers/openai-shared.d.ts +624 -0
  107. package/dist/types/providers/pi-native-client.d.ts +13 -0
  108. package/dist/types/providers/pi-native-server.d.ts +69 -0
  109. package/dist/types/providers/register-builtins.d.ts +37 -0
  110. package/dist/types/providers/synthetic.d.ts +26 -0
  111. package/dist/types/providers/transform-messages.d.ts +32 -0
  112. package/dist/types/providers/vision-guard.d.ts +20 -0
  113. package/dist/types/registry/aiand.d.ts +7 -0
  114. package/dist/types/registry/aimlapi.d.ts +4 -0
  115. package/dist/types/registry/alibaba-coding-plan.d.ts +8 -0
  116. package/dist/types/registry/alibaba-token-plan.d.ts +18 -0
  117. package/dist/types/registry/amazon-bedrock.d.ts +5 -0
  118. package/dist/types/registry/anthropic.d.ts +10 -0
  119. package/dist/types/registry/api-key-login.d.ts +42 -0
  120. package/dist/types/registry/api-key-validation.d.ts +43 -0
  121. package/dist/types/registry/aws.d.ts +13 -0
  122. package/dist/types/registry/azure.d.ts +4 -0
  123. package/dist/types/registry/baseten.d.ts +7 -0
  124. package/dist/types/registry/bedrock-mantle.d.ts +22 -0
  125. package/dist/types/registry/cerebras.d.ts +7 -0
  126. package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
  127. package/dist/types/registry/coreweave.d.ts +7 -0
  128. package/dist/types/registry/cursor.d.ts +7 -0
  129. package/dist/types/registry/deepseek.d.ts +8 -0
  130. package/dist/types/registry/derived.d.ts +5 -0
  131. package/dist/types/registry/devin.d.ts +8 -0
  132. package/dist/types/registry/exa.d.ts +8 -0
  133. package/dist/types/registry/firepass.d.ts +16 -0
  134. package/dist/types/registry/fireworks.d.ts +7 -0
  135. package/dist/types/registry/github-copilot.d.ts +7 -0
  136. package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
  137. package/dist/types/registry/gitlab-duo.d.ts +9 -0
  138. package/dist/types/registry/gmi-cloud.d.ts +7 -0
  139. package/dist/types/registry/google-antigravity.d.ts +9 -0
  140. package/dist/types/registry/google-gemini-cli.d.ts +9 -0
  141. package/dist/types/registry/google-vertex.d.ts +5 -0
  142. package/dist/types/registry/google.d.ts +4 -0
  143. package/dist/types/registry/groq.d.ts +4 -0
  144. package/dist/types/registry/huggingface.d.ts +7 -0
  145. package/dist/types/registry/index.d.ts +4 -0
  146. package/dist/types/registry/kagi.d.ts +14 -0
  147. package/dist/types/registry/kilo.d.ts +7 -0
  148. package/dist/types/registry/kimi-code.d.ts +7 -0
  149. package/dist/types/registry/litellm.d.ts +13 -0
  150. package/dist/types/registry/llama-cpp.d.ts +8 -0
  151. package/dist/types/registry/lm-studio.d.ts +8 -0
  152. package/dist/types/registry/meta.d.ts +7 -0
  153. package/dist/types/registry/minimax-code-cn.d.ts +6 -0
  154. package/dist/types/registry/minimax-code.d.ts +6 -0
  155. package/dist/types/registry/minimax.d.ts +4 -0
  156. package/dist/types/registry/mistral.d.ts +4 -0
  157. package/dist/types/registry/moonshot.d.ts +7 -0
  158. package/dist/types/registry/nanogpt.d.ts +7 -0
  159. package/dist/types/registry/novita.d.ts +6 -0
  160. package/dist/types/registry/nvidia.d.ts +7 -0
  161. package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
  162. package/dist/types/registry/oauth/anthropic.d.ts +24 -0
  163. package/dist/types/registry/oauth/callback-server.d.ts +74 -0
  164. package/dist/types/registry/oauth/cursor.d.ts +16 -0
  165. package/dist/types/registry/oauth/device-code.d.ts +25 -0
  166. package/dist/types/registry/oauth/devin.d.ts +5 -0
  167. package/dist/types/registry/oauth/github-copilot.d.ts +30 -0
  168. package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
  169. package/dist/types/registry/oauth/gitlab-duo.d.ts +3 -0
  170. package/dist/types/registry/oauth/google-antigravity.d.ts +11 -0
  171. package/dist/types/registry/oauth/google-gemini-cli.d.ts +22 -0
  172. package/dist/types/registry/oauth/google-oauth-shared.d.ts +56 -0
  173. package/dist/types/registry/oauth/index.d.ts +45 -0
  174. package/dist/types/registry/oauth/kimi.d.ts +21 -0
  175. package/dist/types/registry/oauth/minimax-code.d.ts +27 -0
  176. package/dist/types/registry/oauth/openai-codex.d.ts +33 -0
  177. package/dist/types/registry/oauth/opencode.d.ts +18 -0
  178. package/dist/types/registry/oauth/perplexity.d.ts +9 -0
  179. package/dist/types/registry/oauth/pkce.d.ts +8 -0
  180. package/dist/types/registry/oauth/types.d.ts +87 -0
  181. package/dist/types/registry/oauth/wafer.d.ts +1 -0
  182. package/dist/types/registry/oauth/xai-oauth.d.ts +46 -0
  183. package/dist/types/registry/oauth/xiaomi.d.ts +25 -0
  184. package/dist/types/registry/oauth/zai.d.ts +25 -0
  185. package/dist/types/registry/ollama-cloud.d.ts +7 -0
  186. package/dist/types/registry/ollama.d.ts +12 -0
  187. package/dist/types/registry/openai-codex-device.d.ts +8 -0
  188. package/dist/types/registry/openai-codex.d.ts +9 -0
  189. package/dist/types/registry/openai.d.ts +4 -0
  190. package/dist/types/registry/opencode-go.d.ts +6 -0
  191. package/dist/types/registry/opencode-zen.d.ts +6 -0
  192. package/dist/types/registry/openrouter.d.ts +13 -0
  193. package/dist/types/registry/parallel.d.ts +14 -0
  194. package/dist/types/registry/perplexity.d.ts +7 -0
  195. package/dist/types/registry/qianfan.d.ts +7 -0
  196. package/dist/types/registry/qwen-portal.d.ts +7 -0
  197. package/dist/types/registry/registry.d.ts +367 -0
  198. package/dist/types/registry/sakana.d.ts +7 -0
  199. package/dist/types/registry/siliconflow-cn.d.ts +7 -0
  200. package/dist/types/registry/siliconflow.d.ts +7 -0
  201. package/dist/types/registry/synthetic.d.ts +6 -0
  202. package/dist/types/registry/tavily.d.ts +14 -0
  203. package/dist/types/registry/together.d.ts +6 -0
  204. package/dist/types/registry/types.d.ts +75 -0
  205. package/dist/types/registry/umans.d.ts +7 -0
  206. package/dist/types/registry/venice.d.ts +13 -0
  207. package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
  208. package/dist/types/registry/vllm.d.ts +7 -0
  209. package/dist/types/registry/wafer-serverless.d.ts +6 -0
  210. package/dist/types/registry/xai-oauth.d.ts +7 -0
  211. package/dist/types/registry/xai.d.ts +7 -0
  212. package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
  213. package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
  214. package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
  215. package/dist/types/registry/xiaomi.d.ts +6 -0
  216. package/dist/types/registry/zai.d.ts +15 -0
  217. package/dist/types/registry/zenmux.d.ts +7 -0
  218. package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
  219. package/dist/types/stream.d.ts +46 -0
  220. package/dist/types/types.d.ts +1064 -0
  221. package/dist/types/usage/alibaba-token-plan.d.ts +3 -0
  222. package/dist/types/usage/claude.d.ts +4 -0
  223. package/dist/types/usage/cursor.d.ts +4 -0
  224. package/dist/types/usage/gemini.d.ts +2 -0
  225. package/dist/types/usage/github-copilot.d.ts +7 -0
  226. package/dist/types/usage/google-antigravity.d.ts +15 -0
  227. package/dist/types/usage/kimi.d.ts +2 -0
  228. package/dist/types/usage/minimax-code.d.ts +3 -0
  229. package/dist/types/usage/ollama.d.ts +5 -0
  230. package/dist/types/usage/openai-codex-base-url.d.ts +18 -0
  231. package/dist/types/usage/openai-codex-reset.d.ts +88 -0
  232. package/dist/types/usage/openai-codex.d.ts +10 -0
  233. package/dist/types/usage/opencode-go.d.ts +2 -0
  234. package/dist/types/usage/shared.d.ts +1 -0
  235. package/dist/types/usage/synthetic.d.ts +2 -0
  236. package/dist/types/usage/umans.d.ts +2 -0
  237. package/dist/types/usage/xai-oauth.d.ts +12 -0
  238. package/dist/types/usage/zai.d.ts +3 -0
  239. package/dist/types/usage.d.ts +527 -0
  240. package/dist/types/utils/abort.d.ts +25 -0
  241. package/dist/types/utils/anthropic-auth.d.ts +35 -0
  242. package/dist/types/utils/aws-profile.d.ts +17 -0
  243. package/dist/types/utils/block-symbols.d.ts +62 -0
  244. package/dist/types/utils/deterministic-id.d.ts +16 -0
  245. package/dist/types/utils/empty-completion-retry.d.ts +21 -0
  246. package/dist/types/utils/event-stream.d.ts +39 -0
  247. package/dist/types/utils/foundry.d.ts +1 -0
  248. package/dist/types/utils/google-validation.d.ts +2 -0
  249. package/dist/types/utils/harmony-leak.d.ts +135 -0
  250. package/dist/types/utils/http-inspector.d.ts +49 -0
  251. package/dist/types/utils/idle-iterator.d.ts +149 -0
  252. package/dist/types/utils/leaked-thinking-stream.d.ts +33 -0
  253. package/dist/types/utils/openai-http.d.ts +48 -0
  254. package/dist/types/utils/openrouter-headers.d.ts +1 -0
  255. package/dist/types/utils/parse-bind.d.ts +23 -0
  256. package/dist/types/utils/provider-response.d.ts +3 -0
  257. package/dist/types/utils/proxy.d.ts +39 -0
  258. package/dist/types/utils/request-debug.d.ts +29 -0
  259. package/dist/types/utils/retry-after.d.ts +4 -0
  260. package/dist/types/utils/retry.d.ts +14 -0
  261. package/dist/types/utils/schema/adapt.d.ts +24 -0
  262. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  263. package/dist/types/utils/schema/dereference.d.ts +11 -0
  264. package/dist/types/utils/schema/draft.d.ts +10 -0
  265. package/dist/types/utils/schema/equality.d.ts +4 -0
  266. package/dist/types/utils/schema/fields.d.ts +54 -0
  267. package/dist/types/utils/schema/index.d.ts +14 -0
  268. package/dist/types/utils/schema/json-schema-validator.d.ts +20 -0
  269. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  270. package/dist/types/utils/schema/normalize.d.ts +153 -0
  271. package/dist/types/utils/schema/spill.d.ts +8 -0
  272. package/dist/types/utils/schema/stamps.d.ts +17 -0
  273. package/dist/types/utils/schema/strict-tool-validation.d.ts +16 -0
  274. package/dist/types/utils/schema/types.d.ts +4 -0
  275. package/dist/types/utils/schema/typescript.d.ts +24 -0
  276. package/dist/types/utils/schema/wire.d.ts +52 -0
  277. package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
  278. package/dist/types/utils/sse-debug.d.ts +5 -0
  279. package/dist/types/utils/stream-markup-healing.d.ts +87 -0
  280. package/dist/types/utils/thinking-loop.d.ts +102 -0
  281. package/dist/types/utils/tool-call-loop-guard.d.ts +26 -0
  282. package/dist/types/utils/tool-choice.d.ts +52 -0
  283. package/dist/types/utils/validation.d.ts +28 -0
  284. package/dist/types/utils.d.ts +57 -0
  285. package/package.json +138 -0
  286. package/src/api-registry.ts +109 -0
  287. package/src/auth/sqlite-credential-store.ts +2066 -0
  288. package/src/auth-broker/client.ts +471 -0
  289. package/src/auth-broker/discover.ts +310 -0
  290. package/src/auth-broker/index.ts +7 -0
  291. package/src/auth-broker/refresher.ts +117 -0
  292. package/src/auth-broker/remote-store.ts +1332 -0
  293. package/src/auth-broker/server.ts +898 -0
  294. package/src/auth-broker/snapshot-cache.ts +200 -0
  295. package/src/auth-broker/types.ts +193 -0
  296. package/src/auth-broker/wire-schema-resource.ts +487 -0
  297. package/src/auth-broker/wire-schemas.ts +43 -0
  298. package/src/auth-gateway/http.ts +227 -0
  299. package/src/auth-gateway/index.ts +3 -0
  300. package/src/auth-gateway/server.ts +836 -0
  301. package/src/auth-gateway/types.ts +153 -0
  302. package/src/auth-retry.ts +401 -0
  303. package/src/auth-storage.ts +6540 -0
  304. package/src/dialect/anthropic.md +31 -0
  305. package/src/dialect/anthropic.ts +608 -0
  306. package/src/dialect/catalog.ts +29 -0
  307. package/src/dialect/coercion.ts +136 -0
  308. package/src/dialect/deepseek.md +24 -0
  309. package/src/dialect/deepseek.ts +609 -0
  310. package/src/dialect/demotion.ts +40 -0
  311. package/src/dialect/examples.ts +71 -0
  312. package/src/dialect/factory.ts +34 -0
  313. package/src/dialect/fenced-thinking.ts +184 -0
  314. package/src/dialect/gemini.md +44 -0
  315. package/src/dialect/gemini.ts +583 -0
  316. package/src/dialect/gemma.md +33 -0
  317. package/src/dialect/gemma.ts +387 -0
  318. package/src/dialect/glm.md +32 -0
  319. package/src/dialect/glm.ts +579 -0
  320. package/src/dialect/harmony.md +31 -0
  321. package/src/dialect/harmony.ts +345 -0
  322. package/src/dialect/hermes.md +25 -0
  323. package/src/dialect/hermes.ts +206 -0
  324. package/src/dialect/history.ts +81 -0
  325. package/src/dialect/index.ts +15 -0
  326. package/src/dialect/inventory.ts +30 -0
  327. package/src/dialect/kimi.md +24 -0
  328. package/src/dialect/kimi.ts +340 -0
  329. package/src/dialect/minimax.md +31 -0
  330. package/src/dialect/minimax.ts +95 -0
  331. package/src/dialect/owned-stream.ts +481 -0
  332. package/src/dialect/prompt-template.md +12 -0
  333. package/src/dialect/qwen3.md +28 -0
  334. package/src/dialect/qwen3.ts +240 -0
  335. package/src/dialect/rendering.ts +304 -0
  336. package/src/dialect/thinking.ts +292 -0
  337. package/src/dialect/types.ts +56 -0
  338. package/src/dialect/xml.md +22 -0
  339. package/src/dialect/xml.ts +90 -0
  340. package/src/error/abort.ts +18 -0
  341. package/src/error/auth-classify.ts +47 -0
  342. package/src/error/auth.ts +48 -0
  343. package/src/error/aws.ts +35 -0
  344. package/src/error/classes.ts +281 -0
  345. package/src/error/finalize.ts +69 -0
  346. package/src/error/flags.ts +602 -0
  347. package/src/error/format.ts +45 -0
  348. package/src/error/gateway.ts +96 -0
  349. package/src/error/index.ts +14 -0
  350. package/src/error/oauth.ts +58 -0
  351. package/src/error/provider.ts +63 -0
  352. package/src/error/rate-limit.ts +303 -0
  353. package/src/error/retryable.ts +70 -0
  354. package/src/error/validation.ts +44 -0
  355. package/src/index.ts +51 -0
  356. package/src/provider-details.ts +90 -0
  357. package/src/providers/amazon-bedrock.ts +1064 -0
  358. package/src/providers/anthropic-client.ts +317 -0
  359. package/src/providers/anthropic-messages-server-schema.ts +252 -0
  360. package/src/providers/anthropic-messages-server.ts +818 -0
  361. package/src/providers/anthropic-wire.ts +359 -0
  362. package/src/providers/anthropic.ts +4539 -0
  363. package/src/providers/aws-credentials.ts +772 -0
  364. package/src/providers/aws-eventstream.ts +181 -0
  365. package/src/providers/aws-sigv4.ts +218 -0
  366. package/src/providers/azure-openai-responses.ts +438 -0
  367. package/src/providers/bedrock-mantle.ts +110 -0
  368. package/src/providers/claude-code-fingerprint.ts +20 -0
  369. package/src/providers/cowork-fetch.ts +201 -0
  370. package/src/providers/cursor/exec-modern.ts +496 -0
  371. package/src/providers/cursor/proto/agent.proto +4533 -0
  372. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  373. package/src/providers/cursor/proto/buf.yaml +17 -0
  374. package/src/providers/cursor-pi-args.ts +165 -0
  375. package/src/providers/cursor.ts +4689 -0
  376. package/src/providers/devin/proto/buf/validate/validate.proto +468 -0
  377. package/src/providers/devin/proto/buf.gen.yaml +33 -0
  378. package/src/providers/devin/proto/buf.yaml +17 -0
  379. package/src/providers/devin/proto/cel/expr/checked.proto +103 -0
  380. package/src/providers/devin/proto/cel/expr/eval.proto +38 -0
  381. package/src/providers/devin/proto/cel/expr/explain.proto +15 -0
  382. package/src/providers/devin/proto/cel/expr/syntax.proto +113 -0
  383. package/src/providers/devin/proto/cel/expr/value.proto +41 -0
  384. package/src/providers/devin/proto/connectext/grpc/status/v1/status.proto +11 -0
  385. package/src/providers/devin/proto/errorspb/errors.proto +56 -0
  386. package/src/providers/devin/proto/errorspb/hintdetail.proto +7 -0
  387. package/src/providers/devin/proto/errorspb/markers.proto +10 -0
  388. package/src/providers/devin/proto/errorspb/tags.proto +12 -0
  389. package/src/providers/devin/proto/errorspb/testing.proto +6 -0
  390. package/src/providers/devin/proto/exa/analytics_pb/analytics.proto +188 -0
  391. package/src/providers/devin/proto/exa/api_server_pb/api_server.proto +2461 -0
  392. package/src/providers/devin/proto/exa/auth_pb/auth.proto +19 -0
  393. package/src/providers/devin/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +79 -0
  394. package/src/providers/devin/proto/exa/browser_preview_pb/browser_preview.proto +32 -0
  395. package/src/providers/devin/proto/exa/bug_checker_pb/bug_checker.proto +22 -0
  396. package/src/providers/devin/proto/exa/cascade_plugins_pb/cascade_plugins.proto +262 -0
  397. package/src/providers/devin/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
  398. package/src/providers/devin/proto/exa/chat_pb/chat.proto +449 -0
  399. package/src/providers/devin/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
  400. package/src/providers/devin/proto/exa/codeium_common_pb/codeium_common.proto +4157 -0
  401. package/src/providers/devin/proto/exa/context_module_pb/context_module.proto +175 -0
  402. package/src/providers/devin/proto/exa/cortex_pb/cortex.proto +3268 -0
  403. package/src/providers/devin/proto/exa/dev_pb/dev.proto +26 -0
  404. package/src/providers/devin/proto/exa/diff_action_pb/diff_action.proto +75 -0
  405. package/src/providers/devin/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +103 -0
  406. package/src/providers/devin/proto/exa/eval_pb/eval.proto +1315 -0
  407. package/src/providers/devin/proto/exa/extension_server_pb/extension_server.proto +556 -0
  408. package/src/providers/devin/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
  409. package/src/providers/devin/proto/exa/index_pb/index.proto +461 -0
  410. package/src/providers/devin/proto/exa/knowledge_base_pb/knowledge_base.proto +144 -0
  411. package/src/providers/devin/proto/exa/language_server_pb/language_server.proto +2385 -0
  412. package/src/providers/devin/proto/exa/model_management_pb/model_management.proto +186 -0
  413. package/src/providers/devin/proto/exa/opensearch_clients_pb/opensearch_clients.proto +503 -0
  414. package/src/providers/devin/proto/exa/product_analytics_pb/product_analytics.proto +37 -0
  415. package/src/providers/devin/proto/exa/prompt_pb/prompt.proto +92 -0
  416. package/src/providers/devin/proto/exa/reactive_component_pb/reactive_component.proto +96 -0
  417. package/src/providers/devin/proto/exa/seat_management_pb/seat_management.proto +2680 -0
  418. package/src/providers/devin/proto/exa/tokenizer_pb/tokenizer.proto +37 -0
  419. package/src/providers/devin/proto/exa/trainer_pb/config.proto +647 -0
  420. package/src/providers/devin/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
  421. package/src/providers/devin/proto/exa/trust_pb/trust.proto +157 -0
  422. package/src/providers/devin/proto/exa/user_analytics_pb/user_analytics.proto +519 -0
  423. package/src/providers/devin/proto/google.golang.org/appengine/internal/base/api_base.proto +28 -0
  424. package/src/providers/devin/proto/google.golang.org/appengine/internal/datastore/datastore_v3.proto +484 -0
  425. package/src/providers/devin/proto/google.golang.org/appengine/internal/log/log_service.proto +136 -0
  426. package/src/providers/devin/proto/google.golang.org/appengine/internal/remote_api/remote_api.proto +42 -0
  427. package/src/providers/devin/proto/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto +61 -0
  428. package/src/providers/devin/proto/grpc/binlog/v1/binarylog.proto +84 -0
  429. package/src/providers/devin/proto/io/prometheus/client/metrics.proto +98 -0
  430. package/src/providers/devin.ts +679 -0
  431. package/src/providers/error-message.ts +23 -0
  432. package/src/providers/github-copilot-headers.ts +141 -0
  433. package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
  434. package/src/providers/gitlab-duo-workflow.ts +3135 -0
  435. package/src/providers/gitlab-duo.ts +399 -0
  436. package/src/providers/google-auth.ts +330 -0
  437. package/src/providers/google-gemini-cli.ts +1370 -0
  438. package/src/providers/google-shared.ts +1122 -0
  439. package/src/providers/google-types.ts +180 -0
  440. package/src/providers/google-vertex.ts +135 -0
  441. package/src/providers/google.ts +47 -0
  442. package/src/providers/grammar.ts +70 -0
  443. package/src/providers/kimi.ts +51 -0
  444. package/src/providers/mock.ts +514 -0
  445. package/src/providers/ollama.ts +776 -0
  446. package/src/providers/openai-anthropic-shim.ts +166 -0
  447. package/src/providers/openai-chat-server-schema.ts +243 -0
  448. package/src/providers/openai-chat-server.ts +752 -0
  449. package/src/providers/openai-chat-wire.ts +859 -0
  450. package/src/providers/openai-codex/request-transformer.ts +491 -0
  451. package/src/providers/openai-codex/response-handler.ts +102 -0
  452. package/src/providers/openai-codex-responses.ts +4716 -0
  453. package/src/providers/openai-completions.ts +2389 -0
  454. package/src/providers/openai-reasoning-fallback.ts +269 -0
  455. package/src/providers/openai-responses-server-schema.ts +397 -0
  456. package/src/providers/openai-responses-server.ts +1466 -0
  457. package/src/providers/openai-responses-wire.ts +6416 -0
  458. package/src/providers/openai-responses.ts +1393 -0
  459. package/src/providers/openai-shared.ts +3500 -0
  460. package/src/providers/pi-native-client.ts +275 -0
  461. package/src/providers/pi-native-server.ts +245 -0
  462. package/src/providers/register-builtins.ts +503 -0
  463. package/src/providers/synthetic.ts +50 -0
  464. package/src/providers/transform-messages.ts +1083 -0
  465. package/src/providers/vision-guard.ts +54 -0
  466. package/src/registry/aiand.ts +22 -0
  467. package/src/registry/aimlapi.ts +6 -0
  468. package/src/registry/alibaba-coding-plan.ts +104 -0
  469. package/src/registry/alibaba-token-plan.ts +125 -0
  470. package/src/registry/amazon-bedrock.ts +22 -0
  471. package/src/registry/anthropic.ts +26 -0
  472. package/src/registry/api-key-login.ts +115 -0
  473. package/src/registry/api-key-validation.ts +145 -0
  474. package/src/registry/aws.ts +57 -0
  475. package/src/registry/azure.ts +6 -0
  476. package/src/registry/baseten.ts +22 -0
  477. package/src/registry/bedrock-mantle.ts +34 -0
  478. package/src/registry/cerebras.ts +23 -0
  479. package/src/registry/cloudflare-ai-gateway.ts +45 -0
  480. package/src/registry/coreweave.ts +40 -0
  481. package/src/registry/cursor.ts +20 -0
  482. package/src/registry/deepseek.ts +46 -0
  483. package/src/registry/derived.ts +9 -0
  484. package/src/registry/devin.ts +15 -0
  485. package/src/registry/exa.ts +19 -0
  486. package/src/registry/firepass.ts +32 -0
  487. package/src/registry/fireworks.ts +28 -0
  488. package/src/registry/github-copilot.ts +22 -0
  489. package/src/registry/gitlab-duo-workflow.ts +20 -0
  490. package/src/registry/gitlab-duo.ts +19 -0
  491. package/src/registry/gmi-cloud.ts +22 -0
  492. package/src/registry/google-antigravity.ts +22 -0
  493. package/src/registry/google-gemini-cli.ts +22 -0
  494. package/src/registry/google-vertex.ts +38 -0
  495. package/src/registry/google.ts +6 -0
  496. package/src/registry/groq.ts +6 -0
  497. package/src/registry/huggingface.ts +29 -0
  498. package/src/registry/index.ts +4 -0
  499. package/src/registry/kagi.ts +46 -0
  500. package/src/registry/kilo.ts +114 -0
  501. package/src/registry/kimi-code.ts +17 -0
  502. package/src/registry/litellm.ts +45 -0
  503. package/src/registry/llama-cpp.ts +35 -0
  504. package/src/registry/lm-studio.ts +31 -0
  505. package/src/registry/meta.ts +22 -0
  506. package/src/registry/minimax-code-cn.ts +12 -0
  507. package/src/registry/minimax-code.ts +12 -0
  508. package/src/registry/minimax.ts +6 -0
  509. package/src/registry/mistral.ts +6 -0
  510. package/src/registry/moonshot.ts +28 -0
  511. package/src/registry/nanogpt.ts +22 -0
  512. package/src/registry/novita.ts +25 -0
  513. package/src/registry/nvidia.ts +61 -0
  514. package/src/registry/oauth/anthropic-constants.ts +12 -0
  515. package/src/registry/oauth/anthropic.ts +346 -0
  516. package/src/registry/oauth/callback-server.ts +438 -0
  517. package/src/registry/oauth/cursor.ts +187 -0
  518. package/src/registry/oauth/device-code.ts +92 -0
  519. package/src/registry/oauth/devin.ts +124 -0
  520. package/src/registry/oauth/github-copilot.ts +369 -0
  521. package/src/registry/oauth/gitlab-duo-workflow.ts +146 -0
  522. package/src/registry/oauth/gitlab-duo.ts +222 -0
  523. package/src/registry/oauth/google-antigravity.ts +225 -0
  524. package/src/registry/oauth/google-gemini-cli.ts +297 -0
  525. package/src/registry/oauth/google-oauth-shared.ts +211 -0
  526. package/src/registry/oauth/index.ts +187 -0
  527. package/src/registry/oauth/kimi.ts +297 -0
  528. package/src/registry/oauth/minimax-code.ts +53 -0
  529. package/src/registry/oauth/oauth.html +317 -0
  530. package/src/registry/oauth/openai-codex.ts +384 -0
  531. package/src/registry/oauth/opencode.ts +50 -0
  532. package/src/registry/oauth/perplexity.ts +228 -0
  533. package/src/registry/oauth/pkce.ts +18 -0
  534. package/src/registry/oauth/types.ts +96 -0
  535. package/src/registry/oauth/wafer.ts +24 -0
  536. package/src/registry/oauth/xai-oauth.ts +559 -0
  537. package/src/registry/oauth/xiaomi.ts +211 -0
  538. package/src/registry/oauth/zai.ts +285 -0
  539. package/src/registry/ollama-cloud.ts +36 -0
  540. package/src/registry/ollama.ts +43 -0
  541. package/src/registry/openai-codex-device.ts +18 -0
  542. package/src/registry/openai-codex.ts +19 -0
  543. package/src/registry/openai.ts +6 -0
  544. package/src/registry/opencode-go.ts +12 -0
  545. package/src/registry/opencode-zen.ts +12 -0
  546. package/src/registry/openrouter.ts +28 -0
  547. package/src/registry/parallel.ts +45 -0
  548. package/src/registry/perplexity.ts +13 -0
  549. package/src/registry/qianfan.ts +27 -0
  550. package/src/registry/qwen-portal.ts +50 -0
  551. package/src/registry/registry.ts +182 -0
  552. package/src/registry/sakana.ts +22 -0
  553. package/src/registry/siliconflow-cn.ts +22 -0
  554. package/src/registry/siliconflow.ts +22 -0
  555. package/src/registry/synthetic.ts +21 -0
  556. package/src/registry/tavily.ts +45 -0
  557. package/src/registry/together.ts +22 -0
  558. package/src/registry/types.ts +86 -0
  559. package/src/registry/umans.ts +23 -0
  560. package/src/registry/venice.ts +33 -0
  561. package/src/registry/vercel-ai-gateway.ts +38 -0
  562. package/src/registry/vllm.ts +34 -0
  563. package/src/registry/wafer-serverless.ts +12 -0
  564. package/src/registry/xai-oauth.ts +17 -0
  565. package/src/registry/xai.ts +22 -0
  566. package/src/registry/xiaomi-token-plan-ams.ts +12 -0
  567. package/src/registry/xiaomi-token-plan-cn.ts +12 -0
  568. package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
  569. package/src/registry/xiaomi.ts +12 -0
  570. package/src/registry/zai.ts +41 -0
  571. package/src/registry/zenmux.ts +22 -0
  572. package/src/registry/zhipu-coding-plan.ts +27 -0
  573. package/src/stream.ts +1944 -0
  574. package/src/types.ts +1243 -0
  575. package/src/usage/alibaba-token-plan.ts +230 -0
  576. package/src/usage/claude.ts +830 -0
  577. package/src/usage/cursor.ts +335 -0
  578. package/src/usage/gemini.ts +258 -0
  579. package/src/usage/github-copilot.ts +424 -0
  580. package/src/usage/google-antigravity.ts +497 -0
  581. package/src/usage/kimi.ts +277 -0
  582. package/src/usage/minimax-code.ts +291 -0
  583. package/src/usage/ollama.ts +41 -0
  584. package/src/usage/openai-codex-base-url.ts +35 -0
  585. package/src/usage/openai-codex-reset.ts +205 -0
  586. package/src/usage/openai-codex.ts +627 -0
  587. package/src/usage/opencode-go.ts +89 -0
  588. package/src/usage/shared.ts +10 -0
  589. package/src/usage/synthetic.ts +180 -0
  590. package/src/usage/umans.ts +192 -0
  591. package/src/usage/xai-oauth.ts +414 -0
  592. package/src/usage/zai.ts +370 -0
  593. package/src/usage.ts +411 -0
  594. package/src/utils/abort.ts +67 -0
  595. package/src/utils/anthropic-auth.ts +93 -0
  596. package/src/utils/aws-profile.ts +88 -0
  597. package/src/utils/block-symbols.ts +78 -0
  598. package/src/utils/deterministic-id.ts +20 -0
  599. package/src/utils/empty-completion-retry.ts +161 -0
  600. package/src/utils/event-stream.ts +202 -0
  601. package/src/utils/foundry.ts +8 -0
  602. package/src/utils/google-validation.ts +25 -0
  603. package/src/utils/harmony-leak.ts +500 -0
  604. package/src/utils/http-inspector.ts +196 -0
  605. package/src/utils/idle-iterator.ts +531 -0
  606. package/src/utils/leaked-thinking-stream.ts +483 -0
  607. package/src/utils/openai-http.ts +119 -0
  608. package/src/utils/openrouter-headers.ts +12 -0
  609. package/src/utils/parse-bind.ts +56 -0
  610. package/src/utils/provider-response.ts +30 -0
  611. package/src/utils/proxy.ts +314 -0
  612. package/src/utils/request-debug.ts +351 -0
  613. package/src/utils/retry-after.ts +121 -0
  614. package/src/utils/retry.ts +77 -0
  615. package/src/utils/schema/CONSTRAINTS.md +168 -0
  616. package/src/utils/schema/adapt.ts +36 -0
  617. package/src/utils/schema/compatibility.ts +435 -0
  618. package/src/utils/schema/dereference.ts +98 -0
  619. package/src/utils/schema/draft.ts +341 -0
  620. package/src/utils/schema/equality.ts +97 -0
  621. package/src/utils/schema/fields.ts +210 -0
  622. package/src/utils/schema/index.ts +14 -0
  623. package/src/utils/schema/json-schema-validator.ts +595 -0
  624. package/src/utils/schema/meta-validator.ts +167 -0
  625. package/src/utils/schema/normalize.ts +2314 -0
  626. package/src/utils/schema/spill.ts +43 -0
  627. package/src/utils/schema/stamps.ts +109 -0
  628. package/src/utils/schema/strict-tool-validation.ts +117 -0
  629. package/src/utils/schema/types.ts +10 -0
  630. package/src/utils/schema/typescript.ts +212 -0
  631. package/src/utils/schema/wire.ts +662 -0
  632. package/src/utils/sdk-stream-timeout.ts +43 -0
  633. package/src/utils/sse-debug.ts +18 -0
  634. package/src/utils/stream-markup-healing.ts +247 -0
  635. package/src/utils/thinking-loop.ts +552 -0
  636. package/src/utils/tool-call-loop-guard.ts +107 -0
  637. package/src/utils/tool-choice.ts +101 -0
  638. package/src/utils/validation.ts +1932 -0
  639. package/src/utils.ts +492 -0
@@ -0,0 +1,1083 @@
1
+ import { renderDemotedThinking } from "../dialect/demotion";
2
+ import type {
3
+ Api,
4
+ AssistantMessage,
5
+ DeveloperMessage,
6
+ Message,
7
+ Model,
8
+ ToolCall,
9
+ ToolResultMessage,
10
+ UserMessage,
11
+ } from "../types";
12
+ import { isDemotedThinking, kDemotedThinking } from "../utils/block-symbols";
13
+
14
+ const enum ToolCallStatus {
15
+ /** A tool result has already been emitted for this tool call; later duplicates must be skipped. */
16
+ Resolved = 1,
17
+ /** A synthetic aborted result was emitted; later real results must be skipped. */
18
+ Aborted = 2,
19
+ }
20
+
21
+ /**
22
+ * Maximum tool-call id length the strictest replay provider accepts.
23
+ *
24
+ * Anthropic requires `^[a-zA-Z0-9_-]+$` with a 64-char cap; Google and Codex
25
+ * `normalizeToolCallId` implementations cap individual id segments to the same
26
+ * 64-char ceiling. Replacement ids minted here flow back through
27
+ * `convertAnthropicMessages` (and friends) unchanged, so the `_dupN` suffix
28
+ * MUST not push a normalized id past this bound.
29
+ */
30
+ const MAX_TOOL_CALL_ID_LENGTH = 64;
31
+
32
+ function appendDuplicateSuffix(originalId: string, suffix: string, maxLength: number): string {
33
+ // Responses-family ids are composites (`callId|itemId`): the wire call_id is
34
+ // the FIRST segment (normalizeResponsesToolCallId splits on `|`), so the
35
+ // suffix must land on every segment or the duplicate collapses back onto the
36
+ // original call_id at encode time. The length budget applies per segment,
37
+ // matching the per-segment caps of the provider normalizers.
38
+ if (originalId.includes("|")) {
39
+ return originalId
40
+ .split("|")
41
+ .map(segment => appendSegmentDuplicateSuffix(segment, suffix, maxLength))
42
+ .join("|");
43
+ }
44
+ return appendSegmentDuplicateSuffix(originalId, suffix, maxLength);
45
+ }
46
+
47
+ function appendSegmentDuplicateSuffix(segment: string, suffix: string, maxLength: number): string {
48
+ if (segment.length + suffix.length <= maxLength) return `${segment}${suffix}`;
49
+ const prefixBudget = Math.max(0, maxLength - suffix.length);
50
+ return `${segment.slice(0, prefixBudget)}${suffix}`;
51
+ }
52
+
53
+ type PendingToolResultRewrite = { replacementId: string } | undefined;
54
+
55
+ function deduplicateToolCallIds(
56
+ messages: Message[],
57
+ maxToolCallIdLength = MAX_TOOL_CALL_ID_LENGTH,
58
+ duplicateSuffixPrefix = "_dup",
59
+ ): Message[] {
60
+ const seenToolCallIds = new Map<string, number>();
61
+ const pendingToolResultRewrites = new Map<string, PendingToolResultRewrite[]>();
62
+
63
+ return messages.map(msg => {
64
+ if (msg.role === "toolResult") {
65
+ const rewrites = pendingToolResultRewrites.get(msg.toolCallId);
66
+ if (!rewrites || rewrites.length === 0) return msg;
67
+
68
+ const rewrite = rewrites.shift();
69
+ if (rewrites.length === 0) pendingToolResultRewrites.delete(msg.toolCallId);
70
+ if (rewrite) return { ...msg, toolCallId: rewrite.replacementId };
71
+ return msg;
72
+ }
73
+
74
+ if (msg.role !== "assistant") return msg;
75
+
76
+ const enqueueToolResultRewrite = (id: string, rewrite: PendingToolResultRewrite): void => {
77
+ const rewrites = pendingToolResultRewrites.get(id);
78
+ if (rewrites) {
79
+ rewrites.push(rewrite);
80
+ return;
81
+ }
82
+ pendingToolResultRewrites.set(id, [rewrite]);
83
+ };
84
+
85
+ // Ids this turn has already touched; used to scope the "drop carried-over
86
+ // pending rewrites" semantics to the FIRST occurrence per turn so multiple
87
+ // blocks of the same id within one turn still accumulate as duplicates.
88
+ const idsTouchedInTurn = new Set<string>();
89
+ let contentChanged = false;
90
+ const content = msg.content.map(block => {
91
+ if (block.type !== "toolCall") return block;
92
+
93
+ // Drop any pending rewrites carried over from a prior assistant turn
94
+ // for this id on its first appearance this turn. When a later turn
95
+ // re-emits the same id, the older duplicate call's expected result
96
+ // never landed in time — the second pass synthesizes
97
+ // "No result provided" for it, and the upcoming real result(id) must
98
+ // route to one of THIS turn's calls. Without this guard the older
99
+ // `_dup` id would steal the next result.
100
+ if (!idsTouchedInTurn.has(block.id)) {
101
+ pendingToolResultRewrites.delete(block.id);
102
+ idsTouchedInTurn.add(block.id);
103
+ }
104
+
105
+ const previousCount = seenToolCallIds.get(block.id) ?? 0;
106
+ if (previousCount === 0) {
107
+ seenToolCallIds.set(block.id, 1);
108
+ enqueueToolResultRewrite(block.id, undefined);
109
+ return block;
110
+ }
111
+
112
+ let duplicateIndex = previousCount;
113
+ let replacementId = appendDuplicateSuffix(
114
+ block.id,
115
+ `${duplicateSuffixPrefix}${duplicateIndex}`,
116
+ maxToolCallIdLength,
117
+ );
118
+ while (seenToolCallIds.has(replacementId)) {
119
+ duplicateIndex += 1;
120
+ replacementId = appendDuplicateSuffix(
121
+ block.id,
122
+ `${duplicateSuffixPrefix}${duplicateIndex}`,
123
+ maxToolCallIdLength,
124
+ );
125
+ }
126
+ seenToolCallIds.set(block.id, duplicateIndex + 1);
127
+ seenToolCallIds.set(replacementId, 1);
128
+ enqueueToolResultRewrite(block.id, { replacementId });
129
+ contentChanged = true;
130
+ return { ...block, id: replacementId };
131
+ });
132
+
133
+ if (!contentChanged) return msg;
134
+ return { ...msg, content };
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Drop assistant `toolCall` blocks whose `id` or `name` is empty / whitespace-only,
140
+ * the `toolResult` messages they point at, and any assistant turn that has no
141
+ * replayable content left.
142
+ *
143
+ * Models occasionally emit malformed calls such as `{ "name": "", "arguments": "{}" }`
144
+ * (observed: GLM-5.2 + thinking on long turns, #3458) or a structurally valid
145
+ * `toolCall` whose provider/native passthrough id never materialized (`id: ""`).
146
+ * The agent loop rejects or skips these at execution time, but the malformed block
147
+ * and its error tool-result can stay in `currentContext.messages`, so every
148
+ * subsequent request replays them. Every provider validates the call shape —
149
+ * Anthropic 400s on `tool_use.name` / `tool_use.id` (alongside an orphan
150
+ * `tool_result`), OpenAI Chat Completions 400s on malformed
151
+ * `tool_calls[i].function.*` — wedging the session in a 400 loop until manual
152
+ * `/clear`.
153
+ *
154
+ * Run before any other transform so the rest of the pipeline never sees a
155
+ * malformed call. Idempotent: a re-run on an already-sanitized list returns
156
+ * the input untouched. Provider-agnostic — any wire model could surface this.
157
+ */
158
+ function isMalformedToolCallName(name: string | undefined): boolean {
159
+ return !name || name.trim().length === 0;
160
+ }
161
+
162
+ function isMalformedToolCallId(id: string | undefined): boolean {
163
+ return !id || id.trim().length === 0;
164
+ }
165
+
166
+ function isMalformedToolCall(block: { id: string; name: string }): boolean {
167
+ return isMalformedToolCallId(block.id) || isMalformedToolCallName(block.name);
168
+ }
169
+
170
+ function sanitizeMalformedToolCalls(messages: Message[]): Message[] {
171
+ // Fast path: skip the rewrite entirely when nothing is malformed.
172
+ let hasMalformed = false;
173
+ outer: for (const msg of messages) {
174
+ if (msg.role !== "assistant") continue;
175
+ for (const block of msg.content) {
176
+ if (block.type === "toolCall" && isMalformedToolCall(block)) {
177
+ hasMalformed = true;
178
+ break outer;
179
+ }
180
+ }
181
+ }
182
+ if (!hasMalformed) return messages;
183
+
184
+ // Positional FIFO pairing within one assistant→tool-result window: a tool-call
185
+ // id can repeat across history when an OpenAI-Responses composite id
186
+ // (`callId|itemId`) collapses on the wire to the same `callId` (see
187
+ // `deduplicateToolCallIds` + `transform-messages-dedup`). A set-based "drop
188
+ // every result for this id" loses the real output for the surviving valid
189
+ // occurrence whenever one duplicate is malformed. Track each `toolCall`
190
+ // occurrence's malformed-ness on a per-id queue and pop on matching
191
+ // `toolResult`, but clear the queues at every non-result boundary so a
192
+ // malformed call whose rejection result never arrived cannot consume a later
193
+ // valid call's real result when the id is reused.
194
+ const dropQueues = new Map<string, boolean[]>();
195
+ const result: Message[] = [];
196
+ for (const msg of messages) {
197
+ if (msg.role === "assistant") {
198
+ dropQueues.clear();
199
+ const filtered: AssistantMessage["content"] = [];
200
+ for (const block of msg.content) {
201
+ if (block.type === "toolCall") {
202
+ const malformed = isMalformedToolCall(block);
203
+ const queue = dropQueues.get(block.id);
204
+ if (queue) queue.push(malformed);
205
+ else dropQueues.set(block.id, [malformed]);
206
+ if (malformed) continue;
207
+ }
208
+ filtered.push(block);
209
+ }
210
+ if (filtered.length === 0) continue;
211
+ result.push(filtered.length === msg.content.length ? msg : { ...msg, content: filtered });
212
+ continue;
213
+ }
214
+ if (msg.role === "toolResult") {
215
+ const queue = dropQueues.get(msg.toolCallId);
216
+ if (queue && queue.length > 0) {
217
+ const drop = queue.shift() === true;
218
+ if (queue.length === 0) dropQueues.delete(msg.toolCallId);
219
+ if (drop) continue;
220
+ }
221
+ result.push(msg);
222
+ continue;
223
+ }
224
+ dropQueues.clear();
225
+ result.push(msg);
226
+ }
227
+ return result;
228
+ }
229
+
230
+ function shouldDropTruncatedThinkingOnlyAssistant(msg: AssistantMessage): boolean {
231
+ const isTruncatedStop = msg.stopReason === "length" || msg.stopReason === "error" || msg.stopReason === "aborted";
232
+ return isTruncatedStop && !msg.content.some(block => block.type === "toolCall" || block.type === "text");
233
+ }
234
+
235
+ function getLatestSurvivingAssistantIndex(messages: readonly Message[]): number {
236
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
237
+ const msg = messages[index]!;
238
+ if (msg.role === "assistant" && !shouldDropTruncatedThinkingOnlyAssistant(msg)) {
239
+ return index;
240
+ }
241
+ }
242
+ return -1;
243
+ }
244
+
245
+ function isAnthropicMessagesModel(model: Model): model is Model<"anthropic-messages"> {
246
+ return model.api === "anthropic-messages";
247
+ }
248
+
249
+ /**
250
+ * Targets that have proven they read unsigned foreign thinking when replayed
251
+ * natively. This is a semantic-carry allowlist only: OpenAI-compatible
252
+ * `reasoning_content` schema requirements and llama.cpp cache-prefix replay are
253
+ * handled by their encoders and MUST NOT make foreign thinking look meaningful.
254
+ */
255
+ function targetReadsForeignThinking(model: Model, compat: Model["compat"]): boolean {
256
+ if (compat === undefined) return false;
257
+ if (model.api === "anthropic-messages") {
258
+ return "replayUnsignedThinking" in compat && compat.replayUnsignedThinking === true;
259
+ }
260
+ if (model.api !== "openai-completions") return false;
261
+ if (!("thinkingFormat" in compat)) return false;
262
+ if (compat.requiresThinkingAsText) return false;
263
+ return model.reasoning && compat.thinkingFormat === "zai";
264
+ }
265
+
266
+ const ANTHROPIC_TOOL_CALL_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
267
+
268
+ function isValidAnthropicToolCallId(id: string): boolean {
269
+ return ANTHROPIC_TOOL_CALL_ID_PATTERN.test(id);
270
+ }
271
+
272
+ function fallbackAnthropicToolCallId(originalId: string): string {
273
+ return `toolu_${Bun.hash(originalId).toString(36)}`;
274
+ }
275
+
276
+ function normalizeAnthropicTargetToolCallId<TApi extends Api>(
277
+ id: string,
278
+ model: Model<TApi>,
279
+ source: AssistantMessage,
280
+ normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,
281
+ ): string {
282
+ if (isValidAnthropicToolCallId(id)) return id;
283
+ const normalized =
284
+ normalizeToolCallId?.(id, model, source) ?? id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, MAX_TOOL_CALL_ID_LENGTH);
285
+ if (isValidAnthropicToolCallId(normalized)) return normalized;
286
+ return fallbackAnthropicToolCallId(id);
287
+ }
288
+
289
+ /**
290
+ * Normalize tool call ID for cross-provider compatibility.
291
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
292
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
293
+ *
294
+ * For aborted/errored turns, this function:
295
+ * - Preserves tool call structure (unlike converting to text summaries)
296
+ * - Injects synthetic "aborted" tool results
297
+ */
298
+ /**
299
+ * Credential-shaped token patterns scrubbed from outbound provider traffic when
300
+ * credential redaction is enabled. Exported so hosts can route the same shapes
301
+ * through reversible obfuscation (keyed placeholders restored before local tool
302
+ * execution) instead of the irreversible `[*_token_redacted]` rewrite below —
303
+ * an irreversible placeholder echoed back in edit-tool `old_string` can never
304
+ * match the real bytes on disk.
305
+ */
306
+ export const SENSITIVE_TOKEN_RE =
307
+ /(?<![a-zA-Z0-9_*-])(gh[opusr]_[a-zA-Z0-9_*]{36,}|github_pat_[a-zA-Z0-9_*]{36,}|glpat-[a-zA-Z0-9_*-]{20,}|sk-proj-[a-zA-Z0-9_*-]{36,}|sk-ant-[a-zA-Z0-9_*-]{36,}|sk-[a-zA-Z0-9_*-]{48,})(?![a-zA-Z0-9_*-])/gi;
308
+
309
+ function hasPlausibleCredentialEntropy(token: string): boolean {
310
+ const lower = token.toLowerCase();
311
+ const prefixLength = lower.startsWith("github_pat_")
312
+ ? "github_pat_".length
313
+ : lower.startsWith("glpat-")
314
+ ? "glpat-".length
315
+ : lower.startsWith("sk-proj-")
316
+ ? "sk-proj-".length
317
+ : lower.startsWith("sk-ant-")
318
+ ? "sk-ant-".length
319
+ : lower.startsWith("gh")
320
+ ? 4
321
+ : 3;
322
+ const secret = token.slice(prefixLength);
323
+ if (/^\*+$/.test(secret)) return true;
324
+ return [/[a-z]/, /[A-Z]/, /\d/, /[_-]/].filter(pattern => pattern.test(secret)).length >= 2;
325
+ }
326
+
327
+ /**
328
+ * Whether outbound credential-pattern redaction is active. Off by default;
329
+ * hosts opt in explicitly (the coding agent wires this to the
330
+ * `secrets.enabled` setting).
331
+ */
332
+ let credentialRedactionEnabled = false;
333
+
334
+ /**
335
+ * Toggle outbound credential-pattern redaction. When disabled (the default),
336
+ * {@link redactSensitiveCredentials} and {@link redactSensitiveInObject} are
337
+ * pass-throughs and outbound messages/system prompts leave the process
338
+ * unmodified.
339
+ */
340
+ export function configureCredentialRedaction(enabled: boolean): void {
341
+ credentialRedactionEnabled = enabled;
342
+ }
343
+
344
+ export function redactSensitiveCredentials(text: string): string {
345
+ if (!credentialRedactionEnabled) return text;
346
+ return text.replace(SENSITIVE_TOKEN_RE, match => {
347
+ if (!hasPlausibleCredentialEntropy(match)) return match;
348
+ const lower = match.toLowerCase();
349
+ if (lower.startsWith("gh")) {
350
+ return "[github_token_redacted]";
351
+ }
352
+ if (lower.startsWith("gl")) {
353
+ return "[gitlab_token_redacted]";
354
+ }
355
+ if (lower.startsWith("sk-ant-")) {
356
+ return "[anthropic_token_redacted]";
357
+ }
358
+ if (lower.startsWith("sk")) {
359
+ return "[openai_token_redacted]";
360
+ }
361
+ return "[token_redacted]";
362
+ });
363
+ }
364
+
365
+ export function redactSensitiveInObject(val: unknown): { result: unknown; changed: boolean } {
366
+ if (!credentialRedactionEnabled) return { result: val, changed: false };
367
+ if (typeof val === "string") {
368
+ const redacted = redactSensitiveCredentials(val);
369
+ return { result: redacted, changed: redacted !== val };
370
+ }
371
+ if (Array.isArray(val)) {
372
+ let changed = false;
373
+ const result = val.map(item => {
374
+ const res = redactSensitiveInObject(item);
375
+ if (res.changed) changed = true;
376
+ return res.result;
377
+ });
378
+ return { result, changed };
379
+ }
380
+ if (val !== null && typeof val === "object") {
381
+ let changed = false;
382
+ const res: Record<string, unknown> = {};
383
+ for (const [k, v] of Object.entries(val)) {
384
+ const sub = redactSensitiveInObject(v);
385
+ if (sub.changed) changed = true;
386
+ res[k] = sub.result;
387
+ }
388
+ return { result: res, changed };
389
+ }
390
+ return { result: val, changed: false };
391
+ }
392
+
393
+ function redactSensitiveCredentialsInMessages(messages: Message[]): Message[] {
394
+ if (!credentialRedactionEnabled) return messages;
395
+ return messages.map((msg): Message => {
396
+ if (msg.role === "user" || msg.role === "developer") {
397
+ const userMsg = msg as UserMessage | DeveloperMessage;
398
+ if (typeof userMsg.content === "string") {
399
+ const redacted = redactSensitiveCredentials(userMsg.content);
400
+ if (redacted === userMsg.content) return msg;
401
+ return { ...userMsg, content: redacted } as Message;
402
+ }
403
+ const contentArray = userMsg.content;
404
+ let changed = false;
405
+ const content = contentArray.map((block): UserMessage["content"][number] => {
406
+ if (block.type === "text") {
407
+ const redacted = redactSensitiveCredentials(block.text);
408
+ if (redacted !== block.text) {
409
+ changed = true;
410
+ return { ...block, text: redacted };
411
+ }
412
+ }
413
+ return block;
414
+ });
415
+ return (changed ? { ...userMsg, content } : userMsg) as Message;
416
+ }
417
+
418
+ if (msg.role === "toolResult") {
419
+ const toolResultMsg = msg as ToolResultMessage;
420
+ let changed = false;
421
+ const content = toolResultMsg.content.map((block): ToolResultMessage["content"][number] => {
422
+ if (block.type === "text") {
423
+ const redacted = redactSensitiveCredentials(block.text);
424
+ if (redacted !== block.text) {
425
+ changed = true;
426
+ return { ...block, text: redacted };
427
+ }
428
+ }
429
+ return block;
430
+ });
431
+ return (changed ? { ...toolResultMsg, content } : toolResultMsg) as Message;
432
+ }
433
+
434
+ if (msg.role === "assistant") {
435
+ const assistantMsg = msg as AssistantMessage;
436
+ let changed = false;
437
+ const content = assistantMsg.content.map((block): AssistantMessage["content"][number] => {
438
+ if (block.type === "text") {
439
+ const redacted = redactSensitiveCredentials(block.text);
440
+ if (redacted !== block.text) {
441
+ changed = true;
442
+ return { ...block, text: redacted };
443
+ }
444
+ } else if (block.type === "thinking") {
445
+ const redacted = redactSensitiveCredentials(block.thinking);
446
+ if (redacted !== block.thinking) {
447
+ changed = true;
448
+ return { ...block, thinking: redacted, thinkingSignature: undefined };
449
+ }
450
+ } else if (block.type === "toolCall") {
451
+ if (block.arguments) {
452
+ const { result: redactedArgs, changed: argsChanged } = redactSensitiveInObject(block.arguments);
453
+ if (argsChanged) {
454
+ changed = true;
455
+ const castArgs =
456
+ redactedArgs && typeof redactedArgs === "object" && !Array.isArray(redactedArgs)
457
+ ? (redactedArgs as Record<string, unknown>)
458
+ : undefined;
459
+ return {
460
+ ...block,
461
+ arguments: castArgs,
462
+ thoughtSignature: undefined,
463
+ } as AssistantMessage["content"][number];
464
+ }
465
+ }
466
+ }
467
+ return block;
468
+ });
469
+ return (changed ? { ...assistantMsg, content } : assistantMsg) as Message;
470
+ }
471
+
472
+ return msg;
473
+ });
474
+ }
475
+
476
+ export function transformMessages<TApi extends Api>(
477
+ messages: Message[],
478
+ model: Model<TApi>,
479
+ normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,
480
+ maxNormalizedToolCallIdLength = MAX_TOOL_CALL_ID_LENGTH,
481
+ duplicateToolCallIdSuffixPrefix = "_dup",
482
+ targetCompat: Model<TApi>["compat"] = model.compat,
483
+ ): Message[] {
484
+ // Redact sensitive credential-like patterns from all outbound messages when
485
+ // the host opted in via `configureCredentialRedaction` — prevents security
486
+ // block errors from LLM providers (e.g. invalid_prompt).
487
+ messages = redactSensitiveCredentialsInMessages(messages);
488
+
489
+ // Drop assistant `toolCall` blocks with empty/whitespace `id` or `name`
490
+ // (and their matched `toolResult` messages) before anything else looks at
491
+ // the history. Replays of these would 400 every provider — see
492
+ // `sanitizeMalformedToolCalls`.
493
+ messages = sanitizeMalformedToolCalls(messages);
494
+
495
+ // Build a map of original tool call IDs to normalized IDs
496
+ const toolCallIdMap = new Map<string, string>();
497
+
498
+ const latestSurvivingAssistantIndex = getLatestSurvivingAssistantIndex(messages);
499
+ // First pass: transform messages (thinking blocks, tool call ID normalization)
500
+ const normalizedMessages = messages.map((msg, index) => {
501
+ // User and developer messages pass through unchanged
502
+ if (msg.role === "user" || msg.role === "developer") {
503
+ return msg;
504
+ }
505
+
506
+ // Handle toolResult messages - normalize toolCallId if we have a mapping
507
+ if (msg.role === "toolResult") {
508
+ const normalizedId = toolCallIdMap.get(msg.toolCallId);
509
+ if (normalizedId && normalizedId !== msg.toolCallId) {
510
+ return { ...msg, toolCallId: normalizedId };
511
+ }
512
+ return msg;
513
+ }
514
+
515
+ // Assistant messages need transformation check
516
+ if (msg.role === "assistant") {
517
+ const assistantMsg = msg as AssistantMessage;
518
+ const isSameModel =
519
+ assistantMsg.provider === model.provider &&
520
+ assistantMsg.api === model.api &&
521
+ assistantMsg.model === model.id;
522
+
523
+ const isAnthropicTarget = isAnthropicMessagesModel(model);
524
+ // Anthropic's all-or-none contract on prior-turn thinking blocks
525
+ // applies to every `anthropic-messages → anthropic-messages` replay,
526
+ // not just the latest assistant turn. The legacy
527
+ // `mustPreserveLatestAnthropicThinking` flag only honored it for the
528
+ // latest turn; every prior turn fell through to the cross-API
529
+ // text-demotion path whenever the conversation crossed a model id,
530
+ // silently dropping the reasoning chain on continuation for custom
531
+ // anthropic-messages providers configured via `models.yaml` and
532
+ // session-level model swaps (#2257).
533
+ const isAnthropicReplay = isAnthropicTarget && assistantMsg.api === "anthropic-messages";
534
+ const isLatestSurvivingAssistant = index === latestSurvivingAssistantIndex;
535
+ // Signature policy is a second axis. Anthropic cryptographically
536
+ // binds reasoning signatures to its key+session+model, so cross-model
537
+ // signatures must be stripped whenever a signing Anthropic endpoint
538
+ // is on either end of the replay:
539
+ // * official Anthropic (source): the 3p target can't reverify a
540
+ // foreign signature and keeping it leaks continuation metadata
541
+ // for no benefit.
542
+ // * signing Anthropic (target): official Anthropic, GitHub Copilot,
543
+ // ZenMux, Cloudflare AI Gateway `/anthropic`, and Google Vertex
544
+ // `publishers/anthropic/…` all forward to signature-enforcing
545
+ // Anthropic. Any stale/cross-model signature on the wire triggers
546
+ // `400 Invalid signature in thinking block` — same failure class
547
+ // whether `officialEndpoint` is true or the endpoint is one of
548
+ // the known signing proxies (#4297).
549
+ // 3p ↔ 3p replays preserve signatures because compatible providers
550
+ // (Z.AI, DeepSeek, custom `models.yaml` providers) treat them as
551
+ // opaque continuation hints rather than verified material; stripping
552
+ // degrades the reasoning chain into unsigned/text on the next turn
553
+ // (#2265). Source-side official detection uses the canonical catalog
554
+ // provider id `"anthropic"` because assistant messages carry no
555
+ // `baseUrl` — a user who manually points `provider: "anthropic"` at
556
+ // a custom proxy via `models.yaml` will see signatures stripped, the
557
+ // conservative direction (degraded reasoning, not broken requests).
558
+ const isOfficialAnthropicSource = isAnthropicReplay && assistantMsg.provider === "anthropic";
559
+ const isSigningAnthropicTarget = isAnthropicTarget && model.compat.signingEndpoint;
560
+ const signingAnthropicInvolved = isOfficialAnthropicSource || isSigningAnthropicTarget;
561
+ // Compatible Anthropic-messages reasoning targets that accept
562
+ // unsigned thinking natively (Z.AI, DeepSeek, the generic
563
+ // `reasoning && !official` case in the compat builder). Used to keep
564
+ // `redacted_thinking` siblings beside unsigned visible thinking on
565
+ // targets that won't text-demote it.
566
+ const replaysUnsignedAnthropicThinking = isAnthropicTarget && model.compat.replayUnsignedThinking;
567
+ // Thinking signatures can be untrustworthy for two distinct reasons with very
568
+ // different blast radii:
569
+ //
570
+ // 1. Aborted/errored turns: the stream stopped mid-block, so only the block
571
+ // that was streaming at the abort point — always the FINAL content block —
572
+ // can carry a partially-streamed (invalid) signature. Every earlier block
573
+ // completed: Anthropic delivers a block's signature at its
574
+ // `content_block_stop`, which necessarily fired before the next block began,
575
+ // so those signatures are whole and valid. Stripping them would needlessly
576
+ // discard a replayable thinking chain — e.g. interrupting during the visible
577
+ // text output after thinking already finished leaves a fully-signed thinking
578
+ // block that must be kept, or Anthropic rejects the replay with HTTP 400
579
+ // "Invalid `signature` in `thinking` block".
580
+ //
581
+ // 2. Abandoned tool-use turns: a turn that carries toolCall blocks but did NOT
582
+ // request tool execution (stopReason !== "toolUse" — e.g. adaptive-thinking
583
+ // Opus emitting tool calls and then ending on `end_turn`/`stop`). The agent
584
+ // loop pairs those calls with placeholder tool_results to keep the
585
+ // tool_use/tool_result contract valid. The turn completed cleanly, but its
586
+ // signatures are end_turn-bound and cannot be replayed in that synthesized
587
+ // continuation, so EVERY thinking signature is stripped.
588
+ //
589
+ // Latest abandoned turns are exempt because Anthropic requires thinking blocks
590
+ // from its most recent response to remain byte-for-byte unmodified.
591
+ const invalidStopReason = assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error";
592
+ const abandonedToolUse =
593
+ !invalidStopReason &&
594
+ assistantMsg.stopReason !== "toolUse" &&
595
+ assistantMsg.content.some(b => b.type === "toolCall");
596
+ const lastBlockIndex = assistantMsg.content.length - 1;
597
+
598
+ const anthropicVisibleThinkingSurvivesReplay = (
599
+ candidate: AssistantMessage["content"][number],
600
+ candidateIndex: number,
601
+ ): boolean => {
602
+ if (candidate.type !== "thinking") return false;
603
+ if (!isAnthropicReplay) return false;
604
+ if (isLatestSurvivingAssistant && abandonedToolUse) return true;
605
+ const candidateSignatureUntrustworthy =
606
+ abandonedToolUse || (invalidStopReason && candidateIndex === lastBlockIndex);
607
+ const replaySignature =
608
+ candidateSignatureUntrustworthy && candidate.thinkingSignature ? undefined : candidate.thinkingSignature;
609
+ if (!replaySignature && (!candidate.thinking || candidate.thinking.trim() === "")) return false;
610
+ if (isSameModel && isSigningAnthropicTarget && (!replaySignature || replaySignature.trim() === "")) {
611
+ return false;
612
+ }
613
+ return true;
614
+ };
615
+ const hasVisibleAnthropicThinking = assistantMsg.content.some(candidate => candidate.type === "thinking");
616
+ const dropsAllSameModelVisibleThinking =
617
+ isAnthropicReplay &&
618
+ isSameModel &&
619
+ isSigningAnthropicTarget &&
620
+ hasVisibleAnthropicThinking &&
621
+ !assistantMsg.content.some(anthropicVisibleThinkingSurvivesReplay);
622
+
623
+ const transformedContent = assistantMsg.content.flatMap((block, blockIndex) => {
624
+ if (block.type === "thinking") {
625
+ // Only an aborted/errored turn's final (mid-stream) block can hold a
626
+ // partial signature; abandoned tool-use turns strip all. Drop the
627
+ // untrustworthy signature so the encoder can downgrade the block to text.
628
+ const signatureUntrustworthy = abandonedToolUse || (invalidStopReason && blockIndex === lastBlockIndex);
629
+ let sanitized: typeof block =
630
+ signatureUntrustworthy && block.thinkingSignature
631
+ ? { ...block, thinkingSignature: undefined }
632
+ : block;
633
+ if (isAnthropicReplay) {
634
+ // A signature is only replayable where its issuer can verify it.
635
+ // Same-provider replays (including cross-model-id switches within
636
+ // official Anthropic — pinned by the prefill suite) keep the
637
+ // latest turn byte-for-byte per Anthropic's rule for its own most
638
+ // recent response. A latest turn minted by a DIFFERENT provider
639
+ // is not "Anthropic's own response": its signature can never
640
+ // verify on a signing Anthropic target and wedges the session
641
+ // with `400 Invalid signature in thinking block` on every
642
+ // attempt until the poisoned turn ages out of the replay window
643
+ // (observed live: a kimi-code/k3 turn replayed to official
644
+ // Anthropic after a session-level model switch mid tool-loop).
645
+ const crossProviderSource = assistantMsg.provider !== model.provider;
646
+ // Latest abandoned turn: Anthropic's byte-for-byte rule forbids
647
+ // even stripping a signature on the latest message — but only
648
+ // for turns the target's own provider issued.
649
+ if (isLatestSurvivingAssistant && abandonedToolUse && !crossProviderSource) return block;
650
+ // Strip source signatures crossing an official Anthropic
651
+ // endpoint so the downstream encoder applies its
652
+ // `replayUnsignedThinking` policy (unsigned thinking is emitted
653
+ // natively on Anthropic-compatible reasoning endpoints and
654
+ // demoted to text on official Anthropic). Prior turns strip on
655
+ // any cross-model transition (#4297); the latest turn strips
656
+ // only on a cross-provider transition so same-provider
657
+ // continuations stay byte-for-byte. 3p ↔ 3p replays keep the
658
+ // signature so the reasoning chain stays signed on continuation
659
+ // (#2265).
660
+ const staleSignature = isLatestSurvivingAssistant ? crossProviderSource : !isSameModel;
661
+ if (staleSignature && signingAnthropicInvolved && sanitized.thinkingSignature) {
662
+ sanitized = { ...sanitized, thinkingSignature: undefined };
663
+ }
664
+ // Drop blocks with neither a signature anchor nor any text —
665
+ // nothing for the next turn to replay.
666
+ if (!sanitized.thinkingSignature && (!sanitized.thinking || sanitized.thinking.trim() === "")) {
667
+ return [];
668
+ }
669
+ // Same-model Anthropic replay to a signature-enforcing endpoint
670
+ // requires valid signatures to natively replay thinking blocks.
671
+ // Both undefined and empty string signatures are invalid and must
672
+ // be dropped entirely — not demoted to text. Demotion would cause
673
+ // the reasoning_extraction safety classifier to refuse the response.
674
+ if (
675
+ isSameModel &&
676
+ isSigningAnthropicTarget &&
677
+ (!sanitized.thinkingSignature || sanitized.thinkingSignature.trim() === "")
678
+ ) {
679
+ return [];
680
+ }
681
+ return sanitized;
682
+ }
683
+ // Cross-API target: same-model replay keeps signatures untouched
684
+ // (the encoder needs them for native replay; an OpenAI encrypted
685
+ // reasoning blob has empty text but a load-bearing signature).
686
+ if (isSameModel && sanitized.thinkingSignature) return sanitized;
687
+ // Nothing left for the next turn to replay: drop empty/no-anchor
688
+ // thinking blocks before the cross-model paths.
689
+ if (!sanitized.thinking || sanitized.thinking.trim() === "") return [];
690
+ if (isSameModel) return sanitized;
691
+ // Cross-model + cross-API: preserve native thinking only for
692
+ // targets proven to read unsigned foreign reasoning (Z.AI-format
693
+ // OpenAI-compatible targets, plus Anthropic-compatible
694
+ // `replayUnsignedThinking`). Tool-call schema requirements and
695
+ // llama.cpp cache-prefix replay are orthogonal encoder concerns;
696
+ // keeping inert foreign CoT native for those flags loses the
697
+ // canonical visible-text fallback without adding model context.
698
+ if (targetReadsForeignThinking(model, targetCompat)) {
699
+ return sanitized.thinkingSignature ? { ...sanitized, thinkingSignature: undefined } : sanitized;
700
+ }
701
+ // Other cross-API targets (openai-responses encrypted blobs, google
702
+ // thought parts, anthropic-target from a non-Anthropic source, or any
703
+ // reasoning-disabled target) can't replay an unsigned thinking block:
704
+ // the native reasoning slot either rejects a foreign signature or — as
705
+ // verified end-to-end against Gemini 3 — silently discards unsigned
706
+ // thought content (it is neither recalled nor influences generation).
707
+ // Demote to text so the reasoning survives as context, wrapped in the
708
+ // TARGET model's own canonical thinking-block dialect (e.g. a ```thinking
709
+ // fence for Gemini) so it reads as reasoning rather than bare prose the
710
+ // model might mimic.
711
+ // Mark the demoted block (symbol-keyed, never serialized) instead of
712
+ // baking a separator into its text: the openai-completions flatten —
713
+ // the one consumer that joins adjacent text blocks into a single
714
+ // string — inserts a paragraph break after marked blocks, so the
715
+ // bare Anthropic-dialect output (or any dialect's wrapped output
716
+ // whose closing tag isn't a natural word boundary) can't glue onto
717
+ // the following visible-text block, while ordinary adjacent text
718
+ // blocks stitched from streaming / bridges / imported transcripts
719
+ // stay byte-identical. A separator baked into the block text would
720
+ // leak to non-flattening targets: Anthropic/Bedrock reject a
721
+ // terminal assistant message whose text ends with whitespace.
722
+ return {
723
+ type: "text" as const,
724
+ text: renderDemotedThinking(model.id, sanitized.thinking),
725
+ [kDemotedThinking]: true,
726
+ };
727
+ }
728
+
729
+ if (block.type === "redactedThinking") {
730
+ // Redacted thinking is native-only. Keep it for same-model
731
+ // signed replay, for the latest byte-for-byte turn issued by the
732
+ // target's own provider, or for compatible targets that will
733
+ // also emit sibling unsigned thinking natively. Drop it when the
734
+ // matching visible thinking was discarded, or when visible
735
+ // thinking was stripped and will be demoted to text — a foreign
736
+ // redacted payload can no more verify on a signing target than a
737
+ // foreign visible signature can, even on the latest turn.
738
+ if (isAnthropicReplay) {
739
+ if (dropsAllSameModelVisibleThinking) return [];
740
+ if (
741
+ isSameModel ||
742
+ (isLatestSurvivingAssistant && assistantMsg.provider === model.provider) ||
743
+ replaysUnsignedAnthropicThinking
744
+ ) {
745
+ return block;
746
+ }
747
+ return [];
748
+ }
749
+ if (isSameModel) return block;
750
+ return [];
751
+ }
752
+
753
+ if (block.type === "anthropicServerTool") {
754
+ // Anthropic requires native server-tool calls and results to be
755
+ // replayed unchanged. They are meaningful only to the provider
756
+ // that produced them; every cross-provider target drops them.
757
+ if (isAnthropicReplay && assistantMsg.provider === model.provider) return block;
758
+ return [];
759
+ }
760
+
761
+ if (block.type === "fallback") {
762
+ // Server-side-fallback boundary marker (Anthropic beta
763
+ // `server-side-fallback-2026-06-01`). Only the official
764
+ // Anthropic endpoint accepts this block on replay: every
765
+ // other target either rejects unknown content blocks with a
766
+ // 400 (anthropic-compatible endpoints like Umans/Z.AI/MiniMax,
767
+ // and older omp gateways whose schema pre-dates this feature)
768
+ // or throws in its converter (Bedrock). Even the official
769
+ // replay path only accepts the block when the current request
770
+ // itself opts into the beta — but we don't know that here, so
771
+ // keep it and let `convertAnthropicMessages` re-check the
772
+ // per-request opt-in before serializing.
773
+ if (isAnthropicTarget && model.compat.officialEndpoint) return block;
774
+ return [];
775
+ }
776
+
777
+ if (block.type === "image") {
778
+ // Assistant images are display artifacts. No provider accepts them
779
+ // in an assistant replay turn; the native Responses result remains
780
+ // in providerPayload for OpenAI replay.
781
+ return [];
782
+ }
783
+
784
+ if (block.type === "text") {
785
+ if (isSameModel) return block;
786
+ return {
787
+ type: "text" as const,
788
+ text: block.text,
789
+ };
790
+ }
791
+
792
+ if (block.type === "toolCall") {
793
+ const toolCall = block as ToolCall;
794
+ let normalizedToolCall: ToolCall = toolCall;
795
+
796
+ if (!isSameModel && toolCall.thoughtSignature) {
797
+ normalizedToolCall = { ...toolCall, thoughtSignature: undefined };
798
+ }
799
+
800
+ if (isAnthropicTarget) {
801
+ const normalizedId = normalizeAnthropicTargetToolCallId(
802
+ toolCall.id,
803
+ model,
804
+ assistantMsg,
805
+ normalizeToolCallId,
806
+ );
807
+ if (normalizedId !== toolCall.id) {
808
+ toolCallIdMap.set(toolCall.id, normalizedId);
809
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
810
+ }
811
+ } else if (!isSameModel && normalizeToolCallId) {
812
+ const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
813
+ if (normalizedId !== toolCall.id) {
814
+ toolCallIdMap.set(toolCall.id, normalizedId);
815
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
816
+ }
817
+ }
818
+
819
+ return normalizedToolCall;
820
+ }
821
+
822
+ return block;
823
+ });
824
+
825
+ // A demoted-thinking block that survived as the message's final block can
826
+ // still end with the thinking text's own trailing whitespace (bare
827
+ // Anthropic-dialect demotion copies it verbatim), and Anthropic rejects a
828
+ // terminal assistant message whose text ends with trailing whitespace
829
+ // ("final assistant content cannot end with trailing whitespace").
830
+ // trimEnd() is safe: demoted text is synthesized context, never
831
+ // byte-exact replay material.
832
+ const finalBlock = transformedContent[transformedContent.length - 1];
833
+ if (finalBlock?.type === "text" && isDemotedThinking(finalBlock)) {
834
+ transformedContent[transformedContent.length - 1] = { ...finalBlock, text: finalBlock.text.trimEnd() };
835
+ }
836
+
837
+ return {
838
+ ...assistantMsg,
839
+ content: transformedContent,
840
+ };
841
+ }
842
+ return msg;
843
+ });
844
+ const transformed = deduplicateToolCallIds(
845
+ normalizedMessages,
846
+ maxNormalizedToolCallIdLength,
847
+ duplicateToolCallIdSuffixPrefix,
848
+ );
849
+ // All real tool results, keyed by id, in document order. One id can map to
850
+ // more than one result: compaction can fold an assistant `tool_use` into a
851
+ // summary string while its `tool_result` survives, and a later turn may reuse
852
+ // the id. `takeRealToolResult` pulls the earliest unconsumed result positioned
853
+ // AFTER the call's assistant turn, so an orphaned earlier result is never
854
+ // pulled forward onto a later call (which would surface a prior turn's output).
855
+ type IndexedToolResult = { index: number; msg: ToolResultMessage; consumed: boolean };
856
+ const realToolResultsById = new Map<string, IndexedToolResult[]>();
857
+ for (let index = 0; index < transformed.length; index++) {
858
+ const msg = transformed[index];
859
+ if (msg.role === "toolResult") {
860
+ const entry: IndexedToolResult = { index, msg, consumed: false };
861
+ const entries = realToolResultsById.get(msg.toolCallId);
862
+ if (entries) entries.push(entry);
863
+ else realToolResultsById.set(msg.toolCallId, [entry]);
864
+ }
865
+ }
866
+ const takeRealToolResult = (id: string, afterIndex: number): ToolResultMessage | undefined => {
867
+ const entries = realToolResultsById.get(id);
868
+ if (!entries) return undefined;
869
+ for (const entry of entries) {
870
+ if (entry.consumed || entry.index <= afterIndex) continue;
871
+ entry.consumed = true;
872
+ return entry.msg;
873
+ }
874
+ return undefined;
875
+ };
876
+
877
+ // Anthropic rejects `tool_result` blocks whose `tool_use_id` does not appear in a prior
878
+ // `tool_use` block. After handoff/compaction folds an assistant turn into a summary
879
+ // string, the user-side `toolResult` for that turn can survive while the originating
880
+ // `tool_use` disappears — leaving an orphan that triggers HTTP 400. Track the set of
881
+ // `tool_use` ids that survive transformation so the second pass can drop orphans cleanly.
882
+ const validToolUseIds = new Set<string>();
883
+ for (const msg of transformed) {
884
+ if (msg.role !== "assistant") continue;
885
+ for (const block of msg.content) {
886
+ if (block.type === "toolCall") validToolUseIds.add(block.id);
887
+ }
888
+ }
889
+
890
+ // Second pass: ensure each surviving assistant tool call is immediately
891
+ // followed by exactly one corresponding tool result.
892
+ const result: Message[] = [];
893
+ let pendingToolCalls: ToolCall[] = [];
894
+ // Index of the assistant turn that declared `pendingToolCalls`; a pulled
895
+ // result must be positioned after it (see `takeRealToolResult`).
896
+ let pendingToolCallsStartIndex = -1;
897
+ let pendingAbortedToolCalls = new Map<string, ToolCall>();
898
+ let pendingAbortedTimestamp: number | undefined;
899
+ let pendingAbortedStartIndex = -1;
900
+ // Track which tool calls already have an emitted result so delayed/duplicate
901
+ // toolResult messages cannot create a second provider-visible result.
902
+ const toolCallStatus = new Map<string, ToolCallStatus>();
903
+
904
+ const flushPendingToolCalls = (timestamp: number): void => {
905
+ if (pendingToolCalls.length === 0) return;
906
+ for (const tc of pendingToolCalls) {
907
+ if (toolCallStatus.has(tc.id)) continue;
908
+ const realToolResult = takeRealToolResult(tc.id, pendingToolCallsStartIndex);
909
+ if (realToolResult) {
910
+ result.push(realToolResult);
911
+ toolCallStatus.set(tc.id, ToolCallStatus.Resolved);
912
+ continue;
913
+ }
914
+ result.push({
915
+ role: "toolResult",
916
+ toolCallId: tc.id,
917
+ toolName: tc.name,
918
+ content: [{ type: "text", text: "No result provided" }],
919
+ isError: true,
920
+ timestamp,
921
+ } as ToolResultMessage);
922
+ toolCallStatus.set(tc.id, ToolCallStatus.Resolved);
923
+ }
924
+ pendingToolCalls = [];
925
+ };
926
+
927
+ const flushPendingAbortedToolCalls = (): void => {
928
+ if (pendingAbortedTimestamp === undefined) return;
929
+ for (const tc of pendingAbortedToolCalls.values()) {
930
+ if (toolCallStatus.has(tc.id)) continue;
931
+ const realToolResult = takeRealToolResult(tc.id, pendingAbortedStartIndex);
932
+ if (realToolResult) {
933
+ result.push(realToolResult);
934
+ toolCallStatus.set(tc.id, ToolCallStatus.Resolved);
935
+ continue;
936
+ }
937
+ result.push({
938
+ role: "toolResult",
939
+ toolCallId: tc.id,
940
+ toolName: tc.name,
941
+ content: [{ type: "text", text: "aborted" }],
942
+ isError: true,
943
+ timestamp: pendingAbortedTimestamp,
944
+ } as ToolResultMessage);
945
+ toolCallStatus.set(tc.id, ToolCallStatus.Aborted);
946
+ }
947
+ pendingAbortedToolCalls = new Map();
948
+ pendingAbortedTimestamp = undefined;
949
+ };
950
+
951
+ for (let i = 0; i < transformed.length; i++) {
952
+ const msg = transformed[i];
953
+ const messageTimestamp = "timestamp" in msg && typeof msg.timestamp === "number" ? msg.timestamp : Date.now();
954
+
955
+ if (msg.role === "assistant") {
956
+ flushPendingToolCalls(messageTimestamp);
957
+ flushPendingAbortedToolCalls();
958
+
959
+ const assistantMsg = msg as AssistantMessage;
960
+
961
+ // Drop assistant turns that carry no actionable content (no `text`, no `toolCall`)
962
+ // AND were terminated by a truncating stop reason (`length` / `error` / `aborted`).
963
+ // These are produced when the provider returns `stop_reason: "max_tokens"` (or a
964
+ // stream error) mid-thinking, leaving a `[thinking]`-only message with a valid
965
+ // signature but nothing for the next turn to anchor on. Keeping it creates
966
+ // back-to-back assistant turns once the next response lands, which Anthropic
967
+ // rejects with "messages.X.content.Y: `thinking` blocks in the latest assistant
968
+ // message cannot be modified".
969
+ //
970
+ // `stopReason: "stop"` thinking-only messages are intentionally preserved: they
971
+ // represent reasoning-only assistant turns used for replay round-trips
972
+ // (OpenAI completions `reasoning_text`, Google signed thought parts).
973
+ const originalMsg = messages[i]!;
974
+ if (originalMsg.role === "assistant" && shouldDropTruncatedThinkingOnlyAssistant(originalMsg)) {
975
+ continue;
976
+ }
977
+
978
+ const toolCalls = assistantMsg.content.filter(b => b.type === "toolCall") as ToolCall[];
979
+
980
+ if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
981
+ // Keep the assistant message with tool calls intact. Real tool results are
982
+ // emitted immediately if available; otherwise synthesize aborted results
983
+ // before the next turn boundary.
984
+ result.push(msg);
985
+ pendingAbortedToolCalls = new Map(toolCalls.map(toolCall => [toolCall.id, toolCall] as const));
986
+ pendingAbortedTimestamp = assistantMsg.timestamp;
987
+ pendingAbortedStartIndex = i;
988
+ continue;
989
+ }
990
+
991
+ if (toolCalls.length > 0) {
992
+ pendingToolCalls = toolCalls;
993
+ pendingToolCallsStartIndex = i;
994
+ }
995
+
996
+ result.push(msg);
997
+ } else if (msg.role === "toolResult") {
998
+ if (toolCallStatus.has(msg.toolCallId)) continue;
999
+
1000
+ if (pendingAbortedToolCalls.has(msg.toolCallId)) {
1001
+ pendingAbortedToolCalls.delete(msg.toolCallId);
1002
+ toolCallStatus.set(msg.toolCallId, ToolCallStatus.Resolved);
1003
+ result.push(msg);
1004
+ continue;
1005
+ }
1006
+
1007
+ if (pendingToolCalls.some(tc => tc.id === msg.toolCallId)) {
1008
+ toolCallStatus.set(msg.toolCallId, ToolCallStatus.Resolved);
1009
+ result.push(msg);
1010
+ continue;
1011
+ }
1012
+
1013
+ if (!validToolUseIds.has(msg.toolCallId)) {
1014
+ // Orphan `tool_result`: the originating `tool_use` is not present in the
1015
+ // transformed history (typically because handoff/compaction folded the
1016
+ // assistant message into a summary string while the user-side result
1017
+ // survived). Sending the block as-is would 400 the request, so it must
1018
+ // be dropped.
1019
+ //
1020
+ // If a pending tool-call window is still open (either normal or
1021
+ // aborted), the orphan cannot be replaced with a developer note here:
1022
+ //
1023
+ // * Anthropic requires the next message after an assistant `tool_use`
1024
+ // to be the matching `tool_result`. Inserting a developer message
1025
+ // would break that contiguity.
1026
+ // * Flushing pending aborted calls here would wedge synthetic results
1027
+ // between the assistant turn and a real result that may still arrive
1028
+ // inside the current contiguous result window.
1029
+ //
1030
+ // Drop the orphan silently in that case; the pending calls will be
1031
+ // resolved in their own contiguous result window or at the next boundary.
1032
+ if (pendingToolCalls.some(tc => !toolCallStatus.has(tc.id)) || pendingAbortedToolCalls.size > 0) {
1033
+ continue;
1034
+ }
1035
+ // No pending tool-call window: safe to preserve the text payload so the
1036
+ // model still sees what the tool returned.
1037
+ //
1038
+ // The note is emitted with `role: "user"` rather than `role: "developer"`
1039
+ // because the developer role is elevated by some providers:
1040
+ //
1041
+ // * Ollama maps `developer` -> `system` (highest instruction priority).
1042
+ // * OpenAI chat-completions reasoning models forward `developer` as
1043
+ // `developer` (above-user instruction priority).
1044
+ //
1045
+ // Stale, model-untrusted tool output must not gain instruction priority
1046
+ // above user/developer messages it lived alongside before compaction.
1047
+ // `user` role is mapped to plain user content by every provider, so the
1048
+ // content survives without ever being treated as an instruction the
1049
+ // model should obey.
1050
+ const textParts: string[] = [];
1051
+ for (const part of msg.content) {
1052
+ if (part.type === "text" && part.text.trim() !== "") textParts.push(part.text);
1053
+ }
1054
+ if (textParts.length > 0) {
1055
+ const errorAttr = msg.isError ? ' is-error="true"' : "";
1056
+ result.push({
1057
+ role: "user",
1058
+ content: `<stale-tool-result tool="${msg.toolName}" id="${msg.toolCallId}"${errorAttr}>\n${textParts.join("\n")}\n</stale-tool-result>`,
1059
+ timestamp: messageTimestamp,
1060
+ } as UserMessage);
1061
+ }
1062
+ }
1063
+
1064
+ // The matching tool_use exists elsewhere, but this result is not in
1065
+ // the currently open result window. Emitting it here would break the
1066
+ // provider invariant; the first real result is pulled into the correct
1067
+ // slot by the pending-call flush instead.
1068
+ } else if (msg.role === "user" || msg.role === "developer") {
1069
+ flushPendingToolCalls(messageTimestamp);
1070
+ flushPendingAbortedToolCalls();
1071
+ result.push(msg);
1072
+ } else {
1073
+ flushPendingToolCalls(messageTimestamp);
1074
+ flushPendingAbortedToolCalls();
1075
+ result.push(msg);
1076
+ }
1077
+ }
1078
+
1079
+ flushPendingToolCalls(Date.now());
1080
+ flushPendingAbortedToolCalls();
1081
+
1082
+ return result;
1083
+ }