@fgv/ts-extras 5.1.0-2 → 5.1.0-21

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 (272) hide show
  1. package/dist/index.browser.js +2 -1
  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 +807 -67
  5. package/dist/packlets/ai-assist/apiClient.js.map +1 -0
  6. package/dist/packlets/ai-assist/chatRequestBuilders.js +180 -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/index.js +4 -3
  13. package/dist/packlets/ai-assist/index.js.map +1 -0
  14. package/dist/packlets/ai-assist/model.js +20 -3
  15. package/dist/packlets/ai-assist/model.js.map +1 -0
  16. package/dist/packlets/ai-assist/registry.js +137 -10
  17. package/dist/packlets/ai-assist/registry.js.map +1 -0
  18. package/dist/packlets/ai-assist/sseParser.js +122 -0
  19. package/dist/packlets/ai-assist/sseParser.js.map +1 -0
  20. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +192 -0
  21. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  22. package/dist/packlets/ai-assist/streamingAdapters/common.js +77 -0
  23. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  24. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +160 -0
  25. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  26. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +150 -0
  27. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  28. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +164 -0
  29. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  30. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +157 -0
  31. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  32. package/dist/packlets/ai-assist/streamingClient.js +94 -0
  33. package/dist/packlets/ai-assist/streamingClient.js.map +1 -0
  34. package/dist/packlets/ai-assist/toolFormats.js.map +1 -0
  35. package/dist/packlets/conversion/converters.js +34 -1
  36. package/dist/packlets/conversion/converters.js.map +1 -0
  37. package/dist/packlets/conversion/index.js.map +1 -0
  38. package/dist/packlets/crypto-utils/constants.js.map +1 -0
  39. package/dist/packlets/crypto-utils/converters.js.map +1 -0
  40. package/dist/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  41. package/dist/packlets/crypto-utils/encryptedFile.js.map +1 -0
  42. package/dist/packlets/crypto-utils/index.browser.js +2 -0
  43. package/dist/packlets/crypto-utils/index.browser.js.map +1 -0
  44. package/dist/packlets/crypto-utils/index.js +2 -0
  45. package/dist/packlets/crypto-utils/index.js.map +1 -0
  46. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +63 -0
  47. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  48. package/dist/packlets/crypto-utils/keystore/converters.js +101 -9
  49. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -0
  50. package/dist/packlets/crypto-utils/keystore/index.js +1 -0
  51. package/dist/packlets/crypto-utils/keystore/index.js.map +1 -0
  52. package/dist/packlets/crypto-utils/keystore/keyStore.js +431 -118
  53. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  54. package/dist/packlets/crypto-utils/keystore/model.js +22 -1
  55. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -0
  56. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js +21 -0
  57. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  58. package/dist/packlets/crypto-utils/model.js +10 -0
  59. package/dist/packlets/crypto-utils/model.js.map +1 -0
  60. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +163 -1
  61. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  62. package/dist/packlets/csv/csvFileHelpers.js.map +1 -0
  63. package/dist/packlets/csv/csvHelpers.js.map +1 -0
  64. package/dist/packlets/csv/index.browser.js.map +1 -0
  65. package/dist/packlets/csv/index.js.map +1 -0
  66. package/dist/packlets/experimental/extendedArray.js.map +1 -0
  67. package/dist/packlets/experimental/formatter.js.map +1 -0
  68. package/dist/packlets/experimental/index.js.map +1 -0
  69. package/dist/packlets/experimental/rangeOf.js.map +1 -0
  70. package/dist/packlets/hash/index.browser.js.map +1 -0
  71. package/dist/packlets/hash/index.js.map +1 -0
  72. package/dist/packlets/hash/index.node.js.map +1 -0
  73. package/dist/packlets/hash/md5Normalizer.browser.js.map +1 -0
  74. package/dist/packlets/hash/md5Normalizer.js.map +1 -0
  75. package/dist/packlets/mustache/index.js.map +1 -0
  76. package/dist/packlets/mustache/interfaces.js.map +1 -0
  77. package/dist/packlets/mustache/mustacheTemplate.js.map +1 -0
  78. package/dist/packlets/record-jar/index.browser.js.map +1 -0
  79. package/dist/packlets/record-jar/index.js.map +1 -0
  80. package/dist/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  81. package/dist/packlets/record-jar/recordJarHelpers.js.map +1 -0
  82. package/dist/packlets/yaml/converters.js.map +1 -0
  83. package/dist/packlets/yaml/index.js +1 -0
  84. package/dist/packlets/yaml/index.js.map +1 -0
  85. package/dist/packlets/yaml/serializers.js +48 -0
  86. package/dist/packlets/yaml/serializers.js.map +1 -0
  87. package/dist/packlets/zip-file-tree/index.js.map +1 -0
  88. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  89. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  90. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  91. package/dist/ts-extras.d.ts +1499 -41
  92. package/dist/tsdoc-metadata.json +1 -1
  93. package/lib/index.browser.d.ts +2 -1
  94. package/lib/index.browser.d.ts.map +1 -0
  95. package/lib/index.browser.js +3 -1
  96. package/lib/index.browser.js.map +1 -0
  97. package/lib/index.d.ts.map +1 -0
  98. package/lib/index.js.map +1 -0
  99. package/lib/packlets/ai-assist/apiClient.d.ts +140 -1
  100. package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -0
  101. package/lib/packlets/ai-assist/apiClient.js +810 -66
  102. package/lib/packlets/ai-assist/apiClient.js.map +1 -0
  103. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +89 -0
  104. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -0
  105. package/lib/packlets/ai-assist/chatRequestBuilders.js +189 -0
  106. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  107. package/lib/packlets/ai-assist/converters.d.ts.map +1 -0
  108. package/lib/packlets/ai-assist/converters.js +2 -1
  109. package/lib/packlets/ai-assist/converters.js.map +1 -0
  110. package/lib/packlets/ai-assist/endpoint.d.ts +28 -0
  111. package/lib/packlets/ai-assist/endpoint.d.ts.map +1 -0
  112. package/lib/packlets/ai-assist/endpoint.js +82 -0
  113. package/lib/packlets/ai-assist/endpoint.js.map +1 -0
  114. package/lib/packlets/ai-assist/index.d.ts +4 -3
  115. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  116. package/lib/packlets/ai-assist/index.js +12 -1
  117. package/lib/packlets/ai-assist/index.js.map +1 -0
  118. package/lib/packlets/ai-assist/model.d.ts +341 -3
  119. package/lib/packlets/ai-assist/model.d.ts.map +1 -0
  120. package/lib/packlets/ai-assist/model.js +21 -3
  121. package/lib/packlets/ai-assist/model.js.map +1 -0
  122. package/lib/packlets/ai-assist/registry.d.ts +34 -1
  123. package/lib/packlets/ai-assist/registry.d.ts.map +1 -0
  124. package/lib/packlets/ai-assist/registry.js +140 -11
  125. package/lib/packlets/ai-assist/registry.js.map +1 -0
  126. package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
  127. package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -0
  128. package/lib/packlets/ai-assist/sseParser.js +127 -0
  129. package/lib/packlets/ai-assist/sseParser.js.map +1 -0
  130. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +18 -0
  131. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -0
  132. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +195 -0
  133. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  134. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +79 -0
  135. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -0
  136. package/lib/packlets/ai-assist/streamingAdapters/common.js +81 -0
  137. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  138. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +19 -0
  139. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -0
  140. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +163 -0
  141. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  142. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +18 -0
  143. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -0
  144. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +153 -0
  145. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  146. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +19 -0
  147. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -0
  148. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +167 -0
  149. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  150. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
  151. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -0
  152. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +160 -0
  153. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  154. package/lib/packlets/ai-assist/streamingClient.d.ts +33 -0
  155. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -0
  156. package/lib/packlets/ai-assist/streamingClient.js +99 -0
  157. package/lib/packlets/ai-assist/streamingClient.js.map +1 -0
  158. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -0
  159. package/lib/packlets/ai-assist/toolFormats.js.map +1 -0
  160. package/lib/packlets/conversion/converters.d.ts +8 -1
  161. package/lib/packlets/conversion/converters.d.ts.map +1 -0
  162. package/lib/packlets/conversion/converters.js +35 -2
  163. package/lib/packlets/conversion/converters.js.map +1 -0
  164. package/lib/packlets/conversion/index.d.ts.map +1 -0
  165. package/lib/packlets/conversion/index.js.map +1 -0
  166. package/lib/packlets/crypto-utils/constants.d.ts.map +1 -0
  167. package/lib/packlets/crypto-utils/constants.js.map +1 -0
  168. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -0
  169. package/lib/packlets/crypto-utils/converters.js.map +1 -0
  170. package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts.map +1 -0
  171. package/lib/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  172. package/lib/packlets/crypto-utils/encryptedFile.d.ts.map +1 -0
  173. package/lib/packlets/crypto-utils/encryptedFile.js.map +1 -0
  174. package/lib/packlets/crypto-utils/index.browser.d.ts +1 -0
  175. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -0
  176. package/lib/packlets/crypto-utils/index.browser.js +4 -1
  177. package/lib/packlets/crypto-utils/index.browser.js.map +1 -0
  178. package/lib/packlets/crypto-utils/index.d.ts +1 -0
  179. package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
  180. package/lib/packlets/crypto-utils/index.js +4 -1
  181. package/lib/packlets/crypto-utils/index.js.map +1 -0
  182. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +50 -0
  183. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -0
  184. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +66 -0
  185. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  186. package/lib/packlets/crypto-utils/keystore/converters.d.ts +68 -6
  187. package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -0
  188. package/lib/packlets/crypto-utils/keystore/converters.js +100 -8
  189. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -0
  190. package/lib/packlets/crypto-utils/keystore/index.d.ts +1 -0
  191. package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -0
  192. package/lib/packlets/crypto-utils/keystore/index.js +1 -0
  193. package/lib/packlets/crypto-utils/keystore/index.js.map +1 -0
  194. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +125 -12
  195. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -0
  196. package/lib/packlets/crypto-utils/keystore/keyStore.js +431 -118
  197. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  198. package/lib/packlets/crypto-utils/keystore/model.d.ts +248 -17
  199. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -0
  200. package/lib/packlets/crypto-utils/keystore/model.js +24 -2
  201. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -0
  202. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +50 -0
  203. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -0
  204. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js +22 -0
  205. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  206. package/lib/packlets/crypto-utils/model.d.ts +145 -0
  207. package/lib/packlets/crypto-utils/model.d.ts.map +1 -0
  208. package/lib/packlets/crypto-utils/model.js +11 -1
  209. package/lib/packlets/crypto-utils/model.js.map +1 -0
  210. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +51 -1
  211. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -0
  212. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +162 -0
  213. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  214. package/lib/packlets/csv/csvFileHelpers.d.ts.map +1 -0
  215. package/lib/packlets/csv/csvFileHelpers.js.map +1 -0
  216. package/lib/packlets/csv/csvHelpers.d.ts.map +1 -0
  217. package/lib/packlets/csv/csvHelpers.js.map +1 -0
  218. package/lib/packlets/csv/index.browser.d.ts.map +1 -0
  219. package/lib/packlets/csv/index.browser.js.map +1 -0
  220. package/lib/packlets/csv/index.d.ts.map +1 -0
  221. package/lib/packlets/csv/index.js.map +1 -0
  222. package/lib/packlets/experimental/extendedArray.d.ts.map +1 -0
  223. package/lib/packlets/experimental/extendedArray.js.map +1 -0
  224. package/lib/packlets/experimental/formatter.d.ts.map +1 -0
  225. package/lib/packlets/experimental/formatter.js.map +1 -0
  226. package/lib/packlets/experimental/index.d.ts.map +1 -0
  227. package/lib/packlets/experimental/index.js.map +1 -0
  228. package/lib/packlets/experimental/rangeOf.d.ts.map +1 -0
  229. package/lib/packlets/experimental/rangeOf.js.map +1 -0
  230. package/lib/packlets/hash/index.browser.d.ts.map +1 -0
  231. package/lib/packlets/hash/index.browser.js.map +1 -0
  232. package/lib/packlets/hash/index.d.ts.map +1 -0
  233. package/lib/packlets/hash/index.js.map +1 -0
  234. package/lib/packlets/hash/index.node.d.ts.map +1 -0
  235. package/lib/packlets/hash/index.node.js.map +1 -0
  236. package/lib/packlets/hash/md5Normalizer.browser.d.ts.map +1 -0
  237. package/lib/packlets/hash/md5Normalizer.browser.js.map +1 -0
  238. package/lib/packlets/hash/md5Normalizer.d.ts.map +1 -0
  239. package/lib/packlets/hash/md5Normalizer.js.map +1 -0
  240. package/lib/packlets/mustache/index.d.ts.map +1 -0
  241. package/lib/packlets/mustache/index.js.map +1 -0
  242. package/lib/packlets/mustache/interfaces.d.ts.map +1 -0
  243. package/lib/packlets/mustache/interfaces.js.map +1 -0
  244. package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -0
  245. package/lib/packlets/mustache/mustacheTemplate.js.map +1 -0
  246. package/lib/packlets/record-jar/index.browser.d.ts.map +1 -0
  247. package/lib/packlets/record-jar/index.browser.js.map +1 -0
  248. package/lib/packlets/record-jar/index.d.ts.map +1 -0
  249. package/lib/packlets/record-jar/index.js.map +1 -0
  250. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts.map +1 -0
  251. package/lib/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  252. package/lib/packlets/record-jar/recordJarHelpers.d.ts.map +1 -0
  253. package/lib/packlets/record-jar/recordJarHelpers.js.map +1 -0
  254. package/lib/packlets/yaml/converters.d.ts.map +1 -0
  255. package/lib/packlets/yaml/converters.js.map +1 -0
  256. package/lib/packlets/yaml/index.d.ts +1 -0
  257. package/lib/packlets/yaml/index.d.ts.map +1 -0
  258. package/lib/packlets/yaml/index.js +1 -0
  259. package/lib/packlets/yaml/index.js.map +1 -0
  260. package/lib/packlets/yaml/serializers.d.ts +45 -0
  261. package/lib/packlets/yaml/serializers.d.ts.map +1 -0
  262. package/lib/packlets/yaml/serializers.js +84 -0
  263. package/lib/packlets/yaml/serializers.js.map +1 -0
  264. package/lib/packlets/zip-file-tree/index.d.ts.map +1 -0
  265. package/lib/packlets/zip-file-tree/index.js.map +1 -0
  266. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +2 -2
  267. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts.map +1 -0
  268. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +2 -2
  269. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  270. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts.map +1 -0
  271. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  272. package/package.json +24 -23
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
22
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var m = o[Symbol.asyncIterator], i;
25
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
26
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
27
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
28
+ };
29
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
30
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
31
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
32
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
33
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
34
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
35
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
36
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
37
+ function fulfill(value) { resume("next", value); }
38
+ function reject(value) { resume("throw", value); }
39
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.callAnthropicStream = callAnthropicStream;
43
+ /**
44
+ * Streaming adapter for the Anthropic Messages API. Surfaces server tool
45
+ * use (e.g. `web_search`) as unified `tool-event` markers based on the
46
+ * `content_block_start` / `content_block_stop` lifecycle.
47
+ *
48
+ * @packageDocumentation
49
+ */
50
+ const ts_utils_1 = require("@fgv/ts-utils");
51
+ const chatRequestBuilders_1 = require("../chatRequestBuilders");
52
+ const sseParser_1 = require("../sseParser");
53
+ const toolFormats_1 = require("../toolFormats");
54
+ const common_1 = require("./common");
55
+ const anthropicContentBlockInner = ts_utils_1.Validators.object({
56
+ type: ts_utils_1.Validators.string.optional(),
57
+ name: ts_utils_1.Validators.string.optional()
58
+ }, { options: { optionalFields: ['type', 'name'] } });
59
+ const anthropicContentBlockStartPayload = ts_utils_1.Validators.object({
60
+ content_block: anthropicContentBlockInner
61
+ });
62
+ const anthropicContentBlockDeltaInner = ts_utils_1.Validators.object({
63
+ type: ts_utils_1.Validators.string.optional(),
64
+ text: ts_utils_1.Validators.string.optional()
65
+ }, { options: { optionalFields: ['type', 'text'] } });
66
+ const anthropicContentBlockDeltaPayload = ts_utils_1.Validators.object({
67
+ delta: anthropicContentBlockDeltaInner
68
+ });
69
+ const anthropicMessageDeltaInner = ts_utils_1.Validators.object({ stop_reason: ts_utils_1.Validators.string.optional() }, { options: { optionalFields: ['stop_reason'] } });
70
+ const anthropicMessageDeltaPayload = ts_utils_1.Validators.object({
71
+ delta: anthropicMessageDeltaInner
72
+ });
73
+ const anthropicErrorInner = ts_utils_1.Validators.object({ message: ts_utils_1.Validators.string.optional() }, { options: { optionalFields: ['message'] } });
74
+ const anthropicErrorPayload = ts_utils_1.Validators.object({ error: anthropicErrorInner.optional() }, { options: { optionalFields: ['error'] } });
75
+ // ============================================================================
76
+ // Stream translator
77
+ // ============================================================================
78
+ /**
79
+ * Translates an Anthropic Messages API SSE stream into unified events.
80
+ *
81
+ * @internal
82
+ */
83
+ function translateAnthropicStream(response) {
84
+ return __asyncGenerator(this, arguments, function* translateAnthropicStream_1() {
85
+ var _a, e_1, _b, _c;
86
+ var _d, _e;
87
+ let fullText = '';
88
+ let truncated = false;
89
+ let stopped = false;
90
+ try {
91
+ /* c8 ignore next - body is non-null at this point per openSseConnection */
92
+ if (!response.body)
93
+ return yield __await(void 0);
94
+ try {
95
+ for (var _f = true, _g = __asyncValues((0, sseParser_1.readSseEvents)(response.body)), _h; _h = yield __await(_g.next()), _a = _h.done, !_a; _f = true) {
96
+ _c = _h.value;
97
+ _f = false;
98
+ const message = _c;
99
+ const eventName = message.event;
100
+ if (eventName === 'content_block_start') {
101
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicContentBlockStartPayload);
102
+ const block = payload === null || payload === void 0 ? void 0 : payload.content_block;
103
+ if ((block === null || block === void 0 ? void 0 : block.type) === 'server_tool_use' && block.name === 'web_search') {
104
+ yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'started' });
105
+ }
106
+ else if ((block === null || block === void 0 ? void 0 : block.type) === 'web_search_tool_result') {
107
+ yield yield __await({ type: 'tool-event', toolType: 'web_search', phase: 'completed' });
108
+ }
109
+ }
110
+ else if (eventName === 'content_block_delta') {
111
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicContentBlockDeltaPayload);
112
+ if ((payload === null || payload === void 0 ? void 0 : payload.delta.type) === 'text_delta' && typeof payload.delta.text === 'string') {
113
+ const delta = payload.delta.text;
114
+ if (delta.length > 0) {
115
+ fullText += delta;
116
+ yield yield __await({ type: 'text-delta', delta });
117
+ }
118
+ }
119
+ }
120
+ else if (eventName === 'message_delta') {
121
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicMessageDeltaPayload);
122
+ if ((payload === null || payload === void 0 ? void 0 : payload.delta.stop_reason) === 'max_tokens') {
123
+ truncated = true;
124
+ }
125
+ }
126
+ else if (eventName === 'message_stop') {
127
+ stopped = true;
128
+ }
129
+ else if (eventName === 'error') {
130
+ const payload = (0, common_1.validateEventPayload)((0, sseParser_1.parseSseEventJson)(message.data), anthropicErrorPayload);
131
+ yield yield __await({
132
+ type: 'error',
133
+ message: (_e = (_d = payload === null || payload === void 0 ? void 0 : payload.error) === null || _d === void 0 ? void 0 : _d.message) !== null && _e !== void 0 ? _e : 'Anthropic stream returned an error event'
134
+ });
135
+ return yield __await(void 0);
136
+ }
137
+ }
138
+ }
139
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
140
+ finally {
141
+ try {
142
+ if (!_f && !_a && (_b = _g.return)) yield __await(_b.call(_g));
143
+ }
144
+ finally { if (e_1) throw e_1.error; }
145
+ }
146
+ }
147
+ catch (err) {
148
+ yield yield __await({ type: 'error', message: err instanceof Error ? err.message : String(err) });
149
+ return yield __await(void 0);
150
+ }
151
+ if (stopped) {
152
+ yield yield __await({ type: 'done', truncated, fullText });
153
+ }
154
+ else {
155
+ yield yield __await({ type: 'error', message: 'Anthropic stream ended without a message_stop event' });
156
+ }
157
+ });
158
+ }
159
+ // ============================================================================
160
+ // Per-format request caller
161
+ // ============================================================================
162
+ /**
163
+ * Issues a streaming Anthropic Messages request and returns the
164
+ * unified-event iterable on success.
165
+ *
166
+ * @internal
167
+ */
168
+ async function callAnthropicStream(config, prompt, messagesBefore, temperature, tools, logger, signal) {
169
+ const url = `${config.baseUrl}/messages`;
170
+ const messages = (0, chatRequestBuilders_1.buildAnthropicMessages)(prompt, { head: messagesBefore });
171
+ const body = {
172
+ model: config.model,
173
+ system: prompt.system,
174
+ messages,
175
+ max_tokens: 4096,
176
+ temperature,
177
+ stream: true
178
+ };
179
+ if (tools && tools.length > 0) {
180
+ body.tools = (0, toolFormats_1.toAnthropicTools)(tools);
181
+ }
182
+ const headers = {
183
+ 'x-api-key': config.apiKey,
184
+ 'anthropic-version': '2023-06-01',
185
+ 'anthropic-dangerous-direct-browser-access': 'true'
186
+ };
187
+ /* c8 ignore next 3 - optional logger diagnostic output */
188
+ if (logger) {
189
+ const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';
190
+ logger.info(`Anthropic streaming: model=${config.model}, tools=${toolTypes}`);
191
+ }
192
+ const conn = await (0, common_1.openSseConnection)(url, headers, body, logger, signal);
193
+ return conn.onSuccess((response) => (0, ts_utils_1.succeed)(translateAnthropicStream(response)));
194
+ }
195
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Streaming adapter for the Anthropic Messages API. Surfaces server tool\n * use (e.g. `web_search`) as unified `tool-event` markers based on the\n * `content_block_start` / `content_block_stop` lifecycle.\n *\n * @packageDocumentation\n */\n\nimport { type Logging, Result, succeed, type Validator, Validators } from '@fgv/ts-utils';\n\nimport { buildAnthropicMessages } from '../chatRequestBuilders';\nimport { AiPrompt, type AiServerToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';\nimport { parseSseEventJson, readSseEvents } from '../sseParser';\nimport { toAnthropicTools } from '../toolFormats';\nimport { IStreamApiConfig, openSseConnection, validateEventPayload } from './common';\n\n// ============================================================================\n// Event payload shapes\n// ============================================================================\n\n/**\n * Payload of a `content_block_start` SSE event. The `type` discriminator\n * tells us whether the block is text, a server-tool invocation, or a\n * tool result.\n *\n * @internal\n */\ninterface IAnthropicContentBlockStartPayload {\n readonly content_block: { readonly type?: string; readonly name?: string };\n}\n\n/**\n * Payload of a `content_block_delta` SSE event. The inner `delta.type`\n * discriminator is `text_delta` for the text we care about; other values\n * (e.g. `input_json_delta` for tool args) are ignored.\n *\n * @internal\n */\ninterface IAnthropicContentBlockDeltaPayload {\n readonly delta: { readonly type?: string; readonly text?: string };\n}\n\n/**\n * Payload of a `message_delta` SSE event carrying the final stop reason.\n *\n * @internal\n */\ninterface IAnthropicMessageDeltaPayload {\n readonly delta: { readonly stop_reason?: string };\n}\n\n/**\n * Payload of an `error` SSE event.\n *\n * @internal\n */\ninterface IAnthropicErrorPayload {\n readonly error?: { readonly message?: string };\n}\n\nconst anthropicContentBlockInner: Validator<{ type?: string; name?: string }> = Validators.object<{\n type?: string;\n name?: string;\n}>(\n {\n type: Validators.string.optional(),\n name: Validators.string.optional()\n },\n { options: { optionalFields: ['type', 'name'] } }\n);\n\nconst anthropicContentBlockStartPayload: Validator<IAnthropicContentBlockStartPayload> =\n Validators.object<IAnthropicContentBlockStartPayload>({\n content_block: anthropicContentBlockInner\n });\n\nconst anthropicContentBlockDeltaInner: Validator<{ type?: string; text?: string }> = Validators.object<{\n type?: string;\n text?: string;\n}>(\n {\n type: Validators.string.optional(),\n text: Validators.string.optional()\n },\n { options: { optionalFields: ['type', 'text'] } }\n);\n\nconst anthropicContentBlockDeltaPayload: Validator<IAnthropicContentBlockDeltaPayload> =\n Validators.object<IAnthropicContentBlockDeltaPayload>({\n delta: anthropicContentBlockDeltaInner\n });\n\nconst anthropicMessageDeltaInner: Validator<{ stop_reason?: string }> = Validators.object<{\n stop_reason?: string;\n}>({ stop_reason: Validators.string.optional() }, { options: { optionalFields: ['stop_reason'] } });\n\nconst anthropicMessageDeltaPayload: Validator<IAnthropicMessageDeltaPayload> =\n Validators.object<IAnthropicMessageDeltaPayload>({\n delta: anthropicMessageDeltaInner\n });\n\nconst anthropicErrorInner: Validator<{ message?: string }> = Validators.object<{ message?: string }>(\n { message: Validators.string.optional() },\n { options: { optionalFields: ['message'] } }\n);\n\nconst anthropicErrorPayload: Validator<IAnthropicErrorPayload> = Validators.object<IAnthropicErrorPayload>(\n { error: anthropicErrorInner.optional() },\n { options: { optionalFields: ['error'] } }\n);\n\n// ============================================================================\n// Stream translator\n// ============================================================================\n\n/**\n * Translates an Anthropic Messages API SSE stream into unified events.\n *\n * @internal\n */\nasync function* translateAnthropicStream(response: Response): AsyncGenerator<IAiStreamEvent> {\n let fullText = '';\n let truncated = false;\n let stopped = false;\n\n try {\n /* c8 ignore next - body is non-null at this point per openSseConnection */\n if (!response.body) return;\n for await (const message of readSseEvents(response.body)) {\n const eventName = message.event;\n if (eventName === 'content_block_start') {\n const payload = validateEventPayload(\n parseSseEventJson(message.data),\n anthropicContentBlockStartPayload\n );\n const block = payload?.content_block;\n if (block?.type === 'server_tool_use' && block.name === 'web_search') {\n yield { type: 'tool-event', toolType: 'web_search', phase: 'started' };\n } else if (block?.type === 'web_search_tool_result') {\n yield { type: 'tool-event', toolType: 'web_search', phase: 'completed' };\n }\n } else if (eventName === 'content_block_delta') {\n const payload = validateEventPayload(\n parseSseEventJson(message.data),\n anthropicContentBlockDeltaPayload\n );\n if (payload?.delta.type === 'text_delta' && typeof payload.delta.text === 'string') {\n const delta = payload.delta.text;\n if (delta.length > 0) {\n fullText += delta;\n yield { type: 'text-delta', delta };\n }\n }\n } else if (eventName === 'message_delta') {\n const payload = validateEventPayload(parseSseEventJson(message.data), anthropicMessageDeltaPayload);\n if (payload?.delta.stop_reason === 'max_tokens') {\n truncated = true;\n }\n } else if (eventName === 'message_stop') {\n stopped = true;\n } else if (eventName === 'error') {\n const payload = validateEventPayload(parseSseEventJson(message.data), anthropicErrorPayload);\n yield {\n type: 'error',\n message: payload?.error?.message ?? 'Anthropic stream returned an error event'\n };\n return;\n }\n }\n } catch (err: unknown) {\n yield { type: 'error', message: err instanceof Error ? err.message : String(err) };\n return;\n }\n\n if (stopped) {\n yield { type: 'done', truncated, fullText };\n } else {\n yield { type: 'error', message: 'Anthropic stream ended without a message_stop event' };\n }\n}\n\n// ============================================================================\n// Per-format request caller\n// ============================================================================\n\n/**\n * Issues a streaming Anthropic Messages request and returns the\n * unified-event iterable on success.\n *\n * @internal\n */\nexport async function callAnthropicStream(\n config: IStreamApiConfig,\n prompt: AiPrompt,\n messagesBefore: ReadonlyArray<IChatMessage> | undefined,\n temperature: number,\n tools: ReadonlyArray<AiServerToolConfig> | undefined,\n logger?: Logging.ILogger,\n signal?: AbortSignal\n): Promise<Result<AsyncIterable<IAiStreamEvent>>> {\n const url = `${config.baseUrl}/messages`;\n const messages = buildAnthropicMessages(prompt, { head: messagesBefore });\n const body: Record<string, unknown> = {\n model: config.model,\n system: prompt.system,\n messages,\n max_tokens: 4096,\n temperature,\n stream: true\n };\n if (tools && tools.length > 0) {\n body.tools = toAnthropicTools(tools);\n }\n const headers: Record<string, string> = {\n 'x-api-key': config.apiKey,\n 'anthropic-version': '2023-06-01',\n 'anthropic-dangerous-direct-browser-access': 'true'\n };\n /* c8 ignore next 3 - optional logger diagnostic output */\n if (logger) {\n const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';\n logger.info(`Anthropic streaming: model=${config.model}, tools=${toolTypes}`);\n }\n const conn = await openSseConnection(url, headers, body, logger, signal);\n return conn.onSuccess((response) => succeed(translateAnthropicStream(response)));\n}\n"]}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Shared infrastructure for the per-format streaming adapters: HTTP connection
3
+ * helper, common config and request-params types, and a typed-validation
4
+ * helper for SSE event payloads.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import { type Logging, Result, type Validator } from '@fgv/ts-utils';
9
+ import { AiPrompt, type AiServerToolConfig, type IAiProviderDescriptor, type IChatMessage, type ModelSpec } from '../model';
10
+ /**
11
+ * Parameters for a streaming completion request. Structurally identical to
12
+ * the non-streaming `IProviderCompletionParams`; kept as its own interface
13
+ * so callers can be explicit about which path they're invoking.
14
+ *
15
+ * @public
16
+ */
17
+ export interface IProviderCompletionStreamParams {
18
+ /** The provider descriptor */
19
+ readonly descriptor: IAiProviderDescriptor;
20
+ /** API key for authentication */
21
+ readonly apiKey: string;
22
+ /** The structured prompt to send */
23
+ readonly prompt: AiPrompt;
24
+ /**
25
+ * Prior conversation history to insert between the system prompt and the
26
+ * prompt's user message. The new user turn (carried by `prompt.user`) is
27
+ * always sent last, so the wire shape becomes
28
+ * `[system, ...messagesBefore, user=prompt.user]`.
29
+ */
30
+ readonly messagesBefore?: ReadonlyArray<IChatMessage>;
31
+ /** Sampling temperature (default: 0.7) */
32
+ readonly temperature?: number;
33
+ /** Optional model override — string or context-aware map. */
34
+ readonly modelOverride?: ModelSpec;
35
+ /** Optional logger for request/response observability. */
36
+ readonly logger?: Logging.ILogger;
37
+ /** Server-side tools to include in the request. */
38
+ readonly tools?: ReadonlyArray<AiServerToolConfig>;
39
+ /** Optional abort signal for cancelling the in-flight stream. */
40
+ readonly signal?: AbortSignal;
41
+ /**
42
+ * Optional override of the descriptor's default base URL. Same semantics as
43
+ * the non-streaming completion path: a well-formed `http`/`https` URL is
44
+ * substituted for `descriptor.baseUrl` when composing the streaming
45
+ * request, with the per-format suffix appended unchanged. Validated at the
46
+ * dispatcher; auth shape is unaffected.
47
+ */
48
+ readonly endpoint?: string;
49
+ }
50
+ /**
51
+ * Configuration for a single per-format streaming call: where to POST, what
52
+ * key to authenticate with, and which model to request.
53
+ *
54
+ * @internal
55
+ */
56
+ export interface IStreamApiConfig {
57
+ readonly baseUrl: string;
58
+ readonly apiKey: string;
59
+ readonly model: string;
60
+ }
61
+ /**
62
+ * Opens an SSE-style POST connection. Returns the underlying Response on a
63
+ * 2xx; failures (network, non-2xx, missing body) surface as Result.fail
64
+ * carrying the body text.
65
+ *
66
+ * @internal
67
+ */
68
+ export declare function openSseConnection(url: string, headers: Record<string, string>, body: unknown, logger?: Logging.ILogger, signal?: AbortSignal): Promise<Result<Response>>;
69
+ /**
70
+ * Validates a parsed SSE event payload against a typed shape, returning the
71
+ * validated value or `undefined` if validation fails. Adapters use the
72
+ * `undefined` return to skip unrecognized event shapes — the same lenient
73
+ * behavior the inline casts had, but with a real type-checked path on the
74
+ * happy case.
75
+ *
76
+ * @internal
77
+ */
78
+ export declare function validateEventPayload<T>(json: unknown, validator: Validator<T>): T | undefined;
79
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/common.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AAEH,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,EAAW,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAEpF,OAAO,EACL,QAAQ,EACR,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AACH,MAAM,WAAW,+BAA+B;IAC9C,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACtD,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC;IAClC,mDAAmD;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACnD,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAiC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAG7F"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.openSseConnection = openSseConnection;
23
+ exports.validateEventPayload = validateEventPayload;
24
+ /**
25
+ * Shared infrastructure for the per-format streaming adapters: HTTP connection
26
+ * helper, common config and request-params types, and a typed-validation
27
+ * helper for SSE event payloads.
28
+ *
29
+ * @packageDocumentation
30
+ */
31
+ const ts_utils_1 = require("@fgv/ts-utils");
32
+ /**
33
+ * Opens an SSE-style POST connection. Returns the underlying Response on a
34
+ * 2xx; failures (network, non-2xx, missing body) surface as Result.fail
35
+ * carrying the body text.
36
+ *
37
+ * @internal
38
+ */
39
+ async function openSseConnection(url, headers, body, logger, signal) {
40
+ /* c8 ignore next 1 - optional logger */
41
+ logger === null || logger === void 0 ? void 0 : logger.detail(`AI streaming request: POST ${url}`);
42
+ let response;
43
+ try {
44
+ response = await fetch(url, {
45
+ method: 'POST',
46
+ headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers),
47
+ body: JSON.stringify(body),
48
+ signal
49
+ });
50
+ }
51
+ catch (err) {
52
+ const detail = err instanceof Error ? err.message : String(err);
53
+ /* c8 ignore next 1 - optional logger */
54
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI streaming request failed: ${detail}`);
55
+ return (0, ts_utils_1.fail)(`AI streaming request failed: ${detail}`);
56
+ }
57
+ if (!response.ok) {
58
+ const errorText = await response.text().catch(() => 'unknown error');
59
+ /* c8 ignore next 1 - optional logger */
60
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI streaming API returned ${response.status}: ${errorText}`);
61
+ return (0, ts_utils_1.fail)(`AI streaming API returned ${response.status}: ${errorText}`);
62
+ }
63
+ if (!response.body) {
64
+ return (0, ts_utils_1.fail)('AI streaming API returned an empty body');
65
+ }
66
+ return (0, ts_utils_1.succeed)(response);
67
+ }
68
+ /**
69
+ * Validates a parsed SSE event payload against a typed shape, returning the
70
+ * validated value or `undefined` if validation fails. Adapters use the
71
+ * `undefined` return to skip unrecognized event shapes — the same lenient
72
+ * behavior the inline casts had, but with a real type-checked path on the
73
+ * happy case.
74
+ *
75
+ * @internal
76
+ */
77
+ function validateEventPayload(json, validator) {
78
+ const result = validator.validate(json);
79
+ return result.isSuccess() ? result.value : undefined;
80
+ }
81
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/common.ts"],"names":[],"mappings":";AAAA,kCAAkC;AAClC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AACxE,2DAA2D;AAC3D,EAAE;AACF,iFAAiF;AACjF,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,YAAY;;AAgFZ,8CAuCC;AAWD,oDAGC;AAnID;;;;;;GAMG;AAEH,4CAAoF;AA+DpF;;;;;;GAMG;AACI,KAAK,UAAU,iBAAiB,CACrC,GAAW,EACX,OAA+B,EAC/B,IAAa,EACb,MAAwB,EACxB,MAAoB;IAEpB,wCAAwC;IACxC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,MAAM,CAAC,8BAA8B,GAAG,EAAE,CAAC,CAAC;IAEpD,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM,EAAE,MAAM;YACd,OAAO,kBACL,cAAc,EAAE,kBAAkB,EAClC,MAAM,EAAE,mBAAmB,IACxB,OAAO,CACX;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,wCAAwC;QACxC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,KAAK,CAAC,gCAAgC,MAAM,EAAE,CAAC,CAAC;QACxD,OAAO,IAAA,eAAI,EAAC,gCAAgC,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;QACrE,wCAAwC;QACxC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,KAAK,CAAC,6BAA6B,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC;QAC5E,OAAO,IAAA,eAAI,EAAC,6BAA6B,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,OAAO,IAAA,eAAI,EAAC,yCAAyC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,IAAA,kBAAO,EAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAAI,IAAa,EAAE,SAAuB;IAC5E,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Shared infrastructure for the per-format streaming adapters: HTTP connection\n * helper, common config and request-params types, and a typed-validation\n * helper for SSE event payloads.\n *\n * @packageDocumentation\n */\n\nimport { fail, type Logging, Result, succeed, type Validator } from '@fgv/ts-utils';\n\nimport {\n AiPrompt,\n type AiServerToolConfig,\n type IAiProviderDescriptor,\n type IChatMessage,\n type ModelSpec\n} from '../model';\n\n/**\n * Parameters for a streaming completion request. Structurally identical to\n * the non-streaming `IProviderCompletionParams`; kept as its own interface\n * so callers can be explicit about which path they're invoking.\n *\n * @public\n */\nexport interface IProviderCompletionStreamParams {\n /** The provider descriptor */\n readonly descriptor: IAiProviderDescriptor;\n /** API key for authentication */\n readonly apiKey: string;\n /** The structured prompt to send */\n readonly prompt: AiPrompt;\n /**\n * Prior conversation history to insert between the system prompt and the\n * prompt's user message. The new user turn (carried by `prompt.user`) is\n * always sent last, so the wire shape becomes\n * `[system, ...messagesBefore, user=prompt.user]`.\n */\n readonly messagesBefore?: ReadonlyArray<IChatMessage>;\n /** Sampling temperature (default: 0.7) */\n readonly temperature?: number;\n /** Optional model override — string or context-aware map. */\n readonly modelOverride?: ModelSpec;\n /** Optional logger for request/response observability. */\n readonly logger?: Logging.ILogger;\n /** Server-side tools to include in the request. */\n readonly tools?: ReadonlyArray<AiServerToolConfig>;\n /** Optional abort signal for cancelling the in-flight stream. */\n readonly signal?: AbortSignal;\n /**\n * Optional override of the descriptor's default base URL. Same semantics as\n * the non-streaming completion path: a well-formed `http`/`https` URL is\n * substituted for `descriptor.baseUrl` when composing the streaming\n * request, with the per-format suffix appended unchanged. Validated at the\n * dispatcher; auth shape is unaffected.\n */\n readonly endpoint?: string;\n}\n\n/**\n * Configuration for a single per-format streaming call: where to POST, what\n * key to authenticate with, and which model to request.\n *\n * @internal\n */\nexport interface IStreamApiConfig {\n readonly baseUrl: string;\n readonly apiKey: string;\n readonly model: string;\n}\n\n/**\n * Opens an SSE-style POST connection. Returns the underlying Response on a\n * 2xx; failures (network, non-2xx, missing body) surface as Result.fail\n * carrying the body text.\n *\n * @internal\n */\nexport async function openSseConnection(\n url: string,\n headers: Record<string, string>,\n body: unknown,\n logger?: Logging.ILogger,\n signal?: AbortSignal\n): Promise<Result<Response>> {\n /* c8 ignore next 1 - optional logger */\n logger?.detail(`AI streaming request: POST ${url}`);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...headers\n },\n body: JSON.stringify(body),\n signal\n });\n } catch (err: unknown) {\n const detail = err instanceof Error ? err.message : String(err);\n /* c8 ignore next 1 - optional logger */\n logger?.error(`AI streaming request failed: ${detail}`);\n return fail(`AI streaming request failed: ${detail}`);\n }\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => 'unknown error');\n /* c8 ignore next 1 - optional logger */\n logger?.error(`AI streaming API returned ${response.status}: ${errorText}`);\n return fail(`AI streaming API returned ${response.status}: ${errorText}`);\n }\n if (!response.body) {\n return fail('AI streaming API returned an empty body');\n }\n return succeed(response);\n}\n\n/**\n * Validates a parsed SSE event payload against a typed shape, returning the\n * validated value or `undefined` if validation fails. Adapters use the\n * `undefined` return to skip unrecognized event shapes — the same lenient\n * behavior the inline casts had, but with a real type-checked path on the\n * happy case.\n *\n * @internal\n */\nexport function validateEventPayload<T>(json: unknown, validator: Validator<T>): T | undefined {\n const result = validator.validate(json);\n return result.isSuccess() ? result.value : undefined;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Streaming adapter for Gemini's `streamGenerateContent` endpoint. Gemini
3
+ * emits no explicit tool-progress events even when `google_search` is
4
+ * enabled — grounding metadata arrives attached to text chunks — so this
5
+ * adapter never yields `tool-event`s.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { type Logging, Result } from '@fgv/ts-utils';
10
+ import { AiPrompt, type AiServerToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';
11
+ import { IStreamApiConfig } from './common';
12
+ /**
13
+ * Issues a streaming Gemini request and returns the unified-event iterable
14
+ * on success.
15
+ *
16
+ * @internal
17
+ */
18
+ export declare function callGeminiStream(config: IStreamApiConfig, prompt: AiPrompt, messagesBefore: ReadonlyArray<IChatMessage> | undefined, temperature: number, tools: ReadonlyArray<AiServerToolConfig> | undefined, logger?: Logging.ILogger, signal?: AbortSignal): Promise<Result<AsyncIterable<IAiStreamEvent>>>;
19
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/gemini.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAuC,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAGrG,OAAO,EAAE,gBAAgB,EAA2C,MAAM,UAAU,CAAC;AAoHrF;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,QAAQ,EAChB,cAAc,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,SAAS,EACvD,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,SAAS,EACpD,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CAmBhD"}
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
22
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
23
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
24
+ var m = o[Symbol.asyncIterator], i;
25
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
26
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
27
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
28
+ };
29
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
30
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
31
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
32
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
33
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
34
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
35
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
36
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
37
+ function fulfill(value) { resume("next", value); }
38
+ function reject(value) { resume("throw", value); }
39
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.callGeminiStream = callGeminiStream;
43
+ /**
44
+ * Streaming adapter for Gemini's `streamGenerateContent` endpoint. Gemini
45
+ * emits no explicit tool-progress events even when `google_search` is
46
+ * enabled — grounding metadata arrives attached to text chunks — so this
47
+ * adapter never yields `tool-event`s.
48
+ *
49
+ * @packageDocumentation
50
+ */
51
+ const ts_utils_1 = require("@fgv/ts-utils");
52
+ const chatRequestBuilders_1 = require("../chatRequestBuilders");
53
+ const sseParser_1 = require("../sseParser");
54
+ const toolFormats_1 = require("../toolFormats");
55
+ const common_1 = require("./common");
56
+ const geminiStreamPart = ts_utils_1.Validators.object({ text: ts_utils_1.Validators.string.optional() }, { options: { optionalFields: ['text'] } });
57
+ const geminiStreamContent = ts_utils_1.Validators.object({ parts: ts_utils_1.Validators.arrayOf(geminiStreamPart).optional() }, { options: { optionalFields: ['parts'] } });
58
+ const geminiStreamCandidate = ts_utils_1.Validators.object({
59
+ content: geminiStreamContent.optional(),
60
+ finishReason: ts_utils_1.Validators.string.optional()
61
+ }, { options: { optionalFields: ['content', 'finishReason'] } });
62
+ const geminiStreamChunk = ts_utils_1.Validators.object({
63
+ candidates: ts_utils_1.Validators.arrayOf(geminiStreamCandidate)
64
+ });
65
+ // ============================================================================
66
+ // Stream translator
67
+ // ============================================================================
68
+ /**
69
+ * Translates a Gemini streamGenerateContent SSE stream into unified events.
70
+ *
71
+ * @internal
72
+ */
73
+ function translateGeminiStream(response) {
74
+ return __asyncGenerator(this, arguments, function* translateGeminiStream_1() {
75
+ var _a, e_1, _b, _c;
76
+ var _d;
77
+ let fullText = '';
78
+ let truncated = false;
79
+ let receivedFinishReason = false;
80
+ try {
81
+ /* c8 ignore next - body is non-null at this point per openSseConnection */
82
+ if (!response.body)
83
+ return yield __await(void 0);
84
+ try {
85
+ for (var _e = true, _f = __asyncValues((0, sseParser_1.readSseEvents)(response.body)), _g; _g = yield __await(_f.next()), _a = _g.done, !_a; _e = true) {
86
+ _c = _g.value;
87
+ _e = false;
88
+ const message = _c;
89
+ const json = (0, sseParser_1.parseSseEventJson)(message.data);
90
+ if (json === undefined) {
91
+ continue;
92
+ }
93
+ const chunk = (0, common_1.validateEventPayload)(json, geminiStreamChunk);
94
+ const candidate = chunk === null || chunk === void 0 ? void 0 : chunk.candidates[0];
95
+ if (!candidate) {
96
+ continue;
97
+ }
98
+ const parts = (_d = candidate.content) === null || _d === void 0 ? void 0 : _d.parts;
99
+ if (parts) {
100
+ for (const part of parts) {
101
+ if (typeof part.text === 'string' && part.text.length > 0) {
102
+ fullText += part.text;
103
+ yield yield __await({ type: 'text-delta', delta: part.text });
104
+ }
105
+ }
106
+ }
107
+ const finishReason = candidate.finishReason;
108
+ if (typeof finishReason === 'string' && finishReason.length > 0) {
109
+ truncated = finishReason === 'MAX_TOKENS';
110
+ receivedFinishReason = true;
111
+ }
112
+ }
113
+ }
114
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
115
+ finally {
116
+ try {
117
+ if (!_e && !_a && (_b = _f.return)) yield __await(_b.call(_f));
118
+ }
119
+ finally { if (e_1) throw e_1.error; }
120
+ }
121
+ }
122
+ catch (err) {
123
+ yield yield __await({ type: 'error', message: err instanceof Error ? err.message : String(err) });
124
+ return yield __await(void 0);
125
+ }
126
+ if (receivedFinishReason) {
127
+ yield yield __await({ type: 'done', truncated, fullText });
128
+ }
129
+ else {
130
+ yield yield __await({ type: 'error', message: 'Gemini stream ended without a finishReason' });
131
+ }
132
+ });
133
+ }
134
+ // ============================================================================
135
+ // Per-format request caller
136
+ // ============================================================================
137
+ /**
138
+ * Issues a streaming Gemini request and returns the unified-event iterable
139
+ * on success.
140
+ *
141
+ * @internal
142
+ */
143
+ async function callGeminiStream(config, prompt, messagesBefore, temperature, tools, logger, signal) {
144
+ const url = `${config.baseUrl}/models/${config.model}:streamGenerateContent?alt=sse`;
145
+ const contents = (0, chatRequestBuilders_1.buildGeminiContents)(prompt, { head: messagesBefore });
146
+ const body = {
147
+ systemInstruction: { parts: [{ text: prompt.system }] },
148
+ contents,
149
+ generationConfig: { temperature }
150
+ };
151
+ if (tools && tools.length > 0) {
152
+ body.tools = (0, toolFormats_1.toGeminiTools)(tools);
153
+ }
154
+ const headers = { 'x-goog-api-key': config.apiKey };
155
+ /* c8 ignore next 3 - optional logger diagnostic output */
156
+ if (logger) {
157
+ const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';
158
+ logger.info(`Gemini streaming: model=${config.model}, tools=${toolTypes}`);
159
+ }
160
+ const conn = await (0, common_1.openSseConnection)(url, headers, body, logger, signal);
161
+ return conn.onSuccess((response) => (0, ts_utils_1.succeed)(translateGeminiStream(response)));
162
+ }
163
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/gemini.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n/**\n * Streaming adapter for Gemini's `streamGenerateContent` endpoint. Gemini\n * emits no explicit tool-progress events even when `google_search` is\n * enabled — grounding metadata arrives attached to text chunks — so this\n * adapter never yields `tool-event`s.\n *\n * @packageDocumentation\n */\n\nimport { type Logging, Result, succeed, type Validator, Validators } from '@fgv/ts-utils';\n\nimport { buildGeminiContents } from '../chatRequestBuilders';\nimport { AiPrompt, type AiServerToolConfig, type IAiStreamEvent, type IChatMessage } from '../model';\nimport { parseSseEventJson, readSseEvents } from '../sseParser';\nimport { toGeminiTools } from '../toolFormats';\nimport { IStreamApiConfig, openSseConnection, validateEventPayload } from './common';\n\n// ============================================================================\n// Event payload shapes\n// ============================================================================\n\n/**\n * One `parts[]` element in a Gemini streaming chunk. Only `text` parts are\n * surfaced — non-text parts (e.g. function calls, inline data) are ignored.\n *\n * @internal\n */\ninterface IGeminiStreamPart {\n readonly text?: string;\n}\n\n/**\n * One `candidates[]` element in a Gemini streaming chunk. Both `content` and\n * `finishReason` are optional — text arrives in intermediate chunks,\n * finishReason in the terminal chunk.\n *\n * @internal\n */\ninterface IGeminiStreamCandidate {\n readonly content?: { readonly parts?: ReadonlyArray<IGeminiStreamPart> };\n readonly finishReason?: string;\n}\n\n/**\n * One streaming chunk from `streamGenerateContent?alt=sse`.\n *\n * @internal\n */\ninterface IGeminiStreamChunk {\n readonly candidates: ReadonlyArray<IGeminiStreamCandidate>;\n}\n\nconst geminiStreamPart: Validator<IGeminiStreamPart> = Validators.object<IGeminiStreamPart>(\n { text: Validators.string.optional() },\n { options: { optionalFields: ['text'] } }\n);\n\nconst geminiStreamContent: Validator<{ parts?: ReadonlyArray<IGeminiStreamPart> }> = Validators.object<{\n parts?: ReadonlyArray<IGeminiStreamPart>;\n}>({ parts: Validators.arrayOf(geminiStreamPart).optional() }, { options: { optionalFields: ['parts'] } });\n\nconst geminiStreamCandidate: Validator<IGeminiStreamCandidate> = Validators.object<IGeminiStreamCandidate>(\n {\n content: geminiStreamContent.optional(),\n finishReason: Validators.string.optional()\n },\n { options: { optionalFields: ['content', 'finishReason'] } }\n);\n\nconst geminiStreamChunk: Validator<IGeminiStreamChunk> = Validators.object<IGeminiStreamChunk>({\n candidates: Validators.arrayOf(geminiStreamCandidate)\n});\n\n// ============================================================================\n// Stream translator\n// ============================================================================\n\n/**\n * Translates a Gemini streamGenerateContent SSE stream into unified events.\n *\n * @internal\n */\nasync function* translateGeminiStream(response: Response): AsyncGenerator<IAiStreamEvent> {\n let fullText = '';\n let truncated = false;\n let receivedFinishReason = false;\n\n try {\n /* c8 ignore next - body is non-null at this point per openSseConnection */\n if (!response.body) return;\n for await (const message of readSseEvents(response.body)) {\n const json = parseSseEventJson(message.data);\n if (json === undefined) {\n continue;\n }\n const chunk = validateEventPayload(json, geminiStreamChunk);\n const candidate = chunk?.candidates[0];\n if (!candidate) {\n continue;\n }\n const parts = candidate.content?.parts;\n if (parts) {\n for (const part of parts) {\n if (typeof part.text === 'string' && part.text.length > 0) {\n fullText += part.text;\n yield { type: 'text-delta', delta: part.text };\n }\n }\n }\n const finishReason = candidate.finishReason;\n if (typeof finishReason === 'string' && finishReason.length > 0) {\n truncated = finishReason === 'MAX_TOKENS';\n receivedFinishReason = true;\n }\n }\n } catch (err: unknown) {\n yield { type: 'error', message: err instanceof Error ? err.message : String(err) };\n return;\n }\n\n if (receivedFinishReason) {\n yield { type: 'done', truncated, fullText };\n } else {\n yield { type: 'error', message: 'Gemini stream ended without a finishReason' };\n }\n}\n\n// ============================================================================\n// Per-format request caller\n// ============================================================================\n\n/**\n * Issues a streaming Gemini request and returns the unified-event iterable\n * on success.\n *\n * @internal\n */\nexport async function callGeminiStream(\n config: IStreamApiConfig,\n prompt: AiPrompt,\n messagesBefore: ReadonlyArray<IChatMessage> | undefined,\n temperature: number,\n tools: ReadonlyArray<AiServerToolConfig> | undefined,\n logger?: Logging.ILogger,\n signal?: AbortSignal\n): Promise<Result<AsyncIterable<IAiStreamEvent>>> {\n const url = `${config.baseUrl}/models/${config.model}:streamGenerateContent?alt=sse`;\n const contents = buildGeminiContents(prompt, { head: messagesBefore });\n const body: Record<string, unknown> = {\n systemInstruction: { parts: [{ text: prompt.system }] },\n contents,\n generationConfig: { temperature }\n };\n if (tools && tools.length > 0) {\n body.tools = toGeminiTools(tools);\n }\n const headers: Record<string, string> = { 'x-goog-api-key': config.apiKey };\n /* c8 ignore next 3 - optional logger diagnostic output */\n if (logger) {\n const toolTypes = tools && tools.length > 0 ? tools.map((t) => t.type).join(',') : 'none';\n logger.info(`Gemini streaming: model=${config.model}, tools=${toolTypes}`);\n }\n const conn = await openSseConnection(url, headers, body, logger, signal);\n return conn.onSuccess((response) => succeed(translateGeminiStream(response)));\n}\n"]}