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