@juspay/neurolink 9.63.1 → 9.65.0

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 (358) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +18 -17
  3. package/dist/adapters/providerImageAdapter.js +29 -1
  4. package/dist/adapters/replicate/auth.d.ts +19 -0
  5. package/dist/adapters/replicate/auth.js +32 -0
  6. package/dist/adapters/replicate/predictionLifecycle.d.ts +46 -0
  7. package/dist/adapters/replicate/predictionLifecycle.js +283 -0
  8. package/dist/adapters/video/klingVideoHandler.d.ts +37 -0
  9. package/dist/adapters/video/klingVideoHandler.js +305 -0
  10. package/dist/adapters/video/replicateVideoHandler.d.ts +29 -0
  11. package/dist/adapters/video/replicateVideoHandler.js +157 -0
  12. package/dist/adapters/video/runwayVideoHandler.d.ts +32 -0
  13. package/dist/adapters/video/runwayVideoHandler.js +316 -0
  14. package/dist/adapters/video/vertexVideoHandler.d.ts +19 -1
  15. package/dist/adapters/video/vertexVideoHandler.js +42 -11
  16. package/dist/autoresearch/runner.js +8 -2
  17. package/dist/avatar/index.d.ts +13 -0
  18. package/dist/avatar/index.js +13 -0
  19. package/dist/avatar/providers/DIDAvatar.d.ts +49 -0
  20. package/dist/avatar/providers/DIDAvatar.js +501 -0
  21. package/dist/avatar/providers/HeyGenAvatar.d.ts +30 -0
  22. package/dist/avatar/providers/HeyGenAvatar.js +337 -0
  23. package/dist/avatar/providers/ReplicateAvatar.d.ts +36 -0
  24. package/dist/avatar/providers/ReplicateAvatar.js +267 -0
  25. package/dist/browser/neurolink.min.js +573 -554
  26. package/dist/cli/commands/mcp.js +29 -0
  27. package/dist/cli/commands/proxy.js +24 -5
  28. package/dist/cli/factories/commandFactory.d.ts +25 -1
  29. package/dist/cli/factories/commandFactory.js +341 -63
  30. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  31. package/dist/cli/loop/optionsSchema.js +12 -0
  32. package/dist/constants/contextWindows.js +101 -0
  33. package/dist/constants/enums.d.ts +273 -2
  34. package/dist/constants/enums.js +290 -1
  35. package/dist/constants/videoErrors.d.ts +4 -0
  36. package/dist/constants/videoErrors.js +4 -0
  37. package/dist/core/baseProvider.d.ts +23 -3
  38. package/dist/core/baseProvider.js +217 -11
  39. package/dist/core/constants.d.ts +11 -0
  40. package/dist/core/constants.js +69 -1
  41. package/dist/core/modules/MessageBuilder.js +20 -0
  42. package/dist/core/redisConversationMemoryManager.js +6 -0
  43. package/dist/evaluation/index.d.ts +2 -0
  44. package/dist/evaluation/index.js +4 -0
  45. package/dist/factories/providerFactory.js +7 -1
  46. package/dist/factories/providerRegistry.js +203 -2
  47. package/dist/features/ppt/contentPlanner.js +42 -14
  48. package/dist/index.d.ts +9 -1
  49. package/dist/index.js +16 -1
  50. package/dist/lib/adapters/providerImageAdapter.js +29 -1
  51. package/dist/lib/adapters/replicate/auth.d.ts +19 -0
  52. package/dist/lib/adapters/replicate/auth.js +33 -0
  53. package/dist/lib/adapters/replicate/predictionLifecycle.d.ts +46 -0
  54. package/dist/lib/adapters/replicate/predictionLifecycle.js +284 -0
  55. package/dist/lib/adapters/video/klingVideoHandler.d.ts +37 -0
  56. package/dist/lib/adapters/video/klingVideoHandler.js +306 -0
  57. package/dist/lib/adapters/video/replicateVideoHandler.d.ts +29 -0
  58. package/dist/lib/adapters/video/replicateVideoHandler.js +158 -0
  59. package/dist/lib/adapters/video/runwayVideoHandler.d.ts +32 -0
  60. package/dist/lib/adapters/video/runwayVideoHandler.js +317 -0
  61. package/dist/lib/adapters/video/vertexVideoHandler.d.ts +19 -1
  62. package/dist/lib/adapters/video/vertexVideoHandler.js +42 -11
  63. package/dist/lib/autoresearch/runner.js +8 -2
  64. package/dist/lib/avatar/index.d.ts +13 -0
  65. package/dist/lib/avatar/index.js +14 -0
  66. package/dist/lib/avatar/providers/DIDAvatar.d.ts +49 -0
  67. package/dist/lib/avatar/providers/DIDAvatar.js +502 -0
  68. package/dist/lib/avatar/providers/HeyGenAvatar.d.ts +30 -0
  69. package/dist/lib/avatar/providers/HeyGenAvatar.js +338 -0
  70. package/dist/lib/avatar/providers/ReplicateAvatar.d.ts +36 -0
  71. package/dist/lib/avatar/providers/ReplicateAvatar.js +268 -0
  72. package/dist/lib/constants/contextWindows.js +101 -0
  73. package/dist/lib/constants/enums.d.ts +273 -2
  74. package/dist/lib/constants/enums.js +290 -1
  75. package/dist/lib/constants/videoErrors.d.ts +4 -0
  76. package/dist/lib/constants/videoErrors.js +4 -0
  77. package/dist/lib/core/baseProvider.d.ts +23 -3
  78. package/dist/lib/core/baseProvider.js +217 -11
  79. package/dist/lib/core/constants.d.ts +11 -0
  80. package/dist/lib/core/constants.js +69 -1
  81. package/dist/lib/core/modules/MessageBuilder.js +20 -0
  82. package/dist/lib/core/redisConversationMemoryManager.js +6 -0
  83. package/dist/lib/evaluation/index.d.ts +2 -0
  84. package/dist/lib/evaluation/index.js +4 -0
  85. package/dist/lib/factories/providerFactory.js +7 -1
  86. package/dist/lib/factories/providerRegistry.js +203 -2
  87. package/dist/lib/features/ppt/contentPlanner.js +42 -14
  88. package/dist/lib/index.d.ts +9 -1
  89. package/dist/lib/index.js +16 -1
  90. package/dist/lib/memory/hippocampusInitializer.d.ts +2 -2
  91. package/dist/lib/memory/hippocampusInitializer.js +32 -2
  92. package/dist/lib/middleware/builtin/lifecycle.js +52 -51
  93. package/dist/lib/music/index.d.ts +13 -0
  94. package/dist/lib/music/index.js +14 -0
  95. package/dist/lib/music/providers/BeatovenMusic.d.ts +31 -0
  96. package/dist/lib/music/providers/BeatovenMusic.js +334 -0
  97. package/dist/lib/music/providers/ElevenLabsMusic.d.ts +30 -0
  98. package/dist/lib/music/providers/ElevenLabsMusic.js +169 -0
  99. package/dist/lib/music/providers/LyriaMusic.d.ts +29 -0
  100. package/dist/lib/music/providers/LyriaMusic.js +173 -0
  101. package/dist/lib/music/providers/ReplicateMusic.d.ts +31 -0
  102. package/dist/lib/music/providers/ReplicateMusic.js +262 -0
  103. package/dist/lib/neurolink.d.ts +30 -0
  104. package/dist/lib/neurolink.js +342 -49
  105. package/dist/lib/providers/amazonBedrock.d.ts +10 -0
  106. package/dist/lib/providers/amazonBedrock.js +94 -39
  107. package/dist/lib/providers/anthropic.js +55 -7
  108. package/dist/lib/providers/anthropicBaseProvider.js +1 -1
  109. package/dist/lib/providers/azureOpenai.js +66 -17
  110. package/dist/lib/providers/cloudflare.d.ts +35 -0
  111. package/dist/lib/providers/cloudflare.js +174 -0
  112. package/dist/lib/providers/cohere.d.ts +52 -0
  113. package/dist/lib/providers/cohere.js +253 -0
  114. package/dist/lib/providers/deepseek.js +72 -17
  115. package/dist/lib/providers/fireworks.d.ts +33 -0
  116. package/dist/lib/providers/fireworks.js +164 -0
  117. package/dist/lib/providers/googleAiStudio.d.ts +11 -3
  118. package/dist/lib/providers/googleAiStudio.js +336 -344
  119. package/dist/lib/providers/googleNativeGemini3.d.ts +107 -2
  120. package/dist/lib/providers/googleNativeGemini3.js +381 -25
  121. package/dist/lib/providers/googleVertex.d.ts +116 -129
  122. package/dist/lib/providers/googleVertex.js +3002 -1988
  123. package/dist/lib/providers/groq.d.ts +33 -0
  124. package/dist/lib/providers/groq.js +181 -0
  125. package/dist/lib/providers/huggingFace.js +9 -8
  126. package/dist/lib/providers/ideogram.d.ts +34 -0
  127. package/dist/lib/providers/ideogram.js +184 -0
  128. package/dist/lib/providers/index.d.ts +13 -0
  129. package/dist/lib/providers/index.js +13 -0
  130. package/dist/lib/providers/jina.d.ts +59 -0
  131. package/dist/lib/providers/jina.js +218 -0
  132. package/dist/lib/providers/llamaCpp.js +14 -46
  133. package/dist/lib/providers/lmStudio.js +14 -47
  134. package/dist/lib/providers/mistral.js +7 -7
  135. package/dist/lib/providers/nvidiaNim.js +160 -19
  136. package/dist/lib/providers/ollama.js +7 -7
  137. package/dist/lib/providers/openAI.d.ts +22 -1
  138. package/dist/lib/providers/openAI.js +181 -0
  139. package/dist/lib/providers/openRouter.js +38 -22
  140. package/dist/lib/providers/openaiCompatible.js +9 -8
  141. package/dist/lib/providers/perplexity.d.ts +33 -0
  142. package/dist/lib/providers/perplexity.js +179 -0
  143. package/dist/lib/providers/recraft.d.ts +34 -0
  144. package/dist/lib/providers/recraft.js +197 -0
  145. package/dist/lib/providers/replicate.d.ts +75 -0
  146. package/dist/lib/providers/replicate.js +403 -0
  147. package/dist/lib/providers/stability.d.ts +37 -0
  148. package/dist/lib/providers/stability.js +191 -0
  149. package/dist/lib/providers/togetherAi.d.ts +33 -0
  150. package/dist/lib/providers/togetherAi.js +176 -0
  151. package/dist/lib/providers/voyage.d.ts +47 -0
  152. package/dist/lib/providers/voyage.js +177 -0
  153. package/dist/lib/providers/xai.d.ts +33 -0
  154. package/dist/lib/providers/xai.js +172 -0
  155. package/dist/lib/telemetry/index.d.ts +1 -1
  156. package/dist/lib/telemetry/index.js +1 -1
  157. package/dist/lib/telemetry/tracers.d.ts +19 -0
  158. package/dist/lib/telemetry/tracers.js +19 -0
  159. package/dist/lib/telemetry/withSpan.d.ts +35 -0
  160. package/dist/lib/telemetry/withSpan.js +103 -0
  161. package/dist/lib/types/aliases.d.ts +14 -0
  162. package/dist/lib/types/avatar.d.ts +143 -0
  163. package/dist/lib/types/avatar.js +20 -0
  164. package/dist/lib/types/cli.d.ts +6 -0
  165. package/dist/lib/types/common.d.ts +0 -3
  166. package/dist/lib/types/conversation.d.ts +10 -3
  167. package/dist/lib/types/generate.d.ts +76 -5
  168. package/dist/lib/types/index.d.ts +6 -0
  169. package/dist/lib/types/index.js +8 -0
  170. package/dist/lib/types/memory.d.ts +96 -0
  171. package/dist/lib/types/memory.js +23 -0
  172. package/dist/lib/types/middleware.d.ts +27 -0
  173. package/dist/lib/types/multimodal.d.ts +35 -2
  174. package/dist/lib/types/music.d.ts +165 -0
  175. package/dist/lib/types/music.js +21 -0
  176. package/dist/lib/types/providers.d.ts +284 -3
  177. package/dist/lib/types/replicate.d.ts +67 -0
  178. package/dist/lib/types/replicate.js +10 -0
  179. package/dist/lib/types/safeFetch.d.ts +15 -0
  180. package/dist/lib/types/safeFetch.js +7 -0
  181. package/dist/lib/types/stream.d.ts +8 -1
  182. package/dist/lib/types/tools.d.ts +13 -0
  183. package/dist/lib/types/video.d.ts +89 -0
  184. package/dist/lib/types/video.js +15 -0
  185. package/dist/lib/utils/avatarProcessor.d.ts +68 -0
  186. package/dist/lib/utils/avatarProcessor.js +172 -0
  187. package/dist/lib/utils/cloneOptions.d.ts +36 -0
  188. package/dist/lib/utils/cloneOptions.js +62 -0
  189. package/dist/lib/utils/lifecycleCallbacks.d.ts +56 -0
  190. package/dist/lib/utils/lifecycleCallbacks.js +100 -0
  191. package/dist/lib/utils/lifecycleTimeout.d.ts +25 -0
  192. package/dist/lib/utils/lifecycleTimeout.js +39 -0
  193. package/dist/lib/utils/logSanitize.d.ts +49 -0
  194. package/dist/lib/utils/logSanitize.js +170 -0
  195. package/dist/lib/utils/loggingFetch.d.ts +29 -0
  196. package/dist/lib/utils/loggingFetch.js +60 -0
  197. package/dist/lib/utils/messageBuilder.d.ts +10 -0
  198. package/dist/lib/utils/messageBuilder.js +83 -30
  199. package/dist/lib/utils/modelChoices.js +236 -3
  200. package/dist/lib/utils/modelDetection.d.ts +11 -0
  201. package/dist/lib/utils/modelDetection.js +27 -0
  202. package/dist/lib/utils/musicProcessor.d.ts +67 -0
  203. package/dist/lib/utils/musicProcessor.js +189 -0
  204. package/dist/lib/utils/optionsConversion.js +3 -2
  205. package/dist/lib/utils/parameterValidation.js +14 -4
  206. package/dist/lib/utils/pricing.js +193 -0
  207. package/dist/lib/utils/providerConfig.d.ts +55 -0
  208. package/dist/lib/utils/providerConfig.js +224 -0
  209. package/dist/lib/utils/providerHealth.js +7 -7
  210. package/dist/lib/utils/safeFetch.d.ts +26 -0
  211. package/dist/lib/utils/safeFetch.js +83 -0
  212. package/dist/lib/utils/schemaConversion.d.ts +1 -1
  213. package/dist/lib/utils/schemaConversion.js +59 -4
  214. package/dist/lib/utils/sizeGuard.d.ts +34 -0
  215. package/dist/lib/utils/sizeGuard.js +45 -0
  216. package/dist/lib/utils/ssrfGuard.d.ts +52 -0
  217. package/dist/lib/utils/ssrfGuard.js +411 -0
  218. package/dist/lib/utils/tokenLimits.js +23 -32
  219. package/dist/lib/utils/videoProcessor.d.ts +60 -0
  220. package/dist/lib/utils/videoProcessor.js +201 -0
  221. package/dist/lib/voice/providers/FishAudioTTS.d.ts +27 -0
  222. package/dist/lib/voice/providers/FishAudioTTS.js +183 -0
  223. package/dist/lib/workflow/core/ensembleExecutor.js +26 -9
  224. package/dist/memory/hippocampusInitializer.d.ts +2 -2
  225. package/dist/memory/hippocampusInitializer.js +32 -2
  226. package/dist/middleware/builtin/lifecycle.js +52 -51
  227. package/dist/music/index.d.ts +13 -0
  228. package/dist/music/index.js +13 -0
  229. package/dist/music/providers/BeatovenMusic.d.ts +31 -0
  230. package/dist/music/providers/BeatovenMusic.js +333 -0
  231. package/dist/music/providers/ElevenLabsMusic.d.ts +30 -0
  232. package/dist/music/providers/ElevenLabsMusic.js +168 -0
  233. package/dist/music/providers/LyriaMusic.d.ts +29 -0
  234. package/dist/music/providers/LyriaMusic.js +172 -0
  235. package/dist/music/providers/ReplicateMusic.d.ts +31 -0
  236. package/dist/music/providers/ReplicateMusic.js +261 -0
  237. package/dist/neurolink.d.ts +30 -0
  238. package/dist/neurolink.js +342 -49
  239. package/dist/providers/amazonBedrock.d.ts +10 -0
  240. package/dist/providers/amazonBedrock.js +94 -39
  241. package/dist/providers/anthropic.js +55 -7
  242. package/dist/providers/anthropicBaseProvider.js +1 -1
  243. package/dist/providers/azureOpenai.js +66 -17
  244. package/dist/providers/cloudflare.d.ts +35 -0
  245. package/dist/providers/cloudflare.js +173 -0
  246. package/dist/providers/cohere.d.ts +52 -0
  247. package/dist/providers/cohere.js +252 -0
  248. package/dist/providers/deepseek.js +72 -17
  249. package/dist/providers/fireworks.d.ts +33 -0
  250. package/dist/providers/fireworks.js +163 -0
  251. package/dist/providers/googleAiStudio.d.ts +11 -3
  252. package/dist/providers/googleAiStudio.js +335 -344
  253. package/dist/providers/googleNativeGemini3.d.ts +107 -2
  254. package/dist/providers/googleNativeGemini3.js +381 -25
  255. package/dist/providers/googleVertex.d.ts +116 -129
  256. package/dist/providers/googleVertex.js +3000 -1987
  257. package/dist/providers/groq.d.ts +33 -0
  258. package/dist/providers/groq.js +180 -0
  259. package/dist/providers/huggingFace.js +9 -8
  260. package/dist/providers/ideogram.d.ts +34 -0
  261. package/dist/providers/ideogram.js +183 -0
  262. package/dist/providers/index.d.ts +13 -0
  263. package/dist/providers/index.js +13 -0
  264. package/dist/providers/jina.d.ts +59 -0
  265. package/dist/providers/jina.js +217 -0
  266. package/dist/providers/llamaCpp.js +14 -46
  267. package/dist/providers/lmStudio.js +14 -47
  268. package/dist/providers/mistral.js +7 -7
  269. package/dist/providers/nvidiaNim.js +160 -19
  270. package/dist/providers/ollama.js +7 -7
  271. package/dist/providers/openAI.d.ts +22 -1
  272. package/dist/providers/openAI.js +181 -0
  273. package/dist/providers/openRouter.js +38 -22
  274. package/dist/providers/openaiCompatible.js +9 -8
  275. package/dist/providers/perplexity.d.ts +33 -0
  276. package/dist/providers/perplexity.js +178 -0
  277. package/dist/providers/recraft.d.ts +34 -0
  278. package/dist/providers/recraft.js +196 -0
  279. package/dist/providers/replicate.d.ts +75 -0
  280. package/dist/providers/replicate.js +402 -0
  281. package/dist/providers/stability.d.ts +37 -0
  282. package/dist/providers/stability.js +190 -0
  283. package/dist/providers/togetherAi.d.ts +33 -0
  284. package/dist/providers/togetherAi.js +175 -0
  285. package/dist/providers/voyage.d.ts +47 -0
  286. package/dist/providers/voyage.js +176 -0
  287. package/dist/providers/xai.d.ts +33 -0
  288. package/dist/providers/xai.js +171 -0
  289. package/dist/telemetry/index.d.ts +1 -1
  290. package/dist/telemetry/index.js +1 -1
  291. package/dist/telemetry/tracers.d.ts +19 -0
  292. package/dist/telemetry/tracers.js +19 -0
  293. package/dist/telemetry/withSpan.d.ts +35 -0
  294. package/dist/telemetry/withSpan.js +103 -0
  295. package/dist/types/aliases.d.ts +14 -0
  296. package/dist/types/avatar.d.ts +143 -0
  297. package/dist/types/avatar.js +19 -0
  298. package/dist/types/cli.d.ts +6 -0
  299. package/dist/types/common.d.ts +0 -3
  300. package/dist/types/conversation.d.ts +10 -3
  301. package/dist/types/generate.d.ts +76 -5
  302. package/dist/types/index.d.ts +6 -0
  303. package/dist/types/index.js +8 -0
  304. package/dist/types/memory.d.ts +96 -0
  305. package/dist/types/memory.js +22 -0
  306. package/dist/types/middleware.d.ts +27 -0
  307. package/dist/types/multimodal.d.ts +35 -2
  308. package/dist/types/music.d.ts +165 -0
  309. package/dist/types/music.js +20 -0
  310. package/dist/types/providers.d.ts +284 -3
  311. package/dist/types/replicate.d.ts +67 -0
  312. package/dist/types/replicate.js +9 -0
  313. package/dist/types/safeFetch.d.ts +15 -0
  314. package/dist/types/safeFetch.js +6 -0
  315. package/dist/types/stream.d.ts +8 -1
  316. package/dist/types/tools.d.ts +13 -0
  317. package/dist/types/video.d.ts +89 -0
  318. package/dist/types/video.js +14 -0
  319. package/dist/utils/avatarProcessor.d.ts +68 -0
  320. package/dist/utils/avatarProcessor.js +171 -0
  321. package/dist/utils/cloneOptions.d.ts +36 -0
  322. package/dist/utils/cloneOptions.js +61 -0
  323. package/dist/utils/lifecycleCallbacks.d.ts +56 -0
  324. package/dist/utils/lifecycleCallbacks.js +99 -0
  325. package/dist/utils/lifecycleTimeout.d.ts +25 -0
  326. package/dist/utils/lifecycleTimeout.js +38 -0
  327. package/dist/utils/logSanitize.d.ts +49 -0
  328. package/dist/utils/logSanitize.js +169 -0
  329. package/dist/utils/loggingFetch.d.ts +29 -0
  330. package/dist/utils/loggingFetch.js +59 -0
  331. package/dist/utils/messageBuilder.d.ts +10 -0
  332. package/dist/utils/messageBuilder.js +83 -30
  333. package/dist/utils/modelChoices.js +236 -3
  334. package/dist/utils/modelDetection.d.ts +11 -0
  335. package/dist/utils/modelDetection.js +27 -0
  336. package/dist/utils/musicProcessor.d.ts +67 -0
  337. package/dist/utils/musicProcessor.js +188 -0
  338. package/dist/utils/optionsConversion.js +3 -2
  339. package/dist/utils/parameterValidation.js +14 -4
  340. package/dist/utils/pricing.js +193 -0
  341. package/dist/utils/providerConfig.d.ts +55 -0
  342. package/dist/utils/providerConfig.js +224 -0
  343. package/dist/utils/providerHealth.js +7 -7
  344. package/dist/utils/safeFetch.d.ts +26 -0
  345. package/dist/utils/safeFetch.js +82 -0
  346. package/dist/utils/schemaConversion.d.ts +1 -1
  347. package/dist/utils/schemaConversion.js +59 -4
  348. package/dist/utils/sizeGuard.d.ts +34 -0
  349. package/dist/utils/sizeGuard.js +44 -0
  350. package/dist/utils/ssrfGuard.d.ts +52 -0
  351. package/dist/utils/ssrfGuard.js +410 -0
  352. package/dist/utils/tokenLimits.js +23 -32
  353. package/dist/utils/videoProcessor.d.ts +60 -0
  354. package/dist/utils/videoProcessor.js +200 -0
  355. package/dist/voice/providers/FishAudioTTS.d.ts +27 -0
  356. package/dist/voice/providers/FishAudioTTS.js +182 -0
  357. package/dist/workflow/core/ensembleExecutor.js +26 -9
  358. package/package.json +42 -8
@@ -9,12 +9,69 @@
9
9
  * providers so they can share a single implementation.
10
10
  */
11
11
  import { randomUUID } from "node:crypto";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { extname } from "node:path";
12
14
  import { jsonSchema as aiJsonSchema, tool as createAISDKTool, } from "ai";
13
15
  import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, } from "../core/constants.js";
14
16
  import { logger } from "../utils/logger.js";
15
- import { convertZodToJsonSchema, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
17
+ import { convertZodToJsonSchema, ensureNestedSchemaTypes, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
16
18
  import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
17
19
  // ── Functions ──
20
+ /**
21
+ * Google's `function_declarations[].name` validator regex.
22
+ *
23
+ * Empirically (and per the Vertex/AI Studio API error message), the server
24
+ * enforces `[A-Za-z_][A-Za-z0-9_.:-]{0,127}`. Tool names that don't match
25
+ * fail with HTTP 400 "Invalid function name", which surfaces as a misleading
26
+ * tool-calling failure for the whole request.
27
+ *
28
+ * MCP-imported or user-registered tools may legally contain characters
29
+ * outside this set (e.g. `/`, spaces, unicode), so we sanitize defensively
30
+ * before sending to Google. The sanitized name is also used as the
31
+ * `executeMap` key so the round-trip from Google's function-call response
32
+ * back to our executor still works.
33
+ */
34
+ const GOOGLE_FN_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_.:-]{0,127}$/;
35
+ const GOOGLE_FN_NAME_MAX_LENGTH = 128;
36
+ export function sanitizeForGoogleFunctionName(name) {
37
+ if (GOOGLE_FN_NAME_REGEX.test(name)) {
38
+ return name;
39
+ }
40
+ let sanitized = name.replace(/[^A-Za-z0-9_.:-]/g, "_");
41
+ if (!/^[A-Za-z_]/.test(sanitized)) {
42
+ sanitized = `_${sanitized}`;
43
+ }
44
+ if (sanitized.length > GOOGLE_FN_NAME_MAX_LENGTH) {
45
+ sanitized = sanitized.slice(0, GOOGLE_FN_NAME_MAX_LENGTH);
46
+ }
47
+ return sanitized;
48
+ }
49
+ /**
50
+ * Resolve a sanitized Gemini tool name to one that is both unique within
51
+ * the current request and at most 128 characters. When the candidate
52
+ * collides with an already-used name we append `_2`, `_3`, … — but
53
+ * reserve room for the suffix by truncating the base first so the
54
+ * resolved name never exceeds Google's `function_declarations[].name`
55
+ * limit.
56
+ *
57
+ * @param base The already-sanitized candidate name.
58
+ * @param isTaken Predicate that returns true if `name` is already used.
59
+ */
60
+ export function resolveUniqueGoogleFunctionName(base, isTaken) {
61
+ if (!isTaken(base)) {
62
+ return base;
63
+ }
64
+ let suffix = 2;
65
+ while (true) {
66
+ const suffixStr = `_${suffix}`;
67
+ const trimmedBase = base.slice(0, GOOGLE_FN_NAME_MAX_LENGTH - suffixStr.length);
68
+ const candidate = `${trimmedBase}${suffixStr}`;
69
+ if (!isTaken(candidate)) {
70
+ return candidate;
71
+ }
72
+ suffix++;
73
+ }
74
+ }
18
75
  /**
19
76
  * Sanitize a JSON Schema for Gemini's proto-based API.
20
77
  *
@@ -100,6 +157,40 @@ export function sanitizeSchemaForGemini(schema) {
100
157
  result[branch] = sanitizeSchemaForGemini(result[branch]);
101
158
  }
102
159
  }
160
+ // JSON Schema Draft-4 `exclusiveMinimum: true` / `exclusiveMaximum: true`
161
+ // (boolean form) is rejected by Gemini's OpenAPI 3.0 validator, which
162
+ // expects a numeric bound. zod-to-json-schema's openApi3 target still
163
+ // emits the Draft-4 form for `z.number().positive()` etc. Translate to
164
+ // the numeric form when paired with `minimum`/`maximum`, or drop.
165
+ if (typeof result.exclusiveMinimum === "boolean") {
166
+ if (result.exclusiveMinimum === true &&
167
+ typeof result.minimum === "number") {
168
+ result.exclusiveMinimum = result.minimum;
169
+ delete result.minimum;
170
+ }
171
+ else {
172
+ delete result.exclusiveMinimum;
173
+ }
174
+ }
175
+ if (typeof result.exclusiveMaximum === "boolean") {
176
+ if (result.exclusiveMaximum === true &&
177
+ typeof result.maximum === "number") {
178
+ result.exclusiveMaximum = result.maximum;
179
+ delete result.maximum;
180
+ }
181
+ else {
182
+ delete result.exclusiveMaximum;
183
+ }
184
+ }
185
+ // Clamp `maximum`/`minimum` past int32 — Gemini's protobuf serializer
186
+ // treats `type: "integer"` as int32 and rejects bounds beyond ~2.1e9.
187
+ const INT32_MAX = 2147483647;
188
+ if (typeof result.maximum === "number" && result.maximum > INT32_MAX) {
189
+ delete result.maximum;
190
+ }
191
+ if (typeof result.minimum === "number" && result.minimum < -INT32_MAX) {
192
+ delete result.minimum;
193
+ }
103
194
  return result;
104
195
  }
105
196
  /**
@@ -115,8 +206,27 @@ export function sanitizeSchemaForGemini(schema) {
115
206
  export function sanitizeToolsForGemini(tools) {
116
207
  const sanitized = {};
117
208
  const dropped = [];
209
+ const renamed = [];
210
+ const originalNameMap = new Map();
118
211
  for (const [name, tool] of Object.entries(tools)) {
119
212
  try {
213
+ // Sanitize the tool name to fit Google's function_declarations regex.
214
+ // Without this, MCP-imported or user-registered tools whose names contain
215
+ // characters outside [A-Za-z_][A-Za-z0-9_.:-]{0,127} cause the entire
216
+ // request to 400 with "Invalid function name", surfacing as a misleading
217
+ // tool-calling failure. Distinct originals that collapse onto the same
218
+ // sanitized name (e.g. "my/tool" and "my-tool" → "my_tool") are
219
+ // disambiguated with a numeric suffix that preserves Google's 128-char
220
+ // ceiling.
221
+ const candidate = sanitizeForGoogleFunctionName(name);
222
+ const safeName = resolveUniqueGoogleFunctionName(candidate, (n) => n in sanitized);
223
+ // Always record the mapping so downstream code can translate every
224
+ // safeName back to the original — including the no-rename identity
225
+ // mapping, which simplifies the lookup path.
226
+ originalNameMap.set(safeName, name);
227
+ if (safeName !== name) {
228
+ renamed.push({ from: name, to: safeName });
229
+ }
120
230
  // Access the legacy `parameters` field that may exist on older AI SDK tools.
121
231
  // AI SDK v6 uses `inputSchema`, but v3/v4 tools and third-party wrappers use `parameters`.
122
232
  const legacyTool = tool;
@@ -125,15 +235,15 @@ export function sanitizeToolsForGemini(tools) {
125
235
  typeof params === "object" &&
126
236
  "_def" in params &&
127
237
  typeof params.parse === "function") {
128
- const rawJsonSchema = convertZodToJsonSchema(params);
238
+ const rawJsonSchema = convertZodToJsonSchema(params, "openApi3");
129
239
  const inlined = inlineJsonSchema(rawJsonSchema);
130
240
  // Gemini sanitization strips Zod-only features not supported by the Gemini API:
131
241
  // union types (anyOf/oneOf) are collapsed to string, default values and
132
242
  // additionalProperties are removed. The resulting schema is Gemini-compatible
133
243
  // but loses some type constraints from the original Zod schema.
134
244
  const sanitizedSchema = sanitizeSchemaForGemini(inlined);
135
- sanitized[name] = createAISDKTool({
136
- description: tool.description || `Tool: ${name}`,
245
+ sanitized[safeName] = createAISDKTool({
246
+ description: tool.description || `Tool: ${safeName}`,
137
247
  inputSchema: aiJsonSchema(sanitizedSchema),
138
248
  execute: tool.execute,
139
249
  });
@@ -145,14 +255,14 @@ export function sanitizeToolsForGemini(tools) {
145
255
  const rawSchema = params
146
256
  .jsonSchema;
147
257
  const sanitizedSchema = sanitizeSchemaForGemini(inlineJsonSchema(rawSchema));
148
- sanitized[name] = createAISDKTool({
149
- description: tool.description || `Tool: ${name}`,
258
+ sanitized[safeName] = createAISDKTool({
259
+ description: tool.description || `Tool: ${safeName}`,
150
260
  inputSchema: aiJsonSchema(sanitizedSchema),
151
261
  execute: tool.execute,
152
262
  });
153
263
  }
154
264
  else {
155
- sanitized[name] = tool;
265
+ sanitized[safeName] = tool;
156
266
  }
157
267
  }
158
268
  catch (error) {
@@ -161,7 +271,12 @@ export function sanitizeToolsForGemini(tools) {
161
271
  dropped.push(name);
162
272
  }
163
273
  }
164
- return { tools: sanitized, dropped };
274
+ if (renamed.length > 0) {
275
+ logger.warn(`[Gemini] ${renamed.length} tool name(s) sanitized for Google's function-name regex: ${renamed
276
+ .map((r) => `"${r.from}" -> "${r.to}"`)
277
+ .join(", ")}`);
278
+ }
279
+ return { tools: sanitized, dropped, originalNameMap };
165
280
  }
166
281
  export function normalizeToolsForJsonSchemaProvider(tools) {
167
282
  const normalizedTools = {};
@@ -171,7 +286,7 @@ export function normalizeToolsForJsonSchemaProvider(tools) {
171
286
  const toolParams = legacyTool.parameters || tool.inputSchema;
172
287
  let rawSchema;
173
288
  if (isZodSchema(toolParams)) {
174
- rawSchema = convertZodToJsonSchema(toolParams);
289
+ rawSchema = convertZodToJsonSchema(toolParams, "openApi3");
175
290
  }
176
291
  else if (toolParams && typeof toolParams === "object") {
177
292
  rawSchema = toolParams;
@@ -210,11 +325,29 @@ export function buildNativeToolDeclarations(tools) {
210
325
  const functionDeclarations = [];
211
326
  const executeMap = new Map();
212
327
  const skippedTools = [];
328
+ const renamedTools = [];
329
+ // Disambiguate distinct originals that collapse onto the same sanitized
330
+ // name (e.g. "my/tool" and "my-tool" both → "my_tool") via
331
+ // resolveUniqueGoogleFunctionName, which appends `_N` while keeping the
332
+ // final string within Google's 128-char limit. Track all assigned names
333
+ // regardless of whether the tool has an `execute` function (tools without
334
+ // execute are still pushed to functionDeclarations). The originalNameMap
335
+ // lets the calling stream loop translate Google-returned function-call
336
+ // names back to the consumer-facing identifier so the sanitization is
337
+ // transport-only.
338
+ const usedNames = new Set();
339
+ const originalNameMap = new Map();
213
340
  for (const [name, tool] of Object.entries(tools)) {
214
341
  try {
342
+ const candidate = sanitizeForGoogleFunctionName(name);
343
+ const safeName = resolveUniqueGoogleFunctionName(candidate, (n) => usedNames.has(n));
344
+ originalNameMap.set(safeName, name);
345
+ if (safeName !== name) {
346
+ renamedTools.push({ from: name, to: safeName });
347
+ }
215
348
  const decl = {
216
- name,
217
- description: tool.description || `Tool: ${name}`,
349
+ name: safeName,
350
+ description: tool.description || `Tool: ${safeName}`,
218
351
  };
219
352
  // Access legacy `parameters` (AI SDK v3/v4) or current `inputSchema` (v6)
220
353
  const legacyTool = tool;
@@ -222,7 +355,7 @@ export function buildNativeToolDeclarations(tools) {
222
355
  let rawSchema;
223
356
  const toolParams = legacyTool.parameters || tool.inputSchema;
224
357
  if (isZodSchema(toolParams)) {
225
- rawSchema = convertZodToJsonSchema(toolParams);
358
+ rawSchema = convertZodToJsonSchema(toolParams, "openApi3");
226
359
  }
227
360
  else if (typeof toolParams === "object") {
228
361
  rawSchema = toolParams;
@@ -239,8 +372,9 @@ export function buildNativeToolDeclarations(tools) {
239
372
  decl.parametersJsonSchema = sanitizeSchemaForGemini(inlineJsonSchema(rawSchema));
240
373
  }
241
374
  functionDeclarations.push(decl);
375
+ usedNames.add(safeName);
242
376
  if (tool.execute) {
243
- executeMap.set(name, tool.execute);
377
+ executeMap.set(decl.name, tool.execute);
244
378
  }
245
379
  }
246
380
  catch (err) {
@@ -251,10 +385,27 @@ export function buildNativeToolDeclarations(tools) {
251
385
  if (skippedTools.length > 0) {
252
386
  logger.warn(`[buildNativeToolDeclarations] ${skippedTools.length} tool(s) skipped due to schema errors: ${skippedTools.join(", ")}`);
253
387
  }
254
- return { toolsConfig: [{ functionDeclarations }], executeMap };
388
+ if (renamedTools.length > 0) {
389
+ logger.warn(`[buildNativeToolDeclarations] ${renamedTools.length} tool name(s) sanitized for Google's function-name regex: ${renamedTools
390
+ .map((r) => `"${r.from}" -> "${r.to}"`)
391
+ .join(", ")}`);
392
+ }
393
+ return {
394
+ toolsConfig: [{ functionDeclarations }],
395
+ executeMap,
396
+ originalNameMap,
397
+ };
255
398
  }
256
399
  /**
257
400
  * Build the native @google/genai config object shared by stream and generate.
401
+ *
402
+ * Caller is responsible for the tools-vs-JSON conflict resolution: Gemini's
403
+ * function calling cannot be combined with `responseMimeType:
404
+ * "application/json"`, and `responseSchema` requires that mime type. So
405
+ * when tools are active, callers must NOT pass `wantsJsonOutput`/
406
+ * `responseSchema` here; when JSON/schema output is requested, callers
407
+ * must omit `toolsConfig`. The AI Studio path enforces this by forcing
408
+ * `disableTools: true` whenever JSON/schema output is requested.
258
409
  */
259
410
  export function buildNativeConfig(options, toolsConfig) {
260
411
  const config = {
@@ -272,6 +423,16 @@ export function buildNativeConfig(options, toolsConfig) {
272
423
  if (nativeThinkingConfig) {
273
424
  config.thinkingConfig = nativeThinkingConfig;
274
425
  }
426
+ // Native JSON / schema enforcement. Only set when tools are NOT being sent
427
+ // (Gemini rejects the combination). responseSchema implies JSON mime type.
428
+ if (!toolsConfig) {
429
+ if (options.responseSchema || options.wantsJsonOutput) {
430
+ config.responseMimeType = "application/json";
431
+ }
432
+ if (options.responseSchema) {
433
+ config.responseSchema = options.responseSchema;
434
+ }
435
+ }
275
436
  return config;
276
437
  }
277
438
  /**
@@ -473,27 +634,38 @@ export function extractTextFromParts(rawResponseParts) {
473
634
  * @param executeMap - Map of tool name to execute function
474
635
  * @param failedTools - Mutable map tracking per-tool failure counts
475
636
  * @param allToolCalls - Mutable array accumulating all tool call records
476
- * @param options - Optional settings for execution tracking and cancellation
637
+ * @param options - Optional settings for execution tracking and cancellation,
638
+ * plus an `originalNameMap` (Google-safe → consumer-supplied
639
+ * identifier) so the sanitization stays transport-only and
640
+ * consumers see the names they registered.
477
641
  * @returns Array of function responses for conversation history
478
642
  */
479
643
  export async function executeNativeToolCalls(logLabel, stepFunctionCalls, executeMap, failedTools, allToolCalls, options) {
480
644
  const functionResponses = [];
645
+ // Translate a Google-safe sanitized name back to the consumer-facing
646
+ // original name. Falls back to the safe name if the map is missing or
647
+ // doesn't contain the call (e.g. tool added mid-conversation).
648
+ const externalName = (safeName) => options?.originalNameMap?.get(safeName) ?? safeName;
481
649
  for (const call of stepFunctionCalls) {
482
- allToolCalls.push({ toolName: call.name, args: call.args });
650
+ const exposedName = externalName(call.name);
651
+ allToolCalls.push({ toolName: exposedName, args: call.args });
483
652
  // Check if this tool has already exceeded retry limit
484
653
  const failedInfo = failedTools.get(call.name);
485
654
  if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
486
- logger.warn(`${logLabel} Tool "${call.name}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
655
+ logger.warn(`${logLabel} Tool "${exposedName}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
487
656
  const errorOutput = {
488
- error: `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${failedInfo.count} times and will not be retried. Last error: ${failedInfo.lastError}. Please proceed without using this tool or inform the user that this functionality is unavailable.`,
657
+ error: `TOOL_PERMANENTLY_FAILED: The tool "${exposedName}" has failed ${failedInfo.count} times and will not be retried. Last error: ${failedInfo.lastError}. Please proceed without using this tool or inform the user that this functionality is unavailable.`,
489
658
  status: "permanently_failed",
490
659
  do_not_retry: true,
491
660
  };
661
+ // Wire transport-side `name: call.name` (Google needs the sanitized
662
+ // form to match the function declaration) while exposing the
663
+ // consumer-facing name in toolExecutions metadata.
492
664
  functionResponses.push({
493
665
  functionResponse: { name: call.name, response: errorOutput },
494
666
  });
495
667
  options?.toolExecutions?.push({
496
- name: call.name,
668
+ name: exposedName,
497
669
  input: call.args,
498
670
  output: errorOutput,
499
671
  });
@@ -514,7 +686,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
514
686
  functionResponse: { name: call.name, response: { result } },
515
687
  });
516
688
  options?.toolExecutions?.push({
517
- name: call.name,
689
+ name: exposedName,
518
690
  input: call.args,
519
691
  output: result,
520
692
  });
@@ -529,12 +701,12 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
529
701
  currentFailInfo.count++;
530
702
  currentFailInfo.lastError = errorMessage;
531
703
  failedTools.set(call.name, currentFailInfo);
532
- logger.warn(`${logLabel} Tool "${call.name}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
704
+ logger.warn(`${logLabel} Tool "${exposedName}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
533
705
  // Determine if this is a permanent failure
534
706
  const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
535
707
  const errorOutput = {
536
708
  error: isPermanentFailure
537
- ? `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${currentFailInfo.count} times with error: ${errorMessage}. This tool will not be retried. Please proceed without using this tool or inform the user that this functionality is unavailable.`
709
+ ? `TOOL_PERMANENTLY_FAILED: The tool "${exposedName}" has failed ${currentFailInfo.count} times with error: ${errorMessage}. This tool will not be retried. Please proceed without using this tool or inform the user that this functionality is unavailable.`
538
710
  : `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
539
711
  status: isPermanentFailure ? "permanently_failed" : "failed",
540
712
  do_not_retry: isPermanentFailure,
@@ -545,7 +717,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
545
717
  functionResponse: { name: call.name, response: errorOutput },
546
718
  });
547
719
  options?.toolExecutions?.push({
548
- name: call.name,
720
+ name: exposedName,
549
721
  input: call.args,
550
722
  output: errorOutput,
551
723
  });
@@ -554,7 +726,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
554
726
  else {
555
727
  // Tool not found is a permanent error
556
728
  const errorOutput = {
557
- error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
729
+ error: `TOOL_NOT_FOUND: The tool "${exposedName}" does not exist. Do not attempt to call this tool again.`,
558
730
  status: "permanently_failed",
559
731
  do_not_retry: true,
560
732
  };
@@ -562,7 +734,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
562
734
  functionResponse: { name: call.name, response: errorOutput },
563
735
  });
564
736
  options?.toolExecutions?.push({
565
- name: call.name,
737
+ name: exposedName,
566
738
  input: call.args,
567
739
  output: errorOutput,
568
740
  });
@@ -597,3 +769,187 @@ export function pushModelResponseToHistory(currentContents, rawResponseParts, st
597
769
  : stepFunctionCalls.map((fc) => ({ functionCall: fc })),
598
770
  });
599
771
  }
772
+ /**
773
+ * Convert a Zod schema (or AI SDK `jsonSchema()` wrapper) into the shape
774
+ * `@google/genai` accepts as `responseSchema`. Mirrors the inline pipeline
775
+ * the Vertex Gemini paths already use:
776
+ *
777
+ * convertZodToJsonSchema → inlineJsonSchema → strip `$schema` → ensure
778
+ * every nested schema has a `type` (Vertex/Gemini reject schemas missing
779
+ * that field, even on nested objects).
780
+ *
781
+ * Lives here so the AI Studio and Vertex paths can share the same
782
+ * sanitization without duplicating the schema-conversion churn.
783
+ */
784
+ export function buildGeminiResponseSchema(schema) {
785
+ const raw = convertZodToJsonSchema(schema, "openApi3");
786
+ const inlined = inlineJsonSchema(raw);
787
+ if (inlined.$schema) {
788
+ delete inlined.$schema;
789
+ }
790
+ return ensureNestedSchemaTypes(inlined);
791
+ }
792
+ /**
793
+ * Map NeuroLink ChatMessage[] history into the @google/genai content format
794
+ * and push the entries onto a contents array.
795
+ *
796
+ * Used by the native Vertex Gemini and Google AI Studio paths to honor
797
+ * `options.conversationMessages` so multi-turn conversations (memory, loop
798
+ * REPL, agent flows) actually carry prior turns into the request.
799
+ *
800
+ * Behavior notes:
801
+ * - Only `user` and `assistant` roles are forwarded; system messages are
802
+ * expected to be wired via `systemInstruction`, and tool-call /
803
+ * tool-result roles only appear inside intra-call tool loops which build
804
+ * their own model/function entries.
805
+ * - String content is wrapped as a single `{ text }` part. Empty strings
806
+ * are skipped to avoid sending empty parts that some Gemini regions
807
+ * reject.
808
+ * - The current user input should be appended AFTER calling this helper
809
+ * so the prior turns appear first in chronological order.
810
+ */
811
+ export function prependConversationMessages(contents, conversationMessages) {
812
+ if (!conversationMessages || conversationMessages.length === 0) {
813
+ return;
814
+ }
815
+ for (const msg of conversationMessages) {
816
+ if (msg.role !== "user" && msg.role !== "assistant") {
817
+ continue;
818
+ }
819
+ const text = typeof msg.content === "string" ? msg.content : "";
820
+ if (text.length === 0) {
821
+ continue;
822
+ }
823
+ contents.push({
824
+ role: msg.role === "assistant" ? "model" : "user",
825
+ parts: [{ text }],
826
+ });
827
+ }
828
+ }
829
+ /**
830
+ * Build the `parts` array for the current user turn of a Gemini native
831
+ * `generateContent` request, including inline image + PDF blobs.
832
+ *
833
+ * Both providers that hit the native `@google/genai` SDK — `GoogleVertex`
834
+ * and `GoogleAIStudio` — need this. The previous AI Studio code only
835
+ * pushed a single `{ text }` part, which silently dropped `input.images`
836
+ * and `input.pdfFiles` on the floor: the model received text only and
837
+ * legitimately reported "no image attached". Extracting this from the
838
+ * Vertex copy keeps both providers on one definition.
839
+ *
840
+ * Accepted shapes per element (mirroring the runtime behaviour the Vertex
841
+ * code already supported):
842
+ * - `Buffer` → used as-is
843
+ * - local file path → read via `readFileSync`, MIME guessed from extension
844
+ * - `data:<mime>;base64,...` URL → mime parsed, data base64-decoded
845
+ * - `http(s)://...` URL → fetched, mime from `content-type`
846
+ * - any other string → assumed to be a base64-encoded payload
847
+ *
848
+ * Image MIME guessing is conservative — only known extensions override the
849
+ * default `image/jpeg`. Fetch failures are logged and the offending entry
850
+ * is skipped rather than aborting the entire request, matching prior
851
+ * Vertex behaviour.
852
+ */
853
+ export async function buildUserPartsWithMultimodal(input, textOverride, logPrefix = "[GeminiNative]") {
854
+ const text = typeof textOverride === "string" ? textOverride : (input?.text ?? "");
855
+ const parts = [{ text }];
856
+ if (input?.pdfFiles && input.pdfFiles.length > 0) {
857
+ logger.debug(`${logPrefix} Processing ${input.pdfFiles.length} PDF(s)`);
858
+ for (const pdfFile of input.pdfFiles) {
859
+ let pdfBuffer;
860
+ if (typeof pdfFile === "string") {
861
+ if (existsSync(pdfFile)) {
862
+ pdfBuffer = readFileSync(pdfFile);
863
+ }
864
+ else {
865
+ // Treat as already-base64-encoded payload
866
+ pdfBuffer = Buffer.from(pdfFile, "base64");
867
+ }
868
+ }
869
+ else {
870
+ pdfBuffer = pdfFile;
871
+ }
872
+ parts.push({
873
+ inlineData: {
874
+ mimeType: "application/pdf",
875
+ data: pdfBuffer.toString("base64"),
876
+ },
877
+ });
878
+ }
879
+ }
880
+ if (input?.images && input.images.length > 0) {
881
+ logger.debug(`${logPrefix} Processing ${input.images.length} image(s)`);
882
+ for (const rawImage of input.images) {
883
+ // `images` may carry plain Buffer/string values or `{ data, altText? }`
884
+ // objects. Normalise to the inner payload before format detection.
885
+ const image = rawImage && typeof rawImage === "object" && !Buffer.isBuffer(rawImage)
886
+ ? rawImage.data
887
+ : rawImage;
888
+ let imageBuffer;
889
+ let mimeType = "image/jpeg";
890
+ if (typeof image === "string") {
891
+ if (existsSync(image)) {
892
+ imageBuffer = readFileSync(image);
893
+ const ext = extname(image).toLowerCase();
894
+ if (ext === ".png") {
895
+ mimeType = "image/png";
896
+ }
897
+ else if (ext === ".gif") {
898
+ mimeType = "image/gif";
899
+ }
900
+ else if (ext === ".webp") {
901
+ mimeType = "image/webp";
902
+ }
903
+ }
904
+ else if (image.startsWith("data:")) {
905
+ const matches = image.match(/^data:([^;]+);base64,(.+)$/);
906
+ if (matches) {
907
+ mimeType = matches[1];
908
+ imageBuffer = Buffer.from(matches[2], "base64");
909
+ }
910
+ else {
911
+ continue;
912
+ }
913
+ }
914
+ else if (image.startsWith("http://") ||
915
+ image.startsWith("https://")) {
916
+ try {
917
+ const response = await fetch(image);
918
+ if (!response.ok) {
919
+ logger.warn(`${logPrefix} Image fetch failed: ${response.status} ${response.statusText}, skipping`, { url: image });
920
+ continue;
921
+ }
922
+ const arrayBuffer = await response.arrayBuffer();
923
+ imageBuffer = Buffer.from(arrayBuffer);
924
+ const headerMime = response.headers.get("content-type");
925
+ if (headerMime && headerMime.startsWith("image/")) {
926
+ mimeType = headerMime.split(";")[0];
927
+ }
928
+ }
929
+ catch (fetchError) {
930
+ logger.warn(`${logPrefix} Image URL fetch threw, skipping: ${fetchError instanceof Error
931
+ ? fetchError.message
932
+ : String(fetchError)}`, { url: image });
933
+ continue;
934
+ }
935
+ }
936
+ else {
937
+ imageBuffer = Buffer.from(image, "base64");
938
+ }
939
+ }
940
+ else {
941
+ imageBuffer = image;
942
+ }
943
+ if (!imageBuffer) {
944
+ continue;
945
+ }
946
+ parts.push({
947
+ inlineData: {
948
+ mimeType,
949
+ data: imageBuffer.toString("base64"),
950
+ },
951
+ });
952
+ }
953
+ }
954
+ return parts;
955
+ }