@bastani/pi-ai 0.9.19 → 0.9.20-alpha.10

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 (445) hide show
  1. package/CHANGELOG.md +83 -1
  2. package/NOTICE.md +1 -1
  3. package/README.md +203 -30
  4. package/dist/api/anthropic-messages.d.ts.map +1 -1
  5. package/dist/api/anthropic-messages.js +255 -97
  6. package/dist/api/anthropic-messages.js.map +1 -1
  7. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  8. package/dist/api/azure-openai-responses.js +21 -6
  9. package/dist/api/azure-openai-responses.js.map +1 -1
  10. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  11. package/dist/api/bedrock-converse-stream.js +20 -15
  12. package/dist/api/bedrock-converse-stream.js.map +1 -1
  13. package/dist/api/cloudflare-workers-ai-system-one.d.ts +4 -0
  14. package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -0
  15. package/dist/api/cloudflare-workers-ai-system-one.js +43 -0
  16. package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -0
  17. package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts +3 -0
  18. package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts.map +1 -0
  19. package/dist/api/cloudflare-workers-ai-system-one.lazy.js +4 -0
  20. package/dist/api/cloudflare-workers-ai-system-one.lazy.js.map +1 -0
  21. package/dist/api/cloudflare.d.ts +2 -0
  22. package/dist/api/cloudflare.d.ts.map +1 -1
  23. package/dist/api/cloudflare.js +2 -0
  24. package/dist/api/cloudflare.js.map +1 -1
  25. package/dist/api/constrained-sampling.d.ts +8 -3
  26. package/dist/api/constrained-sampling.d.ts.map +1 -1
  27. package/dist/api/constrained-sampling.js +29 -11
  28. package/dist/api/constrained-sampling.js.map +1 -1
  29. package/dist/api/google-generative-ai.d.ts.map +1 -1
  30. package/dist/api/google-generative-ai.js +23 -80
  31. package/dist/api/google-generative-ai.js.map +1 -1
  32. package/dist/api/google-shared.d.ts +13 -4
  33. package/dist/api/google-shared.d.ts.map +1 -1
  34. package/dist/api/google-shared.js +54 -4
  35. package/dist/api/google-shared.js.map +1 -1
  36. package/dist/api/google-vertex.d.ts.map +1 -1
  37. package/dist/api/google-vertex.js +26 -65
  38. package/dist/api/google-vertex.js.map +1 -1
  39. package/dist/api/mistral-conversations.d.ts.map +1 -1
  40. package/dist/api/mistral-conversations.js +18 -13
  41. package/dist/api/mistral-conversations.js.map +1 -1
  42. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  43. package/dist/api/openai-codex-responses.js +40 -26
  44. package/dist/api/openai-codex-responses.js.map +1 -1
  45. package/dist/api/openai-completions.d.ts +6 -4
  46. package/dist/api/openai-completions.d.ts.map +1 -1
  47. package/dist/api/openai-completions.js +44 -58
  48. package/dist/api/openai-completions.js.map +1 -1
  49. package/dist/api/openai-responses-shared.d.ts +11 -5
  50. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  51. package/dist/api/openai-responses-shared.js +65 -58
  52. package/dist/api/openai-responses-shared.js.map +1 -1
  53. package/dist/api/openai-responses.d.ts.map +1 -1
  54. package/dist/api/openai-responses.js +15 -19
  55. package/dist/api/openai-responses.js.map +1 -1
  56. package/dist/api/openrouter-images.d.ts +2 -1
  57. package/dist/api/openrouter-images.d.ts.map +1 -1
  58. package/dist/api/openrouter-images.js +1 -0
  59. package/dist/api/openrouter-images.js.map +1 -1
  60. package/dist/api/openrouter-images.lazy.d.ts.map +1 -1
  61. package/dist/api/openrouter-images.lazy.js.map +1 -1
  62. package/dist/api/pi-messages.d.ts +3 -3
  63. package/dist/api/pi-messages.d.ts.map +1 -1
  64. package/dist/api/pi-messages.js +3 -2
  65. package/dist/api/pi-messages.js.map +1 -1
  66. package/dist/api/simple-options.d.ts +3 -3
  67. package/dist/api/simple-options.d.ts.map +1 -1
  68. package/dist/api/simple-options.js +1 -0
  69. package/dist/api/simple-options.js.map +1 -1
  70. package/dist/api/system-one-shared.d.ts +23 -0
  71. package/dist/api/system-one-shared.d.ts.map +1 -0
  72. package/dist/api/system-one-shared.js +170 -0
  73. package/dist/api/system-one-shared.js.map +1 -0
  74. package/dist/api/transform-messages.d.ts.map +1 -1
  75. package/dist/api/transform-messages.js +21 -7
  76. package/dist/api/transform-messages.js.map +1 -1
  77. package/dist/api/typesafe-system-one.d.ts +4 -0
  78. package/dist/api/typesafe-system-one.d.ts.map +1 -0
  79. package/dist/api/typesafe-system-one.js +19 -0
  80. package/dist/api/typesafe-system-one.js.map +1 -0
  81. package/dist/api/typesafe-system-one.lazy.d.ts +3 -0
  82. package/dist/api/typesafe-system-one.lazy.d.ts.map +1 -0
  83. package/dist/api/typesafe-system-one.lazy.js +4 -0
  84. package/dist/api/typesafe-system-one.lazy.js.map +1 -0
  85. package/dist/auth/credential-store.d.ts.map +1 -1
  86. package/dist/auth/credential-store.js +3 -2
  87. package/dist/auth/credential-store.js.map +1 -1
  88. package/dist/auth/oauth/load.d.ts +2 -0
  89. package/dist/auth/oauth/load.d.ts.map +1 -1
  90. package/dist/auth/oauth/load.js +5 -0
  91. package/dist/auth/oauth/load.js.map +1 -1
  92. package/dist/auth/oauth/meta.d.ts +17 -0
  93. package/dist/auth/oauth/meta.d.ts.map +1 -0
  94. package/dist/auth/oauth/meta.js +190 -0
  95. package/dist/auth/oauth/meta.js.map +1 -0
  96. package/dist/auth/resolve.d.ts +6 -8
  97. package/dist/auth/resolve.d.ts.map +1 -1
  98. package/dist/auth/resolve.js +29 -26
  99. package/dist/auth/resolve.js.map +1 -1
  100. package/dist/bun-oauth.d.ts.map +1 -1
  101. package/dist/bun-oauth.js +2 -0
  102. package/dist/bun-oauth.js.map +1 -1
  103. package/dist/compat.d.ts +3 -3
  104. package/dist/compat.d.ts.map +1 -1
  105. package/dist/compat.js +9 -6
  106. package/dist/compat.js.map +1 -1
  107. package/dist/decision-models.d.ts +21 -0
  108. package/dist/decision-models.d.ts.map +1 -0
  109. package/dist/decision-models.generated.d.ts +3 -0
  110. package/dist/decision-models.generated.d.ts.map +1 -0
  111. package/dist/decision-models.generated.js +47 -0
  112. package/dist/decision-models.generated.js.map +1 -0
  113. package/dist/decision-models.js +8 -0
  114. package/dist/decision-models.js.map +1 -0
  115. package/dist/env-api-keys.d.ts +2 -0
  116. package/dist/env-api-keys.d.ts.map +1 -1
  117. package/dist/env-api-keys.js +61 -43
  118. package/dist/env-api-keys.js.map +1 -1
  119. package/dist/image-models.d.ts +19 -8
  120. package/dist/image-models.d.ts.map +1 -1
  121. package/dist/image-models.js +14 -13
  122. package/dist/image-models.js.map +1 -1
  123. package/dist/images-api-registry.d.ts +7 -7
  124. package/dist/images-api-registry.d.ts.map +1 -1
  125. package/dist/images-api-registry.js.map +1 -1
  126. package/dist/images.d.ts +7 -2
  127. package/dist/images.d.ts.map +1 -1
  128. package/dist/images.js +5 -0
  129. package/dist/images.js.map +1 -1
  130. package/dist/index.d.ts +4 -2
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +4 -2
  133. package/dist/index.js.map +1 -1
  134. package/dist/model-catalog.d.ts +25 -9
  135. package/dist/model-catalog.d.ts.map +1 -1
  136. package/dist/model-catalog.js +14 -2
  137. package/dist/model-catalog.js.map +1 -1
  138. package/dist/models-store.d.ts +3 -2
  139. package/dist/models-store.d.ts.map +1 -1
  140. package/dist/models-store.js.map +1 -1
  141. package/dist/models.d.ts +99 -31
  142. package/dist/models.d.ts.map +1 -1
  143. package/dist/models.generated.d.ts +133 -39
  144. package/dist/models.generated.d.ts.map +1 -1
  145. package/dist/models.generated.js +133 -39
  146. package/dist/models.generated.js.map +1 -1
  147. package/dist/models.js +161 -31
  148. package/dist/models.js.map +1 -1
  149. package/dist/providers/all.d.ts +19 -13
  150. package/dist/providers/all.d.ts.map +1 -1
  151. package/dist/providers/all.js +27 -21
  152. package/dist/providers/all.js.map +1 -1
  153. package/dist/providers/amazon-bedrock.models.d.ts +4 -3
  154. package/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  155. package/dist/providers/amazon-bedrock.models.js +4 -2
  156. package/dist/providers/amazon-bedrock.models.js.map +1 -1
  157. package/dist/providers/ant-ling.models.d.ts +4 -3
  158. package/dist/providers/ant-ling.models.d.ts.map +1 -1
  159. package/dist/providers/ant-ling.models.js +4 -2
  160. package/dist/providers/ant-ling.models.js.map +1 -1
  161. package/dist/providers/anthropic.models.d.ts +4 -3
  162. package/dist/providers/anthropic.models.d.ts.map +1 -1
  163. package/dist/providers/anthropic.models.js +4 -2
  164. package/dist/providers/anthropic.models.js.map +1 -1
  165. package/dist/providers/azure-openai-responses.models.d.ts +4 -3
  166. package/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  167. package/dist/providers/azure-openai-responses.models.js +4 -2
  168. package/dist/providers/azure-openai-responses.models.js.map +1 -1
  169. package/dist/providers/baseten.models.d.ts +4 -3
  170. package/dist/providers/baseten.models.d.ts.map +1 -1
  171. package/dist/providers/baseten.models.js +4 -2
  172. package/dist/providers/baseten.models.js.map +1 -1
  173. package/dist/providers/cerebras.models.d.ts +4 -3
  174. package/dist/providers/cerebras.models.d.ts.map +1 -1
  175. package/dist/providers/cerebras.models.js +4 -2
  176. package/dist/providers/cerebras.models.js.map +1 -1
  177. package/dist/providers/cloudflare-ai-gateway.models.d.ts +4 -3
  178. package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  179. package/dist/providers/cloudflare-ai-gateway.models.js +4 -2
  180. package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  181. package/dist/providers/cloudflare-stream.d.ts +6 -2
  182. package/dist/providers/cloudflare-stream.d.ts.map +1 -1
  183. package/dist/providers/cloudflare-stream.js +6 -0
  184. package/dist/providers/cloudflare-stream.js.map +1 -1
  185. package/dist/providers/cloudflare-workers-ai.d.ts.map +1 -1
  186. package/dist/providers/cloudflare-workers-ai.js +10 -3
  187. package/dist/providers/cloudflare-workers-ai.js.map +1 -1
  188. package/dist/providers/cloudflare-workers-ai.models.d.ts +4 -3
  189. package/dist/providers/cloudflare-workers-ai.models.d.ts.map +1 -1
  190. package/dist/providers/cloudflare-workers-ai.models.js +4 -2
  191. package/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
  192. package/dist/providers/data/.manifest.json +1 -1
  193. package/dist/providers/data/amazon-bedrock.json +1 -1
  194. package/dist/providers/data/ant-ling.json +1 -1
  195. package/dist/providers/data/anthropic.json +1 -1
  196. package/dist/providers/data/azure-openai-responses.json +1 -1
  197. package/dist/providers/data/baseten.json +1 -1
  198. package/dist/providers/data/cerebras.json +1 -1
  199. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  200. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  201. package/dist/providers/data/deepseek.json +1 -1
  202. package/dist/providers/data/fireworks.json +1 -1
  203. package/dist/providers/data/github-copilot.json +1 -1
  204. package/dist/providers/data/google-vertex.json +1 -1
  205. package/dist/providers/data/google.json +1 -1
  206. package/dist/providers/data/groq.json +1 -1
  207. package/dist/providers/data/huggingface.json +1 -1
  208. package/dist/providers/data/kimi-coding.json +1 -1
  209. package/dist/providers/data/meta.json +1 -0
  210. package/dist/providers/data/minimax-cn.json +1 -1
  211. package/dist/providers/data/minimax.json +1 -1
  212. package/dist/providers/data/mistral.json +1 -1
  213. package/dist/providers/data/moonshotai-cn.json +1 -1
  214. package/dist/providers/data/moonshotai.json +1 -1
  215. package/dist/providers/data/nvidia.json +1 -1
  216. package/dist/providers/data/openai-codex.json +1 -1
  217. package/dist/providers/data/openai.json +1 -1
  218. package/dist/providers/data/opencode-go.json +1 -1
  219. package/dist/providers/data/opencode.json +1 -1
  220. package/dist/providers/data/openrouter.json +1 -1
  221. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  222. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  223. package/dist/providers/data/qwen-token-plan.json +1 -1
  224. package/dist/providers/data/radius.json +1 -0
  225. package/dist/providers/data/together.json +1 -1
  226. package/dist/providers/data/typesafe.json +1 -0
  227. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  228. package/dist/providers/data/xai.json +1 -1
  229. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  230. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  231. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  232. package/dist/providers/data/xiaomi.json +1 -1
  233. package/dist/providers/data/zai-coding-cn.json +1 -1
  234. package/dist/providers/data/zai.json +1 -1
  235. package/dist/providers/deepseek.models.d.ts +4 -3
  236. package/dist/providers/deepseek.models.d.ts.map +1 -1
  237. package/dist/providers/deepseek.models.js +4 -2
  238. package/dist/providers/deepseek.models.js.map +1 -1
  239. package/dist/providers/faux.d.ts +3 -2
  240. package/dist/providers/faux.d.ts.map +1 -1
  241. package/dist/providers/faux.js +12 -11
  242. package/dist/providers/faux.js.map +1 -1
  243. package/dist/providers/fireworks.models.d.ts +4 -3
  244. package/dist/providers/fireworks.models.d.ts.map +1 -1
  245. package/dist/providers/fireworks.models.js +4 -2
  246. package/dist/providers/fireworks.models.js.map +1 -1
  247. package/dist/providers/github-copilot.models.d.ts +4 -3
  248. package/dist/providers/github-copilot.models.d.ts.map +1 -1
  249. package/dist/providers/github-copilot.models.js +4 -2
  250. package/dist/providers/github-copilot.models.js.map +1 -1
  251. package/dist/providers/google-vertex.models.d.ts +4 -3
  252. package/dist/providers/google-vertex.models.d.ts.map +1 -1
  253. package/dist/providers/google-vertex.models.js +4 -2
  254. package/dist/providers/google-vertex.models.js.map +1 -1
  255. package/dist/providers/google.models.d.ts +4 -3
  256. package/dist/providers/google.models.d.ts.map +1 -1
  257. package/dist/providers/google.models.js +4 -2
  258. package/dist/providers/google.models.js.map +1 -1
  259. package/dist/providers/groq.models.d.ts +4 -3
  260. package/dist/providers/groq.models.d.ts.map +1 -1
  261. package/dist/providers/groq.models.js +4 -2
  262. package/dist/providers/groq.models.js.map +1 -1
  263. package/dist/providers/huggingface.models.d.ts +4 -3
  264. package/dist/providers/huggingface.models.d.ts.map +1 -1
  265. package/dist/providers/huggingface.models.js +4 -2
  266. package/dist/providers/huggingface.models.js.map +1 -1
  267. package/dist/providers/images/register-builtins.d.ts +1 -1
  268. package/dist/providers/images/register-builtins.d.ts.map +1 -1
  269. package/dist/providers/images/register-builtins.js.map +1 -1
  270. package/dist/providers/kimi-coding.models.d.ts +4 -3
  271. package/dist/providers/kimi-coding.models.d.ts.map +1 -1
  272. package/dist/providers/kimi-coding.models.js +4 -2
  273. package/dist/providers/kimi-coding.models.js.map +1 -1
  274. package/dist/providers/meta.d.ts +3 -0
  275. package/dist/providers/meta.d.ts.map +1 -0
  276. package/dist/providers/meta.js +24 -0
  277. package/dist/providers/meta.js.map +1 -0
  278. package/dist/providers/meta.models.d.ts +5 -0
  279. package/dist/providers/meta.models.d.ts.map +1 -0
  280. package/dist/providers/meta.models.js +8 -0
  281. package/dist/providers/meta.models.js.map +1 -0
  282. package/dist/providers/minimax-cn.models.d.ts +4 -3
  283. package/dist/providers/minimax-cn.models.d.ts.map +1 -1
  284. package/dist/providers/minimax-cn.models.js +4 -2
  285. package/dist/providers/minimax-cn.models.js.map +1 -1
  286. package/dist/providers/minimax.models.d.ts +4 -3
  287. package/dist/providers/minimax.models.d.ts.map +1 -1
  288. package/dist/providers/minimax.models.js +4 -2
  289. package/dist/providers/minimax.models.js.map +1 -1
  290. package/dist/providers/mistral.models.d.ts +4 -3
  291. package/dist/providers/mistral.models.d.ts.map +1 -1
  292. package/dist/providers/mistral.models.js +4 -2
  293. package/dist/providers/mistral.models.js.map +1 -1
  294. package/dist/providers/moonshotai-cn.models.d.ts +4 -3
  295. package/dist/providers/moonshotai-cn.models.d.ts.map +1 -1
  296. package/dist/providers/moonshotai-cn.models.js +4 -2
  297. package/dist/providers/moonshotai-cn.models.js.map +1 -1
  298. package/dist/providers/moonshotai.models.d.ts +4 -3
  299. package/dist/providers/moonshotai.models.d.ts.map +1 -1
  300. package/dist/providers/moonshotai.models.js +4 -2
  301. package/dist/providers/moonshotai.models.js.map +1 -1
  302. package/dist/providers/nvidia.models.d.ts +4 -3
  303. package/dist/providers/nvidia.models.d.ts.map +1 -1
  304. package/dist/providers/nvidia.models.js +4 -2
  305. package/dist/providers/nvidia.models.js.map +1 -1
  306. package/dist/providers/openai-codex.models.d.ts +4 -3
  307. package/dist/providers/openai-codex.models.d.ts.map +1 -1
  308. package/dist/providers/openai-codex.models.js +4 -2
  309. package/dist/providers/openai-codex.models.js.map +1 -1
  310. package/dist/providers/openai.models.d.ts +4 -3
  311. package/dist/providers/openai.models.d.ts.map +1 -1
  312. package/dist/providers/openai.models.js +4 -2
  313. package/dist/providers/openai.models.js.map +1 -1
  314. package/dist/providers/opencode-go.models.d.ts +4 -3
  315. package/dist/providers/opencode-go.models.d.ts.map +1 -1
  316. package/dist/providers/opencode-go.models.js +4 -2
  317. package/dist/providers/opencode-go.models.js.map +1 -1
  318. package/dist/providers/opencode.models.d.ts +4 -3
  319. package/dist/providers/opencode.models.d.ts.map +1 -1
  320. package/dist/providers/opencode.models.js +4 -2
  321. package/dist/providers/opencode.models.js.map +1 -1
  322. package/dist/providers/openrouter.d.ts.map +1 -1
  323. package/dist/providers/openrouter.js +11 -2
  324. package/dist/providers/openrouter.js.map +1 -1
  325. package/dist/providers/openrouter.models.d.ts +4 -3
  326. package/dist/providers/openrouter.models.d.ts.map +1 -1
  327. package/dist/providers/openrouter.models.js +4 -2
  328. package/dist/providers/openrouter.models.js.map +1 -1
  329. package/dist/providers/qwen-token-plan-cn.models.d.ts +4 -3
  330. package/dist/providers/qwen-token-plan-cn.models.d.ts.map +1 -1
  331. package/dist/providers/qwen-token-plan-cn.models.js +4 -2
  332. package/dist/providers/qwen-token-plan-cn.models.js.map +1 -1
  333. package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -3
  334. package/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -1
  335. package/dist/providers/qwen-token-plan-individual.models.js +4 -2
  336. package/dist/providers/qwen-token-plan-individual.models.js.map +1 -1
  337. package/dist/providers/qwen-token-plan.models.d.ts +4 -3
  338. package/dist/providers/qwen-token-plan.models.d.ts.map +1 -1
  339. package/dist/providers/qwen-token-plan.models.js +4 -2
  340. package/dist/providers/qwen-token-plan.models.js.map +1 -1
  341. package/dist/providers/radius.d.ts.map +1 -1
  342. package/dist/providers/radius.js +19 -5
  343. package/dist/providers/radius.js.map +1 -1
  344. package/dist/providers/radius.models.d.ts +5 -0
  345. package/dist/providers/radius.models.d.ts.map +1 -0
  346. package/dist/providers/radius.models.js +8 -0
  347. package/dist/providers/radius.models.js.map +1 -0
  348. package/dist/providers/together.models.d.ts +4 -3
  349. package/dist/providers/together.models.d.ts.map +1 -1
  350. package/dist/providers/together.models.js +4 -2
  351. package/dist/providers/together.models.js.map +1 -1
  352. package/dist/providers/typesafe.d.ts +3 -0
  353. package/dist/providers/typesafe.d.ts.map +1 -0
  354. package/dist/providers/typesafe.js +18 -0
  355. package/dist/providers/typesafe.js.map +1 -0
  356. package/dist/providers/typesafe.models.d.ts +5 -0
  357. package/dist/providers/typesafe.models.d.ts.map +1 -0
  358. package/dist/providers/typesafe.models.js +8 -0
  359. package/dist/providers/typesafe.models.js.map +1 -0
  360. package/dist/providers/vercel-ai-gateway.models.d.ts +4 -3
  361. package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  362. package/dist/providers/vercel-ai-gateway.models.js +4 -2
  363. package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  364. package/dist/providers/xai.models.d.ts +4 -3
  365. package/dist/providers/xai.models.d.ts.map +1 -1
  366. package/dist/providers/xai.models.js +4 -2
  367. package/dist/providers/xai.models.js.map +1 -1
  368. package/dist/providers/xiaomi-token-plan-ams.models.d.ts +4 -3
  369. package/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  370. package/dist/providers/xiaomi-token-plan-ams.models.js +4 -2
  371. package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  372. package/dist/providers/xiaomi-token-plan-cn.models.d.ts +4 -3
  373. package/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  374. package/dist/providers/xiaomi-token-plan-cn.models.js +4 -2
  375. package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  376. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +4 -3
  377. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  378. package/dist/providers/xiaomi-token-plan-sgp.models.js +4 -2
  379. package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  380. package/dist/providers/xiaomi.models.d.ts +4 -3
  381. package/dist/providers/xiaomi.models.d.ts.map +1 -1
  382. package/dist/providers/xiaomi.models.js +4 -2
  383. package/dist/providers/xiaomi.models.js.map +1 -1
  384. package/dist/providers/zai-coding-cn.models.d.ts +4 -3
  385. package/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
  386. package/dist/providers/zai-coding-cn.models.js +4 -2
  387. package/dist/providers/zai-coding-cn.models.js.map +1 -1
  388. package/dist/providers/zai.models.d.ts +4 -3
  389. package/dist/providers/zai.models.d.ts.map +1 -1
  390. package/dist/providers/zai.models.js +4 -2
  391. package/dist/providers/zai.models.js.map +1 -1
  392. package/dist/types.d.ts +227 -51
  393. package/dist/types.d.ts.map +1 -1
  394. package/dist/types.js.map +1 -1
  395. package/dist/utils/diagnostics.d.ts +3 -2
  396. package/dist/utils/diagnostics.d.ts.map +1 -1
  397. package/dist/utils/diagnostics.js.map +1 -1
  398. package/dist/utils/estimate.d.ts +2 -2
  399. package/dist/utils/estimate.d.ts.map +1 -1
  400. package/dist/utils/estimate.js +8 -29
  401. package/dist/utils/estimate.js.map +1 -1
  402. package/dist/utils/headers.d.ts +1 -1
  403. package/dist/utils/headers.d.ts.map +1 -1
  404. package/dist/utils/headers.js +10 -8
  405. package/dist/utils/headers.js.map +1 -1
  406. package/dist/utils/model-operations.d.ts +11 -0
  407. package/dist/utils/model-operations.d.ts.map +1 -0
  408. package/dist/utils/model-operations.js +47 -0
  409. package/dist/utils/model-operations.js.map +1 -0
  410. package/dist/utils/models-error.d.ts +8 -0
  411. package/dist/utils/models-error.d.ts.map +1 -0
  412. package/dist/utils/models-error.js +19 -0
  413. package/dist/utils/models-error.js.map +1 -0
  414. package/dist/utils/overflow.d.ts +1 -0
  415. package/dist/utils/overflow.d.ts.map +1 -1
  416. package/dist/utils/overflow.js +17 -5
  417. package/dist/utils/overflow.js.map +1 -1
  418. package/dist/utils/retry.d.ts.map +1 -1
  419. package/dist/utils/retry.js +2 -0
  420. package/dist/utils/retry.js.map +1 -1
  421. package/dist/utils/text.d.ts +9 -1
  422. package/dist/utils/text.d.ts.map +1 -1
  423. package/dist/utils/text.js +26 -0
  424. package/dist/utils/text.js.map +1 -1
  425. package/dist/utils/transcript.d.ts +84 -0
  426. package/dist/utils/transcript.d.ts.map +1 -0
  427. package/dist/utils/transcript.js +204 -0
  428. package/dist/utils/transcript.js.map +1 -0
  429. package/package.json +4 -4
  430. package/dist/image-models.generated.d.ts +0 -785
  431. package/dist/image-models.generated.d.ts.map +0 -1
  432. package/dist/image-models.generated.js +0 -787
  433. package/dist/image-models.generated.js.map +0 -1
  434. package/dist/images-models.d.ts +0 -95
  435. package/dist/images-models.d.ts.map +0 -1
  436. package/dist/images-models.js +0 -143
  437. package/dist/images-models.js.map +0 -1
  438. package/dist/providers/openrouter-images.d.ts +0 -3
  439. package/dist/providers/openrouter-images.d.ts.map +0 -1
  440. package/dist/providers/openrouter-images.js +0 -22
  441. package/dist/providers/openrouter-images.js.map +0 -1
  442. package/dist/utils/deferred-tools.d.ts +0 -9
  443. package/dist/utils/deferred-tools.d.ts.map +0 -1
  444. package/dist/utils/deferred-tools.js +0 -36
  445. package/dist/utils/deferred-tools.js.map +0 -1
@@ -25,6 +25,7 @@ export function buildBaseOptions(model, context, options, apiKey) {
25
25
  headers: options?.headers,
26
26
  onPayload: options?.onPayload,
27
27
  onResponse: options?.onResponse,
28
+ onProviderStreamEvent: options?.onProviderStreamEvent,
28
29
  timeoutMs: options?.timeoutMs,
29
30
  websocketConnectTimeoutMs: options?.websocketConnectTimeoutMs,
30
31
  streamDeadlineMs: options?.streamDeadlineMs,
@@ -1 +1 @@
1
- {"version":3,"file":"simple-options.js","sourceRoot":"","sources":["../../src/api/simple-options.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,MAAM,UAAU,uBAAuB,CAAC,KAAiB,EAAE,OAAgB,EAAE,SAAiB,EAAU;IACvG,IAAI,KAAK,CAAC,aAAa,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACzE,MAAM,SAAS,GAAG,KAAK,CAAC,aAAa,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,qBAAqB,CAAC;IACtG,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC,CAAC;AAAA,CAChE;AAED,MAAM,UAAU,gBAAgB,CAC/B,KAAiB,EACjB,OAAgB,EAChB,OAA6B,EAC7B,MAAe,EACC;IAChB,MAAM,cAAc,GACnB,KAAK,CAAC,cAAc,IAAI,OAAO,EAAE,cAAc;QAC9C,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,cAAc,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE;QACzD,CAAC,CAAC,SAAS,CAAC;IACd,OAAO;QACN,WAAW,EAAE,OAAO,EAAE,WAAW;QACjC,cAAc;QACd,SAAS,EAAE,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;QACzF,MAAM,EAAE,OAAO,EAAE,MAAM;QACvB,gBAAgB,EAAE,OAAO,EAAE,gBAAgB;QAC3C,MAAM,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM;QACjC,KAAK,EAAE,OAAO,EAAE,KAAK;QACrB,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,cAAc,EAAE,OAAO,EAAE,cAAc;QACvC,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,UAAU,EAAE,OAAO,EAAE,UAAU;QAC/B,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,yBAAyB,EAAE,OAAO,EAAE,yBAAyB;QAC7D,gBAAgB,EAAE,OAAO,EAAE,gBAAgB;QAC3C,UAAU,EAAE,OAAO,EAAE,UAAU;QAC/B,eAAe,EAAE,OAAO,EAAE,eAAe;QACzC,QAAQ,EAAE,OAAO,EAAE,QAAQ;QAC3B,GAAG,EAAE,OAAO,EAAE,GAAG;KACjB,CAAC;AAAA,CACF;AAED,4FAA4F;AAC5F,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAEtC,MAAM,CAAC,MAAM,wBAAwB,GAAoB;IACxD,OAAO,EAAE,IAAI;IACb,GAAG,EAAE,IAAI;IACT,MAAM,EAAE,IAAI;IACZ,IAAI,EAAE,KAAK;CACX,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,MAAiC,EAAuD;IACtH,OAAO,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AAAA,CAChE;AAED,MAAM,UAAU,sBAAsB,CAAC,cAA6B,EAAE,aAA+B,EAAU;IAC9G,MAAM,OAAO,GAAG,EAAE,GAAG,wBAAwB,EAAE,GAAG,aAAa,EAAE,CAAC;IAClE,MAAM,KAAK,GAAG,cAAc,CAAC,cAAc,CAAE,CAAC;IAC9C,OAAO,OAAO,CAAC,KAAK,CAAE,CAAC;AAAA,CACvB;AAED,kGAAkG;AAClG,MAAM,UAAU,+BAA+B,CAAC,cAAsB,EAAE,OAAe,EAAU;IAChG,OAAO,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAC,CAAC,CAAC;AAAA,CAC1E;AAED,MAAM,UAAU,0BAA0B;AACzC,wFAAwF;AACxF,aAAiC,EACjC,cAAsB,EACtB,cAA6B,EAC7B,aAA+B,EACiB;IAChD,IAAI,cAAc,GAAG,sBAAsB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IAC3E,MAAM,SAAS,GACd,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,cAAc,EAAE,cAAc,CAAC,CAAC;IAEzG,IAAI,SAAS,IAAI,cAAc,EAAE,CAAC;QACjC,cAAc,GAAG,+BAA+B,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AAAA,CACrC","sourcesContent":["import type {\n\tApi,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamOptions,\n\tThinkingBudgets,\n\tThinkingLevel,\n} from \"../types.ts\";\nimport { estimateContextTokens } from \"../utils/estimate.ts\";\n\nconst CONTEXT_SAFETY_TOKENS = 4096;\nconst MIN_MAX_TOKENS = 1;\n\nexport function clampMaxTokensToContext(model: Model<Api>, context: Context, maxTokens: number): number {\n\tif (model.contextWindow <= 0) return Math.max(MIN_MAX_TOKENS, maxTokens);\n\tconst available = model.contextWindow - estimateContextTokens(context).tokens - CONTEXT_SAFETY_TOKENS;\n\treturn Math.min(maxTokens, Math.max(MIN_MAX_TOKENS, available));\n}\n\nexport function buildBaseOptions(\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n\tapiKey?: string,\n): StreamOptions {\n\tconst samplingParams =\n\t\tmodel.samplingParams || options?.samplingParams\n\t\t\t? { ...model.samplingParams, ...options?.samplingParams }\n\t\t\t: undefined;\n\treturn {\n\t\ttemperature: options?.temperature,\n\t\tsamplingParams,\n\t\tmaxTokens: clampMaxTokensToContext(model, context, options?.maxTokens ?? model.maxTokens),\n\t\tsignal: options?.signal,\n\t\ttelemetryContext: options?.telemetryContext,\n\t\tapiKey: apiKey || options?.apiKey,\n\t\tfetch: options?.fetch,\n\t\ttransport: options?.transport,\n\t\tcacheRetention: options?.cacheRetention,\n\t\tsessionId: options?.sessionId,\n\t\theaders: options?.headers,\n\t\tonPayload: options?.onPayload,\n\t\tonResponse: options?.onResponse,\n\t\ttimeoutMs: options?.timeoutMs,\n\t\twebsocketConnectTimeoutMs: options?.websocketConnectTimeoutMs,\n\t\tstreamDeadlineMs: options?.streamDeadlineMs,\n\t\tmaxRetries: options?.maxRetries,\n\t\tmaxRetryDelayMs: options?.maxRetryDelayMs,\n\t\tmetadata: options?.metadata,\n\t\tenv: options?.env,\n\t};\n}\n\n/** Tokens always left for the answer when a thinking budget shares the response ceiling. */\nexport const MIN_ANSWER_TOKENS = 1024;\n\nexport const DEFAULT_THINKING_BUDGETS: ThinkingBudgets = {\n\tminimal: 1024,\n\tlow: 2048,\n\tmedium: 8192,\n\thigh: 16384,\n};\n\nexport function clampReasoning(effort: ThinkingLevel | undefined): Exclude<ThinkingLevel, \"xhigh\" | \"max\"> | undefined {\n\treturn effort === \"xhigh\" || effort === \"max\" ? \"high\" : effort;\n}\n\nexport function thinkingBudgetForLevel(reasoningLevel: ThinkingLevel, customBudgets?: ThinkingBudgets): number {\n\tconst budgets = { ...DEFAULT_THINKING_BUDGETS, ...customBudgets };\n\tconst level = clampReasoning(reasoningLevel)!;\n\treturn budgets[level]!;\n}\n\n/** Cap a thinking budget so at least MIN_ANSWER_TOKENS remain under a shared response ceiling. */\nexport function clampThinkingBudgetToAnswerRoom(thinkingBudget: number, ceiling: number): number {\n\treturn Math.min(thinkingBudget, Math.max(0, ceiling - MIN_ANSWER_TOKENS));\n}\n\nexport function adjustMaxTokensForThinking(\n\t// Undefined means no explicit caller cap. Use the model cap and fit thinking inside it.\n\tbaseMaxTokens: number | undefined,\n\tmodelMaxTokens: number,\n\treasoningLevel: ThinkingLevel,\n\tcustomBudgets?: ThinkingBudgets,\n): { maxTokens: number; thinkingBudget: number } {\n\tlet thinkingBudget = thinkingBudgetForLevel(reasoningLevel, customBudgets);\n\tconst maxTokens =\n\t\tbaseMaxTokens === undefined ? modelMaxTokens : Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);\n\n\tif (maxTokens <= thinkingBudget) {\n\t\tthinkingBudget = clampThinkingBudgetToAnswerRoom(thinkingBudget, maxTokens);\n\t}\n\n\treturn { maxTokens, thinkingBudget };\n}\n"]}
1
+ {"version":3,"file":"simple-options.js","sourceRoot":"","sources":["../../src/api/simple-options.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tApi,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamOptions,\n\tThinkingBudgets,\n\tThinkingLevel,\n\tTranscriptContext,\n} from \"../types.ts\";\nimport { estimateContextTokens } from \"../utils/estimate.ts\";\n\nconst CONTEXT_SAFETY_TOKENS = 4096;\nconst MIN_MAX_TOKENS = 1;\n\nexport function clampMaxTokensToContext(model: Model<Api>, context: TranscriptContext, maxTokens: number): number {\n\tif (model.contextWindow <= 0) return Math.max(MIN_MAX_TOKENS, maxTokens);\n\tconst available = model.contextWindow - estimateContextTokens(context).tokens - CONTEXT_SAFETY_TOKENS;\n\treturn Math.min(maxTokens, Math.max(MIN_MAX_TOKENS, available));\n}\n\nexport function buildBaseOptions(\n\tmodel: Model<Api>,\n\tcontext: TranscriptContext,\n\toptions?: SimpleStreamOptions,\n\tapiKey?: string,\n): StreamOptions {\n\tconst samplingParams =\n\t\tmodel.samplingParams || options?.samplingParams\n\t\t\t? { ...model.samplingParams, ...options?.samplingParams }\n\t\t\t: undefined;\n\treturn {\n\t\ttemperature: options?.temperature,\n\t\tsamplingParams,\n\t\tmaxTokens: clampMaxTokensToContext(model, context, options?.maxTokens ?? model.maxTokens),\n\t\tsignal: options?.signal,\n\t\ttelemetryContext: options?.telemetryContext,\n\t\tapiKey: apiKey || options?.apiKey,\n\t\tfetch: options?.fetch,\n\t\ttransport: options?.transport,\n\t\tcacheRetention: options?.cacheRetention,\n\t\tsessionId: options?.sessionId,\n\t\theaders: options?.headers,\n\t\tonPayload: options?.onPayload,\n\t\tonResponse: options?.onResponse,\n\t\tonProviderStreamEvent: options?.onProviderStreamEvent,\n\t\ttimeoutMs: options?.timeoutMs,\n\t\twebsocketConnectTimeoutMs: options?.websocketConnectTimeoutMs,\n\t\tstreamDeadlineMs: options?.streamDeadlineMs,\n\t\tmaxRetries: options?.maxRetries,\n\t\tmaxRetryDelayMs: options?.maxRetryDelayMs,\n\t\tmetadata: options?.metadata,\n\t\tenv: options?.env,\n\t};\n}\n\n/** Tokens always left for the answer when a thinking budget shares the response ceiling. */\nexport const MIN_ANSWER_TOKENS = 1024;\n\nexport const DEFAULT_THINKING_BUDGETS: ThinkingBudgets = {\n\tminimal: 1024,\n\tlow: 2048,\n\tmedium: 8192,\n\thigh: 16384,\n};\n\nexport function clampReasoning(effort: ThinkingLevel | undefined): Exclude<ThinkingLevel, \"xhigh\" | \"max\"> | undefined {\n\treturn effort === \"xhigh\" || effort === \"max\" ? \"high\" : effort;\n}\n\nexport function thinkingBudgetForLevel(reasoningLevel: ThinkingLevel, customBudgets?: ThinkingBudgets): number {\n\tconst budgets = { ...DEFAULT_THINKING_BUDGETS, ...customBudgets };\n\tconst level = clampReasoning(reasoningLevel)!;\n\treturn budgets[level]!;\n}\n\n/** Cap a thinking budget so at least MIN_ANSWER_TOKENS remain under a shared response ceiling. */\nexport function clampThinkingBudgetToAnswerRoom(thinkingBudget: number, ceiling: number): number {\n\treturn Math.min(thinkingBudget, Math.max(0, ceiling - MIN_ANSWER_TOKENS));\n}\n\nexport function adjustMaxTokensForThinking(\n\t// Undefined means no explicit caller cap. Use the model cap and fit thinking inside it.\n\tbaseMaxTokens: number | undefined,\n\tmodelMaxTokens: number,\n\treasoningLevel: ThinkingLevel,\n\tcustomBudgets?: ThinkingBudgets,\n): { maxTokens: number; thinkingBudget: number } {\n\tlet thinkingBudget = thinkingBudgetForLevel(reasoningLevel, customBudgets);\n\tconst maxTokens =\n\t\tbaseMaxTokens === undefined ? modelMaxTokens : Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);\n\n\tif (maxTokens <= thinkingBudget) {\n\t\tthinkingBudget = clampThinkingBudgetToAnswerRoom(thinkingBudget, maxTokens);\n\t}\n\n\treturn { maxTokens, thinkingBudget };\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { ClassifierApi, ClassifierContext, ClassifierModel, ClassifierOptions, ClassifierResult } from "../types.ts";
2
+ /** TypeSafe System One request body without the transport-specific envelope. */
3
+ export interface SystemOneWireRequest {
4
+ state: ClassifierContext["state"];
5
+ questions: Record<string, unknown>;
6
+ }
7
+ /** Differences between services that serve System One models. */
8
+ export interface SystemOneTransport {
9
+ /** Classifier API implemented by this transport. */
10
+ api: ClassifierApi;
11
+ /** Service name used in error messages. */
12
+ label: string;
13
+ /** Absolute request URL. */
14
+ url(model: ClassifierModel<ClassifierApi>): URL;
15
+ /** Wraps the System One request in the service's request envelope. */
16
+ payload(model: ClassifierModel<ClassifierApi>, request: SystemOneWireRequest): unknown;
17
+ /** Extracts the System One `answers` object from the service's response envelope. */
18
+ answers(body: unknown): unknown;
19
+ }
20
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
21
+ /** Runs one System One classification over the given transport. */
22
+ export declare function classifySystemOne(transport: SystemOneTransport, model: ClassifierModel<ClassifierApi>, context: ClassifierContext, options: ClassifierOptions | undefined): Promise<ClassifierResult>;
23
+ //# sourceMappingURL=system-one-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-one-shared.d.ts","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAEhB,MAAM,aAAa,CAAC;AAKrB,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IAClC,oDAAoD;IACpD,GAAG,EAAE,aAAa,CAAC;IACnB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;IAChD,sEAAsE;IACtE,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC;IACvF,qFAAqF;IACrF,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;CAChC;AAyBD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AA2GD,mEAAmE;AACnE,wBAAsB,iBAAiB,CACtC,SAAS,EAAE,kBAAkB,EAC7B,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,EACrC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,iBAAiB,GAAG,SAAS,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAqD3B","sourcesContent":["import type {\n\tClassifierAnswer,\n\tClassifierApi,\n\tClassifierContext,\n\tClassifierModel,\n\tClassifierOptions,\n\tClassifierResult,\n\tProviderHeaders,\n} from \"../types.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { headersToRecord, providerHeadersToRecord } from \"../utils/headers.ts\";\nimport { retryProviderRequest } from \"../utils/provider-retry.ts\";\n\n/** TypeSafe System One request body without the transport-specific envelope. */\nexport interface SystemOneWireRequest {\n\tstate: ClassifierContext[\"state\"];\n\tquestions: Record<string, unknown>;\n}\n\n/** Differences between services that serve System One models. */\nexport interface SystemOneTransport {\n\t/** Classifier API implemented by this transport. */\n\tapi: ClassifierApi;\n\t/** Service name used in error messages. */\n\tlabel: string;\n\t/** Absolute request URL. */\n\turl(model: ClassifierModel<ClassifierApi>): URL;\n\t/** Wraps the System One request in the service's request envelope. */\n\tpayload(model: ClassifierModel<ClassifierApi>, request: SystemOneWireRequest): unknown;\n\t/** Extracts the System One `answers` object from the service's response envelope. */\n\tanswers(body: unknown): unknown;\n}\n\ninterface SystemOneHttpError extends Error {\n\tstatus: number | undefined;\n\theaders: Headers | undefined;\n\tbody: string;\n}\n\nfunction httpError(label: string, response: Response, body: string): SystemOneHttpError {\n\tconst error = new Error(`${label} returned ${response.status}`) as SystemOneHttpError;\n\terror.status = response.status;\n\terror.headers = response.headers;\n\terror.body = body;\n\treturn error;\n}\n\nfunction timeoutError(timeoutMs: number): SystemOneHttpError {\n\tconst error = new Error(`Request timed out after ${timeoutMs}ms`) as SystemOneHttpError;\n\terror.name = \"TimeoutError\";\n\terror.status = undefined;\n\terror.headers = undefined;\n\terror.body = \"\";\n\treturn error;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction requiredNumber(label: string, value: unknown, field: string): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(`${label} returned an invalid ${field}`);\n\t}\n\treturn value;\n}\n\nfunction unitInterval(label: string, value: unknown, field: string): number {\n\tconst number = requiredNumber(label, value, field);\n\tif (number < 0 || number > 1) throw new Error(`${label} returned an invalid ${field}`);\n\treturn number;\n}\n\nfunction probabilities(label: string, value: unknown, id: string): Record<string, number> {\n\tif (!isRecord(value)) throw new Error(`${label} returned invalid probabilities for ${id}`);\n\treturn Object.fromEntries(\n\t\tObject.entries(value).map(([key, probability]) => [\n\t\t\tkey,\n\t\t\tunitInterval(label, probability, `probability for ${id}.${key}`),\n\t\t]),\n\t);\n}\n\nfunction parseAnswers(label: string, value: unknown, context: ClassifierContext): Record<string, ClassifierAnswer> {\n\tif (!isRecord(value)) throw new Error(`${label} returned an unexpected response`);\n\tconst answers: Array<[string, ClassifierAnswer]> = [];\n\tfor (const [id, question] of Object.entries(context.questions)) {\n\t\tconst answer = value[id];\n\t\tif (!isRecord(answer)) throw new Error(`${label} did not return an answer for ${id}`);\n\t\tif (question.type === \"choice\") {\n\t\t\tif (\n\t\t\t\tanswer.type !== \"choice\" ||\n\t\t\t\ttypeof answer.choice !== \"string\" ||\n\t\t\t\t!Object.hasOwn(question.criteria, answer.choice)\n\t\t\t) {\n\t\t\t\tthrow new Error(`${label} did not return a valid choice answer for ${id}`);\n\t\t\t}\n\t\t\tconst choiceProbabilities = probabilities(label, answer.probabilities, id);\n\t\t\tif (Object.keys(choiceProbabilities).some((key) => !Object.hasOwn(question.criteria, key))) {\n\t\t\t\tthrow new Error(`${label} returned an unknown choice probability for ${id}`);\n\t\t\t}\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"choice\",\n\t\t\t\t\tchoice: answer.choice,\n\t\t\t\t\tprobabilities: choiceProbabilities,\n\t\t\t\t\tconfidence: unitInterval(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else if (question.type === \"score\") {\n\t\t\tif (answer.type !== \"score\") throw new Error(`${label} did not return a score answer for ${id}`);\n\t\t\tconst score = requiredNumber(label, answer.score, `score for ${id}`);\n\t\t\tif (score < 0 || score > question.criteria.length - 1) {\n\t\t\t\tthrow new Error(`${label} returned an invalid score for ${id}`);\n\t\t\t}\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"score\",\n\t\t\t\t\tscore,\n\t\t\t\t\tconfidence: unitInterval(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else {\n\t\t\tif (answer.type !== \"noul\") throw new Error(`${label} did not return a bool answer for ${id}`);\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"bool\",\n\t\t\t\t\tprobability: unitInterval(label, answer.noul, `probability for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\t}\n\treturn Object.fromEntries(answers);\n}\n\n/** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */\nfunction wireRequest(context: ClassifierContext): SystemOneWireRequest {\n\treturn {\n\t\tstate: context.state,\n\t\tquestions: Object.fromEntries(\n\t\t\tObject.entries(context.questions).map(([id, question]) => [\n\t\t\t\tid,\n\t\t\t\tquestion.type === \"bool\" ? { ...question, type: \"noul\" } : question,\n\t\t\t]),\n\t\t),\n\t};\n}\n\nfunction requestHeaders(\n\tmodel: ClassifierModel<ClassifierApi>,\n\tapiKey: string,\n\toptionsHeaders?: ProviderHeaders,\n): Record<string, string> {\n\treturn (\n\t\tproviderHeadersToRecord(\n\t\t\t{ authorization: `Bearer ${apiKey}`, \"content-type\": \"application/json\" },\n\t\t\tmodel.headers,\n\t\t\toptionsHeaders,\n\t\t) ?? {}\n\t);\n}\n\n/** Runs one System One classification over the given transport. */\nexport async function classifySystemOne(\n\ttransport: SystemOneTransport,\n\tmodel: ClassifierModel<ClassifierApi>,\n\tcontext: ClassifierContext,\n\toptions: ClassifierOptions | undefined,\n): Promise<ClassifierResult> {\n\tconst output: ClassifierResult = {\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tanswers: {},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\n\ttry {\n\t\tif (model.api !== transport.api) throw new Error(`Unsupported classifier API: ${model.api}`);\n\t\tif (!options?.apiKey) throw new Error(`No API key for provider: ${model.provider}`);\n\t\tconst apiKey = options.apiKey;\n\t\tlet payload = transport.payload(model, wireRequest(context));\n\t\tconst transformed = await options.onPayload?.(payload, model);\n\t\tif (transformed !== undefined) payload = transformed;\n\t\tconst requestFetch = options.fetch ?? globalThis.fetch;\n\t\tconst { response, body } = await retryProviderRequest(\n\t\t\tasync () => {\n\t\t\t\tconst timeoutSignal = options.timeoutMs !== undefined ? AbortSignal.timeout(options.timeoutMs) : undefined;\n\t\t\t\tconst signal =\n\t\t\t\t\toptions.signal && timeoutSignal\n\t\t\t\t\t\t? AbortSignal.any([options.signal, timeoutSignal])\n\t\t\t\t\t\t: (options.signal ?? timeoutSignal);\n\t\t\t\ttry {\n\t\t\t\t\tconst next = await requestFetch(transport.url(model), {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: requestHeaders(model, apiKey, options.headers),\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t});\n\t\t\t\t\tif (!next.ok) throw httpError(transport.label, next, await next.text());\n\t\t\t\t\treturn { response: next, body: (await next.json()) as unknown };\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (timeoutSignal?.aborted && !options.signal?.aborted) throw timeoutError(options.timeoutMs!);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tmaxRetries: options.maxRetries ?? 2,\n\t\t\t\tmaxRetryDelayMs: options.maxRetryDelayMs,\n\t\t\t\tsignal: options.signal,\n\t\t\t},\n\t\t);\n\t\tawait options.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\toutput.answers = parseAnswers(transport.label, transport.answers(body), context);\n\t\treturn output;\n\t} catch (error) {\n\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error), `${transport.label} error`);\n\t\treturn output;\n\t}\n}\n"]}
@@ -0,0 +1,170 @@
1
+ import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
2
+ import { headersToRecord, providerHeadersToRecord } from "../utils/headers.js";
3
+ import { retryProviderRequest } from "../utils/provider-retry.js";
4
+ function httpError(label, response, body) {
5
+ const error = new Error(`${label} returned ${response.status}`);
6
+ error.status = response.status;
7
+ error.headers = response.headers;
8
+ error.body = body;
9
+ return error;
10
+ }
11
+ function timeoutError(timeoutMs) {
12
+ const error = new Error(`Request timed out after ${timeoutMs}ms`);
13
+ error.name = "TimeoutError";
14
+ error.status = undefined;
15
+ error.headers = undefined;
16
+ error.body = "";
17
+ return error;
18
+ }
19
+ export function isRecord(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
22
+ function requiredNumber(label, value, field) {
23
+ if (typeof value !== "number" || !Number.isFinite(value)) {
24
+ throw new Error(`${label} returned an invalid ${field}`);
25
+ }
26
+ return value;
27
+ }
28
+ function unitInterval(label, value, field) {
29
+ const number = requiredNumber(label, value, field);
30
+ if (number < 0 || number > 1)
31
+ throw new Error(`${label} returned an invalid ${field}`);
32
+ return number;
33
+ }
34
+ function probabilities(label, value, id) {
35
+ if (!isRecord(value))
36
+ throw new Error(`${label} returned invalid probabilities for ${id}`);
37
+ return Object.fromEntries(Object.entries(value).map(([key, probability]) => [
38
+ key,
39
+ unitInterval(label, probability, `probability for ${id}.${key}`),
40
+ ]));
41
+ }
42
+ function parseAnswers(label, value, context) {
43
+ if (!isRecord(value))
44
+ throw new Error(`${label} returned an unexpected response`);
45
+ const answers = [];
46
+ for (const [id, question] of Object.entries(context.questions)) {
47
+ const answer = value[id];
48
+ if (!isRecord(answer))
49
+ throw new Error(`${label} did not return an answer for ${id}`);
50
+ if (question.type === "choice") {
51
+ if (answer.type !== "choice" ||
52
+ typeof answer.choice !== "string" ||
53
+ !Object.hasOwn(question.criteria, answer.choice)) {
54
+ throw new Error(`${label} did not return a valid choice answer for ${id}`);
55
+ }
56
+ const choiceProbabilities = probabilities(label, answer.probabilities, id);
57
+ if (Object.keys(choiceProbabilities).some((key) => !Object.hasOwn(question.criteria, key))) {
58
+ throw new Error(`${label} returned an unknown choice probability for ${id}`);
59
+ }
60
+ answers.push([
61
+ id,
62
+ {
63
+ type: "choice",
64
+ choice: answer.choice,
65
+ probabilities: choiceProbabilities,
66
+ confidence: unitInterval(label, answer.confidence, `confidence for ${id}`),
67
+ },
68
+ ]);
69
+ }
70
+ else if (question.type === "score") {
71
+ if (answer.type !== "score")
72
+ throw new Error(`${label} did not return a score answer for ${id}`);
73
+ const score = requiredNumber(label, answer.score, `score for ${id}`);
74
+ if (score < 0 || score > question.criteria.length - 1) {
75
+ throw new Error(`${label} returned an invalid score for ${id}`);
76
+ }
77
+ answers.push([
78
+ id,
79
+ {
80
+ type: "score",
81
+ score,
82
+ confidence: unitInterval(label, answer.confidence, `confidence for ${id}`),
83
+ },
84
+ ]);
85
+ }
86
+ else {
87
+ if (answer.type !== "noul")
88
+ throw new Error(`${label} did not return a bool answer for ${id}`);
89
+ answers.push([
90
+ id,
91
+ {
92
+ type: "bool",
93
+ probability: unitInterval(label, answer.noul, `probability for ${id}`),
94
+ },
95
+ ]);
96
+ }
97
+ }
98
+ return Object.fromEntries(answers);
99
+ }
100
+ /** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */
101
+ function wireRequest(context) {
102
+ return {
103
+ state: context.state,
104
+ questions: Object.fromEntries(Object.entries(context.questions).map(([id, question]) => [
105
+ id,
106
+ question.type === "bool" ? { ...question, type: "noul" } : question,
107
+ ])),
108
+ };
109
+ }
110
+ function requestHeaders(model, apiKey, optionsHeaders) {
111
+ return (providerHeadersToRecord({ authorization: `Bearer ${apiKey}`, "content-type": "application/json" }, model.headers, optionsHeaders) ?? {});
112
+ }
113
+ /** Runs one System One classification over the given transport. */
114
+ export async function classifySystemOne(transport, model, context, options) {
115
+ const output = {
116
+ api: model.api,
117
+ provider: model.provider,
118
+ model: model.id,
119
+ answers: {},
120
+ stopReason: "stop",
121
+ timestamp: Date.now(),
122
+ };
123
+ try {
124
+ if (model.api !== transport.api)
125
+ throw new Error(`Unsupported classifier API: ${model.api}`);
126
+ if (!options?.apiKey)
127
+ throw new Error(`No API key for provider: ${model.provider}`);
128
+ const apiKey = options.apiKey;
129
+ let payload = transport.payload(model, wireRequest(context));
130
+ const transformed = await options.onPayload?.(payload, model);
131
+ if (transformed !== undefined)
132
+ payload = transformed;
133
+ const requestFetch = options.fetch ?? globalThis.fetch;
134
+ const { response, body } = await retryProviderRequest(async () => {
135
+ const timeoutSignal = options.timeoutMs !== undefined ? AbortSignal.timeout(options.timeoutMs) : undefined;
136
+ const signal = options.signal && timeoutSignal
137
+ ? AbortSignal.any([options.signal, timeoutSignal])
138
+ : (options.signal ?? timeoutSignal);
139
+ try {
140
+ const next = await requestFetch(transport.url(model), {
141
+ method: "POST",
142
+ headers: requestHeaders(model, apiKey, options.headers),
143
+ body: JSON.stringify(payload),
144
+ signal,
145
+ });
146
+ if (!next.ok)
147
+ throw httpError(transport.label, next, await next.text());
148
+ return { response: next, body: (await next.json()) };
149
+ }
150
+ catch (error) {
151
+ if (timeoutSignal?.aborted && !options.signal?.aborted)
152
+ throw timeoutError(options.timeoutMs);
153
+ throw error;
154
+ }
155
+ }, {
156
+ maxRetries: options.maxRetries ?? 2,
157
+ maxRetryDelayMs: options.maxRetryDelayMs,
158
+ signal: options.signal,
159
+ });
160
+ await options.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
161
+ output.answers = parseAnswers(transport.label, transport.answers(body), context);
162
+ return output;
163
+ }
164
+ catch (error) {
165
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
166
+ output.errorMessage = formatProviderError(normalizeProviderError(error), `${transport.label} error`);
167
+ return output;
168
+ }
169
+ }
170
+ //# sourceMappingURL=system-one-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-one-shared.js","sourceRoot":"","sources":["../../src/api/system-one-shared.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tClassifierAnswer,\n\tClassifierApi,\n\tClassifierContext,\n\tClassifierModel,\n\tClassifierOptions,\n\tClassifierResult,\n\tProviderHeaders,\n} from \"../types.ts\";\nimport { formatProviderError, normalizeProviderError } from \"../utils/error-body.ts\";\nimport { headersToRecord, providerHeadersToRecord } from \"../utils/headers.ts\";\nimport { retryProviderRequest } from \"../utils/provider-retry.ts\";\n\n/** TypeSafe System One request body without the transport-specific envelope. */\nexport interface SystemOneWireRequest {\n\tstate: ClassifierContext[\"state\"];\n\tquestions: Record<string, unknown>;\n}\n\n/** Differences between services that serve System One models. */\nexport interface SystemOneTransport {\n\t/** Classifier API implemented by this transport. */\n\tapi: ClassifierApi;\n\t/** Service name used in error messages. */\n\tlabel: string;\n\t/** Absolute request URL. */\n\turl(model: ClassifierModel<ClassifierApi>): URL;\n\t/** Wraps the System One request in the service's request envelope. */\n\tpayload(model: ClassifierModel<ClassifierApi>, request: SystemOneWireRequest): unknown;\n\t/** Extracts the System One `answers` object from the service's response envelope. */\n\tanswers(body: unknown): unknown;\n}\n\ninterface SystemOneHttpError extends Error {\n\tstatus: number | undefined;\n\theaders: Headers | undefined;\n\tbody: string;\n}\n\nfunction httpError(label: string, response: Response, body: string): SystemOneHttpError {\n\tconst error = new Error(`${label} returned ${response.status}`) as SystemOneHttpError;\n\terror.status = response.status;\n\terror.headers = response.headers;\n\terror.body = body;\n\treturn error;\n}\n\nfunction timeoutError(timeoutMs: number): SystemOneHttpError {\n\tconst error = new Error(`Request timed out after ${timeoutMs}ms`) as SystemOneHttpError;\n\terror.name = \"TimeoutError\";\n\terror.status = undefined;\n\terror.headers = undefined;\n\terror.body = \"\";\n\treturn error;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction requiredNumber(label: string, value: unknown, field: string): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(`${label} returned an invalid ${field}`);\n\t}\n\treturn value;\n}\n\nfunction unitInterval(label: string, value: unknown, field: string): number {\n\tconst number = requiredNumber(label, value, field);\n\tif (number < 0 || number > 1) throw new Error(`${label} returned an invalid ${field}`);\n\treturn number;\n}\n\nfunction probabilities(label: string, value: unknown, id: string): Record<string, number> {\n\tif (!isRecord(value)) throw new Error(`${label} returned invalid probabilities for ${id}`);\n\treturn Object.fromEntries(\n\t\tObject.entries(value).map(([key, probability]) => [\n\t\t\tkey,\n\t\t\tunitInterval(label, probability, `probability for ${id}.${key}`),\n\t\t]),\n\t);\n}\n\nfunction parseAnswers(label: string, value: unknown, context: ClassifierContext): Record<string, ClassifierAnswer> {\n\tif (!isRecord(value)) throw new Error(`${label} returned an unexpected response`);\n\tconst answers: Array<[string, ClassifierAnswer]> = [];\n\tfor (const [id, question] of Object.entries(context.questions)) {\n\t\tconst answer = value[id];\n\t\tif (!isRecord(answer)) throw new Error(`${label} did not return an answer for ${id}`);\n\t\tif (question.type === \"choice\") {\n\t\t\tif (\n\t\t\t\tanswer.type !== \"choice\" ||\n\t\t\t\ttypeof answer.choice !== \"string\" ||\n\t\t\t\t!Object.hasOwn(question.criteria, answer.choice)\n\t\t\t) {\n\t\t\t\tthrow new Error(`${label} did not return a valid choice answer for ${id}`);\n\t\t\t}\n\t\t\tconst choiceProbabilities = probabilities(label, answer.probabilities, id);\n\t\t\tif (Object.keys(choiceProbabilities).some((key) => !Object.hasOwn(question.criteria, key))) {\n\t\t\t\tthrow new Error(`${label} returned an unknown choice probability for ${id}`);\n\t\t\t}\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"choice\",\n\t\t\t\t\tchoice: answer.choice,\n\t\t\t\t\tprobabilities: choiceProbabilities,\n\t\t\t\t\tconfidence: unitInterval(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else if (question.type === \"score\") {\n\t\t\tif (answer.type !== \"score\") throw new Error(`${label} did not return a score answer for ${id}`);\n\t\t\tconst score = requiredNumber(label, answer.score, `score for ${id}`);\n\t\t\tif (score < 0 || score > question.criteria.length - 1) {\n\t\t\t\tthrow new Error(`${label} returned an invalid score for ${id}`);\n\t\t\t}\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"score\",\n\t\t\t\t\tscore,\n\t\t\t\t\tconfidence: unitInterval(label, answer.confidence, `confidence for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t} else {\n\t\t\tif (answer.type !== \"noul\") throw new Error(`${label} did not return a bool answer for ${id}`);\n\t\t\tanswers.push([\n\t\t\t\tid,\n\t\t\t\t{\n\t\t\t\t\ttype: \"bool\",\n\t\t\t\t\tprobability: unitInterval(label, answer.noul, `probability for ${id}`),\n\t\t\t\t},\n\t\t\t]);\n\t\t}\n\t}\n\treturn Object.fromEntries(answers);\n}\n\n/** Maps public `bool` questions to TypeSafe's wire-level `noul` type. */\nfunction wireRequest(context: ClassifierContext): SystemOneWireRequest {\n\treturn {\n\t\tstate: context.state,\n\t\tquestions: Object.fromEntries(\n\t\t\tObject.entries(context.questions).map(([id, question]) => [\n\t\t\t\tid,\n\t\t\t\tquestion.type === \"bool\" ? { ...question, type: \"noul\" } : question,\n\t\t\t]),\n\t\t),\n\t};\n}\n\nfunction requestHeaders(\n\tmodel: ClassifierModel<ClassifierApi>,\n\tapiKey: string,\n\toptionsHeaders?: ProviderHeaders,\n): Record<string, string> {\n\treturn (\n\t\tproviderHeadersToRecord(\n\t\t\t{ authorization: `Bearer ${apiKey}`, \"content-type\": \"application/json\" },\n\t\t\tmodel.headers,\n\t\t\toptionsHeaders,\n\t\t) ?? {}\n\t);\n}\n\n/** Runs one System One classification over the given transport. */\nexport async function classifySystemOne(\n\ttransport: SystemOneTransport,\n\tmodel: ClassifierModel<ClassifierApi>,\n\tcontext: ClassifierContext,\n\toptions: ClassifierOptions | undefined,\n): Promise<ClassifierResult> {\n\tconst output: ClassifierResult = {\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tanswers: {},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\n\ttry {\n\t\tif (model.api !== transport.api) throw new Error(`Unsupported classifier API: ${model.api}`);\n\t\tif (!options?.apiKey) throw new Error(`No API key for provider: ${model.provider}`);\n\t\tconst apiKey = options.apiKey;\n\t\tlet payload = transport.payload(model, wireRequest(context));\n\t\tconst transformed = await options.onPayload?.(payload, model);\n\t\tif (transformed !== undefined) payload = transformed;\n\t\tconst requestFetch = options.fetch ?? globalThis.fetch;\n\t\tconst { response, body } = await retryProviderRequest(\n\t\t\tasync () => {\n\t\t\t\tconst timeoutSignal = options.timeoutMs !== undefined ? AbortSignal.timeout(options.timeoutMs) : undefined;\n\t\t\t\tconst signal =\n\t\t\t\t\toptions.signal && timeoutSignal\n\t\t\t\t\t\t? AbortSignal.any([options.signal, timeoutSignal])\n\t\t\t\t\t\t: (options.signal ?? timeoutSignal);\n\t\t\t\ttry {\n\t\t\t\t\tconst next = await requestFetch(transport.url(model), {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: requestHeaders(model, apiKey, options.headers),\n\t\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t});\n\t\t\t\t\tif (!next.ok) throw httpError(transport.label, next, await next.text());\n\t\t\t\t\treturn { response: next, body: (await next.json()) as unknown };\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (timeoutSignal?.aborted && !options.signal?.aborted) throw timeoutError(options.timeoutMs!);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tmaxRetries: options.maxRetries ?? 2,\n\t\t\t\tmaxRetryDelayMs: options.maxRetryDelayMs,\n\t\t\t\tsignal: options.signal,\n\t\t\t},\n\t\t);\n\t\tawait options.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\toutput.answers = parseAnswers(transport.label, transport.answers(body), context);\n\t\treturn output;\n\t} catch (error) {\n\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\toutput.errorMessage = formatProviderError(normalizeProviderError(error), `${transport.label} error`);\n\t\treturn output;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../src/api/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,GAAG,EACH,gBAAgB,EAEhB,OAAO,EACP,KAAK,EAIL,MAAM,aAAa,CAAC;AA6GrB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,GAAG,EACjD,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,GACxF,OAAO,EAAE,CAoNX","sourcesContent":["import type {\n\tAnthropicMessagesCompat,\n\tApi,\n\tAssistantMessage,\n\tImageContent,\n\tMessage,\n\tModel,\n\tTextContent,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.ts\";\n\nconst NON_VISION_USER_IMAGE_PLACEHOLDER = \"(image omitted: model does not support images)\";\nconst NON_VISION_TOOL_IMAGE_PLACEHOLDER = \"(tool image omitted: model does not support images)\";\nconst NON_DOCUMENT_USER_PLACEHOLDER = \"(document omitted: model does not support document input)\";\n\n/**\n * Whether the target model's API adjudicates thinking-block model binding itself.\n *\n * Anthropic's preserved-thinking rules make the API, not the client, the authority on which\n * thinking blocks a model may read: \"Pass blocks back unchanged. Send every assistant turn\n * exactly as you received it, thinking blocks included, and let the API decide which blocks the\n * model can use\", and \"A block that fails the model check is always dropped\" before the prompt\n * reaches the model, unbilled.\n * https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-thinking\n *\n * Flattening those blocks into visible text client-side is both lossy (a newer model that is\n * allowed to read them loses the reasoning) and destabilizing (it rewrites the conversation\n * prefix that later thinking-block signatures are bound to). Models whose generated metadata\n * sets this flag replay foreign signed blocks unchanged instead.\n */\nfunction delegatesThinkingModelBinding<TApi extends Api>(model: Model<TApi>): boolean {\n\tif (model.api !== \"anthropic-messages\") return false;\n\tconst compat = model.compat as AnthropicMessagesCompat | undefined;\n\treturn compat?.delegatesThinkingModelBinding === true;\n}\n\n// Preserves the caller's block union minus images, so a user message keeps its document blocks\n// while a tool result — which cannot carry one — keeps its narrower type.\nfunction replaceImagesWithPlaceholder<TBlock extends { type: string }>(\n\tcontent: TBlock[],\n\tplaceholder: string,\n): (Exclude<TBlock, ImageContent> | TextContent)[] {\n\tconst result: (Exclude<TBlock, ImageContent> | TextContent)[] = [];\n\tlet previousWasPlaceholder = false;\n\n\tfor (const block of content) {\n\t\tif (block.type === \"image\") {\n\t\t\tif (!previousWasPlaceholder) {\n\t\t\t\tresult.push({ type: \"text\", text: placeholder });\n\t\t\t}\n\t\t\tpreviousWasPlaceholder = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst kept = block as Exclude<TBlock, ImageContent>;\n\t\tresult.push(kept);\n\t\tpreviousWasPlaceholder = kept.type === \"text\" && (kept as unknown as TextContent).text === placeholder;\n\t}\n\n\treturn result;\n}\n\nfunction downgradeUnsupportedImages<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"image\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg): Message => {\n\t\tif (msg.role === \"user\" && Array.isArray(msg.content)) {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\tif (msg.role === \"toolResult\") {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\treturn msg;\n\t});\n}\n\n/**\n * Replace document blocks with a visible placeholder for models that cannot receive one.\n *\n * Mirrors the image downgrade: only the Anthropic Messages and Amazon Bedrock Converse paths can\n * serialize a document, so every other provider — and any Claude mirror whose generated `input`\n * omits `\"pdf\"` — gets a marker rather than a silently missing attachment or a rejected request.\n * Documents only ever appear in user messages; tool results stay text/image-only.\n */\nfunction downgradeUnsupportedDocuments<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"pdf\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg) => {\n\t\tif (msg.role !== \"user\" || !Array.isArray(msg.content)) return msg;\n\t\tif (!msg.content.some((block) => block.type === \"document\")) return msg;\n\n\t\tconst content: (TextContent | ImageContent)[] = [];\n\t\tlet previousWasPlaceholder = false;\n\t\tfor (const block of msg.content) {\n\t\t\tif (block.type === \"document\") {\n\t\t\t\tif (!previousWasPlaceholder) content.push({ type: \"text\", text: NON_DOCUMENT_USER_PLACEHOLDER });\n\t\t\t\tpreviousWasPlaceholder = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcontent.push(block);\n\t\t\tpreviousWasPlaceholder = block.type === \"text\" && block.text === NON_DOCUMENT_USER_PLACEHOLDER;\n\t\t}\n\t\treturn { ...msg, content };\n\t});\n}\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\t// Tool calls dropped for preceding a fallback boundary. Their results must go too: a\n\t// `tool_result` whose `tool_use` is absent is rejected, and `ToolResultMessage` is a separate\n\t// top-level message so removing the call alone would orphan it.\n\tconst droppedToolCallIds = new Set<string>();\n\t// Normalize null/undefined content from untyped callers (custom tools, hand-built\n\t// histories, old session files) so downstream code can rely on the type contract.\n\tconst normalizedMessages = messages.map((msg) => (msg.content == null ? { ...msg, content: [] } : msg));\n\tconst imageAwareMessages = downgradeUnsupportedDocuments(\n\t\tdowngradeUnsupportedImages(normalizedMessages, model),\n\t\tmodel,\n\t);\n\n\t// First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)\n\tconst transformed = imageAwareMessages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\t\t\t// A different model on the same provider and API whose API adjudicates model binding\n\t\t\t// itself. Its signed blocks replay unchanged; the API keeps the ones the target model\n\t\t\t// may read and silently drops the rest.\n\t\t\tconst canReplayForeignThinking =\n\t\t\t\t!isSameModel &&\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tdelegatesThinkingModelBinding(model);\n\t\t\tconst preservesThinking = isSameModel || canReplayForeignThinking;\n\n\t\t\t// After a mid-output server-side fallback the turn holds output from two models either\n\t\t\t// side of a `fallback` marker. Anthropic's replay rules: keep the marker \"exactly where\n\t\t\t// it appeared\", keep `text` and everything after it, and drop `thinking`,\n\t\t\t// `redacted_thinking`, and client-side `tool_use` that precede the final marker. A\n\t\t\t// request that echoes thinking from both sides is rejected outright, so this is a\n\t\t\t// correctness requirement rather than a token saving — and it applies even when the\n\t\t\t// blocks would otherwise be replayable.\n\t\t\t// https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback\n\t\t\t// `findLastIndex` needs ES2023; this package targets ES2022.\n\t\t\tlet finalFallbackIndex = -1;\n\t\t\tfor (let i = assistantMsg.content.length - 1; i >= 0; i--) {\n\t\t\t\tif (assistantMsg.content[i].type === \"fallback\") {\n\t\t\t\t\tfinalFallbackIndex = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block, blockIndex) => {\n\t\t\t\tconst precedesFallbackBoundary = finalFallbackIndex >= 0 && blockIndex < finalFallbackIndex;\n\n\t\t\t\tif (block.type === \"fallback\") {\n\t\t\t\t\t// Only the final marker is load-bearing for validation, but earlier ones are\n\t\t\t\t\t// equally \"where they appeared\", so every marker is preserved in place.\n\t\t\t\t\treturn block;\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\tif (precedesFallbackBoundary) return [];\n\t\t\t\t\t// Redacted thinking is opaque encrypted content the client cannot rewrite.\n\t\t\t\t\t// Replay it where the API adjudicates it; otherwise drop it to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn preservesThinking ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// Keep signed thinking blocks for replay even when the thinking text is empty\n\t\t\t\t\t// (OpenAI encrypted reasoning, or Anthropic `display: \"omitted\"`).\n\t\t\t\t\tif (preservesThinking && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (preservesThinking) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t// Client-side `tool_use` before the final fallback marker must be dropped: the\n\t\t\t\t\t// declining model's tool call was never executed by the model that continued.\n\t\t\t\t\t// Record the id so its `toolResult` message is dropped alongside it.\n\t\t\t\t\tif (precedesFallbackBoundary) {\n\t\t\t\t\t\tdroppedToolCallIds.add(block.id);\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Drop the tool results whose calls were removed at a fallback boundary. Anthropic rejects a\n\t// `tool_result` with no matching `tool_use`, so removing the call without its result would\n\t// trade one rejection for another.\n\tconst boundaryFiltered =\n\t\tdroppedToolCallIds.size === 0\n\t\t\t? transformed\n\t\t\t: transformed.filter((msg) => msg.role !== \"toolResult\" || !droppedToolCallIds.has(msg.toolCallId));\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\tconst insertSyntheticToolResults = () => {\n\t\tif (pendingToolCalls.length > 0) {\n\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\tresult.push({\n\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t}\n\t\t\t}\n\t\t\tpendingToolCalls = [];\n\t\t\texistingToolResultIds = new Set();\n\t\t}\n\t};\n\n\tfor (let i = 0; i < boundaryFiltered.length; i++) {\n\t\tconst msg = boundaryFiltered[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tinsertSyntheticToolResults();\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tinsertSyntheticToolResults();\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\t// If the conversation ends with unresolved tool calls, synthesize results now.\n\tinsertSyntheticToolResults();\n\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../src/api/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,GAAG,EACH,gBAAgB,EAEhB,OAAO,EACP,KAAK,EAIL,MAAM,aAAa,CAAC;AA6GrB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,GAAG,EACjD,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,GACxF,OAAO,EAAE,CAgOX","sourcesContent":["import type {\n\tAnthropicMessagesCompat,\n\tApi,\n\tAssistantMessage,\n\tImageContent,\n\tMessage,\n\tModel,\n\tTextContent,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.ts\";\n\nconst NON_VISION_USER_IMAGE_PLACEHOLDER = \"(image omitted: model does not support images)\";\nconst NON_VISION_TOOL_IMAGE_PLACEHOLDER = \"(tool image omitted: model does not support images)\";\nconst NON_DOCUMENT_USER_PLACEHOLDER = \"(document omitted: model does not support document input)\";\n\n/**\n * Whether the target model's API adjudicates thinking-block model binding itself.\n *\n * Anthropic's preserved-thinking rules make the API, not the client, the authority on which\n * thinking blocks a model may read: \"Pass blocks back unchanged. Send every assistant turn\n * exactly as you received it, thinking blocks included, and let the API decide which blocks the\n * model can use\", and \"A block that fails the model check is always dropped\" before the prompt\n * reaches the model, unbilled.\n * https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-thinking\n *\n * Flattening those blocks into visible text client-side is both lossy (a newer model that is\n * allowed to read them loses the reasoning) and destabilizing (it rewrites the conversation\n * prefix that later thinking-block signatures are bound to). Models whose generated metadata\n * sets this flag replay foreign signed blocks unchanged instead.\n */\nfunction delegatesThinkingModelBinding<TApi extends Api>(model: Model<TApi>): boolean {\n\tif (model.api !== \"anthropic-messages\") return false;\n\tconst compat = model.compat as AnthropicMessagesCompat | undefined;\n\treturn compat?.delegatesThinkingModelBinding === true;\n}\n\n// Preserves the caller's block union minus images, so a user message keeps its document blocks\n// while a tool result — which cannot carry one — keeps its narrower type.\nfunction replaceImagesWithPlaceholder<TBlock extends { type: string }>(\n\tcontent: TBlock[],\n\tplaceholder: string,\n): (Exclude<TBlock, ImageContent> | TextContent)[] {\n\tconst result: (Exclude<TBlock, ImageContent> | TextContent)[] = [];\n\tlet previousWasPlaceholder = false;\n\n\tfor (const block of content) {\n\t\tif (block.type === \"image\") {\n\t\t\tif (!previousWasPlaceholder) {\n\t\t\t\tresult.push({ type: \"text\", text: placeholder });\n\t\t\t}\n\t\t\tpreviousWasPlaceholder = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst kept = block as Exclude<TBlock, ImageContent>;\n\t\tresult.push(kept);\n\t\tpreviousWasPlaceholder = kept.type === \"text\" && (kept as unknown as TextContent).text === placeholder;\n\t}\n\n\treturn result;\n}\n\nfunction downgradeUnsupportedImages<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"image\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg): Message => {\n\t\tif (msg.role === \"user\" && Array.isArray(msg.content)) {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\tif (msg.role === \"toolResult\") {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\treturn msg;\n\t});\n}\n\n/**\n * Replace document blocks with a visible placeholder for models that cannot receive one.\n *\n * Mirrors the image downgrade: only the Anthropic Messages and Amazon Bedrock Converse paths can\n * serialize a document, so every other provider — and any Claude mirror whose generated `input`\n * omits `\"pdf\"` — gets a marker rather than a silently missing attachment or a rejected request.\n * Documents only ever appear in user messages; tool results stay text/image-only.\n */\nfunction downgradeUnsupportedDocuments<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"pdf\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg) => {\n\t\tif (msg.role !== \"user\" || !Array.isArray(msg.content)) return msg;\n\t\tif (!msg.content.some((block) => block.type === \"document\")) return msg;\n\n\t\tconst content: (TextContent | ImageContent)[] = [];\n\t\tlet previousWasPlaceholder = false;\n\t\tfor (const block of msg.content) {\n\t\t\tif (block.type === \"document\") {\n\t\t\t\tif (!previousWasPlaceholder) content.push({ type: \"text\", text: NON_DOCUMENT_USER_PLACEHOLDER });\n\t\t\t\tpreviousWasPlaceholder = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcontent.push(block);\n\t\t\tpreviousWasPlaceholder = block.type === \"text\" && block.text === NON_DOCUMENT_USER_PLACEHOLDER;\n\t\t}\n\t\treturn { ...msg, content };\n\t});\n}\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\t// Tool calls dropped for preceding a fallback boundary. Their results must go too: a\n\t// `tool_result` whose `tool_use` is absent is rejected, and `ToolResultMessage` is a separate\n\t// top-level message so removing the call alone would orphan it.\n\tconst droppedToolCallIds = new Set<string>();\n\t// Normalize null/undefined content from untyped callers (custom tools, hand-built\n\t// histories, old session files) so downstream code can rely on the type contract.\n\tconst normalizedMessages = messages.map((msg) => (msg.content == null ? { ...msg, content: [] } : msg));\n\tconst imageAwareMessages = downgradeUnsupportedDocuments(\n\t\tdowngradeUnsupportedImages(normalizedMessages, model),\n\t\tmodel,\n\t);\n\n\t// First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)\n\tconst transformed = imageAwareMessages.map((msg) => {\n\t\t// System and user messages pass through unchanged\n\t\tif (msg.role === \"system\" || msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\t\t\t// A different model on the same provider and API whose API adjudicates model binding\n\t\t\t// itself. Its signed blocks replay unchanged; the API keeps the ones the target model\n\t\t\t// may read and silently drops the rest.\n\t\t\tconst canReplayForeignThinking =\n\t\t\t\t!isSameModel &&\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tdelegatesThinkingModelBinding(model);\n\t\t\tconst preservesThinking = isSameModel || canReplayForeignThinking;\n\n\t\t\t// After a mid-output server-side fallback the turn holds output from two models either\n\t\t\t// side of a `fallback` marker. Anthropic's replay rules: keep the marker \"exactly where\n\t\t\t// it appeared\", keep `text` and everything after it, and drop `thinking`,\n\t\t\t// `redacted_thinking`, and client-side `tool_use` that precede the final marker. A\n\t\t\t// request that echoes thinking from both sides is rejected outright, so this is a\n\t\t\t// correctness requirement rather than a token saving — and it applies even when the\n\t\t\t// blocks would otherwise be replayable.\n\t\t\t// https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback\n\t\t\t// `findLastIndex` needs ES2023; this package targets ES2022.\n\t\t\tlet finalFallbackIndex = -1;\n\t\t\tfor (let i = assistantMsg.content.length - 1; i >= 0; i--) {\n\t\t\t\tif (assistantMsg.content[i].type === \"fallback\") {\n\t\t\t\t\tfinalFallbackIndex = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block, blockIndex) => {\n\t\t\t\tconst precedesFallbackBoundary = finalFallbackIndex >= 0 && blockIndex < finalFallbackIndex;\n\n\t\t\t\tif (block.type === \"fallback\") {\n\t\t\t\t\t// Only the final marker is load-bearing for validation, but earlier ones are\n\t\t\t\t\t// equally \"where they appeared\", so every marker is preserved in place.\n\t\t\t\t\treturn block;\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\tif (precedesFallbackBoundary) return [];\n\t\t\t\t\t// Redacted thinking is opaque encrypted content the client cannot rewrite.\n\t\t\t\t\t// Replay it where the API adjudicates it; otherwise drop it to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn preservesThinking ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// Keep signed thinking blocks for replay even when the thinking text is empty\n\t\t\t\t\t// (OpenAI encrypted reasoning, or Anthropic `display: \"omitted\"`).\n\t\t\t\t\tif (preservesThinking && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (preservesThinking) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t// Client-side `tool_use` before the final fallback marker must be dropped: the\n\t\t\t\t\t// declining model's tool call was never executed by the model that continued.\n\t\t\t\t\t// Record the id so its `toolResult` message is dropped alongside it.\n\t\t\t\t\tif (precedesFallbackBoundary) {\n\t\t\t\t\t\tdroppedToolCallIds.add(block.id);\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Drop the tool results whose calls were removed at a fallback boundary. Anthropic rejects a\n\t// `tool_result` with no matching `tool_use`, so removing the call without its result would\n\t// trade one rejection for another.\n\tconst boundaryFiltered =\n\t\tdroppedToolCallIds.size === 0\n\t\t\t? transformed\n\t\t\t: transformed.filter((msg) => msg.role !== \"toolResult\" || !droppedToolCallIds.has(msg.toolCallId));\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\t// System messages are transparent to tool-call accounting: one that lands between a tool\n\t// call and its results is held back and emitted after the results (synthetic ones\n\t// included), so it never causes a duplicate result for a call that is answered later.\n\tconst heldSystemMessages: Message[] = [];\n\tconst closePendingToolCalls = () => {\n\t\tif (pendingToolCalls.length > 0) {\n\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\tresult.push({\n\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t}\n\t\t\t}\n\t\t\tpendingToolCalls = [];\n\t\t\texistingToolResultIds = new Set();\n\t\t}\n\t\tresult.push(...heldSystemMessages);\n\t\theldSystemMessages.length = 0;\n\t};\n\n\tfor (let i = 0; i < boundaryFiltered.length; i++) {\n\t\tconst msg = boundaryFiltered[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tclosePendingToolCalls();\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"system\") {\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\theldSystemMessages.push(msg);\n\t\t\t} else {\n\t\t\t\tresult.push(msg);\n\t\t\t}\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// A new user turn interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tclosePendingToolCalls();\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\t// If the conversation ends with unresolved tool calls, synthesize results now.\n\tclosePendingToolCalls();\n\n\treturn result;\n}\n"]}
@@ -111,8 +111,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
111
111
  const imageAwareMessages = downgradeUnsupportedDocuments(downgradeUnsupportedImages(normalizedMessages, model), model);
112
112
  // First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)
113
113
  const transformed = imageAwareMessages.map((msg) => {
114
- // User messages pass through unchanged
115
- if (msg.role === "user") {
114
+ // System and user messages pass through unchanged
115
+ if (msg.role === "system" || msg.role === "user") {
116
116
  return msg;
117
117
  }
118
118
  // Handle toolResult messages - normalize toolCallId if we have a mapping
@@ -233,7 +233,11 @@ export function transformMessages(messages, model, normalizeToolCallId) {
233
233
  const result = [];
234
234
  let pendingToolCalls = [];
235
235
  let existingToolResultIds = new Set();
236
- const insertSyntheticToolResults = () => {
236
+ // System messages are transparent to tool-call accounting: one that lands between a tool
237
+ // call and its results is held back and emitted after the results (synthetic ones
238
+ // included), so it never causes a duplicate result for a call that is answered later.
239
+ const heldSystemMessages = [];
240
+ const closePendingToolCalls = () => {
237
241
  if (pendingToolCalls.length > 0) {
238
242
  for (const tc of pendingToolCalls) {
239
243
  if (!existingToolResultIds.has(tc.id)) {
@@ -250,12 +254,14 @@ export function transformMessages(messages, model, normalizeToolCallId) {
250
254
  pendingToolCalls = [];
251
255
  existingToolResultIds = new Set();
252
256
  }
257
+ result.push(...heldSystemMessages);
258
+ heldSystemMessages.length = 0;
253
259
  };
254
260
  for (let i = 0; i < boundaryFiltered.length; i++) {
255
261
  const msg = boundaryFiltered[i];
256
262
  if (msg.role === "assistant") {
257
263
  // If we have pending orphaned tool calls from a previous assistant, insert synthetic results now
258
- insertSyntheticToolResults();
264
+ closePendingToolCalls();
259
265
  // Skip errored/aborted assistant messages entirely.
260
266
  // These are incomplete turns that shouldn't be replayed:
261
267
  // - May have partial content (reasoning without message, incomplete tool calls)
@@ -277,9 +283,17 @@ export function transformMessages(messages, model, normalizeToolCallId) {
277
283
  existingToolResultIds.add(msg.toolCallId);
278
284
  result.push(msg);
279
285
  }
286
+ else if (msg.role === "system") {
287
+ if (pendingToolCalls.length > 0) {
288
+ heldSystemMessages.push(msg);
289
+ }
290
+ else {
291
+ result.push(msg);
292
+ }
293
+ }
280
294
  else if (msg.role === "user") {
281
- // User message interrupts tool flow - insert synthetic results for orphaned calls
282
- insertSyntheticToolResults();
295
+ // A new user turn interrupts tool flow - insert synthetic results for orphaned calls
296
+ closePendingToolCalls();
283
297
  result.push(msg);
284
298
  }
285
299
  else {
@@ -287,7 +301,7 @@ export function transformMessages(messages, model, normalizeToolCallId) {
287
301
  }
288
302
  }
289
303
  // If the conversation ends with unresolved tool calls, synthesize results now.
290
- insertSyntheticToolResults();
304
+ closePendingToolCalls();
291
305
  return result;
292
306
  }
293
307
  //# sourceMappingURL=transform-messages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/api/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAnthropicMessagesCompat,\n\tApi,\n\tAssistantMessage,\n\tImageContent,\n\tMessage,\n\tModel,\n\tTextContent,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.ts\";\n\nconst NON_VISION_USER_IMAGE_PLACEHOLDER = \"(image omitted: model does not support images)\";\nconst NON_VISION_TOOL_IMAGE_PLACEHOLDER = \"(tool image omitted: model does not support images)\";\nconst NON_DOCUMENT_USER_PLACEHOLDER = \"(document omitted: model does not support document input)\";\n\n/**\n * Whether the target model's API adjudicates thinking-block model binding itself.\n *\n * Anthropic's preserved-thinking rules make the API, not the client, the authority on which\n * thinking blocks a model may read: \"Pass blocks back unchanged. Send every assistant turn\n * exactly as you received it, thinking blocks included, and let the API decide which blocks the\n * model can use\", and \"A block that fails the model check is always dropped\" before the prompt\n * reaches the model, unbilled.\n * https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-thinking\n *\n * Flattening those blocks into visible text client-side is both lossy (a newer model that is\n * allowed to read them loses the reasoning) and destabilizing (it rewrites the conversation\n * prefix that later thinking-block signatures are bound to). Models whose generated metadata\n * sets this flag replay foreign signed blocks unchanged instead.\n */\nfunction delegatesThinkingModelBinding<TApi extends Api>(model: Model<TApi>): boolean {\n\tif (model.api !== \"anthropic-messages\") return false;\n\tconst compat = model.compat as AnthropicMessagesCompat | undefined;\n\treturn compat?.delegatesThinkingModelBinding === true;\n}\n\n// Preserves the caller's block union minus images, so a user message keeps its document blocks\n// while a tool result — which cannot carry one — keeps its narrower type.\nfunction replaceImagesWithPlaceholder<TBlock extends { type: string }>(\n\tcontent: TBlock[],\n\tplaceholder: string,\n): (Exclude<TBlock, ImageContent> | TextContent)[] {\n\tconst result: (Exclude<TBlock, ImageContent> | TextContent)[] = [];\n\tlet previousWasPlaceholder = false;\n\n\tfor (const block of content) {\n\t\tif (block.type === \"image\") {\n\t\t\tif (!previousWasPlaceholder) {\n\t\t\t\tresult.push({ type: \"text\", text: placeholder });\n\t\t\t}\n\t\t\tpreviousWasPlaceholder = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst kept = block as Exclude<TBlock, ImageContent>;\n\t\tresult.push(kept);\n\t\tpreviousWasPlaceholder = kept.type === \"text\" && (kept as unknown as TextContent).text === placeholder;\n\t}\n\n\treturn result;\n}\n\nfunction downgradeUnsupportedImages<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"image\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg): Message => {\n\t\tif (msg.role === \"user\" && Array.isArray(msg.content)) {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\tif (msg.role === \"toolResult\") {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\treturn msg;\n\t});\n}\n\n/**\n * Replace document blocks with a visible placeholder for models that cannot receive one.\n *\n * Mirrors the image downgrade: only the Anthropic Messages and Amazon Bedrock Converse paths can\n * serialize a document, so every other provider — and any Claude mirror whose generated `input`\n * omits `\"pdf\"` — gets a marker rather than a silently missing attachment or a rejected request.\n * Documents only ever appear in user messages; tool results stay text/image-only.\n */\nfunction downgradeUnsupportedDocuments<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"pdf\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg) => {\n\t\tif (msg.role !== \"user\" || !Array.isArray(msg.content)) return msg;\n\t\tif (!msg.content.some((block) => block.type === \"document\")) return msg;\n\n\t\tconst content: (TextContent | ImageContent)[] = [];\n\t\tlet previousWasPlaceholder = false;\n\t\tfor (const block of msg.content) {\n\t\t\tif (block.type === \"document\") {\n\t\t\t\tif (!previousWasPlaceholder) content.push({ type: \"text\", text: NON_DOCUMENT_USER_PLACEHOLDER });\n\t\t\t\tpreviousWasPlaceholder = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcontent.push(block);\n\t\t\tpreviousWasPlaceholder = block.type === \"text\" && block.text === NON_DOCUMENT_USER_PLACEHOLDER;\n\t\t}\n\t\treturn { ...msg, content };\n\t});\n}\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\t// Tool calls dropped for preceding a fallback boundary. Their results must go too: a\n\t// `tool_result` whose `tool_use` is absent is rejected, and `ToolResultMessage` is a separate\n\t// top-level message so removing the call alone would orphan it.\n\tconst droppedToolCallIds = new Set<string>();\n\t// Normalize null/undefined content from untyped callers (custom tools, hand-built\n\t// histories, old session files) so downstream code can rely on the type contract.\n\tconst normalizedMessages = messages.map((msg) => (msg.content == null ? { ...msg, content: [] } : msg));\n\tconst imageAwareMessages = downgradeUnsupportedDocuments(\n\t\tdowngradeUnsupportedImages(normalizedMessages, model),\n\t\tmodel,\n\t);\n\n\t// First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)\n\tconst transformed = imageAwareMessages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\t\t\t// A different model on the same provider and API whose API adjudicates model binding\n\t\t\t// itself. Its signed blocks replay unchanged; the API keeps the ones the target model\n\t\t\t// may read and silently drops the rest.\n\t\t\tconst canReplayForeignThinking =\n\t\t\t\t!isSameModel &&\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tdelegatesThinkingModelBinding(model);\n\t\t\tconst preservesThinking = isSameModel || canReplayForeignThinking;\n\n\t\t\t// After a mid-output server-side fallback the turn holds output from two models either\n\t\t\t// side of a `fallback` marker. Anthropic's replay rules: keep the marker \"exactly where\n\t\t\t// it appeared\", keep `text` and everything after it, and drop `thinking`,\n\t\t\t// `redacted_thinking`, and client-side `tool_use` that precede the final marker. A\n\t\t\t// request that echoes thinking from both sides is rejected outright, so this is a\n\t\t\t// correctness requirement rather than a token saving — and it applies even when the\n\t\t\t// blocks would otherwise be replayable.\n\t\t\t// https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback\n\t\t\t// `findLastIndex` needs ES2023; this package targets ES2022.\n\t\t\tlet finalFallbackIndex = -1;\n\t\t\tfor (let i = assistantMsg.content.length - 1; i >= 0; i--) {\n\t\t\t\tif (assistantMsg.content[i].type === \"fallback\") {\n\t\t\t\t\tfinalFallbackIndex = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block, blockIndex) => {\n\t\t\t\tconst precedesFallbackBoundary = finalFallbackIndex >= 0 && blockIndex < finalFallbackIndex;\n\n\t\t\t\tif (block.type === \"fallback\") {\n\t\t\t\t\t// Only the final marker is load-bearing for validation, but earlier ones are\n\t\t\t\t\t// equally \"where they appeared\", so every marker is preserved in place.\n\t\t\t\t\treturn block;\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\tif (precedesFallbackBoundary) return [];\n\t\t\t\t\t// Redacted thinking is opaque encrypted content the client cannot rewrite.\n\t\t\t\t\t// Replay it where the API adjudicates it; otherwise drop it to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn preservesThinking ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// Keep signed thinking blocks for replay even when the thinking text is empty\n\t\t\t\t\t// (OpenAI encrypted reasoning, or Anthropic `display: \"omitted\"`).\n\t\t\t\t\tif (preservesThinking && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (preservesThinking) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t// Client-side `tool_use` before the final fallback marker must be dropped: the\n\t\t\t\t\t// declining model's tool call was never executed by the model that continued.\n\t\t\t\t\t// Record the id so its `toolResult` message is dropped alongside it.\n\t\t\t\t\tif (precedesFallbackBoundary) {\n\t\t\t\t\t\tdroppedToolCallIds.add(block.id);\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Drop the tool results whose calls were removed at a fallback boundary. Anthropic rejects a\n\t// `tool_result` with no matching `tool_use`, so removing the call without its result would\n\t// trade one rejection for another.\n\tconst boundaryFiltered =\n\t\tdroppedToolCallIds.size === 0\n\t\t\t? transformed\n\t\t\t: transformed.filter((msg) => msg.role !== \"toolResult\" || !droppedToolCallIds.has(msg.toolCallId));\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\tconst insertSyntheticToolResults = () => {\n\t\tif (pendingToolCalls.length > 0) {\n\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\tresult.push({\n\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t}\n\t\t\t}\n\t\t\tpendingToolCalls = [];\n\t\t\texistingToolResultIds = new Set();\n\t\t}\n\t};\n\n\tfor (let i = 0; i < boundaryFiltered.length; i++) {\n\t\tconst msg = boundaryFiltered[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tinsertSyntheticToolResults();\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tinsertSyntheticToolResults();\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\t// If the conversation ends with unresolved tool calls, synthesize results now.\n\tinsertSyntheticToolResults();\n\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/api/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAnthropicMessagesCompat,\n\tApi,\n\tAssistantMessage,\n\tImageContent,\n\tMessage,\n\tModel,\n\tTextContent,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.ts\";\n\nconst NON_VISION_USER_IMAGE_PLACEHOLDER = \"(image omitted: model does not support images)\";\nconst NON_VISION_TOOL_IMAGE_PLACEHOLDER = \"(tool image omitted: model does not support images)\";\nconst NON_DOCUMENT_USER_PLACEHOLDER = \"(document omitted: model does not support document input)\";\n\n/**\n * Whether the target model's API adjudicates thinking-block model binding itself.\n *\n * Anthropic's preserved-thinking rules make the API, not the client, the authority on which\n * thinking blocks a model may read: \"Pass blocks back unchanged. Send every assistant turn\n * exactly as you received it, thinking blocks included, and let the API decide which blocks the\n * model can use\", and \"A block that fails the model check is always dropped\" before the prompt\n * reaches the model, unbilled.\n * https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-thinking\n *\n * Flattening those blocks into visible text client-side is both lossy (a newer model that is\n * allowed to read them loses the reasoning) and destabilizing (it rewrites the conversation\n * prefix that later thinking-block signatures are bound to). Models whose generated metadata\n * sets this flag replay foreign signed blocks unchanged instead.\n */\nfunction delegatesThinkingModelBinding<TApi extends Api>(model: Model<TApi>): boolean {\n\tif (model.api !== \"anthropic-messages\") return false;\n\tconst compat = model.compat as AnthropicMessagesCompat | undefined;\n\treturn compat?.delegatesThinkingModelBinding === true;\n}\n\n// Preserves the caller's block union minus images, so a user message keeps its document blocks\n// while a tool result — which cannot carry one — keeps its narrower type.\nfunction replaceImagesWithPlaceholder<TBlock extends { type: string }>(\n\tcontent: TBlock[],\n\tplaceholder: string,\n): (Exclude<TBlock, ImageContent> | TextContent)[] {\n\tconst result: (Exclude<TBlock, ImageContent> | TextContent)[] = [];\n\tlet previousWasPlaceholder = false;\n\n\tfor (const block of content) {\n\t\tif (block.type === \"image\") {\n\t\t\tif (!previousWasPlaceholder) {\n\t\t\t\tresult.push({ type: \"text\", text: placeholder });\n\t\t\t}\n\t\t\tpreviousWasPlaceholder = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst kept = block as Exclude<TBlock, ImageContent>;\n\t\tresult.push(kept);\n\t\tpreviousWasPlaceholder = kept.type === \"text\" && (kept as unknown as TextContent).text === placeholder;\n\t}\n\n\treturn result;\n}\n\nfunction downgradeUnsupportedImages<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"image\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg): Message => {\n\t\tif (msg.role === \"user\" && Array.isArray(msg.content)) {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\tif (msg.role === \"toolResult\") {\n\t\t\treturn {\n\t\t\t\t...msg,\n\t\t\t\tcontent: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER),\n\t\t\t};\n\t\t}\n\n\t\treturn msg;\n\t});\n}\n\n/**\n * Replace document blocks with a visible placeholder for models that cannot receive one.\n *\n * Mirrors the image downgrade: only the Anthropic Messages and Amazon Bedrock Converse paths can\n * serialize a document, so every other provider — and any Claude mirror whose generated `input`\n * omits `\"pdf\"` — gets a marker rather than a silently missing attachment or a rejected request.\n * Documents only ever appear in user messages; tool results stay text/image-only.\n */\nfunction downgradeUnsupportedDocuments<TApi extends Api>(messages: Message[], model: Model<TApi>): Message[] {\n\tif (model.input.includes(\"pdf\")) {\n\t\treturn messages;\n\t}\n\n\treturn messages.map((msg) => {\n\t\tif (msg.role !== \"user\" || !Array.isArray(msg.content)) return msg;\n\t\tif (!msg.content.some((block) => block.type === \"document\")) return msg;\n\n\t\tconst content: (TextContent | ImageContent)[] = [];\n\t\tlet previousWasPlaceholder = false;\n\t\tfor (const block of msg.content) {\n\t\t\tif (block.type === \"document\") {\n\t\t\t\tif (!previousWasPlaceholder) content.push({ type: \"text\", text: NON_DOCUMENT_USER_PLACEHOLDER });\n\t\t\t\tpreviousWasPlaceholder = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcontent.push(block);\n\t\t\tpreviousWasPlaceholder = block.type === \"text\" && block.text === NON_DOCUMENT_USER_PLACEHOLDER;\n\t\t}\n\t\treturn { ...msg, content };\n\t});\n}\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\t// Tool calls dropped for preceding a fallback boundary. Their results must go too: a\n\t// `tool_result` whose `tool_use` is absent is rejected, and `ToolResultMessage` is a separate\n\t// top-level message so removing the call alone would orphan it.\n\tconst droppedToolCallIds = new Set<string>();\n\t// Normalize null/undefined content from untyped callers (custom tools, hand-built\n\t// histories, old session files) so downstream code can rely on the type contract.\n\tconst normalizedMessages = messages.map((msg) => (msg.content == null ? { ...msg, content: [] } : msg));\n\tconst imageAwareMessages = downgradeUnsupportedDocuments(\n\t\tdowngradeUnsupportedImages(normalizedMessages, model),\n\t\tmodel,\n\t);\n\n\t// First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)\n\tconst transformed = imageAwareMessages.map((msg) => {\n\t\t// System and user messages pass through unchanged\n\t\tif (msg.role === \"system\" || msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\t\t\t// A different model on the same provider and API whose API adjudicates model binding\n\t\t\t// itself. Its signed blocks replay unchanged; the API keeps the ones the target model\n\t\t\t// may read and silently drops the rest.\n\t\t\tconst canReplayForeignThinking =\n\t\t\t\t!isSameModel &&\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tdelegatesThinkingModelBinding(model);\n\t\t\tconst preservesThinking = isSameModel || canReplayForeignThinking;\n\n\t\t\t// After a mid-output server-side fallback the turn holds output from two models either\n\t\t\t// side of a `fallback` marker. Anthropic's replay rules: keep the marker \"exactly where\n\t\t\t// it appeared\", keep `text` and everything after it, and drop `thinking`,\n\t\t\t// `redacted_thinking`, and client-side `tool_use` that precede the final marker. A\n\t\t\t// request that echoes thinking from both sides is rejected outright, so this is a\n\t\t\t// correctness requirement rather than a token saving — and it applies even when the\n\t\t\t// blocks would otherwise be replayable.\n\t\t\t// https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback\n\t\t\t// `findLastIndex` needs ES2023; this package targets ES2022.\n\t\t\tlet finalFallbackIndex = -1;\n\t\t\tfor (let i = assistantMsg.content.length - 1; i >= 0; i--) {\n\t\t\t\tif (assistantMsg.content[i].type === \"fallback\") {\n\t\t\t\t\tfinalFallbackIndex = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block, blockIndex) => {\n\t\t\t\tconst precedesFallbackBoundary = finalFallbackIndex >= 0 && blockIndex < finalFallbackIndex;\n\n\t\t\t\tif (block.type === \"fallback\") {\n\t\t\t\t\t// Only the final marker is load-bearing for validation, but earlier ones are\n\t\t\t\t\t// equally \"where they appeared\", so every marker is preserved in place.\n\t\t\t\t\treturn block;\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\tif (precedesFallbackBoundary) return [];\n\t\t\t\t\t// Redacted thinking is opaque encrypted content the client cannot rewrite.\n\t\t\t\t\t// Replay it where the API adjudicates it; otherwise drop it to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn preservesThinking ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// Keep signed thinking blocks for replay even when the thinking text is empty\n\t\t\t\t\t// (OpenAI encrypted reasoning, or Anthropic `display: \"omitted\"`).\n\t\t\t\t\tif (preservesThinking && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (preservesThinking) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t// Client-side `tool_use` before the final fallback marker must be dropped: the\n\t\t\t\t\t// declining model's tool call was never executed by the model that continued.\n\t\t\t\t\t// Record the id so its `toolResult` message is dropped alongside it.\n\t\t\t\t\tif (precedesFallbackBoundary) {\n\t\t\t\t\t\tdroppedToolCallIds.add(block.id);\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Drop the tool results whose calls were removed at a fallback boundary. Anthropic rejects a\n\t// `tool_result` with no matching `tool_use`, so removing the call without its result would\n\t// trade one rejection for another.\n\tconst boundaryFiltered =\n\t\tdroppedToolCallIds.size === 0\n\t\t\t? transformed\n\t\t\t: transformed.filter((msg) => msg.role !== \"toolResult\" || !droppedToolCallIds.has(msg.toolCallId));\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\t// System messages are transparent to tool-call accounting: one that lands between a tool\n\t// call and its results is held back and emitted after the results (synthetic ones\n\t// included), so it never causes a duplicate result for a call that is answered later.\n\tconst heldSystemMessages: Message[] = [];\n\tconst closePendingToolCalls = () => {\n\t\tif (pendingToolCalls.length > 0) {\n\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\tresult.push({\n\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t}\n\t\t\t}\n\t\t\tpendingToolCalls = [];\n\t\t\texistingToolResultIds = new Set();\n\t\t}\n\t\tresult.push(...heldSystemMessages);\n\t\theldSystemMessages.length = 0;\n\t};\n\n\tfor (let i = 0; i < boundaryFiltered.length; i++) {\n\t\tconst msg = boundaryFiltered[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tclosePendingToolCalls();\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"system\") {\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\theldSystemMessages.push(msg);\n\t\t\t} else {\n\t\t\t\tresult.push(msg);\n\t\t\t}\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// A new user turn interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tclosePendingToolCalls();\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\t// If the conversation ends with unresolved tool calls, synthesize results now.\n\tclosePendingToolCalls();\n\n\treturn result;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { ClassifierFunction, ClassifierOptions } from "../types.ts";
2
+ /** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */
3
+ export declare const classify: ClassifierFunction<ClassifierOptions>;
4
+ //# sourceMappingURL=typesafe-system-one.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typesafe-system-one.d.ts","sourceRoot":"","sources":["../../src/api/typesafe-system-one.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBzE,gGAAgG;AAChG,eAAO,MAAM,QAAQ,EAAE,kBAAkB,CAAC,iBAAiB,CACL,CAAC","sourcesContent":["import type { ClassifierFunction, ClassifierOptions } from \"../types.ts\";\nimport { classifySystemOne, isRecord, type SystemOneTransport } from \"./system-one-shared.ts\";\n\n/**\n * TypeSafe's native System One protocol. OpenRouter serves the same protocol,\n * so both providers use this API with different base URLs.\n */\nconst transport: SystemOneTransport = {\n\tapi: \"typesafe-system-one\",\n\tlabel: \"System One API\",\n\turl: (model) => new URL(\"systemone\", `${model.baseUrl.replace(/\\/+$/u, \"\")}/`),\n\tpayload: (model, request) => ({ model: model.id, ...request }),\n\tanswers: (body) => {\n\t\tif (!isRecord(body)) throw new Error(\"System One API returned an unexpected response\");\n\t\treturn body.answers;\n\t},\n};\n\n/** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */\nexport const classify: ClassifierFunction<ClassifierOptions> = (model, context, options) =>\n\tclassifySystemOne(transport, model, context, options);\n"]}
@@ -0,0 +1,19 @@
1
+ import { classifySystemOne, isRecord } from "./system-one-shared.js";
2
+ /**
3
+ * TypeSafe's native System One protocol. OpenRouter serves the same protocol,
4
+ * so both providers use this API with different base URLs.
5
+ */
6
+ const transport = {
7
+ api: "typesafe-system-one",
8
+ label: "System One API",
9
+ url: (model) => new URL("systemone", `${model.baseUrl.replace(/\/+$/u, "")}/`),
10
+ payload: (model, request) => ({ model: model.id, ...request }),
11
+ answers: (body) => {
12
+ if (!isRecord(body))
13
+ throw new Error("System One API returned an unexpected response");
14
+ return body.answers;
15
+ },
16
+ };
17
+ /** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */
18
+ export const classify = (model, context, options) => classifySystemOne(transport, model, context, options);
19
+ //# sourceMappingURL=typesafe-system-one.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typesafe-system-one.js","sourceRoot":"","sources":["../../src/api/typesafe-system-one.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAA2B,MAAM,wBAAwB,CAAC;AAE9F;;;GAGG;AACH,MAAM,SAAS,GAAuB;IACrC,GAAG,EAAE,qBAAqB;IAC1B,KAAK,EAAE,gBAAgB;IACvB,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC;IAC9E,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;IAC9D,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC,OAAO,CAAC;IAAA,CACpB;CACD,CAAC;AAEF,gGAAgG;AAChG,MAAM,CAAC,MAAM,QAAQ,GAA0C,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAC1F,iBAAiB,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC","sourcesContent":["import type { ClassifierFunction, ClassifierOptions } from \"../types.ts\";\nimport { classifySystemOne, isRecord, type SystemOneTransport } from \"./system-one-shared.ts\";\n\n/**\n * TypeSafe's native System One protocol. OpenRouter serves the same protocol,\n * so both providers use this API with different base URLs.\n */\nconst transport: SystemOneTransport = {\n\tapi: \"typesafe-system-one\",\n\tlabel: \"System One API\",\n\turl: (model) => new URL(\"systemone\", `${model.baseUrl.replace(/\\/+$/u, \"\")}/`),\n\tpayload: (model, request) => ({ model: model.id, ...request }),\n\tanswers: (body) => {\n\t\tif (!isRecord(body)) throw new Error(\"System One API returned an unexpected response\");\n\t\treturn body.answers;\n\t},\n};\n\n/** TypeSafe System One classification with public `bool` values mapped to wire-level `noul`. */\nexport const classify: ClassifierFunction<ClassifierOptions> = (model, context, options) =>\n\tclassifySystemOne(transport, model, context, options);\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ProviderClassifier } from "../types.ts";
2
+ export declare const typesafeSystemOneApi: () => ProviderClassifier;
3
+ //# sourceMappingURL=typesafe-system-one.lazy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typesafe-system-one.lazy.d.ts","sourceRoot":"","sources":["../../src/api/typesafe-system-one.lazy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,eAAO,MAAM,oBAAoB,0BAG/B,CAAC","sourcesContent":["import type { ProviderClassifier } from \"../types.ts\";\n\nexport const typesafeSystemOneApi = (): ProviderClassifier => ({\n\tclassify: async (model, context, options) =>\n\t\t(await import(\"./typesafe-system-one.ts\")).classify(model, context, options),\n});\n"]}
@@ -0,0 +1,4 @@
1
+ export const typesafeSystemOneApi = () => ({
2
+ classify: async (model, context, options) => (await import("./typesafe-system-one.js")).classify(model, context, options),
3
+ });
4
+ //# sourceMappingURL=typesafe-system-one.lazy.js.map