@fgv/ts-extras 5.1.0-5 → 5.1.0-51

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 (488) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +90 -0
  3. package/dist/index.browser.js +6 -2
  4. package/dist/index.browser.js.map +1 -0
  5. package/dist/index.js +2 -1
  6. package/dist/index.js.map +1 -0
  7. package/dist/packlets/ai-assist/chatRequestBuilders.js +282 -0
  8. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  9. package/dist/packlets/ai-assist/{apiClient.js → completionClient.js} +165 -214
  10. package/dist/packlets/ai-assist/completionClient.js.map +1 -0
  11. package/dist/packlets/ai-assist/converters.js +47 -3
  12. package/dist/packlets/ai-assist/converters.js.map +1 -0
  13. package/dist/packlets/ai-assist/embeddingClient.js +345 -0
  14. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -0
  15. package/dist/packlets/ai-assist/endpoint.js +107 -0
  16. package/dist/packlets/ai-assist/endpoint.js.map +1 -0
  17. package/dist/packlets/ai-assist/http.js +104 -0
  18. package/dist/packlets/ai-assist/http.js.map +1 -0
  19. package/dist/packlets/ai-assist/imageGenerationClient.js +454 -0
  20. package/dist/packlets/ai-assist/imageGenerationClient.js.map +1 -0
  21. package/dist/packlets/ai-assist/imageOptionsResolver.js +193 -0
  22. package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  23. package/dist/packlets/ai-assist/index.js +12 -4
  24. package/dist/packlets/ai-assist/index.js.map +1 -0
  25. package/dist/packlets/ai-assist/jsonCompletion.js +93 -0
  26. package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -0
  27. package/dist/packlets/ai-assist/jsonResponse.js +423 -0
  28. package/dist/packlets/ai-assist/jsonResponse.js.map +1 -0
  29. package/dist/packlets/ai-assist/listModelsClient.js +294 -0
  30. package/dist/packlets/ai-assist/listModelsClient.js.map +1 -0
  31. package/dist/packlets/ai-assist/model.js +301 -9
  32. package/dist/packlets/ai-assist/model.js.map +1 -0
  33. package/dist/packlets/ai-assist/registry.js +376 -12
  34. package/dist/packlets/ai-assist/registry.js.map +1 -0
  35. package/dist/packlets/ai-assist/sseParser.js +123 -0
  36. package/dist/packlets/ai-assist/sseParser.js.map +1 -0
  37. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +376 -0
  38. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  39. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +687 -0
  40. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  41. package/dist/packlets/ai-assist/streamingAdapters/common.js +187 -0
  42. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  43. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +208 -0
  44. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  45. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +175 -0
  46. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  47. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +390 -0
  48. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  49. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +178 -0
  50. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  51. package/dist/packlets/ai-assist/streamingClient.js +140 -0
  52. package/dist/packlets/ai-assist/streamingClient.js.map +1 -0
  53. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +288 -0
  54. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  55. package/dist/packlets/ai-assist/toolFormats.js +106 -10
  56. package/dist/packlets/ai-assist/toolFormats.js.map +1 -0
  57. package/dist/packlets/conversion/converters.js +35 -1
  58. package/dist/packlets/conversion/converters.js.map +1 -0
  59. package/dist/packlets/conversion/index.js.map +1 -0
  60. package/dist/packlets/crypto-utils/constants.js.map +1 -0
  61. package/dist/packlets/crypto-utils/converters.js +42 -4
  62. package/dist/packlets/crypto-utils/converters.js.map +1 -0
  63. package/dist/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  64. package/dist/packlets/crypto-utils/encryptedFile.js +37 -0
  65. package/dist/packlets/crypto-utils/encryptedFile.js.map +1 -0
  66. package/dist/packlets/crypto-utils/hpkeProvider.js +355 -0
  67. package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  68. package/dist/packlets/crypto-utils/index.browser.js +13 -2
  69. package/dist/packlets/crypto-utils/index.browser.js.map +1 -0
  70. package/dist/packlets/crypto-utils/index.js +9 -1
  71. package/dist/packlets/crypto-utils/index.js.map +1 -0
  72. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +71 -0
  73. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  74. package/dist/packlets/crypto-utils/keystore/converters.js +111 -16
  75. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -0
  76. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +287 -0
  77. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  78. package/dist/packlets/crypto-utils/keystore/index.browser.js +36 -0
  79. package/dist/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  80. package/dist/packlets/crypto-utils/keystore/index.js +3 -0
  81. package/dist/packlets/crypto-utils/keystore/index.js.map +1 -0
  82. package/dist/packlets/crypto-utils/keystore/keyStore.js +872 -119
  83. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  84. package/dist/packlets/crypto-utils/keystore/model.js +35 -4
  85. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -0
  86. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js +21 -0
  87. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  88. package/dist/packlets/crypto-utils/model.js +32 -0
  89. package/dist/packlets/crypto-utils/model.js.map +1 -0
  90. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +347 -1
  91. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  92. package/dist/packlets/crypto-utils/seedDerivedKeyPair.js +120 -0
  93. package/dist/packlets/crypto-utils/seedDerivedKeyPair.js.map +1 -0
  94. package/dist/packlets/crypto-utils/spkiHelpers.js +284 -0
  95. package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -0
  96. package/dist/packlets/csv/csvFileHelpers.js +0 -14
  97. package/dist/packlets/csv/csvFileHelpers.js.map +1 -0
  98. package/dist/packlets/csv/csvHelpers.js +14 -0
  99. package/dist/packlets/csv/csvHelpers.js.map +1 -0
  100. package/dist/packlets/csv/index.browser.js +1 -3
  101. package/dist/packlets/csv/index.browser.js.map +1 -0
  102. package/dist/packlets/csv/index.js.map +1 -0
  103. package/dist/packlets/experimental/extendedArray.js.map +1 -0
  104. package/dist/packlets/experimental/formatter.js.map +1 -0
  105. package/dist/packlets/experimental/index.js.map +1 -0
  106. package/dist/packlets/experimental/rangeOf.js.map +1 -0
  107. package/dist/packlets/hash/index.browser.js.map +1 -0
  108. package/dist/packlets/hash/index.js.map +1 -0
  109. package/dist/packlets/hash/index.node.js.map +1 -0
  110. package/dist/packlets/hash/md5Normalizer.browser.js.map +1 -0
  111. package/dist/packlets/hash/md5Normalizer.js.map +1 -0
  112. package/dist/packlets/mustache/index.js.map +1 -0
  113. package/dist/packlets/mustache/interfaces.js.map +1 -0
  114. package/dist/packlets/mustache/mustacheTemplate.js +42 -4
  115. package/dist/packlets/mustache/mustacheTemplate.js.map +1 -0
  116. package/dist/packlets/record-jar/index.browser.js +1 -3
  117. package/dist/packlets/record-jar/index.browser.js.map +1 -0
  118. package/dist/packlets/record-jar/index.js.map +1 -0
  119. package/dist/packlets/record-jar/recordJarFileHelpers.js +0 -18
  120. package/dist/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  121. package/dist/packlets/record-jar/recordJarHelpers.js +18 -0
  122. package/dist/packlets/record-jar/recordJarHelpers.js.map +1 -0
  123. package/dist/packlets/safer-fetch/addressClassification.js +438 -0
  124. package/dist/packlets/safer-fetch/addressClassification.js.map +1 -0
  125. package/dist/packlets/safer-fetch/addressPolicy.js +119 -0
  126. package/dist/packlets/safer-fetch/addressPolicy.js.map +1 -0
  127. package/dist/packlets/safer-fetch/contentType.js +120 -0
  128. package/dist/packlets/safer-fetch/contentType.js.map +1 -0
  129. package/dist/packlets/safer-fetch/deadline.js +254 -0
  130. package/dist/packlets/safer-fetch/deadline.js.map +1 -0
  131. package/dist/packlets/safer-fetch/defaults.js +131 -0
  132. package/dist/packlets/safer-fetch/defaults.js.map +1 -0
  133. package/dist/packlets/safer-fetch/failureReason.js +21 -0
  134. package/dist/packlets/safer-fetch/failureReason.js.map +1 -0
  135. package/dist/packlets/safer-fetch/guards.js +148 -0
  136. package/dist/packlets/safer-fetch/guards.js.map +1 -0
  137. package/dist/packlets/safer-fetch/index.browser.js +58 -0
  138. package/dist/packlets/safer-fetch/index.browser.js.map +1 -0
  139. package/dist/packlets/safer-fetch/index.js +52 -0
  140. package/dist/packlets/safer-fetch/index.js.map +1 -0
  141. package/dist/packlets/safer-fetch/model.js +21 -0
  142. package/dist/packlets/safer-fetch/model.js.map +1 -0
  143. package/dist/packlets/safer-fetch/nodeAddressGuard.js +191 -0
  144. package/dist/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
  145. package/dist/packlets/safer-fetch/redirect.js +93 -0
  146. package/dist/packlets/safer-fetch/redirect.js.map +1 -0
  147. package/dist/packlets/safer-fetch/retry.js +188 -0
  148. package/dist/packlets/safer-fetch/retry.js.map +1 -0
  149. package/dist/packlets/safer-fetch/saferFetch.js +755 -0
  150. package/dist/packlets/safer-fetch/saferFetch.js.map +1 -0
  151. package/dist/packlets/safer-fetch/transport.js +50 -0
  152. package/dist/packlets/safer-fetch/transport.js.map +1 -0
  153. package/dist/packlets/yaml/converters.js.map +1 -0
  154. package/dist/packlets/yaml/index.js +1 -0
  155. package/dist/packlets/yaml/index.js.map +1 -0
  156. package/dist/packlets/yaml/serializers.js +48 -0
  157. package/dist/packlets/yaml/serializers.js.map +1 -0
  158. package/dist/packlets/zip-file-tree/index.js.map +1 -0
  159. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +99 -9
  160. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  161. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js +16 -1
  162. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  163. package/dist/ts-extras.d.ts +6648 -553
  164. package/dist/tsdoc-metadata.json +1 -1
  165. package/lib/index.browser.d.ts +5 -2
  166. package/lib/index.browser.d.ts.map +1 -0
  167. package/lib/index.browser.js +11 -3
  168. package/lib/index.browser.js.map +1 -0
  169. package/lib/index.d.ts +2 -1
  170. package/lib/index.d.ts.map +1 -0
  171. package/lib/index.js +3 -1
  172. package/lib/index.js.map +1 -0
  173. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +149 -0
  174. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -0
  175. package/lib/packlets/ai-assist/chatRequestBuilders.js +293 -0
  176. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  177. package/lib/packlets/ai-assist/completionClient.d.ts +74 -0
  178. package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -0
  179. package/lib/packlets/ai-assist/{apiClient.js → completionClient.js} +164 -213
  180. package/lib/packlets/ai-assist/completionClient.js.map +1 -0
  181. package/lib/packlets/ai-assist/converters.d.ts +15 -1
  182. package/lib/packlets/ai-assist/converters.d.ts.map +1 -0
  183. package/lib/packlets/ai-assist/converters.js +47 -3
  184. package/lib/packlets/ai-assist/converters.js.map +1 -0
  185. package/lib/packlets/ai-assist/embeddingClient.d.ts +69 -0
  186. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -0
  187. package/lib/packlets/ai-assist/embeddingClient.js +349 -0
  188. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -0
  189. package/lib/packlets/ai-assist/endpoint.d.ts +49 -0
  190. package/lib/packlets/ai-assist/endpoint.d.ts.map +1 -0
  191. package/lib/packlets/ai-assist/endpoint.js +113 -0
  192. package/lib/packlets/ai-assist/endpoint.js.map +1 -0
  193. package/lib/packlets/ai-assist/http.d.ts +34 -0
  194. package/lib/packlets/ai-assist/http.d.ts.map +1 -0
  195. package/lib/packlets/ai-assist/http.js +109 -0
  196. package/lib/packlets/ai-assist/http.js.map +1 -0
  197. package/lib/packlets/ai-assist/imageGenerationClient.d.ts +46 -0
  198. package/lib/packlets/ai-assist/imageGenerationClient.d.ts.map +1 -0
  199. package/lib/packlets/ai-assist/imageGenerationClient.js +458 -0
  200. package/lib/packlets/ai-assist/imageGenerationClient.js.map +1 -0
  201. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +66 -0
  202. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -0
  203. package/lib/packlets/ai-assist/imageOptionsResolver.js +197 -0
  204. package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  205. package/lib/packlets/ai-assist/index.d.ts +12 -4
  206. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  207. package/lib/packlets/ai-assist/index.js +47 -4
  208. package/lib/packlets/ai-assist/index.js.map +1 -0
  209. package/lib/packlets/ai-assist/jsonCompletion.d.ts +93 -0
  210. package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -0
  211. package/lib/packlets/ai-assist/jsonCompletion.js +97 -0
  212. package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -0
  213. package/lib/packlets/ai-assist/jsonResponse.d.ts +194 -0
  214. package/lib/packlets/ai-assist/jsonResponse.d.ts.map +1 -0
  215. package/lib/packlets/ai-assist/jsonResponse.js +429 -0
  216. package/lib/packlets/ai-assist/jsonResponse.js.map +1 -0
  217. package/lib/packlets/ai-assist/listModelsClient.d.ts +46 -0
  218. package/lib/packlets/ai-assist/listModelsClient.d.ts.map +1 -0
  219. package/lib/packlets/ai-assist/listModelsClient.js +298 -0
  220. package/lib/packlets/ai-assist/listModelsClient.js.map +1 -0
  221. package/lib/packlets/ai-assist/model.d.ts +1346 -12
  222. package/lib/packlets/ai-assist/model.d.ts.map +1 -0
  223. package/lib/packlets/ai-assist/model.js +309 -10
  224. package/lib/packlets/ai-assist/model.js.map +1 -0
  225. package/lib/packlets/ai-assist/registry.d.ts +76 -1
  226. package/lib/packlets/ai-assist/registry.d.ts.map +1 -0
  227. package/lib/packlets/ai-assist/registry.js +381 -13
  228. package/lib/packlets/ai-assist/registry.js.map +1 -0
  229. package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
  230. package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -0
  231. package/lib/packlets/ai-assist/sseParser.js +128 -0
  232. package/lib/packlets/ai-assist/sseParser.js.map +1 -0
  233. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +72 -0
  234. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -0
  235. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +379 -0
  236. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  237. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +269 -0
  238. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
  239. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +694 -0
  240. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  241. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +163 -0
  242. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -0
  243. package/lib/packlets/ai-assist/streamingAdapters/common.js +193 -0
  244. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  245. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +43 -0
  246. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -0
  247. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +211 -0
  248. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  249. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +19 -0
  250. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -0
  251. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +178 -0
  252. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  253. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +33 -0
  254. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -0
  255. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +393 -0
  256. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  257. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
  258. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -0
  259. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +181 -0
  260. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  261. package/lib/packlets/ai-assist/streamingClient.d.ts +50 -0
  262. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -0
  263. package/lib/packlets/ai-assist/streamingClient.js +146 -0
  264. package/lib/packlets/ai-assist/streamingClient.js.map +1 -0
  265. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +87 -0
  266. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -0
  267. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +294 -0
  268. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  269. package/lib/packlets/ai-assist/toolFormats.d.ts +40 -9
  270. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -0
  271. package/lib/packlets/ai-assist/toolFormats.js +107 -10
  272. package/lib/packlets/ai-assist/toolFormats.js.map +1 -0
  273. package/lib/packlets/conversion/converters.d.ts +8 -1
  274. package/lib/packlets/conversion/converters.d.ts.map +1 -0
  275. package/lib/packlets/conversion/converters.js +36 -2
  276. package/lib/packlets/conversion/converters.js.map +1 -0
  277. package/lib/packlets/conversion/index.d.ts.map +1 -0
  278. package/lib/packlets/conversion/index.js.map +1 -0
  279. package/lib/packlets/crypto-utils/constants.d.ts.map +1 -0
  280. package/lib/packlets/crypto-utils/constants.js.map +1 -0
  281. package/lib/packlets/crypto-utils/converters.d.ts +20 -1
  282. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -0
  283. package/lib/packlets/crypto-utils/converters.js +43 -5
  284. package/lib/packlets/crypto-utils/converters.js.map +1 -0
  285. package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts.map +1 -0
  286. package/lib/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  287. package/lib/packlets/crypto-utils/encryptedFile.d.ts +25 -0
  288. package/lib/packlets/crypto-utils/encryptedFile.d.ts.map +1 -0
  289. package/lib/packlets/crypto-utils/encryptedFile.js +38 -0
  290. package/lib/packlets/crypto-utils/encryptedFile.js.map +1 -0
  291. package/lib/packlets/crypto-utils/hpkeProvider.d.ts +150 -0
  292. package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
  293. package/lib/packlets/crypto-utils/hpkeProvider.js +359 -0
  294. package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  295. package/lib/packlets/crypto-utils/index.browser.d.ts +5 -1
  296. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -0
  297. package/lib/packlets/crypto-utils/index.browser.js +28 -3
  298. package/lib/packlets/crypto-utils/index.browser.js.map +1 -0
  299. package/lib/packlets/crypto-utils/index.d.ts +5 -1
  300. package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
  301. package/lib/packlets/crypto-utils/index.js +24 -1
  302. package/lib/packlets/crypto-utils/index.js.map +1 -0
  303. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +54 -0
  304. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -0
  305. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +74 -0
  306. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  307. package/lib/packlets/crypto-utils/keystore/converters.d.ts +68 -6
  308. package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -0
  309. package/lib/packlets/crypto-utils/keystore/converters.js +109 -14
  310. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -0
  311. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts +148 -0
  312. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts.map +1 -0
  313. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +324 -0
  314. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  315. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts +10 -0
  316. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts.map +1 -0
  317. package/lib/packlets/crypto-utils/keystore/index.browser.js +76 -0
  318. package/lib/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  319. package/lib/packlets/crypto-utils/keystore/index.d.ts +2 -0
  320. package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -0
  321. package/lib/packlets/crypto-utils/keystore/index.js +5 -1
  322. package/lib/packlets/crypto-utils/keystore/index.js.map +1 -0
  323. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +314 -13
  324. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -0
  325. package/lib/packlets/crypto-utils/keystore/keyStore.js +877 -124
  326. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  327. package/lib/packlets/crypto-utils/keystore/model.d.ts +404 -21
  328. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -0
  329. package/lib/packlets/crypto-utils/keystore/model.js +37 -5
  330. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -0
  331. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +53 -0
  332. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -0
  333. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js +22 -0
  334. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  335. package/lib/packlets/crypto-utils/model.d.ts +518 -10
  336. package/lib/packlets/crypto-utils/model.d.ts.map +1 -0
  337. package/lib/packlets/crypto-utils/model.js +33 -1
  338. package/lib/packlets/crypto-utils/model.js.map +1 -0
  339. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +142 -2
  340. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -0
  341. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +346 -0
  342. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  343. package/lib/packlets/crypto-utils/seedDerivedKeyPair.d.ts +42 -0
  344. package/lib/packlets/crypto-utils/seedDerivedKeyPair.d.ts.map +1 -0
  345. package/lib/packlets/crypto-utils/seedDerivedKeyPair.js +123 -0
  346. package/lib/packlets/crypto-utils/seedDerivedKeyPair.js.map +1 -0
  347. package/lib/packlets/crypto-utils/spkiHelpers.d.ts +151 -0
  348. package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -0
  349. package/lib/packlets/crypto-utils/spkiHelpers.js +297 -0
  350. package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -0
  351. package/lib/packlets/csv/csvFileHelpers.d.ts +0 -10
  352. package/lib/packlets/csv/csvFileHelpers.d.ts.map +1 -0
  353. package/lib/packlets/csv/csvFileHelpers.js +0 -15
  354. package/lib/packlets/csv/csvFileHelpers.js.map +1 -0
  355. package/lib/packlets/csv/csvHelpers.d.ts +10 -0
  356. package/lib/packlets/csv/csvHelpers.d.ts.map +1 -0
  357. package/lib/packlets/csv/csvHelpers.js +15 -0
  358. package/lib/packlets/csv/csvHelpers.js.map +1 -0
  359. package/lib/packlets/csv/index.browser.d.ts +0 -1
  360. package/lib/packlets/csv/index.browser.d.ts.map +1 -0
  361. package/lib/packlets/csv/index.browser.js +1 -5
  362. package/lib/packlets/csv/index.browser.js.map +1 -0
  363. package/lib/packlets/csv/index.d.ts.map +1 -0
  364. package/lib/packlets/csv/index.js.map +1 -0
  365. package/lib/packlets/experimental/extendedArray.d.ts.map +1 -0
  366. package/lib/packlets/experimental/extendedArray.js.map +1 -0
  367. package/lib/packlets/experimental/formatter.d.ts.map +1 -0
  368. package/lib/packlets/experimental/formatter.js.map +1 -0
  369. package/lib/packlets/experimental/index.d.ts.map +1 -0
  370. package/lib/packlets/experimental/index.js.map +1 -0
  371. package/lib/packlets/experimental/rangeOf.d.ts.map +1 -0
  372. package/lib/packlets/experimental/rangeOf.js.map +1 -0
  373. package/lib/packlets/hash/index.browser.d.ts.map +1 -0
  374. package/lib/packlets/hash/index.browser.js.map +1 -0
  375. package/lib/packlets/hash/index.d.ts.map +1 -0
  376. package/lib/packlets/hash/index.js.map +1 -0
  377. package/lib/packlets/hash/index.node.d.ts.map +1 -0
  378. package/lib/packlets/hash/index.node.js.map +1 -0
  379. package/lib/packlets/hash/md5Normalizer.browser.d.ts.map +1 -0
  380. package/lib/packlets/hash/md5Normalizer.browser.js.map +1 -0
  381. package/lib/packlets/hash/md5Normalizer.d.ts.map +1 -0
  382. package/lib/packlets/hash/md5Normalizer.js.map +1 -0
  383. package/lib/packlets/mustache/index.d.ts +1 -1
  384. package/lib/packlets/mustache/index.d.ts.map +1 -0
  385. package/lib/packlets/mustache/index.js.map +1 -0
  386. package/lib/packlets/mustache/interfaces.d.ts +34 -0
  387. package/lib/packlets/mustache/interfaces.d.ts.map +1 -0
  388. package/lib/packlets/mustache/interfaces.js.map +1 -0
  389. package/lib/packlets/mustache/mustacheTemplate.d.ts +2 -0
  390. package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -0
  391. package/lib/packlets/mustache/mustacheTemplate.js +42 -4
  392. package/lib/packlets/mustache/mustacheTemplate.js.map +1 -0
  393. package/lib/packlets/record-jar/index.browser.d.ts +0 -1
  394. package/lib/packlets/record-jar/index.browser.d.ts.map +1 -0
  395. package/lib/packlets/record-jar/index.browser.js +1 -5
  396. package/lib/packlets/record-jar/index.browser.js.map +1 -0
  397. package/lib/packlets/record-jar/index.d.ts.map +1 -0
  398. package/lib/packlets/record-jar/index.js.map +1 -0
  399. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts +0 -11
  400. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts.map +1 -0
  401. package/lib/packlets/record-jar/recordJarFileHelpers.js +0 -19
  402. package/lib/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  403. package/lib/packlets/record-jar/recordJarHelpers.d.ts +11 -0
  404. package/lib/packlets/record-jar/recordJarHelpers.d.ts.map +1 -0
  405. package/lib/packlets/record-jar/recordJarHelpers.js +19 -0
  406. package/lib/packlets/record-jar/recordJarHelpers.js.map +1 -0
  407. package/lib/packlets/safer-fetch/addressClassification.d.ts +144 -0
  408. package/lib/packlets/safer-fetch/addressClassification.d.ts.map +1 -0
  409. package/lib/packlets/safer-fetch/addressClassification.js +441 -0
  410. package/lib/packlets/safer-fetch/addressClassification.js.map +1 -0
  411. package/lib/packlets/safer-fetch/addressPolicy.d.ts +113 -0
  412. package/lib/packlets/safer-fetch/addressPolicy.d.ts.map +1 -0
  413. package/lib/packlets/safer-fetch/addressPolicy.js +123 -0
  414. package/lib/packlets/safer-fetch/addressPolicy.js.map +1 -0
  415. package/lib/packlets/safer-fetch/contentType.d.ts +51 -0
  416. package/lib/packlets/safer-fetch/contentType.d.ts.map +1 -0
  417. package/lib/packlets/safer-fetch/contentType.js +127 -0
  418. package/lib/packlets/safer-fetch/contentType.js.map +1 -0
  419. package/lib/packlets/safer-fetch/deadline.d.ts +138 -0
  420. package/lib/packlets/safer-fetch/deadline.d.ts.map +1 -0
  421. package/lib/packlets/safer-fetch/deadline.js +258 -0
  422. package/lib/packlets/safer-fetch/deadline.js.map +1 -0
  423. package/lib/packlets/safer-fetch/defaults.d.ts +108 -0
  424. package/lib/packlets/safer-fetch/defaults.d.ts.map +1 -0
  425. package/lib/packlets/safer-fetch/defaults.js +134 -0
  426. package/lib/packlets/safer-fetch/defaults.js.map +1 -0
  427. package/lib/packlets/safer-fetch/failureReason.d.ts +148 -0
  428. package/lib/packlets/safer-fetch/failureReason.d.ts.map +1 -0
  429. package/lib/packlets/safer-fetch/failureReason.js +22 -0
  430. package/lib/packlets/safer-fetch/failureReason.js.map +1 -0
  431. package/lib/packlets/safer-fetch/guards.d.ts +62 -0
  432. package/lib/packlets/safer-fetch/guards.d.ts.map +1 -0
  433. package/lib/packlets/safer-fetch/guards.js +153 -0
  434. package/lib/packlets/safer-fetch/guards.js.map +1 -0
  435. package/lib/packlets/safer-fetch/index.browser.d.ts +33 -0
  436. package/lib/packlets/safer-fetch/index.browser.d.ts.map +1 -0
  437. package/lib/packlets/safer-fetch/index.browser.js +82 -0
  438. package/lib/packlets/safer-fetch/index.browser.js.map +1 -0
  439. package/lib/packlets/safer-fetch/index.d.ts +27 -0
  440. package/lib/packlets/safer-fetch/index.d.ts.map +1 -0
  441. package/lib/packlets/safer-fetch/index.js +78 -0
  442. package/lib/packlets/safer-fetch/index.js.map +1 -0
  443. package/lib/packlets/safer-fetch/model.d.ts +373 -0
  444. package/lib/packlets/safer-fetch/model.d.ts.map +1 -0
  445. package/lib/packlets/safer-fetch/model.js +22 -0
  446. package/lib/packlets/safer-fetch/model.js.map +1 -0
  447. package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts +129 -0
  448. package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts.map +1 -0
  449. package/lib/packlets/safer-fetch/nodeAddressGuard.js +196 -0
  450. package/lib/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
  451. package/lib/packlets/safer-fetch/redirect.d.ts +62 -0
  452. package/lib/packlets/safer-fetch/redirect.d.ts.map +1 -0
  453. package/lib/packlets/safer-fetch/redirect.js +98 -0
  454. package/lib/packlets/safer-fetch/redirect.js.map +1 -0
  455. package/lib/packlets/safer-fetch/retry.d.ts +162 -0
  456. package/lib/packlets/safer-fetch/retry.d.ts.map +1 -0
  457. package/lib/packlets/safer-fetch/retry.js +197 -0
  458. package/lib/packlets/safer-fetch/retry.js.map +1 -0
  459. package/lib/packlets/safer-fetch/saferFetch.d.ts +108 -0
  460. package/lib/packlets/safer-fetch/saferFetch.d.ts.map +1 -0
  461. package/lib/packlets/safer-fetch/saferFetch.js +760 -0
  462. package/lib/packlets/safer-fetch/saferFetch.js.map +1 -0
  463. package/lib/packlets/safer-fetch/transport.d.ts +18 -0
  464. package/lib/packlets/safer-fetch/transport.d.ts.map +1 -0
  465. package/lib/packlets/safer-fetch/transport.js +53 -0
  466. package/lib/packlets/safer-fetch/transport.js.map +1 -0
  467. package/lib/packlets/yaml/converters.d.ts.map +1 -0
  468. package/lib/packlets/yaml/converters.js.map +1 -0
  469. package/lib/packlets/yaml/index.d.ts +1 -0
  470. package/lib/packlets/yaml/index.d.ts.map +1 -0
  471. package/lib/packlets/yaml/index.js +1 -0
  472. package/lib/packlets/yaml/index.js.map +1 -0
  473. package/lib/packlets/yaml/serializers.d.ts +45 -0
  474. package/lib/packlets/yaml/serializers.d.ts.map +1 -0
  475. package/lib/packlets/yaml/serializers.js +84 -0
  476. package/lib/packlets/yaml/serializers.js.map +1 -0
  477. package/lib/packlets/zip-file-tree/index.d.ts.map +1 -0
  478. package/lib/packlets/zip-file-tree/index.js.map +1 -0
  479. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +84 -8
  480. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts.map +1 -0
  481. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +99 -9
  482. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  483. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts +26 -1
  484. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts.map +1 -0
  485. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js +17 -1
  486. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  487. package/package.json +30 -15
  488. package/lib/packlets/ai-assist/apiClient.d.ts +0 -60
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Erik Fortune
3
+ Copyright (c) 2026 Erik Fortune
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -86,6 +86,96 @@ const rawContents = file.value.getRawContents(); // Raw string
86
86
 
87
87
  **Note**: This implementation uses Node.js-specific dependencies (AdmZip, Buffer). For browser environments, see the browser-specific implementations in individual projects.
88
88
 
89
+ ### Safer Fetch
90
+
91
+ An HTTP fetch primitive with an explicit threat model, exported from `@fgv/ts-extras/safer-fetch`.
92
+ It is deliberately **not** a thin boundary over an upstream library — `fetch` is a platform global
93
+ and there is nothing to wrap. The opinion is the product: the deadlines, the scheme refusal, the
94
+ streaming size cap, the redirect posture, and the required address guard are the deliverable.
95
+
96
+ ```typescript
97
+ import {
98
+ saferFetchJson,
99
+ blockPrivateNetworks,
100
+ allowContentTypes
101
+ } from '@fgv/ts-extras/safer-fetch';
102
+
103
+ const result = await saferFetchJson('https://api.example.com/thing', {
104
+ // Required, with no default: omitting it is a compile error, so no call site can inherit a
105
+ // guarantee it was never given. Every call site's posture is greppable in one search.
106
+ addressGuard: blockPrivateNetworks(),
107
+ responseHeadersGuard: allowContentTypes(['application/json']).orThrow(),
108
+ timeoutMs: 10_000,
109
+ maxResponseBytes: 1024 * 1024,
110
+ retry: { attempts: 2 } // off unless asked for
111
+ });
112
+ ```
113
+
114
+ `addressGuard` has no default. `allowAnyAddress()` is the named, deliberately uncomfortable
115
+ opt-out, and it is the only honest choice in a browser.
116
+
117
+ #### The guarantee table
118
+
119
+ This table is the artifact that keeps the primitive honest. It is reproduced verbatim in
120
+ `@fgv/ts-web-extras`'s README.
121
+
122
+ | Property | Node (`@fgv/ts-extras/safer-fetch`) | Browser (`@fgv/ts-web-extras`) |
123
+ |---|---|---|
124
+ | Overall + headers deadline | ✅ | ✅ |
125
+ | Streaming size cap, enforced during read | ✅ | ✅ |
126
+ | Content-type gate before body read | ✅ | ✅ |
127
+ | Structured failure taxonomy | ✅ | ✅ |
128
+ | Retry with idempotency + budget rules | ✅ | ✅ |
129
+ | Scheme allowlist on URL₀ | ✅ | ✅ |
130
+ | Host / port allowlist on URL₀ | ✅ (`blockPrivateNetworks({ allowHosts, allowPorts })`) | ✅ (caller-supplied guard) |
131
+ | **Resolved-address (private-IP) guard** | ✅ | ❌ **impossible** — no DNS API |
132
+ | **Per-hop revalidation of redirects** | ✅ | ❌ **impossible** — opaque redirect |
133
+ | **Credential stripping on cross-origin hop** | ✅ | n/a — platform does it |
134
+ | Reject-all-redirects mode | ✅ | ✅ (enforced; surfaces as `'redirect-opaque'`) |
135
+ | DNS-rebinding resistance | ❌ **documented limit** | ❌ |
136
+
137
+ Two rows differ from the design document they came from, and the difference is deliberate:
138
+
139
+ - **Host / port allowlist.** `blockPrivateNetworks` takes `allowHosts` / `allowPorts` /
140
+ `allowInsecureHttp`. A host allowlist is the *recommended* posture, because it shrinks the
141
+ DNS-rebinding exposure below to "an allowlisted host's own resolver is hostile". `https:` is
142
+ required unless `allowInsecureHttp` is set; there is no default port restriction, since a
143
+ default of `{443}` would reject the very common `:8443` public endpoint with a failure that
144
+ reads as an SSRF block.
145
+ - **Reject-all-redirects.** Both runtimes use `redirect: 'manual'` and reject the redirect
146
+ themselves. The *guarantee* is identical; the failure **reason** is not — Node reports
147
+ `'redirect-rejected'` with the status, a browser reports `'redirect-opaque'` because an opaque
148
+ response has no status to report. Callers branching on the reason under `'reject'` must handle
149
+ both.
150
+
151
+ #### Stated limits
152
+
153
+ Each of these is a hole in a guarantee this package makes, and each is documented rather than
154
+ implied.
155
+
156
+ - **DNS rebinding** — the guard validates a resolved address and the transport then re-resolves,
157
+ so hostile DNS can answer the two lookups differently. Closing it needs a pinning transport;
158
+ the seam (`IGuardVerdict.pinnedAddress` + `IFetchTransport`) exists, and
159
+ `platformFetchTransport` **fails** rather than silently ignoring a pin it cannot honor. A
160
+ strict `allowHosts` list is the recommended mitigation.
161
+ - **The browser has no SSRF guard at all** — not a partial guarantee, an absent one. See the
162
+ table.
163
+ - **Time-of-check/time-of-use beyond DNS** — the guard authorizes a *destination*, never the
164
+ *content* that comes back.
165
+ - **The failure taxonomy is an information-disclosure surface** — a `'blocked-by-guard'` detail
166
+ names the URL, the hop and the guard, which is an internal-network scanning oracle if echoed
167
+ to an untrusted caller. Log it; return a coarse code.
168
+ - **No egress accounting** — no per-host rate limiting or quota. A retry policy is not a rate
169
+ limiter.
170
+ - **Loopback is blocked by default**, including for this repo's own local-development path. A
171
+ caller who wants it says so, and every deviation is independently greppable:
172
+ `blockPrivateNetworks({ allowLoopback: true, allowInsecureHttp: true, allowHosts: ['localhost'], allowPorts: [11434] })`.
173
+ - **HTTP semantics not implemented** — no cookie jar, no cache, no conditional requests, no
174
+ proxy configuration beyond what the platform picks up from the environment.
175
+
176
+ If your deployment has an egress proxy or firewall, that control is strictly stronger than this
177
+ one; this is defense in depth for deployments that do not.
178
+
89
179
  ### Converters
90
180
 
91
181
  Type-safe data conversion utilities for transforming between different data formats while maintaining type safety.
@@ -20,8 +20,9 @@
20
20
  * SOFTWARE.
21
21
  */
22
22
  /* c8 ignore start - Browser-specific export used conditionally in package.json */
23
+ import * as AiAssist from './packlets/ai-assist';
23
24
  // eslint-disable-next-line @rushstack/packlets/mechanics
24
- import * as Crypto from './packlets/crypto-utils/index.browser';
25
+ import * as CryptoUtils from './packlets/crypto-utils/index.browser';
25
26
  // eslint-disable-next-line @rushstack/packlets/mechanics
26
27
  import * as Csv from './packlets/csv/index.browser';
27
28
  import * as Experimental from './packlets/experimental';
@@ -30,10 +31,13 @@ import * as Hash from './packlets/hash/index.browser';
30
31
  import * as Mustache from './packlets/mustache';
31
32
  // eslint-disable-next-line @rushstack/packlets/mechanics
32
33
  import * as RecordJar from './packlets/record-jar/index.browser';
34
+ // eslint-disable-next-line @rushstack/packlets/mechanics
35
+ import * as SaferFetch from './packlets/safer-fetch/index.browser';
36
+ import * as Yaml from './packlets/yaml';
33
37
  import * as ZipFileTree from './packlets/zip-file-tree';
34
38
  import { Converters } from './packlets/conversion';
35
39
  // Browser-safe exports - Node.js crypto-based providers excluded
36
40
  // Use BrowserCryptoProvider from @fgv/ts-web-extras for browser crypto
37
- export { Converters, Crypto, Csv, Experimental, Hash, Mustache, RecordJar, ZipFileTree };
41
+ export { AiAssist, Converters, CryptoUtils, CryptoUtils as Crypto, Csv, Experimental, Hash, Mustache, RecordJar, SaferFetch, Yaml, ZipFileTree };
38
42
  /* c8 ignore stop */
39
43
  //# sourceMappingURL=index.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,kFAAkF;AAClF,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AACjD,yDAAyD;AACzD,OAAO,KAAK,WAAW,MAAM,uCAAuC,CAAC;AACrE,yDAAyD;AACzD,OAAO,KAAK,GAAG,MAAM,8BAA8B,CAAC;AACpD,OAAO,KAAK,YAAY,MAAM,yBAAyB,CAAC;AACxD,yDAAyD;AACzD,OAAO,KAAK,IAAI,MAAM,+BAA+B,CAAC;AACtD,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,yDAAyD;AACzD,OAAO,KAAK,SAAS,MAAM,qCAAqC,CAAC;AACjE,yDAAyD;AACzD,OAAO,KAAK,UAAU,MAAM,sCAAsC,CAAC;AACnE,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,iEAAiE;AACjE,uEAAuE;AACvE,OAAO,EACL,QAAQ,EACR,UAAU,EACV,WAAW,EACX,WAAW,IAAI,MAAM,EACrB,GAAG,EACH,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,IAAI,EACJ,WAAW,EACZ,CAAC;AACF,oBAAoB","sourcesContent":["/*\n * Copyright (c) 2020 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/* c8 ignore start - Browser-specific export used conditionally in package.json */\nimport * as AiAssist from './packlets/ai-assist';\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as CryptoUtils from './packlets/crypto-utils/index.browser';\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as Csv from './packlets/csv/index.browser';\nimport * as Experimental from './packlets/experimental';\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as Hash from './packlets/hash/index.browser';\nimport * as Mustache from './packlets/mustache';\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as RecordJar from './packlets/record-jar/index.browser';\n// eslint-disable-next-line @rushstack/packlets/mechanics\nimport * as SaferFetch from './packlets/safer-fetch/index.browser';\nimport * as Yaml from './packlets/yaml';\nimport * as ZipFileTree from './packlets/zip-file-tree';\n\nimport { Converters } from './packlets/conversion';\n\n// Browser-safe exports - Node.js crypto-based providers excluded\n// Use BrowserCryptoProvider from @fgv/ts-web-extras for browser crypto\nexport {\n AiAssist,\n Converters,\n CryptoUtils,\n CryptoUtils as Crypto,\n Csv,\n Experimental,\n Hash,\n Mustache,\n RecordJar,\n SaferFetch,\n Yaml,\n ZipFileTree\n};\n/* c8 ignore stop */\n"]}
package/dist/index.js CHANGED
@@ -26,8 +26,9 @@ import * as Experimental from './packlets/experimental';
26
26
  import * as Hash from './packlets/hash';
27
27
  import * as Mustache from './packlets/mustache';
28
28
  import * as RecordJar from './packlets/record-jar';
29
+ import * as SaferFetch from './packlets/safer-fetch';
29
30
  import * as Yaml from './packlets/yaml';
30
31
  import * as ZipFileTree from './packlets/zip-file-tree';
31
32
  import { Converters } from './packlets/conversion';
32
- export { AiAssist, Converters, CryptoUtils, Csv, Experimental, Hash, Mustache, RecordJar, Yaml, ZipFileTree };
33
+ export { AiAssist, Converters, CryptoUtils, Csv, Experimental, Hash, Mustache, RecordJar, SaferFetch, Yaml, ZipFileTree };
33
34
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AACvD,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,YAAY,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EACL,QAAQ,EACR,UAAU,EACV,WAAW,EACX,GAAG,EACH,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,UAAU,EACV,IAAI,EACJ,WAAW,EACZ,CAAC","sourcesContent":["/*\n * Copyright (c) 2020 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\nimport * as AiAssist from './packlets/ai-assist';\nimport * as CryptoUtils from './packlets/crypto-utils';\nimport * as Csv from './packlets/csv';\nimport * as Experimental from './packlets/experimental';\nimport * as Hash from './packlets/hash';\nimport * as Mustache from './packlets/mustache';\nimport * as RecordJar from './packlets/record-jar';\nimport * as SaferFetch from './packlets/safer-fetch';\nimport * as Yaml from './packlets/yaml';\nimport * as ZipFileTree from './packlets/zip-file-tree';\n\nimport { Converters } from './packlets/conversion';\n\nexport {\n AiAssist,\n Converters,\n CryptoUtils,\n Csv,\n Experimental,\n Hash,\n Mustache,\n RecordJar,\n SaferFetch,\n Yaml,\n ZipFileTree\n};\n"]}
@@ -0,0 +1,282 @@
1
+ // Copyright (c) 2026 Erik Fortune
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ /**
21
+ * Per-format chat request shape builders. Shared between the synchronous
22
+ * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire
23
+ * shapes stay consistent.
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+ import { isJsonObject } from '@fgv/ts-json-base';
28
+ import { Converters, fail, succeed } from '@fgv/ts-utils';
29
+ import { AiPrompt, toDataUrl } from './model';
30
+ /**
31
+ * Splits a unified {@link AiAssist.IChatRequest} into `{ prompt, head }` for the
32
+ * per-provider builders. The **last** message is the current `user` turn; the
33
+ * preceding messages are history (`head`). This is the single linearization
34
+ * shared by every turn entry point, so the completion path and the client-tool
35
+ * turn path place history at the identical position relative to the current turn.
36
+ *
37
+ * Fails when `messages` is empty (no current turn) or when the last message is
38
+ * not a `user` turn — relabelling a trailing assistant message as the user turn
39
+ * would be a silent footgun, so it is rejected loudly instead.
40
+ *
41
+ * @internal
42
+ */
43
+ export function splitChatRequest(system, messages) {
44
+ if (messages.length === 0) {
45
+ return fail('messages must contain at least one entry (the current user turn)');
46
+ }
47
+ const current = messages[messages.length - 1];
48
+ if (current.role !== 'user') {
49
+ return fail(`the last message must be the current user turn (role 'user'); got '${current.role}'`);
50
+ }
51
+ const head = messages.slice(0, messages.length - 1);
52
+ const prompt = new AiPrompt(current.content, system !== null && system !== void 0 ? system : '', current.attachments);
53
+ return succeed({ prompt, head });
54
+ }
55
+ /**
56
+ * Rebuilds the ordered messages for a proxy wire body so that only the current
57
+ * turn can carry attachments — history (non-current) messages are reduced to
58
+ * `{ role, content }`. The direct per-provider builders already drop attachments
59
+ * on history turns (only the current user turn's attachments are honored), so
60
+ * normalizing here keeps the proxy wire shape consistent with the direct paths
61
+ * and avoids transmitting attachment payloads the upstream provider would ignore.
62
+ *
63
+ * @internal
64
+ */
65
+ export function normalizeOutboundMessages(split) {
66
+ return [
67
+ ...split.head.map((m) => ({ role: m.role, content: m.content })),
68
+ ...split.prompt.toRequest().messages
69
+ ];
70
+ }
71
+ /**
72
+ * Converter for a rawTail message entry. Narrows a `JsonObject` to
73
+ * `{ role: string; content: string | unknown[] }` at runtime using the
74
+ * Converter pattern. Entries that fail validation are silently skipped — the
75
+ * surrounding function is infallible, and a malformed continuation message is
76
+ * better omitted than transmitted verbatim.
77
+ * @internal
78
+ */
79
+ const rawTailMessageConverter = Converters.object({
80
+ role: Converters.enumeratedValue(['user', 'assistant']),
81
+ content: Converters.oneOf([
82
+ Converters.string,
83
+ Converters.isA('array', (v) => Array.isArray(v))
84
+ ])
85
+ }, { strict: false });
86
+ /**
87
+ * Converter for an OpenAI / xAI Responses API `rawTail` item. These are
88
+ * provider-native input items (`function_call`, `function_call_output`) whose
89
+ * fields differ per item type, so — unlike the Anthropic `{ role, content }`
90
+ * projection — the whole object is preserved verbatim.
91
+ *
92
+ * The static input is already typed `JsonObject`, so the `isJsonObject` guard
93
+ * is a runtime backstop, not a compile-time narrowing: continuation messages
94
+ * originate from a prior turn's `IAiClientToolContinuation.messages` and a
95
+ * consumer may persist and reload them through untyped JSON before passing them
96
+ * back. The guard preserves the same "a malformed continuation message is
97
+ * better omitted than transmitted verbatim" posture as the Anthropic path —
98
+ * non-object entries fail conversion and are skipped by the caller.
99
+ * @internal
100
+ */
101
+ const openAiRawTailItemConverter = Converters.isA('JsonObject', (v) => isJsonObject(v));
102
+ /**
103
+ * Converter for a Gemini `rawTail` item. Gemini continuation messages are
104
+ * `{ role, parts }` turns (a model turn with `functionCall` parts followed by a
105
+ * user turn with `functionResponse` parts). Narrows a `JsonObject` to
106
+ * `{ role: 'user' | 'model'; parts: Array<Record<string, unknown>> }`; entries
107
+ * that fail validation are skipped by the caller.
108
+ * @internal
109
+ */
110
+ const geminiRawTailMessageConverter = Converters.object({
111
+ role: Converters.enumeratedValue(['user', 'model']),
112
+ // `parts` is preserved verbatim and serialized into the request body, so the
113
+ // element shape is not narrowed here — `Array.isArray` soundly guarantees
114
+ // `unknown[]` (narrowing to `Record<string, unknown>[]` would be an unchecked cast).
115
+ parts: Converters.isA('array', (v) => Array.isArray(v))
116
+ }, { strict: false });
117
+ /**
118
+ * Builds the messages array from prompt + optional history (`head`) and raw
119
+ * continuation (`rawTail`) messages. The caller supplies the user content
120
+ * (string for text-only, parts array for vision prompts) since the parts shape
121
+ * differs by format.
122
+ *
123
+ * `rawTail` items (OpenAI / xAI Responses `function_call` /
124
+ * `function_call_output` continuation items) are appended verbatim after the
125
+ * user message — their fields differ per item `type`, so they are preserved
126
+ * rather than projected. The return type is `Array<Record<string, unknown>>`
127
+ * to accommodate both `{ role, content }` messages and these heterogeneous
128
+ * input items.
129
+ *
130
+ * @internal
131
+ */
132
+ export function buildMessages(systemPrompt, userContent, options) {
133
+ const messages = [{ role: 'system', content: systemPrompt }];
134
+ if (options === null || options === void 0 ? void 0 : options.head) {
135
+ for (const msg of options.head) {
136
+ messages.push({ role: msg.role, content: msg.content });
137
+ }
138
+ }
139
+ messages.push({ role: 'user', content: userContent });
140
+ // OpenAI / xAI Responses continuation items (function_call /
141
+ // function_call_output) are appended verbatim — their field set differs per
142
+ // item type, so the whole object is preserved rather than projected.
143
+ if (options === null || options === void 0 ? void 0 : options.rawTail) {
144
+ for (const item of options.rawTail) {
145
+ const converted = openAiRawTailItemConverter.convert(item);
146
+ if (converted.isSuccess()) {
147
+ messages.push(converted.value);
148
+ }
149
+ }
150
+ }
151
+ return messages;
152
+ }
153
+ /**
154
+ * Builds the user content for OpenAI Chat Completions when attachments are
155
+ * present. Returns a string when there are no attachments.
156
+ *
157
+ * @internal
158
+ */
159
+ export function buildOpenAiChatUserContent(prompt) {
160
+ if (prompt.attachments.length === 0) {
161
+ return prompt.user;
162
+ }
163
+ return [
164
+ { type: 'text', text: prompt.user },
165
+ ...prompt.attachments.map((att) => ({
166
+ type: 'image_url',
167
+ image_url: Object.assign({ url: toDataUrl(att) }, (att.detail !== undefined ? { detail: att.detail } : {}))
168
+ }))
169
+ ];
170
+ }
171
+ /**
172
+ * Builds the user content for OpenAI / xAI Responses API when attachments
173
+ * are present. Responses API uses `input_text` / `input_image` part types,
174
+ * distinct from Chat Completions' `text` / `image_url`.
175
+ *
176
+ * @internal
177
+ */
178
+ export function buildOpenAiResponsesUserContent(prompt) {
179
+ if (prompt.attachments.length === 0) {
180
+ return prompt.user;
181
+ }
182
+ return [
183
+ { type: 'input_text', text: prompt.user },
184
+ ...prompt.attachments.map((att) => (Object.assign({ type: 'input_image', image_url: toDataUrl(att) }, (att.detail !== undefined ? { detail: att.detail } : {}))))
185
+ ];
186
+ }
187
+ /**
188
+ * Builds the user-message content for Anthropic when attachments are present.
189
+ *
190
+ * @internal
191
+ */
192
+ export function buildAnthropicUserContent(prompt) {
193
+ if (prompt.attachments.length === 0) {
194
+ return prompt.user;
195
+ }
196
+ return [
197
+ { type: 'text', text: prompt.user },
198
+ ...prompt.attachments.map((att) => ({
199
+ type: 'image',
200
+ source: {
201
+ type: 'base64',
202
+ media_type: att.mimeType,
203
+ data: att.base64
204
+ }
205
+ }))
206
+ ];
207
+ }
208
+ /**
209
+ * Builds the Gemini `parts` array for the user turn, including any image
210
+ * attachments as `inlineData` parts.
211
+ *
212
+ * @internal
213
+ */
214
+ export function buildGeminiUserParts(prompt) {
215
+ const parts = [{ text: prompt.user }];
216
+ for (const att of prompt.attachments) {
217
+ parts.push({ inlineData: { mimeType: att.mimeType, data: att.base64 } });
218
+ }
219
+ return parts;
220
+ }
221
+ /**
222
+ * Builds the Anthropic messages array, weaving any `head` history messages
223
+ * between implicit system + the prompt's user message and appending `rawTail`
224
+ * continuation messages after. System messages are filtered out (Anthropic uses
225
+ * a top-level system field).
226
+ *
227
+ * @internal
228
+ */
229
+ export function buildAnthropicMessages(prompt, options) {
230
+ const messages = [];
231
+ if (options === null || options === void 0 ? void 0 : options.head) {
232
+ for (const msg of options.head) {
233
+ if (msg.role !== 'system') {
234
+ messages.push({ role: msg.role, content: msg.content });
235
+ }
236
+ }
237
+ }
238
+ messages.push({ role: 'user', content: buildAnthropicUserContent(prompt) });
239
+ if (options === null || options === void 0 ? void 0 : options.rawTail) {
240
+ for (const msg of options.rawTail) {
241
+ const converted = rawTailMessageConverter.convert(msg);
242
+ if (converted.isSuccess()) {
243
+ messages.push(converted.value);
244
+ }
245
+ }
246
+ }
247
+ return messages;
248
+ }
249
+ /**
250
+ * Builds the Gemini `contents` array, weaving any `head` history messages before
251
+ * the prompt's user parts and appending `rawTail` continuation messages after.
252
+ * System messages are filtered out (Gemini uses a top-level systemInstruction
253
+ * field) and assistant roles are mapped to Gemini's `model` role.
254
+ *
255
+ * @internal
256
+ */
257
+ export function buildGeminiContents(prompt, options) {
258
+ const contents = [];
259
+ if (options === null || options === void 0 ? void 0 : options.head) {
260
+ for (const msg of options.head) {
261
+ if (msg.role !== 'system') {
262
+ contents.push({
263
+ role: msg.role === 'assistant' ? 'model' : msg.role,
264
+ parts: [{ text: msg.content }]
265
+ });
266
+ }
267
+ }
268
+ }
269
+ contents.push({ role: 'user', parts: buildGeminiUserParts(prompt) });
270
+ // Gemini continuation turns (model `functionCall` parts + user
271
+ // `functionResponse` parts) are projected to `{ role, parts }`.
272
+ if (options === null || options === void 0 ? void 0 : options.rawTail) {
273
+ for (const item of options.rawTail) {
274
+ const converted = geminiRawTailMessageConverter.convert(item);
275
+ if (converted.isSuccess()) {
276
+ contents.push(converted.value);
277
+ }
278
+ }
279
+ }
280
+ return contents;
281
+ }
282
+ //# sourceMappingURL=chatRequestBuilders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatRequestBuilders.js","sourceRoot":"","sources":["../../../src/packlets/ai-assist/chatRequestBuilders.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;AAEZ;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAmB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAkB,UAAU,EAAE,IAAI,EAAU,OAAO,EAAE,MAAM,eAAe,CAAC;AAElF,OAAO,EAAE,QAAQ,EAA8C,SAAS,EAAE,MAAM,SAAS,CAAC;AAe1F;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAA0B,EAC1B,QAAqC;IAErC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,sEAAsE,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IACrG,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,EAAE,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAChF,OAAO,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAwB;IAChE,OAAO;QACL,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ;KACrC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,uBAAuB,GAC3B,UAAU,CAAC,MAAM,CACf;IACE,IAAI,EAAE,UAAU,CAAC,eAAe,CAAuB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7E,OAAO,EAAE,UAAU,CAAC,KAAK,CAAqB;QAC5C,UAAU,CAAC,MAAM;QACjB,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,EAAkB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KACjE,CAAC;CACH,EACD,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;AAEJ;;;;;;;;;;;;;;GAcG;AACH,MAAM,0BAA0B,GAA0B,UAAU,CAAC,GAAG,CACtE,YAAY,EACZ,CAAC,CAAC,EAAmB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CACxC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,6BAA6B,GAG9B,UAAU,CAAC,MAAM,CACpB;IACE,IAAI,EAAE,UAAU,CAAC,eAAe,CAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrE,6EAA6E;IAC7E,0EAA0E;IAC1E,qFAAqF;IACrF,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,EAAkB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,EACD,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;AAoCF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAC3B,YAAoB,EACpB,WAA+B,EAC/B,OAA+B;IAE/B,MAAM,QAAQ,GAAmC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;IAC7F,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;IACtD,6DAA6D;IAC7D,4EAA4E;IAC5E,qEAAqE;IACrE,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,0BAA0B,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC3D,IAAI,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAgB;IACzD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IACD,OAAO;QACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;QACnC,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,CAAC;YACtD,IAAI,EAAE,WAAW;YACjB,SAAS,kBACP,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,IAChB,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC5D;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B,CAAC,MAAgB;IAC9D,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IACD,OAAO;QACL,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;QACzC,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,iBACrD,IAAI,EAAE,aAAa,EACnB,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,IACtB,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC3D,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAgB;IACxD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IACD,OAAO;QACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;QACnC,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,CAAC;YACtD,IAAI,EAAE,OAAO;YACb,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,GAAG,CAAC,QAAQ;gBACxB,IAAI,EAAE,GAAG,CAAC,MAAM;aACjB;SACF,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAgB;IACnD,MAAM,KAAK,GAAmC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACtE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAgB,EAChB,OAA+B;IAE/B,MAAM,QAAQ,GAAyD,EAAE,CAAC;IAC1E,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5E,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,uBAAuB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvD,IAAI,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAgB,EAChB,OAA+B;IAE/B,MAAM,QAAQ,GAA8C,EAAE,CAAC;IAC/D,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,EAAE,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI;oBACnD,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;iBAC/B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACrE,+DAA+D;IAC/D,gEAAgE;IAChE,IAAI,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,SAAS,GAAG,6BAA6B,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9D,IAAI,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC;gBAC1B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,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 * Per-format chat request shape builders. Shared between the synchronous\n * (`apiClient.ts`) and streaming (`streamingClient.ts`) paths so the wire\n * shapes stay consistent.\n *\n * @packageDocumentation\n */\n\nimport { isJsonObject, type JsonObject } from '@fgv/ts-json-base';\nimport { type Converter, Converters, fail, Result, succeed } from '@fgv/ts-utils';\n\nimport { AiPrompt, type IAiImageAttachment, type IChatMessage, toDataUrl } from './model';\n\n/**\n * The result of splitting an {@link AiAssist.IChatRequest} into the per-builder\n * inputs: the current turn (as an {@link AiPrompt}, carrying system + the final\n * user message + any attachments) and the preceding conversation history.\n * @internal\n */\nexport interface ISplitChatRequest {\n /** The current turn lowered to an {@link AiPrompt} (system + user + attachments). */\n readonly prompt: AiPrompt;\n /** Prior conversation history — every message before the current turn. */\n readonly head: ReadonlyArray<IChatMessage>;\n}\n\n/**\n * Splits a unified {@link AiAssist.IChatRequest} into `{ prompt, head }` for the\n * per-provider builders. The **last** message is the current `user` turn; the\n * preceding messages are history (`head`). This is the single linearization\n * shared by every turn entry point, so the completion path and the client-tool\n * turn path place history at the identical position relative to the current turn.\n *\n * Fails when `messages` is empty (no current turn) or when the last message is\n * not a `user` turn — relabelling a trailing assistant message as the user turn\n * would be a silent footgun, so it is rejected loudly instead.\n *\n * @internal\n */\nexport function splitChatRequest(\n system: string | undefined,\n messages: ReadonlyArray<IChatMessage>\n): Result<ISplitChatRequest> {\n if (messages.length === 0) {\n return fail('messages must contain at least one entry (the current user turn)');\n }\n const current = messages[messages.length - 1];\n if (current.role !== 'user') {\n return fail(`the last message must be the current user turn (role 'user'); got '${current.role}'`);\n }\n const head = messages.slice(0, messages.length - 1);\n const prompt = new AiPrompt(current.content, system ?? '', current.attachments);\n return succeed({ prompt, head });\n}\n\n/**\n * Rebuilds the ordered messages for a proxy wire body so that only the current\n * turn can carry attachments — history (non-current) messages are reduced to\n * `{ role, content }`. The direct per-provider builders already drop attachments\n * on history turns (only the current user turn's attachments are honored), so\n * normalizing here keeps the proxy wire shape consistent with the direct paths\n * and avoids transmitting attachment payloads the upstream provider would ignore.\n *\n * @internal\n */\nexport function normalizeOutboundMessages(split: ISplitChatRequest): IChatMessage[] {\n return [\n ...split.head.map((m) => ({ role: m.role, content: m.content })),\n ...split.prompt.toRequest().messages\n ];\n}\n\n/**\n * Converter for a rawTail message entry. Narrows a `JsonObject` to\n * `{ role: string; content: string | unknown[] }` at runtime using the\n * Converter pattern. Entries that fail validation are silently skipped — the\n * surrounding function is infallible, and a malformed continuation message is\n * better omitted than transmitted verbatim.\n * @internal\n */\nconst rawTailMessageConverter: Converter<{ role: 'user' | 'assistant'; content: string | unknown[] }> =\n Converters.object<{ role: 'user' | 'assistant'; content: string | unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'assistant'>(['user', 'assistant']),\n content: Converters.oneOf<string | unknown[]>([\n Converters.string,\n Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n ])\n },\n { strict: false }\n );\n\n/**\n * Converter for an OpenAI / xAI Responses API `rawTail` item. These are\n * provider-native input items (`function_call`, `function_call_output`) whose\n * fields differ per item type, so — unlike the Anthropic `{ role, content }`\n * projection — the whole object is preserved verbatim.\n *\n * The static input is already typed `JsonObject`, so the `isJsonObject` guard\n * is a runtime backstop, not a compile-time narrowing: continuation messages\n * originate from a prior turn's `IAiClientToolContinuation.messages` and a\n * consumer may persist and reload them through untyped JSON before passing them\n * back. The guard preserves the same \"a malformed continuation message is\n * better omitted than transmitted verbatim\" posture as the Anthropic path —\n * non-object entries fail conversion and are skipped by the caller.\n * @internal\n */\nconst openAiRawTailItemConverter: Converter<JsonObject> = Converters.isA<JsonObject>(\n 'JsonObject',\n (v): v is JsonObject => isJsonObject(v)\n);\n\n/**\n * Converter for a Gemini `rawTail` item. Gemini continuation messages are\n * `{ role, parts }` turns (a model turn with `functionCall` parts followed by a\n * user turn with `functionResponse` parts). Narrows a `JsonObject` to\n * `{ role: 'user' | 'model'; parts: Array<Record<string, unknown>> }`; entries\n * that fail validation are skipped by the caller.\n * @internal\n */\nconst geminiRawTailMessageConverter: Converter<{\n role: 'user' | 'model';\n parts: unknown[];\n}> = Converters.object<{ role: 'user' | 'model'; parts: unknown[] }>(\n {\n role: Converters.enumeratedValue<'user' | 'model'>(['user', 'model']),\n // `parts` is preserved verbatim and serialized into the request body, so the\n // element shape is not narrowed here — `Array.isArray` soundly guarantees\n // `unknown[]` (narrowing to `Record<string, unknown>[]` would be an unchecked cast).\n parts: Converters.isA('array', (v): v is unknown[] => Array.isArray(v))\n },\n { strict: false }\n);\n\n/**\n * Optional history (`head`) and raw continuation (`rawTail`) messages to weave\n * around the prompt's current user message.\n *\n * @internal\n */\nexport interface IBuildMessagesOptions {\n /**\n * Prior conversation history inserted between the system prompt and the\n * prompt's current user message (multi-turn chat / correction retries). The\n * single ordered linearization is `[system, ...head, user, ...rawTail]`.\n */\n readonly head?: ReadonlyArray<IChatMessage>;\n /**\n * Raw JSON objects appended after the prompt's user message. Used to\n * inject provider-specific continuation messages (e.g. Anthropic assistant\n * turns with thinking blocks, OpenAI Responses `function_call` /\n * `function_call_output` items, Gemini `functionCall` / `functionResponse`\n * turns) that cannot be expressed as plain {@link IChatMessage} objects.\n *\n * Each builder applies its own provider-specific shape guard:\n * - {@link buildAnthropicMessages} projects each entry to `{ role, content }`.\n * - {@link buildMessages} (OpenAI / xAI Responses) preserves each item\n * verbatim (item fields differ per `type`), guarding only that it is a\n * JSON object.\n * - {@link buildGeminiContents} projects each entry to `{ role, parts }`.\n *\n * Entries that fail their builder's shape check are silently skipped (the\n * caller is responsible for supplying well-formed continuation messages).\n * Appended after the current user message.\n */\n readonly rawTail?: ReadonlyArray<JsonObject>;\n}\n\n/**\n * Builds the messages array from prompt + optional history (`head`) and raw\n * continuation (`rawTail`) messages. The caller supplies the user content\n * (string for text-only, parts array for vision prompts) since the parts shape\n * differs by format.\n *\n * `rawTail` items (OpenAI / xAI Responses `function_call` /\n * `function_call_output` continuation items) are appended verbatim after the\n * user message — their fields differ per item `type`, so they are preserved\n * rather than projected. The return type is `Array<Record<string, unknown>>`\n * to accommodate both `{ role, content }` messages and these heterogeneous\n * input items.\n *\n * @internal\n */\nexport function buildMessages(\n systemPrompt: string,\n userContent: string | unknown[],\n options?: IBuildMessagesOptions\n): Array<Record<string, unknown>> {\n const messages: Array<Record<string, unknown>> = [{ role: 'system', content: systemPrompt }];\n if (options?.head) {\n for (const msg of options.head) {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n messages.push({ role: 'user', content: userContent });\n // OpenAI / xAI Responses continuation items (function_call /\n // function_call_output) are appended verbatim — their field set differs per\n // item type, so the whole object is preserved rather than projected.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = openAiRawTailItemConverter.convert(item);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the user content for OpenAI Chat Completions when attachments are\n * present. Returns a string when there are no attachments.\n *\n * @internal\n */\nexport function buildOpenAiChatUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image_url',\n image_url: {\n url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }\n }))\n ];\n}\n\n/**\n * Builds the user content for OpenAI / xAI Responses API when attachments\n * are present. Responses API uses `input_text` / `input_image` part types,\n * distinct from Chat Completions' `text` / `image_url`.\n *\n * @internal\n */\nexport function buildOpenAiResponsesUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'input_text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'input_image',\n image_url: toDataUrl(att),\n ...(att.detail !== undefined ? { detail: att.detail } : {})\n }))\n ];\n}\n\n/**\n * Builds the user-message content for Anthropic when attachments are present.\n *\n * @internal\n */\nexport function buildAnthropicUserContent(prompt: AiPrompt): string | unknown[] {\n if (prompt.attachments.length === 0) {\n return prompt.user;\n }\n return [\n { type: 'text', text: prompt.user },\n ...prompt.attachments.map((att: IAiImageAttachment) => ({\n type: 'image',\n source: {\n type: 'base64',\n media_type: att.mimeType,\n data: att.base64\n }\n }))\n ];\n}\n\n/**\n * Builds the Gemini `parts` array for the user turn, including any image\n * attachments as `inlineData` parts.\n *\n * @internal\n */\nexport function buildGeminiUserParts(prompt: AiPrompt): Array<Record<string, unknown>> {\n const parts: Array<Record<string, unknown>> = [{ text: prompt.user }];\n for (const att of prompt.attachments) {\n parts.push({ inlineData: { mimeType: att.mimeType, data: att.base64 } });\n }\n return parts;\n}\n\n/**\n * Builds the Anthropic messages array, weaving any `head` history messages\n * between implicit system + the prompt's user message and appending `rawTail`\n * continuation messages after. System messages are filtered out (Anthropic uses\n * a top-level system field).\n *\n * @internal\n */\nexport function buildAnthropicMessages(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; content: string | unknown[] }> {\n const messages: Array<{ role: string; content: string | unknown[] }> = [];\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n messages.push({ role: msg.role, content: msg.content });\n }\n }\n }\n messages.push({ role: 'user', content: buildAnthropicUserContent(prompt) });\n if (options?.rawTail) {\n for (const msg of options.rawTail) {\n const converted = rawTailMessageConverter.convert(msg);\n if (converted.isSuccess()) {\n messages.push(converted.value);\n }\n }\n }\n return messages;\n}\n\n/**\n * Builds the Gemini `contents` array, weaving any `head` history messages before\n * the prompt's user parts and appending `rawTail` continuation messages after.\n * System messages are filtered out (Gemini uses a top-level systemInstruction\n * field) and assistant roles are mapped to Gemini's `model` role.\n *\n * @internal\n */\nexport function buildGeminiContents(\n prompt: AiPrompt,\n options?: IBuildMessagesOptions\n): Array<{ role: string; parts: unknown[] }> {\n const contents: Array<{ role: string; parts: unknown[] }> = [];\n if (options?.head) {\n for (const msg of options.head) {\n if (msg.role !== 'system') {\n contents.push({\n role: msg.role === 'assistant' ? 'model' : msg.role,\n parts: [{ text: msg.content }]\n });\n }\n }\n }\n contents.push({ role: 'user', parts: buildGeminiUserParts(prompt) });\n // Gemini continuation turns (model `functionCall` parts + user\n // `functionResponse` parts) are projected to `{ role, parts }`.\n if (options?.rawTail) {\n for (const item of options.rawTail) {\n const converted = geminiRawTailMessageConverter.convert(item);\n if (converted.isSuccess()) {\n contents.push(converted.value);\n }\n }\n }\n return contents;\n}\n"]}