@fgv/ts-extras 5.1.0-4 → 5.1.0-40

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 (368) hide show
  1. package/dist/index.browser.js +4 -2
  2. package/dist/index.browser.js.map +1 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/packlets/ai-assist/apiClient.js +886 -158
  5. package/dist/packlets/ai-assist/apiClient.js.map +1 -0
  6. package/dist/packlets/ai-assist/chatRequestBuilders.js +282 -0
  7. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  8. package/dist/packlets/ai-assist/converters.js +47 -3
  9. package/dist/packlets/ai-assist/converters.js.map +1 -0
  10. package/dist/packlets/ai-assist/embeddingClient.js +345 -0
  11. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -0
  12. package/dist/packlets/ai-assist/endpoint.js +78 -0
  13. package/dist/packlets/ai-assist/endpoint.js.map +1 -0
  14. package/dist/packlets/ai-assist/http.js +75 -0
  15. package/dist/packlets/ai-assist/http.js.map +1 -0
  16. package/dist/packlets/ai-assist/imageOptionsResolver.js +193 -0
  17. package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  18. package/dist/packlets/ai-assist/index.js +10 -4
  19. package/dist/packlets/ai-assist/index.js.map +1 -0
  20. package/dist/packlets/ai-assist/jsonCompletion.js +93 -0
  21. package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -0
  22. package/dist/packlets/ai-assist/jsonResponse.js +149 -0
  23. package/dist/packlets/ai-assist/jsonResponse.js.map +1 -0
  24. package/dist/packlets/ai-assist/model.js +193 -9
  25. package/dist/packlets/ai-assist/model.js.map +1 -0
  26. package/dist/packlets/ai-assist/registry.js +314 -12
  27. package/dist/packlets/ai-assist/registry.js.map +1 -0
  28. package/dist/packlets/ai-assist/sseParser.js +123 -0
  29. package/dist/packlets/ai-assist/sseParser.js.map +1 -0
  30. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +368 -0
  31. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  32. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +687 -0
  33. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  34. package/dist/packlets/ai-assist/streamingAdapters/common.js +187 -0
  35. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  36. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +204 -0
  37. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  38. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +170 -0
  39. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  40. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +386 -0
  41. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  42. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +173 -0
  43. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  44. package/dist/packlets/ai-assist/streamingClient.js +140 -0
  45. package/dist/packlets/ai-assist/streamingClient.js.map +1 -0
  46. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +288 -0
  47. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  48. package/dist/packlets/ai-assist/toolFormats.js +106 -10
  49. package/dist/packlets/ai-assist/toolFormats.js.map +1 -0
  50. package/dist/packlets/conversion/converters.js +35 -1
  51. package/dist/packlets/conversion/converters.js.map +1 -0
  52. package/dist/packlets/conversion/index.js.map +1 -0
  53. package/dist/packlets/crypto-utils/constants.js.map +1 -0
  54. package/dist/packlets/crypto-utils/converters.js +42 -4
  55. package/dist/packlets/crypto-utils/converters.js.map +1 -0
  56. package/dist/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  57. package/dist/packlets/crypto-utils/encryptedFile.js.map +1 -0
  58. package/dist/packlets/crypto-utils/hpkeProvider.js +333 -0
  59. package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  60. package/dist/packlets/crypto-utils/index.browser.js +10 -2
  61. package/dist/packlets/crypto-utils/index.browser.js.map +1 -0
  62. package/dist/packlets/crypto-utils/index.js +6 -0
  63. package/dist/packlets/crypto-utils/index.js.map +1 -0
  64. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +71 -0
  65. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  66. package/dist/packlets/crypto-utils/keystore/converters.js +103 -11
  67. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -0
  68. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +287 -0
  69. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  70. package/dist/packlets/crypto-utils/keystore/index.browser.js +36 -0
  71. package/dist/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  72. package/dist/packlets/crypto-utils/keystore/index.js +3 -0
  73. package/dist/packlets/crypto-utils/keystore/index.js.map +1 -0
  74. package/dist/packlets/crypto-utils/keystore/keyStore.js +633 -118
  75. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  76. package/dist/packlets/crypto-utils/keystore/model.js +22 -1
  77. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -0
  78. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js +21 -0
  79. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  80. package/dist/packlets/crypto-utils/model.js +32 -0
  81. package/dist/packlets/crypto-utils/model.js.map +1 -0
  82. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +270 -1
  83. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  84. package/dist/packlets/crypto-utils/spkiHelpers.js +209 -0
  85. package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -0
  86. package/dist/packlets/csv/csvFileHelpers.js +0 -14
  87. package/dist/packlets/csv/csvFileHelpers.js.map +1 -0
  88. package/dist/packlets/csv/csvHelpers.js +14 -0
  89. package/dist/packlets/csv/csvHelpers.js.map +1 -0
  90. package/dist/packlets/csv/index.browser.js +1 -3
  91. package/dist/packlets/csv/index.browser.js.map +1 -0
  92. package/dist/packlets/csv/index.js.map +1 -0
  93. package/dist/packlets/experimental/extendedArray.js.map +1 -0
  94. package/dist/packlets/experimental/formatter.js.map +1 -0
  95. package/dist/packlets/experimental/index.js.map +1 -0
  96. package/dist/packlets/experimental/rangeOf.js.map +1 -0
  97. package/dist/packlets/hash/index.browser.js.map +1 -0
  98. package/dist/packlets/hash/index.js.map +1 -0
  99. package/dist/packlets/hash/index.node.js.map +1 -0
  100. package/dist/packlets/hash/md5Normalizer.browser.js.map +1 -0
  101. package/dist/packlets/hash/md5Normalizer.js.map +1 -0
  102. package/dist/packlets/mustache/index.js.map +1 -0
  103. package/dist/packlets/mustache/interfaces.js.map +1 -0
  104. package/dist/packlets/mustache/mustacheTemplate.js +42 -4
  105. package/dist/packlets/mustache/mustacheTemplate.js.map +1 -0
  106. package/dist/packlets/record-jar/index.browser.js +1 -3
  107. package/dist/packlets/record-jar/index.browser.js.map +1 -0
  108. package/dist/packlets/record-jar/index.js.map +1 -0
  109. package/dist/packlets/record-jar/recordJarFileHelpers.js +0 -18
  110. package/dist/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  111. package/dist/packlets/record-jar/recordJarHelpers.js +18 -0
  112. package/dist/packlets/record-jar/recordJarHelpers.js.map +1 -0
  113. package/dist/packlets/yaml/converters.js.map +1 -0
  114. package/dist/packlets/yaml/index.js +1 -0
  115. package/dist/packlets/yaml/index.js.map +1 -0
  116. package/dist/packlets/yaml/serializers.js +48 -0
  117. package/dist/packlets/yaml/serializers.js.map +1 -0
  118. package/dist/packlets/zip-file-tree/index.js.map +1 -0
  119. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  120. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  121. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  122. package/dist/ts-extras.d.ts +3880 -105
  123. package/dist/tsdoc-metadata.json +1 -1
  124. package/lib/index.browser.d.ts +4 -2
  125. package/lib/index.browser.d.ts.map +1 -0
  126. package/lib/index.browser.js +8 -3
  127. package/lib/index.browser.js.map +1 -0
  128. package/lib/index.d.ts.map +1 -0
  129. package/lib/index.js.map +1 -0
  130. package/lib/packlets/ai-assist/apiClient.d.ts +115 -32
  131. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -0
  132. package/lib/packlets/ai-assist/apiClient.js +888 -156
  133. package/lib/packlets/ai-assist/apiClient.js.map +1 -0
  134. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +149 -0
  135. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -0
  136. package/lib/packlets/ai-assist/chatRequestBuilders.js +293 -0
  137. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  138. package/lib/packlets/ai-assist/converters.d.ts +15 -1
  139. package/lib/packlets/ai-assist/converters.d.ts.map +1 -0
  140. package/lib/packlets/ai-assist/converters.js +47 -3
  141. package/lib/packlets/ai-assist/converters.js.map +1 -0
  142. package/lib/packlets/ai-assist/embeddingClient.d.ts +69 -0
  143. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -0
  144. package/lib/packlets/ai-assist/embeddingClient.js +349 -0
  145. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -0
  146. package/lib/packlets/ai-assist/endpoint.d.ts +28 -0
  147. package/lib/packlets/ai-assist/endpoint.d.ts.map +1 -0
  148. package/lib/packlets/ai-assist/endpoint.js +82 -0
  149. package/lib/packlets/ai-assist/endpoint.js.map +1 -0
  150. package/lib/packlets/ai-assist/http.d.ts +24 -0
  151. package/lib/packlets/ai-assist/http.d.ts.map +1 -0
  152. package/lib/packlets/ai-assist/http.js +78 -0
  153. package/lib/packlets/ai-assist/http.js.map +1 -0
  154. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +66 -0
  155. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -0
  156. package/lib/packlets/ai-assist/imageOptionsResolver.js +197 -0
  157. package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  158. package/lib/packlets/ai-assist/index.d.ts +10 -4
  159. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  160. package/lib/packlets/ai-assist/index.js +36 -1
  161. package/lib/packlets/ai-assist/index.js.map +1 -0
  162. package/lib/packlets/ai-assist/jsonCompletion.d.ts +93 -0
  163. package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -0
  164. package/lib/packlets/ai-assist/jsonCompletion.js +97 -0
  165. package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -0
  166. package/lib/packlets/ai-assist/jsonResponse.d.ts +91 -0
  167. package/lib/packlets/ai-assist/jsonResponse.d.ts.map +1 -0
  168. package/lib/packlets/ai-assist/jsonResponse.js +154 -0
  169. package/lib/packlets/ai-assist/jsonResponse.js.map +1 -0
  170. package/lib/packlets/ai-assist/model.d.ts +1202 -12
  171. package/lib/packlets/ai-assist/model.d.ts.map +1 -0
  172. package/lib/packlets/ai-assist/model.js +198 -10
  173. package/lib/packlets/ai-assist/model.js.map +1 -0
  174. package/lib/packlets/ai-assist/registry.d.ts +56 -1
  175. package/lib/packlets/ai-assist/registry.d.ts.map +1 -0
  176. package/lib/packlets/ai-assist/registry.js +319 -13
  177. package/lib/packlets/ai-assist/registry.js.map +1 -0
  178. package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
  179. package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -0
  180. package/lib/packlets/ai-assist/sseParser.js +128 -0
  181. package/lib/packlets/ai-assist/sseParser.js.map +1 -0
  182. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +72 -0
  183. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -0
  184. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +371 -0
  185. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  186. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +260 -0
  187. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
  188. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +694 -0
  189. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  190. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +154 -0
  191. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -0
  192. package/lib/packlets/ai-assist/streamingAdapters/common.js +193 -0
  193. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  194. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +43 -0
  195. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -0
  196. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +207 -0
  197. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  198. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +19 -0
  199. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -0
  200. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +173 -0
  201. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  202. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +33 -0
  203. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -0
  204. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +389 -0
  205. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  206. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
  207. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -0
  208. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +176 -0
  209. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  210. package/lib/packlets/ai-assist/streamingClient.d.ts +50 -0
  211. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -0
  212. package/lib/packlets/ai-assist/streamingClient.js +146 -0
  213. package/lib/packlets/ai-assist/streamingClient.js.map +1 -0
  214. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +87 -0
  215. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -0
  216. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +294 -0
  217. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  218. package/lib/packlets/ai-assist/toolFormats.d.ts +40 -9
  219. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -0
  220. package/lib/packlets/ai-assist/toolFormats.js +107 -10
  221. package/lib/packlets/ai-assist/toolFormats.js.map +1 -0
  222. package/lib/packlets/conversion/converters.d.ts +8 -1
  223. package/lib/packlets/conversion/converters.d.ts.map +1 -0
  224. package/lib/packlets/conversion/converters.js +36 -2
  225. package/lib/packlets/conversion/converters.js.map +1 -0
  226. package/lib/packlets/conversion/index.d.ts.map +1 -0
  227. package/lib/packlets/conversion/index.js.map +1 -0
  228. package/lib/packlets/crypto-utils/constants.d.ts.map +1 -0
  229. package/lib/packlets/crypto-utils/constants.js.map +1 -0
  230. package/lib/packlets/crypto-utils/converters.d.ts +20 -1
  231. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -0
  232. package/lib/packlets/crypto-utils/converters.js +43 -5
  233. package/lib/packlets/crypto-utils/converters.js.map +1 -0
  234. package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts.map +1 -0
  235. package/lib/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  236. package/lib/packlets/crypto-utils/encryptedFile.d.ts.map +1 -0
  237. package/lib/packlets/crypto-utils/encryptedFile.js.map +1 -0
  238. package/lib/packlets/crypto-utils/hpkeProvider.d.ts +142 -0
  239. package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
  240. package/lib/packlets/crypto-utils/hpkeProvider.js +337 -0
  241. package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  242. package/lib/packlets/crypto-utils/index.browser.d.ts +4 -1
  243. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -0
  244. package/lib/packlets/crypto-utils/index.browser.js +21 -3
  245. package/lib/packlets/crypto-utils/index.browser.js.map +1 -0
  246. package/lib/packlets/crypto-utils/index.d.ts +3 -0
  247. package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
  248. package/lib/packlets/crypto-utils/index.js +17 -1
  249. package/lib/packlets/crypto-utils/index.js.map +1 -0
  250. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +54 -0
  251. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -0
  252. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +74 -0
  253. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  254. package/lib/packlets/crypto-utils/keystore/converters.d.ts +68 -6
  255. package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -0
  256. package/lib/packlets/crypto-utils/keystore/converters.js +101 -9
  257. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -0
  258. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts +148 -0
  259. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts.map +1 -0
  260. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +324 -0
  261. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  262. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts +10 -0
  263. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts.map +1 -0
  264. package/lib/packlets/crypto-utils/keystore/index.browser.js +76 -0
  265. package/lib/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  266. package/lib/packlets/crypto-utils/keystore/index.d.ts +2 -0
  267. package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -0
  268. package/lib/packlets/crypto-utils/keystore/index.js +5 -1
  269. package/lib/packlets/crypto-utils/keystore/index.js.map +1 -0
  270. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +205 -13
  271. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -0
  272. package/lib/packlets/crypto-utils/keystore/keyStore.js +639 -124
  273. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  274. package/lib/packlets/crypto-utils/keystore/model.d.ts +275 -19
  275. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -0
  276. package/lib/packlets/crypto-utils/keystore/model.js +24 -2
  277. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -0
  278. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +53 -0
  279. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -0
  280. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js +22 -0
  281. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  282. package/lib/packlets/crypto-utils/model.d.ts +348 -10
  283. package/lib/packlets/crypto-utils/model.d.ts.map +1 -0
  284. package/lib/packlets/crypto-utils/model.js +33 -1
  285. package/lib/packlets/crypto-utils/model.js.map +1 -0
  286. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +110 -2
  287. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -0
  288. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +269 -0
  289. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  290. package/lib/packlets/crypto-utils/spkiHelpers.d.ts +110 -0
  291. package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -0
  292. package/lib/packlets/crypto-utils/spkiHelpers.js +219 -0
  293. package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -0
  294. package/lib/packlets/csv/csvFileHelpers.d.ts +0 -10
  295. package/lib/packlets/csv/csvFileHelpers.d.ts.map +1 -0
  296. package/lib/packlets/csv/csvFileHelpers.js +0 -15
  297. package/lib/packlets/csv/csvFileHelpers.js.map +1 -0
  298. package/lib/packlets/csv/csvHelpers.d.ts +10 -0
  299. package/lib/packlets/csv/csvHelpers.d.ts.map +1 -0
  300. package/lib/packlets/csv/csvHelpers.js +15 -0
  301. package/lib/packlets/csv/csvHelpers.js.map +1 -0
  302. package/lib/packlets/csv/index.browser.d.ts +0 -1
  303. package/lib/packlets/csv/index.browser.d.ts.map +1 -0
  304. package/lib/packlets/csv/index.browser.js +1 -5
  305. package/lib/packlets/csv/index.browser.js.map +1 -0
  306. package/lib/packlets/csv/index.d.ts.map +1 -0
  307. package/lib/packlets/csv/index.js.map +1 -0
  308. package/lib/packlets/experimental/extendedArray.d.ts.map +1 -0
  309. package/lib/packlets/experimental/extendedArray.js.map +1 -0
  310. package/lib/packlets/experimental/formatter.d.ts.map +1 -0
  311. package/lib/packlets/experimental/formatter.js.map +1 -0
  312. package/lib/packlets/experimental/index.d.ts.map +1 -0
  313. package/lib/packlets/experimental/index.js.map +1 -0
  314. package/lib/packlets/experimental/rangeOf.d.ts.map +1 -0
  315. package/lib/packlets/experimental/rangeOf.js.map +1 -0
  316. package/lib/packlets/hash/index.browser.d.ts.map +1 -0
  317. package/lib/packlets/hash/index.browser.js.map +1 -0
  318. package/lib/packlets/hash/index.d.ts.map +1 -0
  319. package/lib/packlets/hash/index.js.map +1 -0
  320. package/lib/packlets/hash/index.node.d.ts.map +1 -0
  321. package/lib/packlets/hash/index.node.js.map +1 -0
  322. package/lib/packlets/hash/md5Normalizer.browser.d.ts.map +1 -0
  323. package/lib/packlets/hash/md5Normalizer.browser.js.map +1 -0
  324. package/lib/packlets/hash/md5Normalizer.d.ts.map +1 -0
  325. package/lib/packlets/hash/md5Normalizer.js.map +1 -0
  326. package/lib/packlets/mustache/index.d.ts +1 -1
  327. package/lib/packlets/mustache/index.d.ts.map +1 -0
  328. package/lib/packlets/mustache/index.js.map +1 -0
  329. package/lib/packlets/mustache/interfaces.d.ts +34 -0
  330. package/lib/packlets/mustache/interfaces.d.ts.map +1 -0
  331. package/lib/packlets/mustache/interfaces.js.map +1 -0
  332. package/lib/packlets/mustache/mustacheTemplate.d.ts +2 -0
  333. package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -0
  334. package/lib/packlets/mustache/mustacheTemplate.js +42 -4
  335. package/lib/packlets/mustache/mustacheTemplate.js.map +1 -0
  336. package/lib/packlets/record-jar/index.browser.d.ts +0 -1
  337. package/lib/packlets/record-jar/index.browser.d.ts.map +1 -0
  338. package/lib/packlets/record-jar/index.browser.js +1 -5
  339. package/lib/packlets/record-jar/index.browser.js.map +1 -0
  340. package/lib/packlets/record-jar/index.d.ts.map +1 -0
  341. package/lib/packlets/record-jar/index.js.map +1 -0
  342. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts +0 -11
  343. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts.map +1 -0
  344. package/lib/packlets/record-jar/recordJarFileHelpers.js +0 -19
  345. package/lib/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  346. package/lib/packlets/record-jar/recordJarHelpers.d.ts +11 -0
  347. package/lib/packlets/record-jar/recordJarHelpers.d.ts.map +1 -0
  348. package/lib/packlets/record-jar/recordJarHelpers.js +19 -0
  349. package/lib/packlets/record-jar/recordJarHelpers.js.map +1 -0
  350. package/lib/packlets/yaml/converters.d.ts.map +1 -0
  351. package/lib/packlets/yaml/converters.js.map +1 -0
  352. package/lib/packlets/yaml/index.d.ts +1 -0
  353. package/lib/packlets/yaml/index.d.ts.map +1 -0
  354. package/lib/packlets/yaml/index.js +1 -0
  355. package/lib/packlets/yaml/index.js.map +1 -0
  356. package/lib/packlets/yaml/serializers.d.ts +45 -0
  357. package/lib/packlets/yaml/serializers.d.ts.map +1 -0
  358. package/lib/packlets/yaml/serializers.js +84 -0
  359. package/lib/packlets/yaml/serializers.js.map +1 -0
  360. package/lib/packlets/zip-file-tree/index.d.ts.map +1 -0
  361. package/lib/packlets/zip-file-tree/index.js.map +1 -0
  362. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +2 -2
  363. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts.map +1 -0
  364. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  365. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  366. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts.map +1 -0
  367. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  368. package/package.json +20 -15
@@ -19,55 +19,135 @@
19
19
  // SOFTWARE.
20
20
  /**
21
21
  * Chat completion client for AI assist with support for multiple provider APIs.
22
- *
23
22
  * Supports OpenAI-compatible providers (xAI, OpenAI, Groq, Mistral) directly,
24
- * plus adapters for Anthropic and Google Gemini.
25
- *
26
- * When server-side tools (e.g. web_search) are configured, providers that support
27
- * them will include tool configuration in the request and handle tool-augmented
28
- * responses.
23
+ * plus adapters for Anthropic and Google Gemini. When server-side tools (e.g.
24
+ * web_search) are configured, providers that support them include tool
25
+ * configuration in the request and handle tool-augmented responses.
29
26
  *
30
27
  * @packageDocumentation
31
28
  */
32
29
  import { isJsonObject } from '@fgv/ts-json-base';
33
- import { fail, succeed, Validators } from '@fgv/ts-utils';
34
- import { resolveModel } from './model';
30
+ import { fail, mapResults, succeed, Validators } from '@fgv/ts-utils';
31
+ import { allModelCapabilities, isResponsesOnlyModel, resolveProviderModel } from './model';
32
+ import { anthropicEffortToBudgetTokens, checkTemperatureConflict, mergeThinkingConfig, providerDiscriminatorForId } from './thinkingOptionsResolver';
33
+ import { buildAnthropicMessages, buildGeminiContents, buildMessages, buildOpenAiChatUserContent, buildOpenAiResponsesUserContent, normalizeOutboundMessages, splitChatRequest } from './chatRequestBuilders';
34
+ import { bearerAuthHeader, resolveEffectiveBaseUrl } from './endpoint';
35
+ import { fetchJson } from './http';
36
+ import { DEFAULT_MODEL_CAPABILITY_CONFIG, resolveImageCapability, supportsImageGeneration } from './registry';
37
+ import { resolveImageOptions, validateResolvedOptions } from './imageOptionsResolver';
35
38
  import { toAnthropicTools, toGeminiTools, toResponsesApiTools } from './toolFormats';
36
39
  // ============================================================================
37
40
  // Shared helpers
38
41
  // ============================================================================
39
42
  /**
40
- * Builds the messages array from prompt + optional correction messages.
43
+ * Makes a multipart/form-data POST request and returns the parsed JSON, or a
44
+ * failure. The Content-Type header (with boundary) is set automatically by
45
+ * `fetch` from the `FormData` body — callers must NOT pass it explicitly.
41
46
  * @internal
42
47
  */
43
- function buildMessages(prompt, additionalMessages) {
44
- const messages = [
45
- { role: 'system', content: prompt.system },
46
- { role: 'user', content: prompt.user }
47
- ];
48
- if (additionalMessages) {
49
- for (const msg of additionalMessages) {
50
- messages.push({ role: msg.role, content: msg.content });
48
+ async function fetchMultipart(url, headers, body, logger, signal) {
49
+ /* c8 ignore next 1 - optional logger */
50
+ logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: POST ${url} (multipart)`);
51
+ let response;
52
+ try {
53
+ response = await fetch(url, {
54
+ method: 'POST',
55
+ headers,
56
+ body,
57
+ signal
58
+ });
59
+ }
60
+ catch (err) {
61
+ /* c8 ignore next 1 - defensive: fetch errors are always Error instances in practice */
62
+ const detail = err instanceof Error ? err.message : String(err);
63
+ /* c8 ignore next 1 - optional logger */
64
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
65
+ return fail(`AI API request failed: ${detail}`);
66
+ }
67
+ if (!response.ok) {
68
+ const errorText = await response.text().catch(() => 'unknown error');
69
+ /* c8 ignore next 1 - optional logger */
70
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI API returned ${response.status}: ${errorText}`);
71
+ return fail(`AI API returned ${response.status}: ${errorText}`);
72
+ }
73
+ /* c8 ignore next 1 - optional logger */
74
+ logger === null || logger === void 0 ? void 0 : logger.detail(`AI API response: ${response.status}`);
75
+ let json;
76
+ try {
77
+ json = await response.json();
78
+ }
79
+ catch /* c8 ignore start - defensive: response.json() failure on a 2xx */ (_a) {
80
+ logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
81
+ return fail('AI API returned invalid JSON response');
82
+ } /* c8 ignore stop */
83
+ /* c8 ignore next 5 - defensive: provider returning non-object JSON on a 2xx */
84
+ if (!isJsonObject(json)) {
85
+ logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
86
+ return fail('AI API returned non-object JSON response');
87
+ }
88
+ return succeed(json);
89
+ }
90
+ /**
91
+ * Decodes a base64-encoded image attachment into a `Blob` suitable for use as
92
+ * a multipart file field. On Node hands the `Buffer` straight to `Blob`
93
+ * (Buffer extends Uint8Array) to skip an intermediate copy; falls back to
94
+ * `atob` in browsers. Inputs come from `FileReader` or prior provider
95
+ * responses, which are trusted to be valid. Note that Node's
96
+ * `Buffer.from(..., 'base64')` silently strips invalid characters rather
97
+ * than throwing, so failures are only observable in the browser path.
98
+ * @internal
99
+ */
100
+ function attachmentToBlob(attachment) {
101
+ if (typeof Buffer !== 'undefined') {
102
+ return succeed(new Blob([Buffer.from(attachment.base64, 'base64')], { type: attachment.mimeType }));
103
+ }
104
+ /* c8 ignore start - Browser-only fallback cannot be tested in Node.js environment */
105
+ try {
106
+ const binary = atob(attachment.base64);
107
+ const bytes = new Uint8Array(binary.length);
108
+ for (let i = 0; i < binary.length; i++) {
109
+ bytes[i] = binary.charCodeAt(i);
51
110
  }
111
+ return succeed(new Blob([bytes], { type: attachment.mimeType }));
52
112
  }
53
- return messages;
113
+ catch (e) {
114
+ const message = e instanceof Error ? e.message : String(e);
115
+ return fail(`Invalid base64: ${message}`);
116
+ }
117
+ /* c8 ignore stop */
54
118
  }
55
119
  /**
56
- * Makes an HTTP request and returns the parsed JSON, or a failure.
120
+ * Maps a MIME type to a sensible file extension for multipart filenames.
57
121
  * @internal
58
122
  */
59
- async function fetchJson(url, headers, body, logger) {
123
+ function extensionForMimeType(mimeType) {
124
+ switch (mimeType) {
125
+ case 'image/png':
126
+ return 'png';
127
+ case 'image/jpeg':
128
+ case 'image/jpg':
129
+ return 'jpg';
130
+ case 'image/webp':
131
+ return 'webp';
132
+ case 'image/gif':
133
+ return 'gif';
134
+ default:
135
+ return 'bin';
136
+ }
137
+ }
138
+ /**
139
+ * Makes an HTTP GET request and returns the parsed JSON, or a failure.
140
+ * @internal
141
+ */
142
+ async function fetchGetJson(url, headers, logger, signal) {
60
143
  /* c8 ignore next 1 - optional logger */
61
- logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: POST ${url}`);
144
+ logger === null || logger === void 0 ? void 0 : logger.detail(`AI API request: GET ${url}`);
62
145
  let response;
63
146
  try {
64
- response = await fetch(url, {
65
- method: 'POST',
66
- headers: Object.assign({ 'Content-Type': 'application/json' }, headers),
67
- body: JSON.stringify(body)
68
- });
147
+ response = await fetch(url, { method: 'GET', headers, signal });
69
148
  }
70
149
  catch (err) {
150
+ /* c8 ignore next 1 - defensive: fetch errors are always Error instances in practice */
71
151
  const detail = err instanceof Error ? err.message : String(err);
72
152
  /* c8 ignore next 1 - optional logger */
73
153
  logger === null || logger === void 0 ? void 0 : logger.error(`AI API request failed: ${detail}`);
@@ -85,13 +165,12 @@ async function fetchJson(url, headers, body, logger) {
85
165
  try {
86
166
  json = await response.json();
87
167
  }
88
- catch (_a) {
89
- /* c8 ignore next 1 - optional logger */
168
+ catch /* c8 ignore start - defensive: response.json() failure on a 2xx */ (_a) {
90
169
  logger === null || logger === void 0 ? void 0 : logger.error('AI API returned invalid JSON response');
91
170
  return fail('AI API returned invalid JSON response');
92
- }
171
+ } /* c8 ignore stop */
172
+ /* c8 ignore next 5 - defensive: provider returning non-object JSON on a 2xx */
93
173
  if (!isJsonObject(json)) {
94
- /* c8 ignore next 1 - optional logger */
95
174
  logger === null || logger === void 0 ? void 0 : logger.error('AI API returned non-object JSON response');
96
175
  return fail('AI API returned non-object JSON response');
97
176
  }
@@ -121,13 +200,6 @@ const responsesApiResponse = Validators.object({
121
200
  output: Validators.arrayOf(responsesApiOutputItem).withConstraint((arr) => arr.length > 0),
122
201
  status: Validators.string
123
202
  });
124
- const anthropicContentBlock = Validators.object({
125
- text: Validators.string
126
- });
127
- const anthropicResponse = Validators.object({
128
- content: Validators.arrayOf(anthropicContentBlock).withConstraint((arr) => arr.length > 0),
129
- stop_reason: Validators.string
130
- });
131
203
  const geminiPart = Validators.object({
132
204
  text: Validators.string
133
205
  });
@@ -149,16 +221,21 @@ const geminiResponse = Validators.object({
149
221
  * Works for xAI Grok, OpenAI, Groq, and Mistral.
150
222
  * @internal
151
223
  */
152
- async function callOpenAiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger) {
224
+ async function callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking) {
225
+ var _a;
153
226
  const url = `${config.baseUrl}/chat/completions`;
154
- const messages = buildMessages(prompt, additionalMessages);
155
- const body = { model: config.model, messages, temperature };
156
- const headers = {
157
- Authorization: `Bearer ${config.apiKey}`
158
- };
227
+ const messages = buildMessages(prompt.system, buildOpenAiChatUserContent(prompt), {
228
+ head
229
+ });
230
+ const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
231
+ const body = Object.assign(Object.assign({ model: config.model, messages }, (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning_effort: effort } : {}));
232
+ if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
233
+ Object.assign(body, resolvedThinking.otherParams);
234
+ }
235
+ const headers = bearerAuthHeader(config.apiKey);
159
236
  /* c8 ignore next 1 - optional logger */
160
237
  logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI completion: model=${config.model}`);
161
- const jsonResult = await fetchJson(url, headers, body, logger);
238
+ const jsonResult = await fetchJson(url, headers, body, logger, signal);
162
239
  if (jsonResult.isFailure()) {
163
240
  return fail(jsonResult.message);
164
241
  }
@@ -197,21 +274,21 @@ function extractResponsesApiText(output) {
197
274
  * Used when tools are configured for an openai-format provider.
198
275
  * @internal
199
276
  */
200
- async function callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature = 0.7, logger) {
277
+ async function callOpenAiResponsesCompletion(config, prompt, tools = [], head, temperature, logger, signal, resolvedThinking) {
278
+ var _a;
201
279
  const url = `${config.baseUrl}/responses`;
202
- const input = buildMessages(prompt, additionalMessages);
203
- const body = {
204
- model: config.model,
205
- input,
206
- tools: toResponsesApiTools(tools),
207
- temperature
208
- };
209
- const headers = {
210
- Authorization: `Bearer ${config.apiKey}`
211
- };
280
+ const input = buildMessages(prompt.system, buildOpenAiResponsesUserContent(prompt), {
281
+ head
282
+ });
283
+ const effort = (_a = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.openAiEffort) !== null && _a !== void 0 ? _a : resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.xaiEffort;
284
+ const body = Object.assign(Object.assign(Object.assign({ model: config.model, input }, (tools.length > 0 ? { tools: toResponsesApiTools(tools) } : {})), (temperature !== undefined ? { temperature } : {})), (effort !== undefined && config.model !== 'grok-4' ? { reasoning: { effort } } : {}));
285
+ if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
286
+ Object.assign(body, resolvedThinking.otherParams);
287
+ }
288
+ const headers = bearerAuthHeader(config.apiKey);
212
289
  /* c8 ignore next 1 - optional logger */
213
290
  logger === null || logger === void 0 ? void 0 : logger.info(`OpenAI Responses API: model=${config.model}, tools=${tools.map((t) => t.type).join(',')}`);
214
- const jsonResult = await fetchJson(url, headers, body, logger);
291
+ const jsonResult = await fetchJson(url, headers, body, logger, signal);
215
292
  if (jsonResult.isFailure()) {
216
293
  return fail(jsonResult.message);
217
294
  }
@@ -250,31 +327,18 @@ function extractAnthropicText(content) {
250
327
  }
251
328
  return succeed(textParts.join(''));
252
329
  }
253
- /**
254
- * Calls the Anthropic Messages API.
255
- * When tools are configured, includes them in the request and handles
256
- * mixed content block responses.
257
- * @internal
258
- */
259
- async function callAnthropicCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools) {
330
+ /** Calls the Anthropic Messages API with optional tool support. @internal */
331
+ async function callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking) {
260
332
  const url = `${config.baseUrl}/messages`;
261
- // Anthropic uses system as a top-level field, not in messages
262
- const messages = [{ role: 'user', content: prompt.user }];
263
- if (additionalMessages) {
264
- for (const msg of additionalMessages) {
265
- // Anthropic doesn't have a system role in messages
266
- if (msg.role !== 'system') {
267
- messages.push({ role: msg.role, content: msg.content });
268
- }
269
- }
333
+ const messages = buildAnthropicMessages(prompt, { head });
334
+ const body = Object.assign({ model: config.model, system: prompt.system, messages, max_tokens: 4096 }, (temperature !== undefined ? { temperature } : {}));
335
+ const effort = resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.anthropicEffort;
336
+ if (effort !== undefined) {
337
+ body.thinking = { type: 'enabled', budget_tokens: anthropicEffortToBudgetTokens(effort) };
338
+ }
339
+ if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
340
+ Object.assign(body, resolvedThinking.otherParams);
270
341
  }
271
- const body = {
272
- model: config.model,
273
- system: prompt.system,
274
- messages,
275
- max_tokens: 4096,
276
- temperature
277
- };
278
342
  if (tools && tools.length > 0) {
279
343
  body.tools = toAnthropicTools(tools);
280
344
  /* c8 ignore next 3 - optional logger diagnostic output */
@@ -289,32 +353,22 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
289
353
  'anthropic-version': '2023-06-01',
290
354
  'anthropic-dangerous-direct-browser-access': 'true'
291
355
  };
292
- const jsonResult = await fetchJson(url, headers, body, logger);
356
+ const jsonResult = await fetchJson(url, headers, body, logger, signal);
293
357
  if (jsonResult.isFailure()) {
294
358
  return fail(jsonResult.message);
295
359
  }
296
- // When tools are used, the response content is a mixed array of block types.
297
- // We need to extract text from all text blocks.
298
- if (tools && tools.length > 0) {
299
- const rawContent = jsonResult.value.content;
300
- const stopReason = jsonResult.value.stop_reason;
301
- if (!Array.isArray(rawContent)) {
302
- return fail('Anthropic API response: content is not an array');
303
- }
304
- return extractAnthropicText(rawContent).onSuccess((text) => succeed({
305
- content: text,
306
- truncated: stopReason === 'max_tokens'
307
- }));
360
+ const rawContent = jsonResult.value.content;
361
+ const stopReason = jsonResult.value.stop_reason;
362
+ if (!Array.isArray(rawContent)) {
363
+ return fail('Anthropic API response: content is not an array');
308
364
  }
309
- return anthropicResponse
310
- .validate(jsonResult.value)
311
- .withErrorFormat((msg) => `Anthropic API response: ${msg}`)
312
- .onSuccess((response) => {
313
- return succeed({
314
- content: response.content[0].text,
315
- truncated: response.stop_reason === 'max_tokens'
316
- });
317
- });
365
+ if (typeof stopReason !== 'string') {
366
+ return fail('Anthropic API response: stop_reason is missing or not a string');
367
+ }
368
+ return extractAnthropicText(rawContent).onSuccess((text) => succeed({
369
+ content: text,
370
+ truncated: stopReason === 'max_tokens'
371
+ }));
318
372
  }
319
373
  // ============================================================================
320
374
  // Google Gemini adapter
@@ -324,26 +378,24 @@ async function callAnthropicCompletion(config, prompt, additionalMessages, tempe
324
378
  * When tools are configured, includes Google Search grounding.
325
379
  * @internal
326
380
  */
327
- async function callGeminiCompletion(config, prompt, additionalMessages, temperature = 0.7, logger, tools) {
381
+ async function callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking) {
328
382
  const url = `${config.baseUrl}/models/${config.model}:generateContent`;
329
- // Gemini uses 'contents' with 'parts', and 'model' role instead of 'assistant'
330
- const contents = [
331
- { role: 'user', parts: [{ text: prompt.user }] }
332
- ];
333
- if (additionalMessages) {
334
- for (const msg of additionalMessages) {
335
- if (msg.role !== 'system') {
336
- contents.push({
337
- role: msg.role === 'assistant' ? 'model' : msg.role,
338
- parts: [{ text: msg.content }]
339
- });
340
- }
341
- }
383
+ const contents = buildGeminiContents(prompt, { head });
384
+ // Temperature is sent only when explicitly provided; otherwise Gemini's default applies.
385
+ const generationConfig = {};
386
+ if (temperature !== undefined) {
387
+ generationConfig.temperature = temperature;
388
+ }
389
+ if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.geminiThinkingBudget) !== undefined) {
390
+ generationConfig.thinkingConfig = { thinkingBudget: resolvedThinking.geminiThinkingBudget };
391
+ }
392
+ if ((resolvedThinking === null || resolvedThinking === void 0 ? void 0 : resolvedThinking.otherParams) !== undefined) {
393
+ Object.assign(generationConfig, resolvedThinking.otherParams);
342
394
  }
343
395
  const body = {
344
396
  systemInstruction: { parts: [{ text: prompt.system }] },
345
397
  contents,
346
- generationConfig: { temperature }
398
+ generationConfig
347
399
  };
348
400
  if (tools && tools.length > 0) {
349
401
  body.tools = toGeminiTools(tools);
@@ -357,7 +409,7 @@ async function callGeminiCompletion(config, prompt, additionalMessages, temperat
357
409
  const headers = {
358
410
  'x-goog-api-key': config.apiKey
359
411
  };
360
- const jsonResult = await fetchJson(url, headers, body, logger);
412
+ const jsonResult = await fetchJson(url, headers, body, logger, signal);
361
413
  if (jsonResult.isFailure()) {
362
414
  return fail(jsonResult.message);
363
415
  }
@@ -376,33 +428,54 @@ async function callGeminiCompletion(config, prompt, additionalMessages, temperat
376
428
  // Provider dispatcher
377
429
  // ============================================================================
378
430
  /**
379
- * Calls the appropriate chat completion API for a given provider.
380
- *
381
- * Routes based on the provider descriptor's `apiFormat` field:
382
- * - `'openai'` for xAI, OpenAI, Groq, Mistral
383
- * - `'anthropic'` for Anthropic Claude
384
- * - `'gemini'` for Google Gemini
385
- *
386
- * When tools are provided and the provider supports them:
387
- * - OpenAI-format providers switch to the Responses API
388
- * - Anthropic includes tools in the Messages API request
389
- * - Gemini includes Google Search grounding
390
- *
391
- * @param params - Request parameters including descriptor, API key, prompt, and optional tools
392
- * @returns The completion response with content and truncation status, or a failure
431
+ * Calls the appropriate chat completion API for a given provider. Routes by
432
+ * `apiFormat`: `'openai'` (xAI/OpenAI/Groq/Mistral — switches to Responses API
433
+ * when tools are set), `'anthropic'`, or `'gemini'`.
393
434
  * @public
394
435
  */
395
436
  export async function callProviderCompletion(params) {
396
- const { descriptor, apiKey, prompt, additionalMessages, temperature = 0.7, modelOverride, logger, tools } = params;
397
- if (!descriptor.baseUrl) {
398
- return fail(`provider "${descriptor.id}" has no API endpoint configured`);
437
+ const { descriptor, apiKey, system, messages, temperature, modelOverride, tier, logger, tools, signal, endpoint, thinking } = params;
438
+ const splitResult = splitChatRequest(system, messages);
439
+ if (splitResult.isFailure()) {
440
+ return fail(splitResult.message);
441
+ }
442
+ const { prompt, head } = splitResult.value;
443
+ const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);
444
+ if (baseUrlResult.isFailure()) {
445
+ return fail(baseUrlResult.message);
446
+ }
447
+ if (prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {
448
+ return fail(`provider "${descriptor.id}" does not accept image input`);
399
449
  }
400
450
  const hasTools = tools !== undefined && tools.length > 0;
401
- const modelContext = hasTools ? 'tools' : undefined;
451
+ const discriminator = providerDiscriminatorForId(descriptor.id);
452
+ // The quality tier is the only completion-model selector; thinking and tools
453
+ // are orthogonal request params/capabilities and never pick a model.
454
+ const modelContext = tier;
455
+ const modelResult = resolveProviderModel(descriptor, modelOverride, modelContext);
456
+ if (modelResult.isFailure()) {
457
+ return fail(modelResult.message);
458
+ }
459
+ const model = modelResult.value;
460
+ let resolvedThinking;
461
+ if (thinking !== undefined) {
462
+ if (discriminator !== undefined) {
463
+ const mergeResult = mergeThinkingConfig(thinking, model, discriminator);
464
+ /* c8 ignore next 3 - mergeThinkingConfig always succeeds; defensive guard */
465
+ if (mergeResult.isFailure()) {
466
+ return fail(mergeResult.message);
467
+ }
468
+ resolvedThinking = mergeResult.value;
469
+ const conflictResult = checkTemperatureConflict(resolvedThinking, discriminator, temperature);
470
+ if (conflictResult.isFailure()) {
471
+ return fail(conflictResult.message);
472
+ }
473
+ }
474
+ }
402
475
  const config = {
403
- baseUrl: descriptor.baseUrl,
476
+ baseUrl: baseUrlResult.value,
404
477
  apiKey,
405
- model: resolveModel(modelOverride !== null && modelOverride !== void 0 ? modelOverride : descriptor.defaultModel, modelContext)
478
+ model
406
479
  };
407
480
  /* c8 ignore next 8 - optional logger diagnostic output */
408
481
  if (logger) {
@@ -413,14 +486,16 @@ export async function callProviderCompletion(params) {
413
486
  }
414
487
  switch (descriptor.apiFormat) {
415
488
  case 'openai':
416
- if (hasTools) {
417
- return callOpenAiResponsesCompletion(config, prompt, tools, additionalMessages, temperature, logger);
489
+ // Responses-API-only models (e.g. gpt-5.5-pro) 400 on /chat/completions, so they route
490
+ // to the Responses path even with no tools requested same path the tools case uses.
491
+ if (hasTools || isResponsesOnlyModel(descriptor, config.model)) {
492
+ return callOpenAiResponsesCompletion(config, prompt, tools, head, temperature, logger, signal, resolvedThinking);
418
493
  }
419
- return callOpenAiCompletion(config, prompt, additionalMessages, temperature, logger);
494
+ return callOpenAiCompletion(config, prompt, head, temperature, logger, signal, resolvedThinking);
420
495
  case 'anthropic':
421
- return callAnthropicCompletion(config, prompt, additionalMessages, temperature, logger, tools);
496
+ return callAnthropicCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking);
422
497
  case 'gemini':
423
- return callGeminiCompletion(config, prompt, additionalMessages, temperature, logger, tools);
498
+ return callGeminiCompletion(config, prompt, head, temperature, logger, tools, signal, resolvedThinking);
424
499
  /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
425
500
  default: {
426
501
  const _exhaustive = descriptor.apiFormat;
@@ -428,32 +503,643 @@ export async function callProviderCompletion(params) {
428
503
  }
429
504
  }
430
505
  }
506
+ const openAiImageItem = Validators.object({
507
+ b64_json: Validators.string,
508
+ revised_prompt: Validators.string.optional()
509
+ });
510
+ const openAiImageResponse = Validators.object({
511
+ data: Validators.arrayOf(openAiImageItem).withConstraint((arr) => arr.length > 0)
512
+ });
513
+ const geminiImageInlineData = Validators.object({
514
+ mimeType: Validators.string,
515
+ data: Validators.string
516
+ });
517
+ const geminiImageOutPart = Validators.object({
518
+ text: Validators.string.optional(),
519
+ inlineData: geminiImageInlineData.optional()
520
+ });
521
+ const geminiImageOutContent = Validators.object({
522
+ parts: Validators.arrayOf(geminiImageOutPart)
523
+ });
524
+ const geminiImageOutCandidate = Validators.object({
525
+ content: geminiImageOutContent.optional(),
526
+ finishReason: Validators.string.optional(),
527
+ finishMessage: Validators.string.optional()
528
+ });
529
+ const geminiImageOutResponse = Validators.object({
530
+ candidates: Validators.arrayOf(geminiImageOutCandidate).withConstraint((arr) => arr.length > 0)
531
+ });
532
+ // ---- Proxied image generation response ----
533
+ const proxiedGeneratedImage = Validators.object({
534
+ mimeType: Validators.string,
535
+ base64: Validators.string,
536
+ revisedPrompt: Validators.string.optional()
537
+ });
538
+ const proxiedImageGenerationResponse = Validators.object({
539
+ images: Validators.arrayOf(proxiedGeneratedImage).withConstraint((arr) => arr.length > 0)
540
+ });
541
+ const proxiedListModelsEntry = Validators.object({
542
+ id: Validators.string,
543
+ capabilities: Validators.arrayOf(Validators.enumeratedValue(allModelCapabilities)),
544
+ displayName: Validators.string.optional()
545
+ });
546
+ const proxiedListModelsResponse = Validators.object({
547
+ models: Validators.arrayOf(proxiedListModelsEntry)
548
+ });
549
+ // ============================================================================
550
+ // Image generation — adapters
551
+ // ============================================================================
552
+ /** Routes to /images/generations or /images/edits; handles outputParamStyle. @internal */
553
+ async function callOpenAiImageGeneration(config, request, capability, resolved, logger, signal) {
554
+ var _a, _b;
555
+ const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
556
+ const headers = bearerAuthHeader(config.apiKey);
557
+ const effectiveMimeType = resolved.outputFormat !== undefined
558
+ ? `image/${resolved.outputFormat}`
559
+ : (_b = capability.defaultOutputMimeType) !== null && _b !== void 0 ? _b : 'image/png';
560
+ const fetched = refs.length > 0
561
+ ? await callOpenAiImagesEdits(config, capability, request, headers, resolved, logger, signal)
562
+ : await callOpenAiImagesGenerations(config, request, headers, resolved, capability, logger, signal);
563
+ return fetched.onSuccess((json) => openAiImageResponse
564
+ .validate(json)
565
+ .withErrorFormat((msg) => `OpenAI images API response: ${msg}`)
566
+ .onSuccess((response) => succeed({
567
+ images: response.data.map((item) => (Object.assign({ mimeType: effectiveMimeType, base64: item.b64_json }, (item.revised_prompt !== undefined ? { revisedPrompt: item.revised_prompt } : {}))))
568
+ })));
569
+ }
570
+ /** Builds the JSON /images/generations request; handles outputParamStyle. @internal */
571
+ function callOpenAiImagesGenerations(config, request, headers, resolved, capability, logger, signal) {
572
+ var _a;
573
+ const body = {
574
+ model: config.model,
575
+ prompt: request.prompt,
576
+ n: resolved.n
577
+ };
578
+ // Output format param — conditional on model capability
579
+ if (capability.outputParamStyle === 'response-format') {
580
+ body.response_format = 'b64_json';
581
+ }
582
+ else if (capability.outputParamStyle === 'output-format') {
583
+ body.output_format = (_a = resolved.outputFormat) !== null && _a !== void 0 ? _a : 'png';
584
+ }
585
+ if (resolved.size !== undefined) {
586
+ body.size = resolved.size;
587
+ }
588
+ if (capability.supportsQualityParam && resolved.quality !== undefined) {
589
+ body.quality = resolved.quality;
590
+ }
591
+ if (resolved.seed !== undefined) {
592
+ body.seed = resolved.seed;
593
+ }
594
+ if (resolved.background !== undefined) {
595
+ body.background = resolved.background;
596
+ }
597
+ if (resolved.moderation !== undefined) {
598
+ body.moderation = resolved.moderation;
599
+ }
600
+ if (resolved.outputCompression !== undefined) {
601
+ body.output_compression = resolved.outputCompression;
602
+ }
603
+ if (resolved.otherParams !== undefined) {
604
+ Object.assign(body, resolved.otherParams);
605
+ }
606
+ /* c8 ignore next 1 - optional logger */
607
+ logger === null || logger === void 0 ? void 0 : logger.info(`Image generation: model=${config.model}, n=${resolved.n}`);
608
+ return fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
609
+ }
610
+ /** Builds the multipart /images/edits request with ref images. @internal */
611
+ async function callOpenAiImagesEdits(config, capability, request, headers, resolved, logger, signal) {
612
+ const refs = request.referenceImages; // callers verify refs.length > 0 before calling this function
613
+ const blobsResult = mapResults(refs.map((ref, i) => attachmentToBlob(ref).withErrorFormat((msg) => `reference image ${i}: ${msg}`)));
614
+ /* c8 ignore next 3 - decode failure unreachable via Node's Buffer.from (silently strips invalid input) */
615
+ if (blobsResult.isFailure()) {
616
+ return fail(blobsResult.message);
617
+ }
618
+ const form = new FormData();
619
+ form.append('model', config.model);
620
+ form.append('prompt', request.prompt);
621
+ form.append('n', String(resolved.n));
622
+ if (capability.outputParamStyle !== 'output-format') {
623
+ form.append('response_format', 'b64_json');
624
+ }
625
+ if (resolved.size !== undefined) {
626
+ form.append('size', resolved.size);
627
+ }
628
+ blobsResult.value.forEach((blob, i) => {
629
+ form.append('image[]', blob, `ref-${i}.${extensionForMimeType(refs[i].mimeType)}`);
630
+ });
631
+ /* c8 ignore next 1 - optional logger */
632
+ logger === null || logger === void 0 ? void 0 : logger.info(`Image edit: model=${config.model}, n=${resolved.n}, refs=${refs.length}`);
633
+ return fetchMultipart(`${config.baseUrl}/images/edits`, headers, form, logger, signal);
634
+ }
635
+ /** Calls xAI /images/edits with JSON body (not multipart); up to 3 source images. @internal */
636
+ async function callXaiImagesEdits(config, request, resolved, logger, signal) {
637
+ var _a;
638
+ /* c8 ignore next 1 - defensive: referenceImages always defined when this function is called */
639
+ const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
640
+ if (refs.length > 3) {
641
+ return fail(`xAI image edits supports at most 3 reference images; got ${refs.length}`);
642
+ }
643
+ const images = refs.map((ref) => ({
644
+ type: 'image_url',
645
+ url: `data:${ref.mimeType};base64,${ref.base64}`
646
+ }));
647
+ const body = {
648
+ model: config.model,
649
+ prompt: request.prompt,
650
+ n: resolved.n,
651
+ response_format: 'b64_json',
652
+ image: images
653
+ };
654
+ if (resolved.aspectRatio !== undefined) {
655
+ body.aspect_ratio = resolved.aspectRatio;
656
+ }
657
+ if (resolved.resolution !== undefined) {
658
+ body.resolution = resolved.resolution;
659
+ }
660
+ if (resolved.otherParams !== undefined) {
661
+ Object.assign(body, resolved.otherParams);
662
+ }
663
+ /* c8 ignore next 1 - optional logger */
664
+ logger === null || logger === void 0 ? void 0 : logger.info(`xAI image edit: model=${config.model}, n=${resolved.n}, refs=${refs.length}`);
665
+ return fetchJson(`${config.baseUrl}/images/edits`, bearerAuthHeader(config.apiKey), body, logger, signal);
666
+ }
667
+ /** Calls xAI /images/generations; uses aspect_ratio instead of size. @internal */
668
+ async function callXaiImageGeneration(config, request, capability, resolved, logger, signal) {
669
+ const headers = bearerAuthHeader(config.apiKey);
670
+ const body = {
671
+ model: config.model,
672
+ prompt: request.prompt,
673
+ n: resolved.n,
674
+ response_format: 'b64_json'
675
+ };
676
+ if (resolved.aspectRatio !== undefined) {
677
+ body.aspect_ratio = resolved.aspectRatio;
678
+ }
679
+ if (resolved.resolution !== undefined) {
680
+ body.resolution = resolved.resolution;
681
+ }
682
+ if (resolved.otherParams !== undefined) {
683
+ Object.assign(body, resolved.otherParams);
684
+ }
685
+ /* c8 ignore next 1 - optional logger */
686
+ logger === null || logger === void 0 ? void 0 : logger.info(`xAI image generation: model=${config.model}, n=${resolved.n}`);
687
+ const fetched = await fetchJson(`${config.baseUrl}/images/generations`, headers, body, logger, signal);
688
+ return fetched.onSuccess((json) => openAiImageResponse
689
+ .validate(json)
690
+ .withErrorFormat((msg) => `xAI images API response: ${msg}`)
691
+ .onSuccess((response) => succeed({
692
+ images: response.data.map((item) => {
693
+ var _a;
694
+ return ({
695
+ mimeType: (_a = capability.defaultOutputMimeType) !== null && _a !== void 0 ? _a : 'image/jpeg',
696
+ base64: item.b64_json
697
+ });
698
+ })
699
+ })));
700
+ }
701
+ /**
702
+ * Gemini `finishReason` values that indicate a normal terminal completion rather
703
+ * than a refusal. `STOP` is set on every successful generation (and on completions
704
+ * that return a text part instead of an image); `MAX_TOKENS` is a benign truncation.
705
+ * A candidate carrying only one of these is NOT a decline — treating it as one would
706
+ * mislabel an ordinary no-image outcome as a policy refusal. @internal
707
+ */
708
+ const benignGeminiImageFinishReasons = new Set(['STOP', 'MAX_TOKENS']);
709
+ /** Calls Gemini :generateContent for image output; accepts ref images as inlineData. @internal */
710
+ async function callGeminiImageOutGeneration(config, request, resolved, logger, signal) {
711
+ var _a;
712
+ const url = `${config.baseUrl}/models/${config.model}:generateContent`;
713
+ const refs = (_a = request.referenceImages) !== null && _a !== void 0 ? _a : [];
714
+ const parts = [{ text: request.prompt }];
715
+ for (const ref of refs) {
716
+ parts.push({ inlineData: { mimeType: ref.mimeType, data: ref.base64 } });
717
+ }
718
+ const generationConfig = {};
719
+ if (resolved.geminiAspectRatio !== undefined) {
720
+ generationConfig.imageConfig = { aspectRatio: resolved.geminiAspectRatio };
721
+ }
722
+ if (resolved.otherParams !== undefined) {
723
+ Object.assign(generationConfig, resolved.otherParams);
724
+ }
725
+ const body = { contents: [{ role: 'user', parts }] };
726
+ if (Object.keys(generationConfig).length > 0) {
727
+ body.generationConfig = generationConfig;
728
+ }
729
+ const headers = {
730
+ 'x-goog-api-key': config.apiKey
731
+ };
732
+ /* c8 ignore next 1 - optional logger */
733
+ logger === null || logger === void 0 ? void 0 : logger.info(`Gemini image-out: model=${config.model}, refs=${refs.length}`);
734
+ return (await fetchJson(url, headers, body, logger, signal)).onSuccess((json) => geminiImageOutResponse
735
+ .validate(json)
736
+ .withErrorFormat((msg) => `Gemini image API response: ${msg}`)
737
+ .onSuccess((response) => {
738
+ var _a, _b;
739
+ const images = [];
740
+ for (const candidate of response.candidates) {
741
+ for (const part of (_b = (_a = candidate.content) === null || _a === void 0 ? void 0 : _a.parts) !== null && _b !== void 0 ? _b : []) {
742
+ if (part.inlineData) {
743
+ images.push({
744
+ mimeType: part.inlineData.mimeType,
745
+ base64: part.inlineData.data
746
+ });
747
+ }
748
+ }
749
+ }
750
+ if (images.length === 0) {
751
+ // A candidate with no image parts is a *decline* only when it carries a
752
+ // refusal-shaped finishReason — i.e. present and not a benign terminal reason
753
+ // (`STOP`/`MAX_TOKENS`). A normal completion that emitted text-instead-of-image
754
+ // carries `finishReason: 'STOP'` and must fall through to the no-image message.
755
+ const declined = response.candidates.find((candidate) => candidate.finishReason !== undefined &&
756
+ !benignGeminiImageFinishReasons.has(candidate.finishReason));
757
+ if ((declined === null || declined === void 0 ? void 0 : declined.finishReason) !== undefined) {
758
+ // Truthiness (not `!== undefined`) so an empty-string finishMessage is treated
759
+ // as "no message" and produces no dangling ` — ` separator.
760
+ const suffix = declined.finishMessage ? ` — ${declined.finishMessage}` : '';
761
+ return fail(`Gemini image generation declined: ${declined.finishReason}${suffix}`);
762
+ }
763
+ return fail('Gemini image API response: no image parts in response');
764
+ }
765
+ return succeed({ images });
766
+ }));
767
+ }
768
+ // ============================================================================
769
+ // Image generation — dispatcher
770
+ // ============================================================================
771
+ /**
772
+ * Calls the appropriate image-generation API for a given provider. Routes by the
773
+ * `format` field of the resolved {@link IAiImageModelCapability}:
774
+ * `'openai-images'`, `'xai-images'`, `'xai-images-edits'`, or
775
+ * `'gemini-image-out'`. Rejects up front if `referenceImages` is set but the
776
+ * capability does not declare `acceptsImageReferenceInput`.
777
+ * @param params - Request parameters including descriptor, API key, and prompt
778
+ * @public
779
+ */
780
+ export async function callProviderImageGeneration(params) {
781
+ var _a, _b, _c;
782
+ const { descriptor, apiKey, params: request, modelOverride, logger, signal, endpoint } = params;
783
+ if (!supportsImageGeneration(descriptor)) {
784
+ return fail(`provider "${descriptor.id}" does not support image generation`);
785
+ }
786
+ const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);
787
+ if (baseUrlResult.isFailure()) {
788
+ return fail(baseUrlResult.message);
789
+ }
790
+ const modelResult = resolveProviderModel(descriptor, modelOverride, 'image');
791
+ if (modelResult.isFailure()) {
792
+ return fail(modelResult.message);
793
+ }
794
+ const model = modelResult.value;
795
+ const capability = resolveImageCapability(descriptor, model);
796
+ if (capability === undefined) {
797
+ return fail(`provider "${descriptor.id}" does not support image generation for model "${model}"`);
798
+ }
799
+ if (((_b = (_a = request.referenceImages) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 && !capability.acceptsImageReferenceInput) {
800
+ return fail(`model "${model}" does not support reference images`);
801
+ }
802
+ const resolved = resolveImageOptions(model, capability, request.options);
803
+ const validationResult = validateResolvedOptions(model, capability, resolved);
804
+ if (validationResult.isFailure()) {
805
+ return fail(validationResult.message);
806
+ }
807
+ const config = {
808
+ baseUrl: baseUrlResult.value,
809
+ apiKey,
810
+ model
811
+ };
812
+ /* c8 ignore next 6 - optional logger diagnostic output */
813
+ if (logger) {
814
+ logger.info(`AI image generation: provider=${descriptor.id}, format=${capability.format}, ` +
815
+ `model=${config.model}`);
816
+ }
817
+ switch (capability.format) {
818
+ case 'openai-images':
819
+ return callOpenAiImageGeneration(config, request, capability, resolved, logger, signal);
820
+ case 'xai-images':
821
+ return callXaiImageGeneration(config, request, capability, resolved, logger, signal);
822
+ case 'xai-images-edits': {
823
+ const refs = (_c = request.referenceImages) !== null && _c !== void 0 ? _c : [];
824
+ if (refs.length > 0) {
825
+ const editsResult = await callXaiImagesEdits(config, request, resolved, logger, signal);
826
+ return editsResult.onSuccess((json) => openAiImageResponse
827
+ .validate(json)
828
+ .withErrorFormat((msg) => `xAI images API response: ${msg}`)
829
+ .onSuccess((response) => succeed({
830
+ images: response.data.map((item) => {
831
+ var _a;
832
+ return ({
833
+ mimeType: (_a = capability.defaultOutputMimeType) !== null && _a !== void 0 ? _a : 'image/jpeg',
834
+ base64: item.b64_json
835
+ });
836
+ })
837
+ })));
838
+ }
839
+ return callXaiImageGeneration(config, request, capability, resolved, logger, signal);
840
+ }
841
+ case 'gemini-image-out':
842
+ return callGeminiImageOutGeneration(config, request, resolved, logger, signal);
843
+ /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
844
+ default: {
845
+ const _exhaustive = capability.format;
846
+ return fail(`unsupported image API format: ${String(_exhaustive)}`);
847
+ }
848
+ }
849
+ }
850
+ const openAiListEntry = Validators.object({
851
+ id: Validators.string
852
+ });
853
+ const openAiListResponse = Validators.object({
854
+ data: Validators.arrayOf(openAiListEntry)
855
+ });
856
+ const anthropicListEntry = Validators.object({
857
+ id: Validators.string,
858
+ display_name: Validators.string.optional()
859
+ });
860
+ const anthropicListResponse = Validators.object({
861
+ data: Validators.arrayOf(anthropicListEntry)
862
+ });
863
+ const geminiListEntry = Validators.object({
864
+ name: Validators.string,
865
+ displayName: Validators.string.optional(),
866
+ supportedGenerationMethods: Validators.arrayOf(Validators.string).optional()
867
+ });
868
+ const geminiListResponse = Validators.object({
869
+ models: Validators.arrayOf(geminiListEntry)
870
+ });
871
+ // ============================================================================
872
+ // List models — capability resolution
873
+ // ============================================================================
874
+ /**
875
+ * Translates Gemini's `supportedGenerationMethods` strings into our abstract
876
+ * capability vocabulary. Methods without a mapping are ignored.
877
+ * @internal
878
+ */
879
+ function geminiMethodsToCapabilities(methods) {
880
+ const out = [];
881
+ for (const m of methods) {
882
+ if (m === 'generateContent') {
883
+ out.push('chat');
884
+ }
885
+ else if (m === 'predict') {
886
+ out.push('image-generation');
887
+ }
888
+ }
889
+ return out;
890
+ }
891
+ /**
892
+ * Strips the `models/` prefix Gemini includes on listed model names.
893
+ * @internal
894
+ */
895
+ function geminiBareId(name) {
896
+ /* c8 ignore next 1 - defensive: Gemini API always returns names prefixed with 'models/' */
897
+ return name.startsWith('models/') ? name.substring('models/'.length) : name;
898
+ }
899
+ /**
900
+ * Applies a capability config to a model id. Walks per-provider rules then
901
+ * global rules; unions all matching rules' capabilities. Returns the union
902
+ * and the first matching `displayName` (if any).
903
+ * @internal
904
+ */
905
+ function applyCapabilityConfig(config, providerId, modelId) {
906
+ var _a, _b, _c;
907
+ const caps = new Set();
908
+ let displayName;
909
+ const rulesets = [
910
+ (_b = (_a = config.perProvider) === null || _a === void 0 ? void 0 : _a[providerId]) !== null && _b !== void 0 ? _b : [],
911
+ (_c = config.global) !== null && _c !== void 0 ? _c : []
912
+ ];
913
+ for (const rules of rulesets) {
914
+ for (const rule of rules) {
915
+ rule.idPattern.lastIndex = 0;
916
+ if (rule.idPattern.test(modelId)) {
917
+ for (const cap of rule.capabilities) {
918
+ caps.add(cap);
919
+ }
920
+ if (displayName === undefined && rule.displayName !== undefined) {
921
+ displayName = typeof rule.displayName === 'function' ? rule.displayName(modelId) : rule.displayName;
922
+ }
923
+ }
924
+ }
925
+ }
926
+ return { capabilities: Array.from(caps), displayName };
927
+ }
928
+ /**
929
+ * Combines provider-native capability info (when supplied) and config-derived
930
+ * capability info into a final {@link IAiModelInfo}.
931
+ * @internal
932
+ */
933
+ function buildModelInfo(providerId, id, nativeCapabilities, nativeDisplayName, config) {
934
+ const fromConfig = applyCapabilityConfig(config, providerId, id);
935
+ const all = new Set([...nativeCapabilities, ...fromConfig.capabilities]);
936
+ return Object.assign({ id, capabilities: all }, (nativeDisplayName !== undefined
937
+ ? { displayName: nativeDisplayName }
938
+ : fromConfig.displayName !== undefined
939
+ ? { displayName: fromConfig.displayName }
940
+ : {}));
941
+ }
942
+ // ============================================================================
943
+ // List models — adapters
944
+ // ============================================================================
945
+ /**
946
+ * Calls the OpenAI-style `GET /models` endpoint. Used by openai, xai-grok,
947
+ * groq, and mistral. Provider supplies no capability info — capabilities are
948
+ * derived entirely from the config.
949
+ * @internal
950
+ */
951
+ async function callOpenAiListModels(config, providerId, capabilityConfig, logger, signal) {
952
+ const url = `${config.baseUrl}/models`;
953
+ const headers = bearerAuthHeader(config.apiKey);
954
+ /* c8 ignore next 1 - optional logger */
955
+ logger === null || logger === void 0 ? void 0 : logger.info(`List models: provider=${providerId}, format=openai`);
956
+ const jsonResult = await fetchGetJson(url, headers, logger, signal);
957
+ if (jsonResult.isFailure()) {
958
+ return fail(jsonResult.message);
959
+ }
960
+ return openAiListResponse
961
+ .validate(jsonResult.value)
962
+ .withErrorFormat((msg) => `OpenAI models API response: ${msg}`)
963
+ .onSuccess((response) => {
964
+ const models = response.data.map((entry) => buildModelInfo(providerId, entry.id, [], undefined, capabilityConfig));
965
+ return succeed(models);
966
+ });
967
+ }
968
+ /**
969
+ * Calls the Anthropic `GET /models` endpoint. Provider supplies a
970
+ * `display_name` but no native capability info.
971
+ * @internal
972
+ */
973
+ async function callAnthropicListModels(config, providerId, capabilityConfig, logger, signal) {
974
+ const url = `${config.baseUrl}/models`;
975
+ const headers = {
976
+ 'x-api-key': config.apiKey,
977
+ 'anthropic-version': '2023-06-01',
978
+ 'anthropic-dangerous-direct-browser-access': 'true'
979
+ };
980
+ /* c8 ignore next 1 - optional logger */
981
+ logger === null || logger === void 0 ? void 0 : logger.info(`List models: provider=${providerId}, format=anthropic`);
982
+ const jsonResult = await fetchGetJson(url, headers, logger, signal);
983
+ if (jsonResult.isFailure()) {
984
+ return fail(jsonResult.message);
985
+ }
986
+ return anthropicListResponse
987
+ .validate(jsonResult.value)
988
+ .withErrorFormat((msg) => `Anthropic models API response: ${msg}`)
989
+ .onSuccess((response) => {
990
+ const models = response.data.map((entry) => buildModelInfo(providerId, entry.id, [], entry.display_name, capabilityConfig));
991
+ return succeed(models);
992
+ });
993
+ }
994
+ /**
995
+ * Calls the Gemini `GET /models` endpoint. Provider supplies both a
996
+ * `displayName` and `supportedGenerationMethods` — translated to native
997
+ * capabilities and unioned with config-derived capabilities.
998
+ * @internal
999
+ */
1000
+ async function callGeminiListModels(config, providerId, capabilityConfig, logger, signal) {
1001
+ const url = `${config.baseUrl}/models`;
1002
+ const headers = {
1003
+ 'x-goog-api-key': config.apiKey
1004
+ };
1005
+ /* c8 ignore next 1 - optional logger */
1006
+ logger === null || logger === void 0 ? void 0 : logger.info(`List models: provider=${providerId}, format=gemini`);
1007
+ const jsonResult = await fetchGetJson(url, headers, logger, signal);
1008
+ if (jsonResult.isFailure()) {
1009
+ return fail(jsonResult.message);
1010
+ }
1011
+ return geminiListResponse
1012
+ .validate(jsonResult.value)
1013
+ .withErrorFormat((msg) => `Gemini models API response: ${msg}`)
1014
+ .onSuccess((response) => {
1015
+ const models = response.models.map((entry) => {
1016
+ const id = geminiBareId(entry.name);
1017
+ const native = entry.supportedGenerationMethods
1018
+ ? geminiMethodsToCapabilities(entry.supportedGenerationMethods)
1019
+ : [];
1020
+ return buildModelInfo(providerId, id, native, entry.displayName, capabilityConfig);
1021
+ });
1022
+ return succeed(models);
1023
+ });
1024
+ }
1025
+ // ============================================================================
1026
+ // List models — dispatcher
1027
+ // ============================================================================
1028
+ /**
1029
+ * Lists models available from a provider, routing by `descriptor.apiFormat`.
1030
+ * Capabilities are resolved from native provider info and a configurable rule set.
1031
+ * @param params - Request parameters (descriptor, API key, optional capability filter)
1032
+ * @public
1033
+ */
1034
+ export async function callProviderListModels(params) {
1035
+ const { descriptor, apiKey, capability, capabilityConfig, logger, signal, endpoint } = params;
1036
+ const baseUrlResult = resolveEffectiveBaseUrl(descriptor, endpoint);
1037
+ if (baseUrlResult.isFailure()) {
1038
+ return fail(baseUrlResult.message);
1039
+ }
1040
+ const config = {
1041
+ baseUrl: baseUrlResult.value,
1042
+ apiKey,
1043
+ model: '' // unused by listing
1044
+ };
1045
+ const effectiveConfig = capabilityConfig !== null && capabilityConfig !== void 0 ? capabilityConfig : DEFAULT_MODEL_CAPABILITY_CONFIG;
1046
+ let listResult;
1047
+ switch (descriptor.apiFormat) {
1048
+ case 'openai':
1049
+ listResult = await callOpenAiListModels(config, descriptor.id, effectiveConfig, logger, signal);
1050
+ break;
1051
+ case 'anthropic':
1052
+ listResult = await callAnthropicListModels(config, descriptor.id, effectiveConfig, logger, signal);
1053
+ break;
1054
+ case 'gemini':
1055
+ listResult = await callGeminiListModels(config, descriptor.id, effectiveConfig, logger, signal);
1056
+ break;
1057
+ /* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
1058
+ default: {
1059
+ const _exhaustive = descriptor.apiFormat;
1060
+ return fail(`unsupported API format: ${String(_exhaustive)}`);
1061
+ }
1062
+ }
1063
+ if (listResult.isFailure()) {
1064
+ return listResult;
1065
+ }
1066
+ if (capability === undefined) {
1067
+ return listResult;
1068
+ }
1069
+ return succeed(listResult.value.filter((m) => m.capabilities.has(capability)));
1070
+ }
1071
+ // ============================================================================
1072
+ // Proxied list models
1073
+ // ============================================================================
1074
+ /**
1075
+ * Calls the model-listing endpoint on a proxy server. Endpoint:
1076
+ * `POST ${proxyUrl}/api/ai/list-models`. Capability config is not forwarded;
1077
+ * `capabilities` is serialized as a string array. Error body `{error: string}`
1078
+ * is surfaced as `proxy: ${error}`.
1079
+ * @public
1080
+ */
1081
+ export async function callProxiedListModels(proxyUrl, params) {
1082
+ const { descriptor, apiKey, capability, logger, signal } = params;
1083
+ const body = {
1084
+ providerId: descriptor.id,
1085
+ apiKey
1086
+ };
1087
+ if (capability !== undefined) {
1088
+ body.capability = capability;
1089
+ }
1090
+ /* c8 ignore next 1 - optional logger */
1091
+ logger === null || logger === void 0 ? void 0 : logger.info(`AI list-models proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
1092
+ const url = `${proxyUrl}/api/ai/list-models`;
1093
+ const jsonResult = await fetchJson(url, {}, body, logger, signal);
1094
+ if (jsonResult.isFailure()) {
1095
+ return fail(jsonResult.message);
1096
+ }
1097
+ const response = jsonResult.value;
1098
+ if (typeof response.error === 'string') {
1099
+ return fail(`proxy: ${response.error}`);
1100
+ }
1101
+ return proxiedListModelsResponse
1102
+ .validate(response)
1103
+ .withErrorFormat((msg) => `proxy returned invalid response: ${msg}`)
1104
+ .onSuccess((parsed) => {
1105
+ const models = parsed.models.map((m) => (Object.assign({ id: m.id, capabilities: new Set(m.capabilities) }, (m.displayName !== undefined ? { displayName: m.displayName } : {}))));
1106
+ return succeed(models);
1107
+ });
1108
+ }
431
1109
  // ============================================================================
432
1110
  // Proxied completion (routes through a backend server)
433
1111
  // ============================================================================
434
1112
  /**
435
- * Calls the AI completion endpoint on a proxy server instead of calling
436
- * the provider API directly from the browser.
437
- *
438
- * The proxy server handles provider dispatch, CORS, and API key forwarding.
439
- * The request shape mirrors {@link IProviderCompletionParams} but is serialized
440
- * as JSON for the proxy endpoint.
441
- *
442
- * @param proxyUrl - Base URL of the proxy server (e.g. `http://localhost:3001`)
1113
+ * Calls the AI completion endpoint on a proxy server instead of calling the
1114
+ * provider API directly from the browser. The proxy handles provider dispatch,
1115
+ * CORS, and API key forwarding. The request body serializes the unified
1116
+ * {@link AiAssist.IChatRequest} shape (`system?` + `messages`). Enforces the same
1117
+ * non-empty / trailing-user-turn and image-input invariants as the direct path.
1118
+ * @param proxyUrl - Base URL of the proxy server
443
1119
  * @param params - Same parameters as {@link callProviderCompletion}
444
- * @returns The completion response, or a failure
445
1120
  * @public
446
1121
  */
447
1122
  export async function callProxiedCompletion(proxyUrl, params) {
448
- const { descriptor, apiKey, prompt, additionalMessages, temperature, modelOverride, logger, tools } = params;
1123
+ const { descriptor, apiKey, system, messages, temperature, modelOverride, logger, tools, signal, thinking } = params;
1124
+ const splitResult = splitChatRequest(system, messages);
1125
+ if (splitResult.isFailure()) {
1126
+ return fail(splitResult.message);
1127
+ }
1128
+ if (splitResult.value.prompt.attachments.length > 0 && !descriptor.acceptsImageInput) {
1129
+ return fail(`provider "${descriptor.id}" does not accept image input`);
1130
+ }
449
1131
  const body = {
450
1132
  providerId: descriptor.id,
451
1133
  apiKey,
452
- prompt: { system: prompt.system, user: prompt.user },
453
- temperature: temperature !== null && temperature !== void 0 ? temperature : 0.7
1134
+ messages: normalizeOutboundMessages(splitResult.value)
454
1135
  };
455
- if (additionalMessages && additionalMessages.length > 0) {
456
- body.additionalMessages = additionalMessages;
1136
+ // Temperature is forwarded only when explicitly provided, matching the direct path — the proxy
1137
+ // omits it from the upstream request so the provider default applies.
1138
+ if (temperature !== undefined) {
1139
+ body.temperature = temperature;
1140
+ }
1141
+ if (system !== undefined) {
1142
+ body.system = system;
457
1143
  }
458
1144
  if (modelOverride !== undefined) {
459
1145
  body.modelOverride = modelOverride;
@@ -461,14 +1147,16 @@ export async function callProxiedCompletion(proxyUrl, params) {
461
1147
  if (tools && tools.length > 0) {
462
1148
  body.tools = tools;
463
1149
  }
1150
+ if (thinking !== undefined) {
1151
+ body.thinking = thinking;
1152
+ }
464
1153
  /* c8 ignore next 1 - optional logger */
465
1154
  logger === null || logger === void 0 ? void 0 : logger.info(`AI proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
466
1155
  const url = `${proxyUrl}/api/ai/completion`;
467
- const jsonResult = await fetchJson(url, {}, body, logger);
1156
+ const jsonResult = await fetchJson(url, {}, body, logger, signal);
468
1157
  if (jsonResult.isFailure()) {
469
1158
  return fail(jsonResult.message);
470
1159
  }
471
- // Check for error response from proxy
472
1160
  const response = jsonResult.value;
473
1161
  if (typeof response.error === 'string') {
474
1162
  return fail(`proxy: ${response.error}`);
@@ -481,4 +1169,44 @@ export async function callProxiedCompletion(proxyUrl, params) {
481
1169
  truncated: response.truncated === true
482
1170
  });
483
1171
  }
1172
+ // ============================================================================
1173
+ // Proxied image generation
1174
+ // ============================================================================
1175
+ /**
1176
+ * Calls the image-generation endpoint on a proxy server instead of calling
1177
+ * the provider API directly from the browser.
1178
+ * Endpoint: `POST ${proxyUrl}/api/ai/image-generation`. Request body:
1179
+ * `{providerId, apiKey, params, modelOverride?}`. The proxy handles descriptor
1180
+ * lookup, model resolution, provider dispatch, and response normalization
1181
+ * (including repackaging `referenceImages` for the upstream wire format).
1182
+ * Error body `{error: string}` is surfaced as `proxy: ${error}`.
1183
+ * @param proxyUrl - Base URL of the proxy server
1184
+ * @param params - Same parameters as {@link callProviderImageGeneration}
1185
+ * @public
1186
+ */
1187
+ export async function callProxiedImageGeneration(proxyUrl, params) {
1188
+ const { descriptor, apiKey, params: request, modelOverride, logger, signal } = params;
1189
+ const body = {
1190
+ providerId: descriptor.id,
1191
+ apiKey,
1192
+ params: request
1193
+ };
1194
+ if (modelOverride !== undefined) {
1195
+ body.modelOverride = modelOverride;
1196
+ }
1197
+ /* c8 ignore next 1 - optional logger */
1198
+ logger === null || logger === void 0 ? void 0 : logger.info(`AI image proxy request: provider=${descriptor.id}, proxy=${proxyUrl}`);
1199
+ const url = `${proxyUrl}/api/ai/image-generation`;
1200
+ const jsonResult = await fetchJson(url, {}, body, logger, signal);
1201
+ if (jsonResult.isFailure()) {
1202
+ return fail(jsonResult.message);
1203
+ }
1204
+ const response = jsonResult.value;
1205
+ if (typeof response.error === 'string') {
1206
+ return fail(`proxy: ${response.error}`);
1207
+ }
1208
+ return proxiedImageGenerationResponse
1209
+ .validate(response)
1210
+ .withErrorFormat((msg) => `proxy returned invalid response: ${msg}`);
1211
+ }
484
1212
  //# sourceMappingURL=apiClient.js.map