@fgv/ts-extras 5.1.0-3 → 5.1.0-30

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