@fractaal/pi-ai 0.86.9 → 1.6.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 (411) hide show
  1. package/README.md +154 -78
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +380 -144
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  6. package/dist/api/azure-openai-responses.js +9 -11
  7. package/dist/api/azure-openai-responses.js.map +1 -1
  8. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  9. package/dist/api/bedrock-converse-stream.js +184 -121
  10. package/dist/api/bedrock-converse-stream.js.map +1 -1
  11. package/dist/api/bedrock-converse-stream.lazy.d.ts +7 -0
  12. package/dist/api/bedrock-converse-stream.lazy.d.ts.map +1 -1
  13. package/dist/api/bedrock-converse-stream.lazy.js +18 -5
  14. package/dist/api/bedrock-converse-stream.lazy.js.map +1 -1
  15. package/dist/api/classifier-shared.d.ts +21 -0
  16. package/dist/api/classifier-shared.d.ts.map +1 -0
  17. package/dist/api/classifier-shared.js +99 -0
  18. package/dist/api/classifier-shared.js.map +1 -0
  19. package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -1
  20. package/dist/api/cloudflare-workers-ai-system-one.js +10 -1
  21. package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -1
  22. package/dist/api/constrained-sampling.d.ts +15 -1
  23. package/dist/api/constrained-sampling.d.ts.map +1 -1
  24. package/dist/api/constrained-sampling.js +51 -18
  25. package/dist/api/constrained-sampling.js.map +1 -1
  26. package/dist/api/google-generative-ai.d.ts.map +1 -1
  27. package/dist/api/google-generative-ai.js +4 -10
  28. package/dist/api/google-generative-ai.js.map +1 -1
  29. package/dist/api/google-shared.d.ts +2 -1
  30. package/dist/api/google-shared.d.ts.map +1 -1
  31. package/dist/api/google-shared.js +60 -3
  32. package/dist/api/google-shared.js.map +1 -1
  33. package/dist/api/google-vertex.d.ts.map +1 -1
  34. package/dist/api/google-vertex.js +9 -15
  35. package/dist/api/google-vertex.js.map +1 -1
  36. package/dist/api/lazy.d.ts +7 -2
  37. package/dist/api/lazy.d.ts.map +1 -1
  38. package/dist/api/lazy.js +33 -12
  39. package/dist/api/lazy.js.map +1 -1
  40. package/dist/api/llama-cpp-classify.d.ts.map +1 -1
  41. package/dist/api/llama-cpp-classify.js +4 -1
  42. package/dist/api/llama-cpp-classify.js.map +1 -1
  43. package/dist/api/mistral-conversations.d.ts.map +1 -1
  44. package/dist/api/mistral-conversations.js +50 -60
  45. package/dist/api/mistral-conversations.js.map +1 -1
  46. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  47. package/dist/api/openai-codex-responses.js +30 -17
  48. package/dist/api/openai-codex-responses.js.map +1 -1
  49. package/dist/api/openai-completions.d.ts +2 -1
  50. package/dist/api/openai-completions.d.ts.map +1 -1
  51. package/dist/api/openai-completions.js +93 -90
  52. package/dist/api/openai-completions.js.map +1 -1
  53. package/dist/api/openai-decisions.d.ts +4 -0
  54. package/dist/api/openai-decisions.d.ts.map +1 -0
  55. package/dist/api/openai-decisions.js +162 -0
  56. package/dist/api/openai-decisions.js.map +1 -0
  57. package/dist/api/openai-decisions.lazy.d.ts +3 -0
  58. package/dist/api/openai-decisions.lazy.d.ts.map +1 -0
  59. package/dist/api/openai-decisions.lazy.js +4 -0
  60. package/dist/api/openai-decisions.lazy.js.map +1 -0
  61. package/dist/api/openai-responses-shared.d.ts +26 -0
  62. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  63. package/dist/api/openai-responses-shared.js +132 -66
  64. package/dist/api/openai-responses-shared.js.map +1 -1
  65. package/dist/api/openai-responses.d.ts.map +1 -1
  66. package/dist/api/openai-responses.js +42 -21
  67. package/dist/api/openai-responses.js.map +1 -1
  68. package/dist/api/openrouter-images.d.ts.map +1 -1
  69. package/dist/api/openrouter-images.js +5 -3
  70. package/dist/api/openrouter-images.js.map +1 -1
  71. package/dist/api/pi-messages.d.ts +2 -0
  72. package/dist/api/pi-messages.d.ts.map +1 -1
  73. package/dist/api/pi-messages.js +27 -12
  74. package/dist/api/pi-messages.js.map +1 -1
  75. package/dist/api/simple-options.d.ts +1 -0
  76. package/dist/api/simple-options.d.ts.map +1 -1
  77. package/dist/api/simple-options.js +18 -3
  78. package/dist/api/simple-options.js.map +1 -1
  79. package/dist/api/system-one-shared.d.ts +29 -3
  80. package/dist/api/system-one-shared.d.ts.map +1 -1
  81. package/dist/api/system-one-shared.js +54 -86
  82. package/dist/api/system-one-shared.js.map +1 -1
  83. package/dist/api/transform-messages.d.ts +16 -1
  84. package/dist/api/transform-messages.d.ts.map +1 -1
  85. package/dist/api/transform-messages.js +28 -15
  86. package/dist/api/transform-messages.js.map +1 -1
  87. package/dist/api/typesafe-system-one.d.ts.map +1 -1
  88. package/dist/api/typesafe-system-one.js +12 -1
  89. package/dist/api/typesafe-system-one.js.map +1 -1
  90. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  91. package/dist/auth/oauth/anthropic.js +14 -6
  92. package/dist/auth/oauth/anthropic.js.map +1 -1
  93. package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  94. package/dist/auth/oauth/github-copilot.js +4 -11
  95. package/dist/auth/oauth/github-copilot.js.map +1 -1
  96. package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  97. package/dist/auth/oauth/kimi-coding.js +4 -22
  98. package/dist/auth/oauth/kimi-coding.js.map +1 -1
  99. package/dist/auth/oauth/load.d.ts.map +1 -1
  100. package/dist/auth/oauth/load.js +5 -2
  101. package/dist/auth/oauth/load.js.map +1 -1
  102. package/dist/auth/oauth/meta.d.ts.map +1 -1
  103. package/dist/auth/oauth/meta.js +1 -14
  104. package/dist/auth/oauth/meta.js.map +1 -1
  105. package/dist/auth/oauth/openai-chatgpt.js +1 -1
  106. package/dist/auth/oauth/openai-chatgpt.js.map +1 -1
  107. package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  108. package/dist/auth/oauth/openai-codex.js +4 -4
  109. package/dist/auth/oauth/openai-codex.js.map +1 -1
  110. package/dist/auth/oauth/radius.d.ts.map +1 -1
  111. package/dist/auth/oauth/radius.js +9 -3
  112. package/dist/auth/oauth/radius.js.map +1 -1
  113. package/dist/auth/oauth/trusted-url.d.ts +7 -0
  114. package/dist/auth/oauth/trusted-url.d.ts.map +1 -0
  115. package/dist/auth/oauth/trusted-url.js +12 -0
  116. package/dist/auth/oauth/trusted-url.js.map +1 -0
  117. package/dist/auth/types.d.ts +9 -0
  118. package/dist/auth/types.d.ts.map +1 -1
  119. package/dist/auth/types.js.map +1 -1
  120. package/dist/cli.js +6 -2
  121. package/dist/cli.js.map +1 -1
  122. package/dist/compat/extension-oauth-types.d.ts +4 -0
  123. package/dist/compat/extension-oauth-types.d.ts.map +1 -1
  124. package/dist/compat/extension-oauth-types.js.map +1 -1
  125. package/dist/env-api-keys.d.ts.map +1 -1
  126. package/dist/env-api-keys.js +1 -0
  127. package/dist/env-api-keys.js.map +1 -1
  128. package/dist/index.d.ts +4 -4
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +4 -3
  131. package/dist/index.js.map +1 -1
  132. package/dist/lazy-bedrock-provider.d.ts +10 -0
  133. package/dist/lazy-bedrock-provider.d.ts.map +1 -0
  134. package/dist/lazy-bedrock-provider.js +13 -0
  135. package/dist/lazy-bedrock-provider.js.map +1 -0
  136. package/dist/lazy-oauth.d.ts +10 -0
  137. package/dist/lazy-oauth.d.ts.map +1 -0
  138. package/dist/lazy-oauth.js +23 -0
  139. package/dist/lazy-oauth.js.map +1 -0
  140. package/dist/models.d.ts +9 -0
  141. package/dist/models.d.ts.map +1 -1
  142. package/dist/models.generated.d.ts +4 -0
  143. package/dist/models.generated.d.ts.map +1 -1
  144. package/dist/models.generated.js +5 -1
  145. package/dist/models.generated.js.map +1 -1
  146. package/dist/models.js +28 -12
  147. package/dist/models.js.map +1 -1
  148. package/dist/providers/all.d.ts.map +1 -1
  149. package/dist/providers/all.js +2 -0
  150. package/dist/providers/all.js.map +1 -1
  151. package/dist/providers/amazon-bedrock.models.js +1 -1
  152. package/dist/providers/amazon-bedrock.models.js.map +1 -1
  153. package/dist/providers/ant-ling.models.js +1 -1
  154. package/dist/providers/ant-ling.models.js.map +1 -1
  155. package/dist/providers/anthropic.models.js +1 -1
  156. package/dist/providers/anthropic.models.js.map +1 -1
  157. package/dist/providers/azure.models.js +1 -1
  158. package/dist/providers/azure.models.js.map +1 -1
  159. package/dist/providers/baseten.models.js +1 -1
  160. package/dist/providers/baseten.models.js.map +1 -1
  161. package/dist/providers/cerebras.models.js +1 -1
  162. package/dist/providers/cerebras.models.js.map +1 -1
  163. package/dist/providers/cloudflare-ai-gateway.models.js +1 -1
  164. package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  165. package/dist/providers/cloudflare-workers-ai.models.js +1 -1
  166. package/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
  167. package/dist/providers/compat-schema.d.ts +494 -0
  168. package/dist/providers/compat-schema.d.ts.map +1 -0
  169. package/dist/providers/compat-schema.js +447 -0
  170. package/dist/providers/compat-schema.js.map +1 -0
  171. package/dist/providers/data/.manifest.json +1 -1
  172. package/dist/providers/data/amazon-bedrock.json +1 -1
  173. package/dist/providers/data/anthropic.json +1 -1
  174. package/dist/providers/data/azure.json +1 -1
  175. package/dist/providers/data/baseten.json +1 -1
  176. package/dist/providers/data/cerebras.json +1 -1
  177. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  178. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  179. package/dist/providers/data/fireworks.json +1 -1
  180. package/dist/providers/data/github-copilot.json +1 -1
  181. package/dist/providers/data/google-vertex.json +1 -1
  182. package/dist/providers/data/google.json +1 -1
  183. package/dist/providers/data/groq.json +1 -1
  184. package/dist/providers/data/huggingface.json +1 -1
  185. package/dist/providers/data/kimi-coding.json +1 -1
  186. package/dist/providers/data/meta.json +1 -1
  187. package/dist/providers/data/minimax-cn.json +1 -1
  188. package/dist/providers/data/minimax.json +1 -1
  189. package/dist/providers/data/mistral.json +1 -1
  190. package/dist/providers/data/moonshotai-cn.json +1 -1
  191. package/dist/providers/data/moonshotai.json +1 -1
  192. package/dist/providers/data/nvidia.json +1 -1
  193. package/dist/providers/data/openai-codex.json +1 -1
  194. package/dist/providers/data/openai.json +1 -1
  195. package/dist/providers/data/opencode-go.json +1 -1
  196. package/dist/providers/data/opencode.json +1 -1
  197. package/dist/providers/data/openrouter.json +1 -1
  198. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  199. package/dist/providers/data/qwen-token-plan-individual-cn.json +1 -0
  200. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  201. package/dist/providers/data/qwen-token-plan.json +1 -1
  202. package/dist/providers/data/radius.json +1 -1
  203. package/dist/providers/data/together.json +1 -1
  204. package/dist/providers/data/typesafe.json +1 -1
  205. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  206. package/dist/providers/data/xai.json +1 -1
  207. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  208. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  209. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  210. package/dist/providers/data/xiaomi.json +1 -1
  211. package/dist/providers/data/zai-coding-cn.json +1 -1
  212. package/dist/providers/data/zai.json +1 -1
  213. package/dist/providers/deepseek.models.js +1 -1
  214. package/dist/providers/deepseek.models.js.map +1 -1
  215. package/dist/providers/faux.d.ts.map +1 -1
  216. package/dist/providers/faux.js +31 -9
  217. package/dist/providers/faux.js.map +1 -1
  218. package/dist/providers/fireworks.models.js +1 -1
  219. package/dist/providers/fireworks.models.js.map +1 -1
  220. package/dist/providers/github-copilot.d.ts +3 -1
  221. package/dist/providers/github-copilot.d.ts.map +1 -1
  222. package/dist/providers/github-copilot.js +13 -2
  223. package/dist/providers/github-copilot.js.map +1 -1
  224. package/dist/providers/github-copilot.models.js +1 -1
  225. package/dist/providers/github-copilot.models.js.map +1 -1
  226. package/dist/providers/google-vertex.models.js +1 -1
  227. package/dist/providers/google-vertex.models.js.map +1 -1
  228. package/dist/providers/google.models.js +1 -1
  229. package/dist/providers/google.models.js.map +1 -1
  230. package/dist/providers/groq.models.js +1 -1
  231. package/dist/providers/groq.models.js.map +1 -1
  232. package/dist/providers/huggingface.models.js +1 -1
  233. package/dist/providers/huggingface.models.js.map +1 -1
  234. package/dist/providers/kimi-coding.d.ts +1 -1
  235. package/dist/providers/kimi-coding.js +3 -3
  236. package/dist/providers/kimi-coding.js.map +1 -1
  237. package/dist/providers/kimi-coding.models.js +1 -1
  238. package/dist/providers/kimi-coding.models.js.map +1 -1
  239. package/dist/providers/meta.models.js +1 -1
  240. package/dist/providers/meta.models.js.map +1 -1
  241. package/dist/providers/minimax-cn.models.js +1 -1
  242. package/dist/providers/minimax-cn.models.js.map +1 -1
  243. package/dist/providers/minimax.models.js +1 -1
  244. package/dist/providers/minimax.models.js.map +1 -1
  245. package/dist/providers/mistral.models.js +1 -1
  246. package/dist/providers/mistral.models.js.map +1 -1
  247. package/dist/providers/model-schema.d.ts +91 -0
  248. package/dist/providers/model-schema.d.ts.map +1 -0
  249. package/dist/providers/model-schema.js +68 -0
  250. package/dist/providers/model-schema.js.map +1 -0
  251. package/dist/providers/moonshotai-cn.models.js +1 -1
  252. package/dist/providers/moonshotai-cn.models.js.map +1 -1
  253. package/dist/providers/moonshotai.models.js +1 -1
  254. package/dist/providers/moonshotai.models.js.map +1 -1
  255. package/dist/providers/nvidia.models.js +1 -1
  256. package/dist/providers/nvidia.models.js.map +1 -1
  257. package/dist/providers/openai-codex-catalog.d.ts.map +1 -1
  258. package/dist/providers/openai-codex-catalog.js +44 -44
  259. package/dist/providers/openai-codex-catalog.js.map +1 -1
  260. package/dist/providers/openai-codex.models.js +1 -1
  261. package/dist/providers/openai-codex.models.js.map +1 -1
  262. package/dist/providers/openai.d.ts.map +1 -1
  263. package/dist/providers/openai.js +12 -3
  264. package/dist/providers/openai.js.map +1 -1
  265. package/dist/providers/openai.models.js +1 -1
  266. package/dist/providers/openai.models.js.map +1 -1
  267. package/dist/providers/opencode-go.d.ts +3 -1
  268. package/dist/providers/opencode-go.d.ts.map +1 -1
  269. package/dist/providers/opencode-go.js +2 -0
  270. package/dist/providers/opencode-go.js.map +1 -1
  271. package/dist/providers/opencode-go.models.js +1 -1
  272. package/dist/providers/opencode-go.models.js.map +1 -1
  273. package/dist/providers/opencode.js +1 -1
  274. package/dist/providers/opencode.js.map +1 -1
  275. package/dist/providers/opencode.models.js +1 -1
  276. package/dist/providers/opencode.models.js.map +1 -1
  277. package/dist/providers/openrouter-api.d.ts +40 -0
  278. package/dist/providers/openrouter-api.d.ts.map +1 -0
  279. package/dist/providers/openrouter-api.js +41 -0
  280. package/dist/providers/openrouter-api.js.map +1 -0
  281. package/dist/providers/openrouter.d.ts +11 -1
  282. package/dist/providers/openrouter.d.ts.map +1 -1
  283. package/dist/providers/openrouter.js +52 -1
  284. package/dist/providers/openrouter.js.map +1 -1
  285. package/dist/providers/openrouter.models.js +1 -1
  286. package/dist/providers/openrouter.models.js.map +1 -1
  287. package/dist/providers/qwen-token-plan-cn.models.js +1 -1
  288. package/dist/providers/qwen-token-plan-cn.models.js.map +1 -1
  289. package/dist/providers/qwen-token-plan-individual-cn.d.ts +3 -0
  290. package/dist/providers/qwen-token-plan-individual-cn.d.ts.map +1 -0
  291. package/dist/providers/qwen-token-plan-individual-cn.js +15 -0
  292. package/dist/providers/qwen-token-plan-individual-cn.js.map +1 -0
  293. package/dist/providers/qwen-token-plan-individual-cn.models.d.ts +6 -0
  294. package/dist/providers/qwen-token-plan-individual-cn.models.d.ts.map +1 -0
  295. package/dist/providers/qwen-token-plan-individual-cn.models.js +8 -0
  296. package/dist/providers/qwen-token-plan-individual-cn.models.js.map +1 -0
  297. package/dist/providers/qwen-token-plan-individual.models.js +1 -1
  298. package/dist/providers/qwen-token-plan-individual.models.js.map +1 -1
  299. package/dist/providers/qwen-token-plan.models.js +1 -1
  300. package/dist/providers/qwen-token-plan.models.js.map +1 -1
  301. package/dist/providers/radius-config.d.ts.map +1 -1
  302. package/dist/providers/radius-config.js +2 -1
  303. package/dist/providers/radius-config.js.map +1 -1
  304. package/dist/providers/radius.d.ts.map +1 -1
  305. package/dist/providers/radius.js +4 -12
  306. package/dist/providers/radius.js.map +1 -1
  307. package/dist/providers/radius.models.js +1 -1
  308. package/dist/providers/radius.models.js.map +1 -1
  309. package/dist/providers/schema-builders.d.ts +10 -0
  310. package/dist/providers/schema-builders.d.ts.map +1 -0
  311. package/dist/providers/schema-builders.js +18 -0
  312. package/dist/providers/schema-builders.js.map +1 -0
  313. package/dist/providers/together.models.js +1 -1
  314. package/dist/providers/together.models.js.map +1 -1
  315. package/dist/providers/typesafe.models.js +1 -1
  316. package/dist/providers/typesafe.models.js.map +1 -1
  317. package/dist/providers/vercel-ai-gateway.models.js +1 -1
  318. package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  319. package/dist/providers/xai.models.js +1 -1
  320. package/dist/providers/xai.models.js.map +1 -1
  321. package/dist/providers/xiaomi-token-plan-ams.models.js +1 -1
  322. package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  323. package/dist/providers/xiaomi-token-plan-cn.models.js +1 -1
  324. package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  325. package/dist/providers/xiaomi-token-plan-sgp.models.js +1 -1
  326. package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  327. package/dist/providers/xiaomi.models.js +1 -1
  328. package/dist/providers/xiaomi.models.js.map +1 -1
  329. package/dist/providers/zai-coding-cn.models.js +1 -1
  330. package/dist/providers/zai-coding-cn.models.js.map +1 -1
  331. package/dist/providers/zai.models.js +1 -1
  332. package/dist/providers/zai.models.js.map +1 -1
  333. package/dist/telemetry.d.ts +288 -0
  334. package/dist/telemetry.d.ts.map +1 -0
  335. package/dist/telemetry.js +192 -0
  336. package/dist/telemetry.js.map +1 -0
  337. package/dist/types.d.ts +57 -310
  338. package/dist/types.d.ts.map +1 -1
  339. package/dist/types.js.map +1 -1
  340. package/dist/utils/error-body.d.ts +8 -0
  341. package/dist/utils/error-body.d.ts.map +1 -1
  342. package/dist/utils/error-body.js +30 -2
  343. package/dist/utils/error-body.js.map +1 -1
  344. package/dist/utils/estimate.js +1 -1
  345. package/dist/utils/estimate.js.map +1 -1
  346. package/dist/utils/event-stream.d.ts +10 -1
  347. package/dist/utils/event-stream.d.ts.map +1 -1
  348. package/dist/utils/event-stream.js +25 -0
  349. package/dist/utils/event-stream.js.map +1 -1
  350. package/dist/utils/identity.d.ts +65 -0
  351. package/dist/utils/identity.d.ts.map +1 -0
  352. package/dist/utils/identity.js +141 -0
  353. package/dist/utils/identity.js.map +1 -0
  354. package/dist/utils/json-parse.d.ts +10 -0
  355. package/dist/utils/json-parse.d.ts.map +1 -1
  356. package/dist/utils/json-parse.js +26 -1
  357. package/dist/utils/json-parse.js.map +1 -1
  358. package/dist/utils/model-operations.d.ts +3 -1
  359. package/dist/utils/model-operations.d.ts.map +1 -1
  360. package/dist/utils/model-operations.js +6 -0
  361. package/dist/utils/model-operations.js.map +1 -1
  362. package/dist/utils/openrouter-cost.d.ts +24 -0
  363. package/dist/utils/openrouter-cost.d.ts.map +1 -0
  364. package/dist/utils/openrouter-cost.js +34 -0
  365. package/dist/utils/openrouter-cost.js.map +1 -0
  366. package/dist/utils/overflow.d.ts +1 -0
  367. package/dist/utils/overflow.d.ts.map +1 -1
  368. package/dist/utils/overflow.js +8 -1
  369. package/dist/utils/overflow.js.map +1 -1
  370. package/dist/utils/provider-retry.d.ts +2 -0
  371. package/dist/utils/provider-retry.d.ts.map +1 -1
  372. package/dist/utils/provider-retry.js +2 -0
  373. package/dist/utils/provider-retry.js.map +1 -1
  374. package/dist/utils/retry.d.ts.map +1 -1
  375. package/dist/utils/retry.js +29 -10
  376. package/dist/utils/retry.js.map +1 -1
  377. package/dist/utils/streaming-json.d.ts +84 -0
  378. package/dist/utils/streaming-json.d.ts.map +1 -0
  379. package/dist/utils/streaming-json.js +513 -0
  380. package/dist/utils/streaming-json.js.map +1 -0
  381. package/dist/utils/{typebox-helpers.d.ts → string-enum.d.ts} +5 -5
  382. package/dist/utils/string-enum.d.ts.map +1 -0
  383. package/dist/utils/{typebox-helpers.js → string-enum.js} +5 -6
  384. package/dist/utils/string-enum.js.map +1 -0
  385. package/dist/utils/text.d.ts +7 -0
  386. package/dist/utils/text.d.ts.map +1 -1
  387. package/dist/utils/text.js +9 -0
  388. package/dist/utils/text.js.map +1 -1
  389. package/dist/utils/transcript.d.ts +23 -9
  390. package/dist/utils/transcript.d.ts.map +1 -1
  391. package/dist/utils/transcript.js +30 -19
  392. package/dist/utils/transcript.js.map +1 -1
  393. package/dist/utils/validation.d.ts +5 -3
  394. package/dist/utils/validation.d.ts.map +1 -1
  395. package/dist/utils/validation.js +27 -445
  396. package/dist/utils/validation.js.map +1 -1
  397. package/dist/utils/version.d.ts +2 -0
  398. package/dist/utils/version.d.ts.map +1 -0
  399. package/dist/utils/version.js +3 -0
  400. package/dist/utils/version.js.map +1 -0
  401. package/package.json +19 -10
  402. package/dist/utils/assistant-message-frame.d.ts +0 -77
  403. package/dist/utils/assistant-message-frame.d.ts.map +0 -1
  404. package/dist/utils/assistant-message-frame.js +0 -425
  405. package/dist/utils/assistant-message-frame.js.map +0 -1
  406. package/dist/utils/pi-user-agent.d.ts +0 -2
  407. package/dist/utils/pi-user-agent.d.ts.map +0 -1
  408. package/dist/utils/pi-user-agent.js +0 -12
  409. package/dist/utils/pi-user-agent.js.map +0 -1
  410. package/dist/utils/typebox-helpers.d.ts.map +0 -1
  411. package/dist/utils/typebox-helpers.js.map +0 -1
@@ -1,20 +1,25 @@
1
1
  import Anthropic, {} from "@anthropic-ai/sdk";
2
2
  import { ANTHROPIC_FEDERATION_RULE_ID_ENV, ANTHROPIC_IDENTITY_TOKEN_FILE_ENV, ANTHROPIC_ORGANIZATION_ID_ENV, ANTHROPIC_SERVICE_ACCOUNT_ID_ENV, ANTHROPIC_WORKSPACE_ID_ENV, } from "../env-api-keys.js";
3
3
  import { calculateCost } from "../models.js";
4
+ import { registerSessionResourceCleanup } from "../session-resources.js";
4
5
  import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
6
+ import { withCauseDetail } from "../utils/error-body.js";
5
7
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
8
+ import { shortHash } from "../utils/hash.js";
6
9
  import { headersToRecord } from "../utils/headers.js";
7
- import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
8
- import { getPiUserAgent } from "../utils/pi-user-agent.js";
10
+ import { getIdentityHeaders } from "../utils/identity.js";
11
+ import { parseJsonWithRepair } from "../utils/json-parse.js";
12
+ import { applyOpenRouterReportedCost, isOpenRouterModel, } from "../utils/openrouter-cost.js";
9
13
  import { getProviderEnvValue } from "../utils/provider-env.js";
10
14
  import { retryProviderRequest } from "../utils/provider-retry.js";
11
15
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
16
+ import { ToolCallArgumentsStream } from "../utils/streaming-json.js";
12
17
  import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
13
18
  import { getCurrentTools, getInitialSystemMessage, resolveTranscript, } from "../utils/transcript.js";
14
- import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
19
+ import { getJsonSchemaToolParameters, isStrictToolLimitErrorMessage, prefersJsonSchemaStrictSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
15
20
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
16
21
  import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
17
- import { transformMessages } from "./transform-messages.js";
22
+ import { splitErrorToolResultImages, transformMessages } from "./transform-messages.js";
18
23
  /**
19
24
  * Resolve cache retention preference.
20
25
  * Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
@@ -75,6 +80,16 @@ const fromClaudeCodeName = (name, tools) => {
75
80
  }
76
81
  return name;
77
82
  };
83
+ function convertImageBlock(image) {
84
+ return {
85
+ type: "image",
86
+ source: {
87
+ type: "base64",
88
+ media_type: image.mimeType,
89
+ data: image.data,
90
+ },
91
+ };
92
+ }
78
93
  /**
79
94
  * Convert content blocks to Anthropic API format
80
95
  */
@@ -92,14 +107,7 @@ function convertContentBlocks(content) {
92
107
  text: sanitizeSurrogates(block.text),
93
108
  };
94
109
  }
95
- return {
96
- type: "image",
97
- source: {
98
- type: "base64",
99
- media_type: block.mimeType,
100
- data: block.data,
101
- },
102
- };
110
+ return convertImageBlock(block);
103
111
  });
104
112
  // If only images (no text), add placeholder text block
105
113
  const hasText = blocks.some((b) => b.type === "text");
@@ -117,6 +125,7 @@ const SERVER_SIDE_FALLBACK_BETA = "server-side-fallback-2026-07-01";
117
125
  const MID_CONVERSATION_OUTPUT_CONFIG_BETA = "mid-conversation-output-config-2026-07-01";
118
126
  const THINKING_BINDING_CONTROLS_BETA = "thinking-binding-controls-2026-08-01";
119
127
  const INLINE_TOOLS_BETA = "inline-tools-2026-09-15";
128
+ const MESSAGE_THREADS_BETA = "message-threads-2026-08-12";
120
129
  /**
121
130
  * Stable deferred tool declared whenever native tool changes are in use. Anthropic adds
122
131
  * hidden prompt scaffolding for mid-conversation tool changes; declaring this placeholder
@@ -146,8 +155,178 @@ function getAnthropicCompat(model) {
146
155
  supportsStrictTools: model.compat?.supportsStrictTools ?? false,
147
156
  supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
148
157
  supportsMidConvoToolChanges: model.compat?.supportsMidConvoToolChanges ?? false,
158
+ // Gateways that drop unknown request fields would silently turn a `continue` into a request
159
+ // without the earlier conversation, so the default is limited to Anthropic's own endpoint.
160
+ // Widen or remove this restriction once proxies generally forward the `thread` field.
161
+ supportsMessageThreads: model.compat?.supportsMessageThreads ??
162
+ (model.provider === "anthropic" && model.baseUrl.includes("api.anthropic.com")),
149
163
  };
150
164
  }
165
+ /** Per session: where the next request can continue, or "off" after the endpoint rejected threads. */
166
+ const threadState = new Map();
167
+ const MAX_THREAD_STATE_ENTRIES = 10_000;
168
+ /** Rejections of a `continue` that a new thread fixes: expired, raced, or a fixed setting changed. */
169
+ const NEW_THREAD_ERRORS = new Set([
170
+ "thread_not_found",
171
+ "thread_already_continued",
172
+ "thread_continue_in_flight",
173
+ "thread_fingerprint_mismatch",
174
+ ]);
175
+ /**
176
+ * Sets a session's thread state, keeping at most `MAX_THREAD_STATE_ENTRIES` sessions.
177
+ *
178
+ * State is normally freed by `cleanupSessionResources(sessionId)` when a session ends, as pi's
179
+ * coding agent does. The cap bounds memory for library users that never call it, for example a
180
+ * server passing a new `sessionId` per conversation. Dropping the least recently updated session
181
+ * is cheap: its thread has most likely expired on the server already (after a few minutes idle),
182
+ * and otherwise its next request just starts a new thread.
183
+ */
184
+ function setThreadState(sessionId, value) {
185
+ // Re-insert so Map order stays oldest-first for the cap.
186
+ threadState.delete(sessionId);
187
+ threadState.set(sessionId, value);
188
+ if (threadState.size > MAX_THREAD_STATE_ENTRIES) {
189
+ const oldest = threadState.keys().next().value;
190
+ if (oldest !== undefined)
191
+ threadState.delete(oldest);
192
+ }
193
+ }
194
+ registerSessionResourceCleanup((sessionId) => {
195
+ if (sessionId)
196
+ threadState.delete(sessionId);
197
+ else
198
+ threadState.clear();
199
+ });
200
+ /**
201
+ * Hash of everything a `continue` builds on: the settings that must stay the same within a thread
202
+ * and the messages the server already holds.
203
+ *
204
+ * - Settings, as sent: the endpoint (a thread exists on one endpoint only), and the fixed request
205
+ * fields. A `continue` omits `system` and `tools` and the server keeps the thread's own, so only
206
+ * this check can notice a change to them, including their cache markers. The server rejects
207
+ * changes to the other fields with `thread_fingerprint_mismatch`; checking them here saves that
208
+ * rejected request. A fixed field missing here costs only that rejection, which also starts a
209
+ * new thread.
210
+ * - Messages, normalized so messages that mean the same to the API hash the same.
211
+ */
212
+ function hashThreadPrefix(model, params, messages) {
213
+ const { system, tools, thinking, output_config, tool_choice, betas, fallbacks } = params;
214
+ const settings = JSON.stringify([
215
+ model.provider,
216
+ model.baseUrl,
217
+ params.model,
218
+ system,
219
+ tools,
220
+ thinking,
221
+ output_config,
222
+ tool_choice,
223
+ betas,
224
+ fallbacks,
225
+ ]);
226
+ const conversation = JSON.stringify(messages, (key, value) => {
227
+ // Cache markers don't change what a message means, but they move: pi puts one on the newest
228
+ // message, so the message that had it in the last request has none in this one. Extensions
229
+ // may add markers anywhere too.
230
+ if (key === "cache_control")
231
+ return undefined;
232
+ // To attach that marker, the newest user message's string content is sent as one text block;
233
+ // the next request sends the same message as a string: "hi" vs [{ type: "text", text: "hi" }].
234
+ if (key === "content" && typeof value === "string")
235
+ return [{ type: "text", text: value }];
236
+ return value;
237
+ });
238
+ return shortHash(settings + conversation);
239
+ }
240
+ function createThreadRequest(sessionId, params) {
241
+ return { sessionId, body: { ...params, thread: { type: "create" } } };
242
+ }
243
+ function prepareThreadRequest(model, sessionId, params) {
244
+ const state = threadState.get(sessionId);
245
+ if (state === "off")
246
+ return undefined;
247
+ if (state !== undefined &&
248
+ params.messages.length > state.prefixCount &&
249
+ hashThreadPrefix(model, params, params.messages.slice(0, state.prefixCount)) === state.prefixHash) {
250
+ // Each reply can be continued once; deleting also keeps a concurrent request from trying.
251
+ threadState.delete(sessionId);
252
+ const { system: _system, tools: _tools, ...rest } = params;
253
+ return {
254
+ body: {
255
+ ...rest,
256
+ messages: params.messages.slice(state.prefixCount),
257
+ thread: { type: "continue", previous_message_id: state.replyId },
258
+ },
259
+ sessionId,
260
+ };
261
+ }
262
+ return createThreadRequest(sessionId, params);
263
+ }
264
+ /** The `error_code` Anthropic puts in `error.details` of thread rejections. */
265
+ function getThreadErrorCode(error) {
266
+ const body = error.error;
267
+ const code = body?.error?.details?.error_code;
268
+ return typeof code === "string" ? code : undefined;
269
+ }
270
+ /**
271
+ * Sends a thread request. A rejection about the thread (never a started stream) is resent once in
272
+ * full: as a new thread when the old one can't be continued, or without `thread` when the endpoint
273
+ * can't use threads, which also turns threads off for the session. Other errors are passed on.
274
+ * Returns the response and the thread request that produced it, if any.
275
+ */
276
+ async function sendThreadRequest(thread, params, send, output, signal) {
277
+ try {
278
+ return { response: await send(thread.body), thread };
279
+ }
280
+ catch (error) {
281
+ if (signal?.aborted || !(error instanceof Anthropic.APIError))
282
+ throw error;
283
+ const errorCode = getThreadErrorCode(error);
284
+ const startNewThread = errorCode !== undefined && NEW_THREAD_ERRORS.has(errorCode);
285
+ // The endpoint can't use threads: it can't thread this request, or it doesn't know the
286
+ // `thread` field and rejects it without a code ("thread: Extra inputs are not permitted"),
287
+ // e.g. with an `anthropic-beta` header that lacks the threads beta.
288
+ const threadsUnsupported = errorCode === "thread_unsupported_request" ||
289
+ (errorCode === undefined && error.status === 400 && error.message.includes("thread"));
290
+ // Any other error (prompt too long, unknown model, strict tool limits, ...) is not about the
291
+ // thread, and resending would only fail again.
292
+ if (!startNewThread && !threadsUnsupported)
293
+ throw error;
294
+ // Recorded before the resend, so a failed resend keeps it.
295
+ appendAssistantMessageDiagnostic(output, {
296
+ type: "anthropic_thread_fallback",
297
+ timestamp: Date.now(),
298
+ error: { message: error.message },
299
+ details: { reason: errorCode ?? String(error.status) },
300
+ });
301
+ if (startNewThread) {
302
+ const created = createThreadRequest(thread.sessionId, params);
303
+ return { response: await send(created.body), thread: created };
304
+ }
305
+ // With the threads beta, a request without `thread` is a normal request.
306
+ const response = await send(params);
307
+ setThreadState(thread.sessionId, "off");
308
+ return { response, thread: undefined };
309
+ }
310
+ }
311
+ /**
312
+ * Records where the session's next request can continue: after `output`, the clean reply to the
313
+ * thread request `thread`, whose full request was `params`.
314
+ */
315
+ function recordThreadReply(model, thread, params, output, isOAuth, allowEmptySignature) {
316
+ // `responseModel` is set only when a fallback model answered; the server does not store those
317
+ // replies, so they cannot be continued.
318
+ if (output.responseId === undefined || output.responseModel !== undefined)
319
+ return;
320
+ // Convert the reply like the next request will, keeping only the assistant message
321
+ // (transformMessages adds placeholder results for trailing tool calls).
322
+ const reply = convertMessages(transformMessages([output], model, normalizeToolCallId), isOAuth, undefined, allowEmptySignature).messages.filter((message) => message.role === "assistant");
323
+ const prefix = [...params.messages, ...reply];
324
+ setThreadState(thread.sessionId, {
325
+ prefixCount: prefix.length,
326
+ prefixHash: hashThreadPrefix(model, params, prefix),
327
+ replyId: output.responseId,
328
+ });
329
+ }
151
330
  function mergeHeaders(...headerSources) {
152
331
  const merged = {};
153
332
  for (const headers of headerSources) {
@@ -157,9 +336,6 @@ function mergeHeaders(...headerSources) {
157
336
  }
158
337
  return merged;
159
338
  }
160
- function mergeClientHeaders(...headerSources) {
161
- return mergeHeaders({ "User-Agent": getPiUserAgent() }, ...headerSources);
162
- }
163
339
  function hasHeader(headers, name) {
164
340
  if (!headers)
165
341
  return false;
@@ -375,17 +551,16 @@ async function* iterateAnthropicEvents(response, signal) {
375
551
  }
376
552
  export const stream = (model, context, options) => {
377
553
  const stream = new AssistantMessageEventStream();
378
- const normalizedContext = resolveTranscript(context, getAnthropicCompat(model).supportsMidConvoSystemMessages);
554
+ const compat = getAnthropicCompat(model);
555
+ const normalizedContext = resolveTranscript(context, compat.supportsMidConvoSystemMessages);
379
556
  const currentTools = getCurrentTools(normalizedContext.messages);
380
557
  (async () => {
381
- const providerThinkingLevel = model.compat?.supportsMidConvoEffort ? (options?.effort ?? "high") : undefined;
382
558
  const output = {
383
559
  role: "assistant",
384
560
  content: [],
385
561
  api: model.api,
386
562
  provider: model.provider,
387
563
  model: model.id,
388
- ...(providerThinkingLevel === undefined ? {} : { providerThinkingLevel }),
389
564
  usage: {
390
565
  input: 0,
391
566
  output: 0,
@@ -402,6 +577,12 @@ export const stream = (model, context, options) => {
402
577
  let isOAuth;
403
578
  let usageModel = model;
404
579
  let inputTransformations;
580
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
581
+ const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
582
+ // Session whose requests may use threads; undefined when threads don't apply. Sessions where
583
+ // the endpoint rejected threads ("off") still send the threads beta, which the resend that
584
+ // turned them off was accepted with; `prepareThreadRequest()` just sends no `thread`.
585
+ const threadSessionId = compat.supportsMessageThreads ? cacheSessionId : undefined;
405
586
  if (options?.client) {
406
587
  client = options.client;
407
588
  isOAuth = false;
@@ -419,30 +600,77 @@ export const stream = (model, context, options) => {
419
600
  hasImages,
420
601
  });
421
602
  }
422
- const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
423
- const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
424
- const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation);
603
+ const created = createClient(model, apiKey, options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, federation, options?.identity);
425
604
  client = created.client;
426
605
  isOAuth = created.isOAuthToken;
427
606
  }
428
- let params = buildParams(model, normalizedContext, isOAuth, options);
429
- const nextParams = await options?.onPayload?.(params, model);
430
- if (nextParams !== undefined) {
431
- params = { ...nextParams, stream: true };
432
- }
433
607
  const requestOptions = {
434
608
  ...(options?.signal ? { signal: options.signal } : {}),
435
609
  ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
436
610
  maxRetries: 0,
437
611
  };
438
- const response = await retryProviderRequest(() => client.beta.messages.create(params, requestOptions).asResponse(), {
612
+ const send = (body) => retryProviderRequest(() => client.beta.messages.create(body, requestOptions).asResponse(), {
439
613
  maxRetries: options?.maxRetries,
440
614
  maxRetryDelayMs: options?.maxRetryDelayMs,
441
615
  signal: options?.signal,
442
616
  });
617
+ let disablePreferredStrictTools = false;
618
+ let response;
619
+ let params;
620
+ // The thread request that produced the response; undefined when no `thread` was sent.
621
+ let thread;
622
+ for (;;) {
623
+ options?.signal?.throwIfAborted();
624
+ const built = buildParams(model, normalizedContext, isOAuth, options, {
625
+ disablePreferredStrictTools,
626
+ messageThreads: threadSessionId !== undefined,
627
+ });
628
+ if (built.providerThinkingLevel)
629
+ output.providerThinkingLevel = built.providerThinkingLevel;
630
+ params = built.params;
631
+ const nextParams = await options?.onPayload?.(params, model);
632
+ if (nextParams !== undefined) {
633
+ params = { ...nextParams, stream: true };
634
+ }
635
+ const prepared = threadSessionId !== undefined && options?.disableContinuation !== true
636
+ ? prepareThreadRequest(model, threadSessionId, params)
637
+ : undefined;
638
+ try {
639
+ const sent = prepared
640
+ ? await sendThreadRequest(prepared, params, send, output, options?.signal)
641
+ : { response: await send(params), thread: undefined };
642
+ response = sent.response;
643
+ thread = sent.thread;
644
+ break;
645
+ }
646
+ catch (error) {
647
+ // Only downgrade a rejected request, never an already-started stream.
648
+ if (options?.signal?.aborted ||
649
+ disablePreferredStrictTools ||
650
+ !built.hasPreferredStrictTools ||
651
+ !(error instanceof Anthropic.APIError) ||
652
+ error.status !== 400 ||
653
+ error.type !== "invalid_request_error" ||
654
+ !isStrictToolLimitErrorMessage(error.message)) {
655
+ throw error;
656
+ }
657
+ disablePreferredStrictTools = true;
658
+ appendAssistantMessageDiagnostic(output, {
659
+ type: "anthropic_strict_tools_fallback",
660
+ timestamp: Date.now(),
661
+ error: { message: error.message },
662
+ details: { reason: "strict_tool_limits_exceeded" },
663
+ });
664
+ }
665
+ }
443
666
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
444
667
  stream.push({ type: "start", partial: output });
445
- const blocks = output.content;
668
+ // Stream state per Anthropic block index, kept off the content blocks so it never reaches partials.
669
+ const openBlocks = new Map();
670
+ const openBlock = (anthropicIndex) => {
671
+ const open = openBlocks.get(anthropicIndex);
672
+ return open === undefined ? undefined : { ...open, block: output.content[open.contentIndex] };
673
+ };
446
674
  for await (const event of iterateAnthropicEvents(response, options?.signal)) {
447
675
  await options?.onProviderStreamEvent?.(event, model);
448
676
  if (event.type === "message_start") {
@@ -477,105 +705,92 @@ export const stream = (model, context, options) => {
477
705
  continue;
478
706
  }
479
707
  if (event.content_block.type === "text") {
480
- const block = {
481
- type: "text",
482
- text: event.content_block.text ?? "",
483
- index: event.index,
484
- };
485
- output.content.push(block);
708
+ output.content.push({ type: "text", text: event.content_block.text ?? "" });
709
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
486
710
  stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
487
711
  }
488
712
  else if (event.content_block.type === "thinking") {
489
- const block = {
713
+ output.content.push({
490
714
  type: "thinking",
491
715
  thinking: event.content_block.thinking ?? "",
492
716
  thinkingSignature: event.content_block.signature ?? "",
493
- index: event.index,
494
- };
495
- output.content.push(block);
717
+ });
718
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
496
719
  stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
497
720
  }
498
721
  else if (event.content_block.type === "redacted_thinking") {
499
- const block = {
722
+ output.content.push({
500
723
  type: "thinking",
501
724
  thinking: "[Reasoning redacted]",
502
725
  thinkingSignature: event.content_block.data,
503
726
  redacted: true,
504
- index: event.index,
505
- };
506
- output.content.push(block);
727
+ });
728
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
507
729
  stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
508
730
  }
509
731
  else if (event.content_block.type === "tool_use") {
510
- const block = {
732
+ output.content.push({
511
733
  type: "toolCall",
512
734
  id: event.content_block.id,
513
735
  name: isOAuth
514
736
  ? fromClaudeCodeName(event.content_block.name, currentTools)
515
737
  : event.content_block.name,
516
738
  arguments: event.content_block.input ?? {},
517
- partialJson: "",
518
- index: event.index,
519
- };
520
- output.content.push(block);
739
+ });
740
+ openBlocks.set(event.index, {
741
+ contentIndex: output.content.length - 1,
742
+ arguments: new ToolCallArgumentsStream(),
743
+ });
521
744
  stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
522
745
  }
523
746
  }
524
747
  else if (event.type === "content_block_delta") {
748
+ const open = openBlock(event.index);
525
749
  if (event.delta.type === "text_delta") {
526
- const index = blocks.findIndex((b) => b.index === event.index);
527
- const block = blocks[index];
528
- if (block && block.type === "text") {
529
- block.text += event.delta.text;
750
+ if (open?.block.type === "text") {
751
+ open.block.text += event.delta.text;
530
752
  stream.push({
531
753
  type: "text_delta",
532
- contentIndex: index,
754
+ contentIndex: open.contentIndex,
533
755
  delta: event.delta.text,
534
756
  partial: output,
535
757
  });
536
758
  }
537
759
  }
538
760
  else if (event.delta.type === "thinking_delta") {
539
- const index = blocks.findIndex((b) => b.index === event.index);
540
- const block = blocks[index];
541
- if (block && block.type === "thinking") {
542
- block.thinking += event.delta.thinking;
761
+ if (open?.block.type === "thinking") {
762
+ open.block.thinking += event.delta.thinking;
543
763
  stream.push({
544
764
  type: "thinking_delta",
545
- contentIndex: index,
765
+ contentIndex: open.contentIndex,
546
766
  delta: event.delta.thinking,
547
767
  partial: output,
548
768
  });
549
769
  }
550
770
  }
551
771
  else if (event.delta.type === "input_json_delta") {
552
- const index = blocks.findIndex((b) => b.index === event.index);
553
- const block = blocks[index];
554
- if (block && block.type === "toolCall") {
555
- block.partialJson += event.delta.partial_json;
556
- block.arguments = parseStreamingJson(block.partialJson);
772
+ if (open?.block.type === "toolCall" && open.arguments !== undefined) {
773
+ open.block.arguments = open.arguments.append(event.delta.partial_json);
557
774
  stream.push({
558
775
  type: "toolcall_delta",
559
- contentIndex: index,
776
+ contentIndex: open.contentIndex,
560
777
  delta: event.delta.partial_json,
561
778
  partial: output,
562
779
  });
563
780
  }
564
781
  }
565
782
  else if (event.delta.type === "signature_delta") {
566
- const index = blocks.findIndex((b) => b.index === event.index);
567
- const block = blocks[index];
568
- if (block && block.type === "thinking") {
569
- block.thinkingSignature = block.thinkingSignature || "";
570
- block.thinkingSignature += event.delta.signature;
783
+ if (open?.block.type === "thinking") {
784
+ open.block.thinkingSignature = open.block.thinkingSignature || "";
785
+ open.block.thinkingSignature += event.delta.signature;
571
786
  }
572
787
  }
573
788
  }
574
789
  else if (event.type === "content_block_stop") {
575
- const index = blocks.findIndex((b) => b.index === event.index);
576
- const block = blocks[index];
577
- if (block) {
578
- delete block.index;
790
+ const open = openBlock(event.index);
791
+ openBlocks.delete(event.index);
792
+ if (open) {
793
+ const { block, contentIndex: index } = open;
579
794
  if (block.type === "text") {
580
795
  stream.push({
581
796
  type: "text_end",
@@ -592,11 +807,8 @@ export const stream = (model, context, options) => {
592
807
  partial: output,
593
808
  });
594
809
  }
595
- else if (block.type === "toolCall") {
596
- block.arguments = parseStreamingJson(block.partialJson);
597
- // Finalize in-place and strip the scratch buffer so replay only
598
- // carries parsed arguments.
599
- delete block.partialJson;
810
+ else if (block.type === "toolCall" && open.arguments !== undefined) {
811
+ Object.assign(block, open.arguments.finish());
600
812
  stream.push({
601
813
  type: "toolcall_end",
602
814
  contentIndex: index,
@@ -648,6 +860,11 @@ export const stream = (model, context, options) => {
648
860
  output.usage.totalTokens =
649
861
  output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
650
862
  calculateCost(usageModel, output.usage);
863
+ if (isOpenRouterModel(model) && event.usage) {
864
+ // OpenRouter adds billing fields to streamed message_delta usage at runtime,
865
+ // although its OpenAPI schema and the Anthropic SDK do not type those fields.
866
+ applyOpenRouterReportedCost(output.usage, event.usage);
867
+ }
651
868
  }
652
869
  }
653
870
  if (options?.signal?.aborted) {
@@ -659,6 +876,8 @@ export const stream = (model, context, options) => {
659
876
  if (output.stopReason === "aborted" || output.stopReason === "error") {
660
877
  throw new Error(output.errorMessage || "An unknown error occurred");
661
878
  }
879
+ if (thread)
880
+ recordThreadReply(model, thread, params, output, isOAuth, compat.allowEmptySignature);
662
881
  if (inputTransformations && inputTransformations.length > 0) {
663
882
  appendAssistantMessageDiagnostic(output, {
664
883
  type: "anthropic_input_transformations",
@@ -676,13 +895,8 @@ export const stream = (model, context, options) => {
676
895
  stream.end();
677
896
  }
678
897
  catch (error) {
679
- for (const block of output.content) {
680
- delete block.index;
681
- // partialJson is only a streaming scratch buffer; never persist it.
682
- delete block.partialJson;
683
- }
684
898
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
685
- output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
899
+ output.errorMessage = error instanceof Error ? withCauseDetail(error) : JSON.stringify(error);
686
900
  stream.push({ type: "error", reason: output.stopReason, error: output });
687
901
  stream.end();
688
902
  }
@@ -748,7 +962,7 @@ export const streamSimple = (model, context, options) => {
748
962
  function isOAuthToken(apiKey) {
749
963
  return apiKey.includes("sk-ant-oat");
750
964
  }
751
- function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sessionId, federation) {
965
+ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sessionId, federation, identity) {
752
966
  // Copilot: Bearer auth.
753
967
  if (model.provider === "github-copilot") {
754
968
  const client = new PiAnthropic({
@@ -757,14 +971,14 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
757
971
  baseURL: model.baseUrl,
758
972
  dangerouslyAllowBrowser: true,
759
973
  fetch,
760
- defaultHeaders: mergeClientHeaders({
974
+ defaultHeaders: mergeHeaders(getIdentityHeaders(model, identity), {
761
975
  accept: "application/json",
762
976
  "anthropic-dangerous-direct-browser-access": "true",
763
977
  }, model.headers, dynamicHeaders, optionsHeaders),
764
978
  });
765
979
  return { client, isOAuthToken: false };
766
980
  }
767
- // OAuth: Bearer auth, Claude Code identity headers
981
+ // OAuth: Bearer auth, Claude Code identity headers instead of the client identity.
768
982
  if (apiKey && isOAuthToken(apiKey)) {
769
983
  const client = new PiAnthropic({
770
984
  apiKey: null,
@@ -772,7 +986,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
772
986
  baseURL: model.baseUrl,
773
987
  dangerouslyAllowBrowser: true,
774
988
  fetch,
775
- defaultHeaders: mergeClientHeaders({
989
+ defaultHeaders: mergeHeaders({
776
990
  accept: "application/json",
777
991
  "anthropic-dangerous-direct-browser-access": "true",
778
992
  "user-agent": `claude-cli/${claudeCodeVersion}`,
@@ -788,7 +1002,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
788
1002
  const header = compat.sessionAffinityFormat === "openrouter" ? "x-session-id" : "x-session-affinity";
789
1003
  sessionAffinityHeaders[header] = sessionId;
790
1004
  }
791
- const defaultHeaders = mergeClientHeaders({
1005
+ const defaultHeaders = mergeHeaders(getIdentityHeaders(model, identity), {
792
1006
  accept: "application/json",
793
1007
  "anthropic-dangerous-direct-browser-access": "true",
794
1008
  }, sessionAffinityHeaders, model.headers, optionsHeaders);
@@ -817,7 +1031,7 @@ function createClient(model, apiKey, optionsHeaders, fetch, dynamicHeaders, sess
817
1031
  });
818
1032
  return { client, isOAuthToken: false };
819
1033
  }
820
- function getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, options) {
1034
+ function getBetaFeatures(model, context, options, { isOAuthToken, nativeToolChanges, messageThreads }) {
821
1035
  let configuredFeatures;
822
1036
  for (const headers of [model.headers, options?.headers]) {
823
1037
  for (const [name, value] of Object.entries(headers ?? {})) {
@@ -853,9 +1067,11 @@ function getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, option
853
1067
  }
854
1068
  if (nativeToolChanges)
855
1069
  features.push(INLINE_TOOLS_BETA);
1070
+ if (messageThreads)
1071
+ features.push(MESSAGE_THREADS_BETA);
856
1072
  return [...new Set(features)];
857
1073
  }
858
- function buildParams(model, context, isOAuthToken, options) {
1074
+ function buildParams(model, context, isOAuthToken, options, flags) {
859
1075
  const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
860
1076
  const compat = getAnthropicCompat(model);
861
1077
  const initialSystemMessage = getInitialSystemMessage(context.messages);
@@ -866,18 +1082,29 @@ function buildParams(model, context, isOAuthToken, options) {
866
1082
  // by value in a `tool_addition` block, which also expresses same-name redefinitions.
867
1083
  // Anthropic rejects a tool list where every tool is deferred, so there must be an initial
868
1084
  // active tool to anchor the placeholder. Otherwise the current tool list is sent.
1085
+ let hasPreferredStrictTools = false;
1086
+ const convertToolDefinitions = (tools, toolCacheControl) => {
1087
+ const definitions = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl, flags.disablePreferredStrictTools);
1088
+ if (definitions.some((definition, index) => definition.strict === true && prefersJsonSchemaStrictSampling(tools[index]))) {
1089
+ hasPreferredStrictTools = true;
1090
+ }
1091
+ return definitions;
1092
+ };
869
1093
  const initialTools = initialSystemMessage?.toolsAdded ?? [];
870
1094
  const nativeToolChanges = compat.supportsMidConvoSystemMessages && compat.supportsMidConvoToolChanges && initialTools.length > 0;
871
- const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, model.compat?.supportsMidConvoEffort === true ? model.provider : undefined, nativeToolChanges
872
- ? (tools) => convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools)
873
- : undefined);
1095
+ const thinkingDisabled = model.reasoning && options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null;
1096
+ // Effort cannot change between messages while thinking is disabled, so "off" sends no effort markers.
1097
+ const effortMessages = model.compat?.supportsMidConvoEffort === true && !thinkingDisabled;
1098
+ const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, effortMessages ? model.provider : undefined, nativeToolChanges ? convertToolDefinitions : undefined);
874
1099
  const activeEffort = options?.effort ?? "high";
875
- const betaFeatures = getBetaFeatures(model, context, isOAuthToken, nativeToolChanges, options);
1100
+ const betaFeatures = getBetaFeatures(model, context, options, {
1101
+ isOAuthToken,
1102
+ nativeToolChanges,
1103
+ messageThreads: flags.messageThreads,
1104
+ });
876
1105
  const params = {
877
1106
  model: model.id,
878
- messages: model.compat?.supportsMidConvoEffort === true
879
- ? insertThinkingLevelMessages(converted, activeEffort)
880
- : converted.messages,
1107
+ messages: effortMessages ? insertThinkingLevelMessages(converted, activeEffort) : converted.messages,
881
1108
  max_tokens: options?.maxTokens ?? model.maxTokens,
882
1109
  stream: true,
883
1110
  ...(betaFeatures.length > 0 ? { betas: betaFeatures } : {}),
@@ -922,50 +1149,50 @@ function buildParams(model, context, isOAuthToken, options) {
922
1149
  // placeholder. The list never changes afterwards: later tools are defined by value in
923
1150
  // `tool_addition` blocks and withdrawn by `tool_removal`, so the cached prefix survives
924
1151
  // every tool change.
925
- params.tools = [
926
- ...convertTools(initialTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
927
- DEFERRED_TOOL_PLACEHOLDER,
928
- ];
1152
+ params.tools = [...convertToolDefinitions(initialTools, toolCacheControl), DEFERRED_TOOL_PLACEHOLDER];
929
1153
  }
930
1154
  else {
931
1155
  const tools = getCurrentTools(context.messages);
932
1156
  if (tools.length > 0) {
933
- params.tools = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl);
1157
+ params.tools = convertToolDefinitions(tools, toolCacheControl);
934
1158
  }
935
1159
  }
936
- // Managed effort models always use adaptive thinking so prefix mismatches can
937
- // be dropped instead of surfacing as persistent 400 responses.
938
- if (model.compat?.supportsMidConvoEffort === true) {
1160
+ if (thinkingDisabled) {
1161
+ // No effort is sent, so the model default applies. Anthropic rejects `block_binding`
1162
+ // with disabled thinking.
1163
+ params.thinking = { type: "disabled" };
1164
+ }
1165
+ else if (effortMessages) {
1166
+ // Managed effort models use adaptive thinking so prefix mismatches can
1167
+ // be dropped instead of surfacing as persistent 400 responses.
939
1168
  params.thinking = {
940
1169
  type: "adaptive",
941
1170
  display: options?.thinkingDisplay ?? "summarized",
942
1171
  block_binding: { prefix_mismatch_behavior: "drop_block" },
943
1172
  };
944
- params.output_config = { effort: "high" };
1173
+ // Effort markers set the level for every reply. The top-level effort is part of the cached
1174
+ // prompt, so it stays fixed. It is "low" because organizations can cap effort and reject
1175
+ // requests containing any level above the cap.
1176
+ params.output_config = { effort: "low" };
945
1177
  }
946
- else if (model.reasoning) {
947
- if (options?.thinkingEnabled) {
948
- // Default to "summarized" so Opus 4.7 and Mythos Preview behave like
949
- // older Claude 4 models (whose API default is also "summarized").
950
- const display = options.thinkingDisplay ?? "summarized";
951
- if (model.compat?.forceAdaptiveThinking === true) {
952
- // Adaptive thinking: Claude decides when and how much to think.
953
- params.thinking = { type: "adaptive", display };
954
- if (options.effort) {
955
- params.output_config = { effort: options.effort };
956
- }
957
- }
958
- else {
959
- // Budget-based thinking for older models
960
- params.thinking = {
961
- type: "enabled",
962
- budget_tokens: options.thinkingBudgetTokens || 1024,
963
- display,
964
- };
1178
+ else if (model.reasoning && options?.thinkingEnabled) {
1179
+ // Default to "summarized" so Opus 4.7 and Mythos Preview behave like
1180
+ // older Claude 4 models (whose API default is also "summarized").
1181
+ const display = options.thinkingDisplay ?? "summarized";
1182
+ if (model.compat?.forceAdaptiveThinking === true) {
1183
+ // Adaptive thinking: Claude decides when and how much to think.
1184
+ params.thinking = { type: "adaptive", display };
1185
+ if (options.effort) {
1186
+ params.output_config = { effort: options.effort };
965
1187
  }
966
1188
  }
967
- else if (options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null) {
968
- params.thinking = { type: "disabled" };
1189
+ else {
1190
+ // Budget-based thinking for older models
1191
+ params.thinking = {
1192
+ type: "enabled",
1193
+ budget_tokens: options.thinkingBudgetTokens || 1024,
1194
+ display,
1195
+ };
969
1196
  }
970
1197
  }
971
1198
  if (options?.metadata) {
@@ -986,17 +1213,17 @@ function buildParams(model, context, isOAuthToken, options) {
986
1213
  if (allowedFallbackModels && allowedFallbackModels.length > 0) {
987
1214
  params.fallbacks = allowedFallbackModels.map((fallback) => ({ model: fallback.model }));
988
1215
  }
989
- return params;
1216
+ return { params, hasPreferredStrictTools, providerThinkingLevel: effortMessages ? activeEffort : undefined };
990
1217
  }
991
1218
  // Normalize tool call IDs to match Anthropic's required pattern and length
992
1219
  function normalizeToolCallId(id) {
993
1220
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
994
1221
  }
995
- function convertToolResult(msg) {
1222
+ function convertToolResult(msg, content = msg.content) {
996
1223
  return {
997
1224
  type: "tool_result",
998
1225
  tool_use_id: msg.toolCallId,
999
- content: convertContentBlocks(msg.content),
1226
+ content: convertContentBlocks(content),
1000
1227
  is_error: msg.isError,
1001
1228
  };
1002
1229
  }
@@ -1006,7 +1233,9 @@ convertToolDefinitions) {
1006
1233
  const params = [];
1007
1234
  const assistantLevels = new Map();
1008
1235
  // Later system messages are held back and emitted directly before the next assistant
1009
- // message (or at the end of the transcript). Anthropic requires `tool_result` blocks to
1236
+ // message that is sent (or at the end of the transcript). Assistant messages that convert to
1237
+ // no blocks are dropped, so holding past them keeps an update from landing before a user
1238
+ // message (#11091). Anthropic requires `tool_result` blocks to
1010
1239
  // immediately follow their `tool_use`, so a system message between them is rejected; this
1011
1240
  // also mirrors where the managed-effort system messages are inserted. As a result an
1012
1241
  // update placed before a user message in the transcript lands after it on the wire.
@@ -1061,14 +1290,7 @@ convertToolDefinitions) {
1061
1290
  };
1062
1291
  }
1063
1292
  else {
1064
- return {
1065
- type: "image",
1066
- source: {
1067
- type: "base64",
1068
- media_type: item.mimeType,
1069
- data: item.data,
1070
- },
1071
- };
1293
+ return convertImageBlock(item);
1072
1294
  }
1073
1295
  });
1074
1296
  const filteredBlocks = blocks.filter((b) => {
@@ -1086,7 +1308,6 @@ convertToolDefinitions) {
1086
1308
  }
1087
1309
  }
1088
1310
  else if (msg.role === "assistant") {
1089
- flushPendingSystemMessages();
1090
1311
  const blocks = [];
1091
1312
  for (const block of msg.content) {
1092
1313
  if (block.type === "text") {
@@ -1144,6 +1365,7 @@ convertToolDefinitions) {
1144
1365
  }
1145
1366
  if (blocks.length === 0)
1146
1367
  continue;
1368
+ flushPendingSystemMessages();
1147
1369
  const messageIndex = params.length;
1148
1370
  params.push({
1149
1371
  role: "assistant",
@@ -1159,16 +1381,24 @@ convertToolDefinitions) {
1159
1381
  else if (msg.role === "toolResult") {
1160
1382
  // Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.
1161
1383
  const toolResults = [];
1384
+ // Anthropic rejects error tool results that contain anything but text, so their images follow
1385
+ // all tool results of the turn, which must come first in the user message (#10794).
1386
+ const errorImages = [];
1162
1387
  let j = i;
1163
1388
  while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
1164
- toolResults.push(convertToolResult(transformedMessages[j]));
1389
+ const toolMsg = transformedMessages[j];
1390
+ const split = splitErrorToolResultImages(toolMsg);
1391
+ toolResults.push(convertToolResult(toolMsg, split?.content));
1392
+ if (split) {
1393
+ errorImages.push({ type: "text", text: split.caption }, ...split.images.map(convertImageBlock));
1394
+ }
1165
1395
  j++;
1166
1396
  }
1167
1397
  // Skip the messages we've already processed.
1168
1398
  i = j - 1;
1169
1399
  params.push({
1170
1400
  role: "user",
1171
- content: toolResults,
1401
+ content: [...toolResults, ...errorImages],
1172
1402
  });
1173
1403
  }
1174
1404
  }
@@ -1255,17 +1485,23 @@ const isAnthropicStrictUnsupportedKeyword = (key, value) => {
1255
1485
  return typeof value !== "string" || !ANTHROPIC_STRICT_STRING_FORMATS.has(value);
1256
1486
  return false;
1257
1487
  };
1258
- function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl) {
1488
+ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl, disablePreferredStrictTools = false) {
1259
1489
  if (!tools)
1260
1490
  return [];
1261
1491
  return tools.map((tool, index) => {
1262
- const strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
1492
+ const strict = disablePreferredStrictTools && prefersJsonSchemaStrictSampling(tool)
1493
+ ? undefined
1494
+ : resolveJsonSchemaStrictSampling(tool, supportsStrictTools, isAnthropicStrictUnsupportedKeyword);
1263
1495
  const parameters = getJsonSchemaToolParameters(tool, strict);
1264
1496
  const schema = parameters;
1497
+ // Keep shared definitions so property $refs still resolve. Other root keywords stay out:
1498
+ // Anthropic rejects root anyOf/oneOf/allOf with a 400.
1265
1499
  const legacyInputSchema = {
1266
1500
  type: "object",
1267
1501
  properties: schema.properties ?? {},
1268
1502
  required: schema.required ?? [],
1503
+ ...(schema.$defs === undefined ? {} : { $defs: schema.$defs }),
1504
+ ...(schema.definitions === undefined ? {} : { definitions: schema.definitions }),
1269
1505
  };
1270
1506
  const inputSchema = strict === true
1271
1507
  ? {