@juspay/neurolink 9.64.0 → 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 (322) hide show
  1. package/CHANGELOG.md +6 -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 +33 -9
  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 +633 -610
  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 +11 -1
  29. package/dist/cli/factories/commandFactory.js +291 -38
  30. package/dist/constants/contextWindows.js +101 -0
  31. package/dist/constants/enums.d.ts +273 -2
  32. package/dist/constants/enums.js +290 -1
  33. package/dist/constants/videoErrors.d.ts +4 -0
  34. package/dist/constants/videoErrors.js +4 -0
  35. package/dist/core/baseProvider.d.ts +22 -2
  36. package/dist/core/baseProvider.js +217 -11
  37. package/dist/core/constants.d.ts +11 -0
  38. package/dist/core/constants.js +69 -1
  39. package/dist/core/redisConversationMemoryManager.js +6 -0
  40. package/dist/evaluation/index.d.ts +2 -0
  41. package/dist/evaluation/index.js +4 -0
  42. package/dist/factories/providerFactory.js +7 -1
  43. package/dist/factories/providerRegistry.js +202 -5
  44. package/dist/features/ppt/contentPlanner.js +42 -14
  45. package/dist/index.d.ts +9 -1
  46. package/dist/index.js +16 -1
  47. package/dist/lib/adapters/providerImageAdapter.js +29 -1
  48. package/dist/lib/adapters/replicate/auth.d.ts +19 -0
  49. package/dist/lib/adapters/replicate/auth.js +33 -0
  50. package/dist/lib/adapters/replicate/predictionLifecycle.d.ts +46 -0
  51. package/dist/lib/adapters/replicate/predictionLifecycle.js +284 -0
  52. package/dist/lib/adapters/video/klingVideoHandler.d.ts +37 -0
  53. package/dist/lib/adapters/video/klingVideoHandler.js +306 -0
  54. package/dist/lib/adapters/video/replicateVideoHandler.d.ts +29 -0
  55. package/dist/lib/adapters/video/replicateVideoHandler.js +158 -0
  56. package/dist/lib/adapters/video/runwayVideoHandler.d.ts +32 -0
  57. package/dist/lib/adapters/video/runwayVideoHandler.js +317 -0
  58. package/dist/lib/adapters/video/vertexVideoHandler.d.ts +19 -1
  59. package/dist/lib/adapters/video/vertexVideoHandler.js +33 -9
  60. package/dist/lib/autoresearch/runner.js +8 -2
  61. package/dist/lib/avatar/index.d.ts +13 -0
  62. package/dist/lib/avatar/index.js +14 -0
  63. package/dist/lib/avatar/providers/DIDAvatar.d.ts +49 -0
  64. package/dist/lib/avatar/providers/DIDAvatar.js +502 -0
  65. package/dist/lib/avatar/providers/HeyGenAvatar.d.ts +30 -0
  66. package/dist/lib/avatar/providers/HeyGenAvatar.js +338 -0
  67. package/dist/lib/avatar/providers/ReplicateAvatar.d.ts +36 -0
  68. package/dist/lib/avatar/providers/ReplicateAvatar.js +268 -0
  69. package/dist/lib/constants/contextWindows.js +101 -0
  70. package/dist/lib/constants/enums.d.ts +273 -2
  71. package/dist/lib/constants/enums.js +290 -1
  72. package/dist/lib/constants/videoErrors.d.ts +4 -0
  73. package/dist/lib/constants/videoErrors.js +4 -0
  74. package/dist/lib/core/baseProvider.d.ts +22 -2
  75. package/dist/lib/core/baseProvider.js +217 -11
  76. package/dist/lib/core/constants.d.ts +11 -0
  77. package/dist/lib/core/constants.js +69 -1
  78. package/dist/lib/core/redisConversationMemoryManager.js +6 -0
  79. package/dist/lib/evaluation/index.d.ts +2 -0
  80. package/dist/lib/evaluation/index.js +4 -0
  81. package/dist/lib/factories/providerFactory.js +7 -1
  82. package/dist/lib/factories/providerRegistry.js +202 -5
  83. package/dist/lib/features/ppt/contentPlanner.js +42 -14
  84. package/dist/lib/index.d.ts +9 -1
  85. package/dist/lib/index.js +16 -1
  86. package/dist/lib/middleware/builtin/lifecycle.js +39 -9
  87. package/dist/lib/music/index.d.ts +13 -0
  88. package/dist/lib/music/index.js +14 -0
  89. package/dist/lib/music/providers/BeatovenMusic.d.ts +31 -0
  90. package/dist/lib/music/providers/BeatovenMusic.js +334 -0
  91. package/dist/lib/music/providers/ElevenLabsMusic.d.ts +30 -0
  92. package/dist/lib/music/providers/ElevenLabsMusic.js +169 -0
  93. package/dist/lib/music/providers/LyriaMusic.d.ts +29 -0
  94. package/dist/lib/music/providers/LyriaMusic.js +173 -0
  95. package/dist/lib/music/providers/ReplicateMusic.d.ts +31 -0
  96. package/dist/lib/music/providers/ReplicateMusic.js +262 -0
  97. package/dist/lib/neurolink.d.ts +30 -0
  98. package/dist/lib/neurolink.js +323 -77
  99. package/dist/lib/providers/amazonBedrock.d.ts +10 -0
  100. package/dist/lib/providers/amazonBedrock.js +94 -39
  101. package/dist/lib/providers/anthropic.js +55 -7
  102. package/dist/lib/providers/anthropicBaseProvider.js +1 -1
  103. package/dist/lib/providers/azureOpenai.js +66 -17
  104. package/dist/lib/providers/cloudflare.d.ts +35 -0
  105. package/dist/lib/providers/cloudflare.js +174 -0
  106. package/dist/lib/providers/cohere.d.ts +52 -0
  107. package/dist/lib/providers/cohere.js +253 -0
  108. package/dist/lib/providers/deepseek.js +72 -17
  109. package/dist/lib/providers/fireworks.d.ts +33 -0
  110. package/dist/lib/providers/fireworks.js +164 -0
  111. package/dist/lib/providers/googleAiStudio.js +45 -6
  112. package/dist/lib/providers/googleNativeGemini3.d.ts +24 -1
  113. package/dist/lib/providers/googleNativeGemini3.js +173 -21
  114. package/dist/lib/providers/googleVertex.js +173 -17
  115. package/dist/lib/providers/groq.d.ts +33 -0
  116. package/dist/lib/providers/groq.js +181 -0
  117. package/dist/lib/providers/huggingFace.js +9 -8
  118. package/dist/lib/providers/ideogram.d.ts +34 -0
  119. package/dist/lib/providers/ideogram.js +184 -0
  120. package/dist/lib/providers/index.d.ts +13 -0
  121. package/dist/lib/providers/index.js +13 -0
  122. package/dist/lib/providers/jina.d.ts +59 -0
  123. package/dist/lib/providers/jina.js +218 -0
  124. package/dist/lib/providers/llamaCpp.js +14 -46
  125. package/dist/lib/providers/lmStudio.js +14 -47
  126. package/dist/lib/providers/mistral.js +7 -7
  127. package/dist/lib/providers/nvidiaNim.js +160 -19
  128. package/dist/lib/providers/ollama.js +7 -7
  129. package/dist/lib/providers/openAI.d.ts +22 -1
  130. package/dist/lib/providers/openAI.js +181 -0
  131. package/dist/lib/providers/openRouter.js +35 -23
  132. package/dist/lib/providers/openaiCompatible.js +9 -8
  133. package/dist/lib/providers/perplexity.d.ts +33 -0
  134. package/dist/lib/providers/perplexity.js +179 -0
  135. package/dist/lib/providers/recraft.d.ts +34 -0
  136. package/dist/lib/providers/recraft.js +197 -0
  137. package/dist/lib/providers/replicate.d.ts +75 -0
  138. package/dist/lib/providers/replicate.js +403 -0
  139. package/dist/lib/providers/stability.d.ts +37 -0
  140. package/dist/lib/providers/stability.js +191 -0
  141. package/dist/lib/providers/togetherAi.d.ts +33 -0
  142. package/dist/lib/providers/togetherAi.js +176 -0
  143. package/dist/lib/providers/voyage.d.ts +47 -0
  144. package/dist/lib/providers/voyage.js +177 -0
  145. package/dist/lib/providers/xai.d.ts +33 -0
  146. package/dist/lib/providers/xai.js +172 -0
  147. package/dist/lib/telemetry/index.d.ts +1 -1
  148. package/dist/lib/telemetry/index.js +1 -1
  149. package/dist/lib/telemetry/tracers.d.ts +19 -0
  150. package/dist/lib/telemetry/tracers.js +19 -0
  151. package/dist/lib/telemetry/withSpan.d.ts +35 -0
  152. package/dist/lib/telemetry/withSpan.js +103 -0
  153. package/dist/lib/types/avatar.d.ts +143 -0
  154. package/dist/lib/types/avatar.js +20 -0
  155. package/dist/lib/types/cli.d.ts +6 -0
  156. package/dist/lib/types/generate.d.ts +62 -5
  157. package/dist/lib/types/index.d.ts +5 -0
  158. package/dist/lib/types/index.js +7 -0
  159. package/dist/lib/types/middleware.d.ts +27 -0
  160. package/dist/lib/types/multimodal.d.ts +35 -2
  161. package/dist/lib/types/music.d.ts +165 -0
  162. package/dist/lib/types/music.js +21 -0
  163. package/dist/lib/types/providers.d.ts +144 -1
  164. package/dist/lib/types/replicate.d.ts +67 -0
  165. package/dist/lib/types/replicate.js +10 -0
  166. package/dist/lib/types/safeFetch.d.ts +15 -0
  167. package/dist/lib/types/safeFetch.js +7 -0
  168. package/dist/lib/types/stream.d.ts +2 -1
  169. package/dist/lib/types/tools.d.ts +13 -0
  170. package/dist/lib/types/video.d.ts +89 -0
  171. package/dist/lib/types/video.js +15 -0
  172. package/dist/lib/utils/avatarProcessor.d.ts +68 -0
  173. package/dist/lib/utils/avatarProcessor.js +172 -0
  174. package/dist/lib/utils/cloneOptions.d.ts +36 -0
  175. package/dist/lib/utils/cloneOptions.js +62 -0
  176. package/dist/lib/utils/lifecycleCallbacks.d.ts +51 -8
  177. package/dist/lib/utils/lifecycleCallbacks.js +82 -26
  178. package/dist/lib/utils/lifecycleTimeout.d.ts +25 -0
  179. package/dist/lib/utils/lifecycleTimeout.js +39 -0
  180. package/dist/lib/utils/logSanitize.d.ts +49 -0
  181. package/dist/lib/utils/logSanitize.js +170 -0
  182. package/dist/lib/utils/loggingFetch.d.ts +29 -0
  183. package/dist/lib/utils/loggingFetch.js +60 -0
  184. package/dist/lib/utils/messageBuilder.js +43 -25
  185. package/dist/lib/utils/modelChoices.js +236 -3
  186. package/dist/lib/utils/musicProcessor.d.ts +67 -0
  187. package/dist/lib/utils/musicProcessor.js +189 -0
  188. package/dist/lib/utils/optionsConversion.js +3 -2
  189. package/dist/lib/utils/parameterValidation.js +14 -4
  190. package/dist/lib/utils/pricing.js +193 -0
  191. package/dist/lib/utils/providerConfig.d.ts +55 -0
  192. package/dist/lib/utils/providerConfig.js +224 -0
  193. package/dist/lib/utils/safeFetch.d.ts +26 -0
  194. package/dist/lib/utils/safeFetch.js +83 -0
  195. package/dist/lib/utils/sizeGuard.d.ts +34 -0
  196. package/dist/lib/utils/sizeGuard.js +45 -0
  197. package/dist/lib/utils/ssrfGuard.d.ts +52 -0
  198. package/dist/lib/utils/ssrfGuard.js +411 -0
  199. package/dist/lib/utils/videoProcessor.d.ts +60 -0
  200. package/dist/lib/utils/videoProcessor.js +201 -0
  201. package/dist/lib/voice/providers/FishAudioTTS.d.ts +27 -0
  202. package/dist/lib/voice/providers/FishAudioTTS.js +183 -0
  203. package/dist/lib/workflow/core/ensembleExecutor.js +26 -9
  204. package/dist/middleware/builtin/lifecycle.js +39 -9
  205. package/dist/music/index.d.ts +13 -0
  206. package/dist/music/index.js +13 -0
  207. package/dist/music/providers/BeatovenMusic.d.ts +31 -0
  208. package/dist/music/providers/BeatovenMusic.js +333 -0
  209. package/dist/music/providers/ElevenLabsMusic.d.ts +30 -0
  210. package/dist/music/providers/ElevenLabsMusic.js +168 -0
  211. package/dist/music/providers/LyriaMusic.d.ts +29 -0
  212. package/dist/music/providers/LyriaMusic.js +172 -0
  213. package/dist/music/providers/ReplicateMusic.d.ts +31 -0
  214. package/dist/music/providers/ReplicateMusic.js +261 -0
  215. package/dist/neurolink.d.ts +30 -0
  216. package/dist/neurolink.js +323 -77
  217. package/dist/providers/amazonBedrock.d.ts +10 -0
  218. package/dist/providers/amazonBedrock.js +94 -39
  219. package/dist/providers/anthropic.js +55 -7
  220. package/dist/providers/anthropicBaseProvider.js +1 -1
  221. package/dist/providers/azureOpenai.js +66 -17
  222. package/dist/providers/cloudflare.d.ts +35 -0
  223. package/dist/providers/cloudflare.js +173 -0
  224. package/dist/providers/cohere.d.ts +52 -0
  225. package/dist/providers/cohere.js +252 -0
  226. package/dist/providers/deepseek.js +72 -17
  227. package/dist/providers/fireworks.d.ts +33 -0
  228. package/dist/providers/fireworks.js +163 -0
  229. package/dist/providers/googleAiStudio.js +45 -6
  230. package/dist/providers/googleNativeGemini3.d.ts +24 -1
  231. package/dist/providers/googleNativeGemini3.js +173 -21
  232. package/dist/providers/googleVertex.js +173 -17
  233. package/dist/providers/groq.d.ts +33 -0
  234. package/dist/providers/groq.js +180 -0
  235. package/dist/providers/huggingFace.js +9 -8
  236. package/dist/providers/ideogram.d.ts +34 -0
  237. package/dist/providers/ideogram.js +183 -0
  238. package/dist/providers/index.d.ts +13 -0
  239. package/dist/providers/index.js +13 -0
  240. package/dist/providers/jina.d.ts +59 -0
  241. package/dist/providers/jina.js +217 -0
  242. package/dist/providers/llamaCpp.js +14 -46
  243. package/dist/providers/lmStudio.js +14 -47
  244. package/dist/providers/mistral.js +7 -7
  245. package/dist/providers/nvidiaNim.js +160 -19
  246. package/dist/providers/ollama.js +7 -7
  247. package/dist/providers/openAI.d.ts +22 -1
  248. package/dist/providers/openAI.js +181 -0
  249. package/dist/providers/openRouter.js +35 -23
  250. package/dist/providers/openaiCompatible.js +9 -8
  251. package/dist/providers/perplexity.d.ts +33 -0
  252. package/dist/providers/perplexity.js +178 -0
  253. package/dist/providers/recraft.d.ts +34 -0
  254. package/dist/providers/recraft.js +196 -0
  255. package/dist/providers/replicate.d.ts +75 -0
  256. package/dist/providers/replicate.js +402 -0
  257. package/dist/providers/stability.d.ts +37 -0
  258. package/dist/providers/stability.js +190 -0
  259. package/dist/providers/togetherAi.d.ts +33 -0
  260. package/dist/providers/togetherAi.js +175 -0
  261. package/dist/providers/voyage.d.ts +47 -0
  262. package/dist/providers/voyage.js +176 -0
  263. package/dist/providers/xai.d.ts +33 -0
  264. package/dist/providers/xai.js +171 -0
  265. package/dist/telemetry/index.d.ts +1 -1
  266. package/dist/telemetry/index.js +1 -1
  267. package/dist/telemetry/tracers.d.ts +19 -0
  268. package/dist/telemetry/tracers.js +19 -0
  269. package/dist/telemetry/withSpan.d.ts +35 -0
  270. package/dist/telemetry/withSpan.js +103 -0
  271. package/dist/types/avatar.d.ts +143 -0
  272. package/dist/types/avatar.js +19 -0
  273. package/dist/types/cli.d.ts +6 -0
  274. package/dist/types/generate.d.ts +62 -5
  275. package/dist/types/index.d.ts +5 -0
  276. package/dist/types/index.js +7 -0
  277. package/dist/types/middleware.d.ts +27 -0
  278. package/dist/types/multimodal.d.ts +35 -2
  279. package/dist/types/music.d.ts +165 -0
  280. package/dist/types/music.js +20 -0
  281. package/dist/types/providers.d.ts +144 -1
  282. package/dist/types/replicate.d.ts +67 -0
  283. package/dist/types/replicate.js +9 -0
  284. package/dist/types/safeFetch.d.ts +15 -0
  285. package/dist/types/safeFetch.js +6 -0
  286. package/dist/types/stream.d.ts +2 -1
  287. package/dist/types/tools.d.ts +13 -0
  288. package/dist/types/video.d.ts +89 -0
  289. package/dist/types/video.js +14 -0
  290. package/dist/utils/avatarProcessor.d.ts +68 -0
  291. package/dist/utils/avatarProcessor.js +171 -0
  292. package/dist/utils/cloneOptions.d.ts +36 -0
  293. package/dist/utils/cloneOptions.js +61 -0
  294. package/dist/utils/lifecycleCallbacks.d.ts +51 -8
  295. package/dist/utils/lifecycleCallbacks.js +82 -26
  296. package/dist/utils/lifecycleTimeout.d.ts +25 -0
  297. package/dist/utils/lifecycleTimeout.js +38 -0
  298. package/dist/utils/logSanitize.d.ts +49 -0
  299. package/dist/utils/logSanitize.js +169 -0
  300. package/dist/utils/loggingFetch.d.ts +29 -0
  301. package/dist/utils/loggingFetch.js +59 -0
  302. package/dist/utils/messageBuilder.js +43 -25
  303. package/dist/utils/modelChoices.js +236 -3
  304. package/dist/utils/musicProcessor.d.ts +67 -0
  305. package/dist/utils/musicProcessor.js +188 -0
  306. package/dist/utils/optionsConversion.js +3 -2
  307. package/dist/utils/parameterValidation.js +14 -4
  308. package/dist/utils/pricing.js +193 -0
  309. package/dist/utils/providerConfig.d.ts +55 -0
  310. package/dist/utils/providerConfig.js +224 -0
  311. package/dist/utils/safeFetch.d.ts +26 -0
  312. package/dist/utils/safeFetch.js +82 -0
  313. package/dist/utils/sizeGuard.d.ts +34 -0
  314. package/dist/utils/sizeGuard.js +44 -0
  315. package/dist/utils/ssrfGuard.d.ts +52 -0
  316. package/dist/utils/ssrfGuard.js +410 -0
  317. package/dist/utils/videoProcessor.d.ts +60 -0
  318. package/dist/utils/videoProcessor.js +200 -0
  319. package/dist/voice/providers/FishAudioTTS.d.ts +27 -0
  320. package/dist/voice/providers/FishAudioTTS.js +182 -0
  321. package/dist/workflow/core/ensembleExecutor.js +26 -9
  322. package/package.json +32 -5
@@ -17,6 +17,61 @@ import { logger } from "../utils/logger.js";
17
17
  import { convertZodToJsonSchema, ensureNestedSchemaTypes, inlineJsonSchema, isZodSchema, normalizeJsonSchemaObject, } from "../utils/schemaConversion.js";
18
18
  import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
19
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
+ }
20
75
  /**
21
76
  * Sanitize a JSON Schema for Gemini's proto-based API.
22
77
  *
@@ -102,6 +157,40 @@ export function sanitizeSchemaForGemini(schema) {
102
157
  result[branch] = sanitizeSchemaForGemini(result[branch]);
103
158
  }
104
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
+ }
105
194
  return result;
106
195
  }
107
196
  /**
@@ -117,8 +206,27 @@ export function sanitizeSchemaForGemini(schema) {
117
206
  export function sanitizeToolsForGemini(tools) {
118
207
  const sanitized = {};
119
208
  const dropped = [];
209
+ const renamed = [];
210
+ const originalNameMap = new Map();
120
211
  for (const [name, tool] of Object.entries(tools)) {
121
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
+ }
122
230
  // Access the legacy `parameters` field that may exist on older AI SDK tools.
123
231
  // AI SDK v6 uses `inputSchema`, but v3/v4 tools and third-party wrappers use `parameters`.
124
232
  const legacyTool = tool;
@@ -134,8 +242,8 @@ export function sanitizeToolsForGemini(tools) {
134
242
  // additionalProperties are removed. The resulting schema is Gemini-compatible
135
243
  // but loses some type constraints from the original Zod schema.
136
244
  const sanitizedSchema = sanitizeSchemaForGemini(inlined);
137
- sanitized[name] = createAISDKTool({
138
- description: tool.description || `Tool: ${name}`,
245
+ sanitized[safeName] = createAISDKTool({
246
+ description: tool.description || `Tool: ${safeName}`,
139
247
  inputSchema: aiJsonSchema(sanitizedSchema),
140
248
  execute: tool.execute,
141
249
  });
@@ -147,14 +255,14 @@ export function sanitizeToolsForGemini(tools) {
147
255
  const rawSchema = params
148
256
  .jsonSchema;
149
257
  const sanitizedSchema = sanitizeSchemaForGemini(inlineJsonSchema(rawSchema));
150
- sanitized[name] = createAISDKTool({
151
- description: tool.description || `Tool: ${name}`,
258
+ sanitized[safeName] = createAISDKTool({
259
+ description: tool.description || `Tool: ${safeName}`,
152
260
  inputSchema: aiJsonSchema(sanitizedSchema),
153
261
  execute: tool.execute,
154
262
  });
155
263
  }
156
264
  else {
157
- sanitized[name] = tool;
265
+ sanitized[safeName] = tool;
158
266
  }
159
267
  }
160
268
  catch (error) {
@@ -163,7 +271,12 @@ export function sanitizeToolsForGemini(tools) {
163
271
  dropped.push(name);
164
272
  }
165
273
  }
166
- 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 };
167
280
  }
168
281
  export function normalizeToolsForJsonSchemaProvider(tools) {
169
282
  const normalizedTools = {};
@@ -212,11 +325,29 @@ export function buildNativeToolDeclarations(tools) {
212
325
  const functionDeclarations = [];
213
326
  const executeMap = new Map();
214
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();
215
340
  for (const [name, tool] of Object.entries(tools)) {
216
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
+ }
217
348
  const decl = {
218
- name,
219
- description: tool.description || `Tool: ${name}`,
349
+ name: safeName,
350
+ description: tool.description || `Tool: ${safeName}`,
220
351
  };
221
352
  // Access legacy `parameters` (AI SDK v3/v4) or current `inputSchema` (v6)
222
353
  const legacyTool = tool;
@@ -241,8 +372,9 @@ export function buildNativeToolDeclarations(tools) {
241
372
  decl.parametersJsonSchema = sanitizeSchemaForGemini(inlineJsonSchema(rawSchema));
242
373
  }
243
374
  functionDeclarations.push(decl);
375
+ usedNames.add(safeName);
244
376
  if (tool.execute) {
245
- executeMap.set(name, tool.execute);
377
+ executeMap.set(decl.name, tool.execute);
246
378
  }
247
379
  }
248
380
  catch (err) {
@@ -253,7 +385,16 @@ export function buildNativeToolDeclarations(tools) {
253
385
  if (skippedTools.length > 0) {
254
386
  logger.warn(`[buildNativeToolDeclarations] ${skippedTools.length} tool(s) skipped due to schema errors: ${skippedTools.join(", ")}`);
255
387
  }
256
- 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
+ };
257
398
  }
258
399
  /**
259
400
  * Build the native @google/genai config object shared by stream and generate.
@@ -493,27 +634,38 @@ export function extractTextFromParts(rawResponseParts) {
493
634
  * @param executeMap - Map of tool name to execute function
494
635
  * @param failedTools - Mutable map tracking per-tool failure counts
495
636
  * @param allToolCalls - Mutable array accumulating all tool call records
496
- * @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.
497
641
  * @returns Array of function responses for conversation history
498
642
  */
499
643
  export async function executeNativeToolCalls(logLabel, stepFunctionCalls, executeMap, failedTools, allToolCalls, options) {
500
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;
501
649
  for (const call of stepFunctionCalls) {
502
- allToolCalls.push({ toolName: call.name, args: call.args });
650
+ const exposedName = externalName(call.name);
651
+ allToolCalls.push({ toolName: exposedName, args: call.args });
503
652
  // Check if this tool has already exceeded retry limit
504
653
  const failedInfo = failedTools.get(call.name);
505
654
  if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
506
- 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`);
507
656
  const errorOutput = {
508
- 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.`,
509
658
  status: "permanently_failed",
510
659
  do_not_retry: true,
511
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.
512
664
  functionResponses.push({
513
665
  functionResponse: { name: call.name, response: errorOutput },
514
666
  });
515
667
  options?.toolExecutions?.push({
516
- name: call.name,
668
+ name: exposedName,
517
669
  input: call.args,
518
670
  output: errorOutput,
519
671
  });
@@ -534,7 +686,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
534
686
  functionResponse: { name: call.name, response: { result } },
535
687
  });
536
688
  options?.toolExecutions?.push({
537
- name: call.name,
689
+ name: exposedName,
538
690
  input: call.args,
539
691
  output: result,
540
692
  });
@@ -549,12 +701,12 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
549
701
  currentFailInfo.count++;
550
702
  currentFailInfo.lastError = errorMessage;
551
703
  failedTools.set(call.name, currentFailInfo);
552
- 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}`);
553
705
  // Determine if this is a permanent failure
554
706
  const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
555
707
  const errorOutput = {
556
708
  error: isPermanentFailure
557
- ? `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.`
558
710
  : `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
559
711
  status: isPermanentFailure ? "permanently_failed" : "failed",
560
712
  do_not_retry: isPermanentFailure,
@@ -565,7 +717,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
565
717
  functionResponse: { name: call.name, response: errorOutput },
566
718
  });
567
719
  options?.toolExecutions?.push({
568
- name: call.name,
720
+ name: exposedName,
569
721
  input: call.args,
570
722
  output: errorOutput,
571
723
  });
@@ -574,7 +726,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
574
726
  else {
575
727
  // Tool not found is a permanent error
576
728
  const errorOutput = {
577
- 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.`,
578
730
  status: "permanently_failed",
579
731
  do_not_retry: true,
580
732
  };
@@ -582,7 +734,7 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
582
734
  functionResponse: { name: call.name, response: errorOutput },
583
735
  });
584
736
  options?.toolExecutions?.push({
585
- name: call.name,
737
+ name: exposedName,
586
738
  input: call.args,
587
739
  output: errorOutput,
588
740
  });
@@ -19,7 +19,7 @@ import { convertZodToJsonSchema, inlineJsonSchema, ensureNestedSchemaTypes, } fr
19
19
  import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
20
20
  import { TimeoutError } from "../utils/async/index.js";
21
21
  import { prependConversationMessages } from "./googleNativeGemini3.js";
22
- import { ATTR, tracers, withClientSpan, withSpan } from "../telemetry/index.js";
22
+ import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "../telemetry/index.js";
23
23
  import { calculateCost } from "../utils/pricing.js";
24
24
  // Import proper types for multimodal message handling
25
25
  // Dynamic import helper for native Anthropic Vertex SDK
@@ -36,6 +36,110 @@ const hasAnthropicSupport = () => {
36
36
  // Actual availability is checked at runtime when creating the client
37
37
  return true;
38
38
  };
39
+ /**
40
+ * Recursively strip JSON-schema fields that Vertex Gemini's function-call
41
+ * validator rejects with 400 INVALID_ARGUMENT. Vertex implements OpenAPI 3.0
42
+ * Schema strictly and rejects extension fields that the broader JSON Schema
43
+ * spec allows. The fields stripped here have no semantic meaning for the
44
+ * model, so removing them is safe for every caller.
45
+ *
46
+ * Fields removed:
47
+ * - `additionalProperties` — extension; Vertex rejects on any nested object.
48
+ * - `default` — Vertex rejects defaults on object/array-typed properties and
49
+ * on properties that are also marked `required`. Safest to strip globally
50
+ * because the model never inspects them.
51
+ * - `$schema`, `$id`, `$ref`, `definitions`, `$defs` — JSON-Schema-meta
52
+ * fields that Vertex doesn't recognise.
53
+ * - `examples` — accepted by some Gemini variants but not 2.5-flash; strip
54
+ * to avoid the model rejecting tool schemas under that path.
55
+ */
56
+ function stripAdditionalPropertiesDeep(schema) {
57
+ if (!schema || typeof schema !== "object") {
58
+ return;
59
+ }
60
+ const FIELDS_TO_STRIP = [
61
+ "additionalProperties",
62
+ "default",
63
+ "$schema",
64
+ "$id",
65
+ "$ref",
66
+ "definitions",
67
+ "$defs",
68
+ "examples",
69
+ ];
70
+ for (const field of FIELDS_TO_STRIP) {
71
+ if (field in schema) {
72
+ delete schema[field];
73
+ }
74
+ }
75
+ // JSON Schema Draft-4 `exclusiveMinimum: true` / `exclusiveMaximum: true`
76
+ // (boolean form) is rejected by Vertex's OpenAPI 3.0 validator, which
77
+ // expects a numeric bound. zod-to-json-schema's openApi3 target still
78
+ // emits the Draft-4 form for `z.number().positive()` etc. Translate the
79
+ // boolean form into the numeric form when paired with `minimum` /
80
+ // `maximum`; otherwise drop it (the model doesn't validate, so the
81
+ // constraint is informational only).
82
+ if (typeof schema.exclusiveMinimum === "boolean") {
83
+ if (schema.exclusiveMinimum === true &&
84
+ typeof schema.minimum === "number") {
85
+ schema.exclusiveMinimum = schema.minimum;
86
+ delete schema.minimum;
87
+ }
88
+ else {
89
+ delete schema.exclusiveMinimum;
90
+ }
91
+ }
92
+ if (typeof schema.exclusiveMaximum === "boolean") {
93
+ if (schema.exclusiveMaximum === true &&
94
+ typeof schema.maximum === "number") {
95
+ schema.exclusiveMaximum = schema.maximum;
96
+ delete schema.maximum;
97
+ }
98
+ else {
99
+ delete schema.exclusiveMaximum;
100
+ }
101
+ }
102
+ // Strip `maximum` values that exceed int32 range — Vertex's protobuf
103
+ // serializer treats `type: "integer"` as int32 and rejects bounds beyond
104
+ // 2^31. zod's `.positive().int()` emits Number.MAX_SAFE_INTEGER as the
105
+ // upper bound (8.9e15), which trips this. The constraint is informational
106
+ // for the model anyway, so dropping it is safe.
107
+ const INT32_MAX = 2147483647;
108
+ if (typeof schema.maximum === "number" && schema.maximum > INT32_MAX) {
109
+ delete schema.maximum;
110
+ }
111
+ if (typeof schema.minimum === "number" && schema.minimum < -INT32_MAX) {
112
+ delete schema.minimum;
113
+ }
114
+ if (schema.properties && typeof schema.properties === "object") {
115
+ for (const child of Object.values(schema.properties)) {
116
+ if (child && typeof child === "object") {
117
+ stripAdditionalPropertiesDeep(child);
118
+ }
119
+ }
120
+ }
121
+ if (schema.items && typeof schema.items === "object") {
122
+ if (Array.isArray(schema.items)) {
123
+ for (const item of schema.items) {
124
+ if (item && typeof item === "object") {
125
+ stripAdditionalPropertiesDeep(item);
126
+ }
127
+ }
128
+ }
129
+ else {
130
+ stripAdditionalPropertiesDeep(schema.items);
131
+ }
132
+ }
133
+ for (const key of ["allOf", "anyOf", "oneOf"]) {
134
+ if (Array.isArray(schema[key])) {
135
+ for (const branch of schema[key]) {
136
+ if (branch && typeof branch === "object") {
137
+ stripAdditionalPropertiesDeep(branch);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
39
143
  // Configuration helpers - now using consolidated utility
40
144
  const getVertexProjectId = () => {
41
145
  return validateApiKey(createVertexProjectConfig());
@@ -605,7 +709,7 @@ export class GoogleVertexProvider extends BaseProvider {
605
709
  // the test:tracing observability harness sees the same span hierarchy
606
710
  // it sees for AI Studio. BaseProvider.stream does NOT emit this span
607
711
  // for any provider — each native provider has to add it itself.
608
- return withClientSpan({
712
+ return withClientStreamSpan({
609
713
  name: "neurolink.provider.stream",
610
714
  tracer: tracers.provider,
611
715
  attributes: {
@@ -672,7 +776,7 @@ export class GoogleVertexProvider extends BaseProvider {
672
776
  this.emitStreamEnd(modelName, streamStartTime, false, error);
673
777
  throw error;
674
778
  }
675
- });
779
+ }, (r) => r.stream, (r, wrapped) => ({ ...r, stream: wrapped }));
676
780
  }
677
781
  /**
678
782
  * Emit `stream:end` so the Pipeline B observability listener creates a
@@ -747,8 +851,11 @@ export class GoogleVertexProvider extends BaseProvider {
747
851
  });
748
852
  // Build contents from input with multimodal support
749
853
  const contents = [];
750
- // Build user message parts - start with text
751
- const userParts = [{ text: options.input.text }];
854
+ // Build user message parts - start with text.
855
+ // `options.input.text` is `string | undefined` in strict mode; the
856
+ // VertexNativePart `text` field requires `string`, so coerce to "" if
857
+ // unset (the multimodal-only path still appends other parts below).
858
+ const userParts = [{ text: options.input.text ?? "" }];
752
859
  // Add PDF files as inlineData parts if present
753
860
  // Cast input to access multimodal properties that may exist at runtime
754
861
  const multimodalInput = options.input;
@@ -887,6 +994,12 @@ export class GoogleVertexProvider extends BaseProvider {
887
994
  // ensureNestedSchemaTypes recursively adds missing type fields to tool schemas
888
995
  // Note: convertZodToJsonSchema now uses openApi3 target which produces nullable: true
889
996
  const typedSchema = ensureNestedSchemaTypes(inlinedSchema);
997
+ // Strip `additionalProperties` recursively — Vertex Gemini's
998
+ // function-call validator rejects it on object schemas (returns
999
+ // 400 INVALID_ARGUMENT) even though it's valid OpenAPI 3. The
1000
+ // field has no semantic meaning to the model, so dropping it
1001
+ // before send is safe for every caller.
1002
+ stripAdditionalPropertiesDeep(typedSchema);
890
1003
  decl.parametersJsonSchema = typedSchema;
891
1004
  }
892
1005
  functionDeclarations.push(decl);
@@ -1427,6 +1540,12 @@ export class GoogleVertexProvider extends BaseProvider {
1427
1540
  // ensureNestedSchemaTypes recursively adds missing type fields to tool schemas
1428
1541
  // Note: convertZodToJsonSchema now uses openApi3 target which produces nullable: true
1429
1542
  const typedSchema = ensureNestedSchemaTypes(inlinedSchema);
1543
+ // Strip `additionalProperties` recursively — Vertex Gemini's
1544
+ // function-call validator rejects it on object schemas (returns
1545
+ // 400 INVALID_ARGUMENT) even though it's valid OpenAPI 3. The
1546
+ // field has no semantic meaning to the model, so dropping it
1547
+ // before send is safe for every caller.
1548
+ stripAdditionalPropertiesDeep(typedSchema);
1430
1549
  decl.parametersJsonSchema = typedSchema;
1431
1550
  }
1432
1551
  functionDeclarations.push(decl);
@@ -2737,6 +2856,17 @@ export class GoogleVertexProvider extends BaseProvider {
2737
2856
  this.emitGenerationEnd(modelName, videoResult, generateStartTime, true);
2738
2857
  return videoResult;
2739
2858
  }
2859
+ // TTS direct-synthesis mode: when caller passes `tts.enabled` without
2860
+ // `tts.useAiResponse`, route to the shared `handleDirectTTSSynthesis`
2861
+ // (synthesise the input text directly; no LLM call). BaseProvider's
2862
+ // standard generate() does the same dispatch — we replicate it here
2863
+ // because Vertex's override bypasses that path.
2864
+ if (options.tts?.enabled && !options.tts?.useAiResponse) {
2865
+ logger.info("[GoogleVertex] Routing TTS direct-synthesis to handleDirectTTSSynthesis", { model: modelName });
2866
+ const ttsResult = await this.handleDirectTTSSynthesis(options, generateStartTime);
2867
+ this.emitGenerationEnd(modelName, ttsResult, generateStartTime, true);
2868
+ return ttsResult;
2869
+ }
2740
2870
  // Check if this is an image generation model - route to executeImageGeneration without tools
2741
2871
  const isImageModel = IMAGE_GENERATION_MODELS.some((m) => modelName.toLowerCase().startsWith(m.toLowerCase()));
2742
2872
  if (isImageModel) {
@@ -2797,23 +2927,41 @@ export class GoogleVertexProvider extends BaseProvider {
2797
2927
  "";
2798
2928
  try {
2799
2929
  let result;
2800
- // Route Claude models to native Anthropic SDK
2801
- if (isAnthropicModel(modelName)) {
2802
- logger.info("[GoogleVertex] Routing Claude generate to native @anthropic-ai/vertex-sdk", {
2803
- model: modelName,
2804
- totalToolCount: Object.keys(mergedOptions.tools).length,
2805
- });
2806
- result = await this.executeNativeAnthropicGenerate(mergedOptions);
2807
- }
2808
- else {
2809
- // ALL Gemini models use native @google/genai SDK
2930
+ // Wrap the actual native generate call in `neurolink.executeGeneration`
2931
+ // so the observability span chain (tested by
2932
+ // "Tracing: Generate Span Chain") sees a third inner span on the
2933
+ // native @google/genai / @anthropic-ai/vertex-sdk path — Pipeline A
2934
+ // gets this for free from GenerationHandler.executeGeneration.
2935
+ result = await withSpan({
2936
+ name: "neurolink.executeGeneration",
2937
+ tracer: tracers.provider,
2938
+ attributes: {
2939
+ [ATTR.GEN_AI_SYSTEM]: this.providerName,
2940
+ [ATTR.GEN_AI_MODEL]: modelName,
2941
+ "neurolink.path": isAnthropicModel(modelName)
2942
+ ? "native.anthropic"
2943
+ : "native.google-genai",
2944
+ },
2945
+ }, async () => {
2946
+ if (isAnthropicModel(modelName)) {
2947
+ logger.info("[GoogleVertex] Routing Claude generate to native @anthropic-ai/vertex-sdk", {
2948
+ model: modelName,
2949
+ totalToolCount: Object.keys(mergedOptions.tools).length,
2950
+ });
2951
+ return this.executeNativeAnthropicGenerate(mergedOptions);
2952
+ }
2810
2953
  logger.info("[GoogleVertex] Routing Gemini generate to native @google/genai", {
2811
2954
  model: modelName,
2812
2955
  totalToolCount: Object.keys(mergedOptions.tools).length,
2813
2956
  });
2814
- result = await this.executeNativeGemini3Generate(mergedOptions);
2815
- }
2957
+ return this.executeNativeGemini3Generate(mergedOptions);
2958
+ });
2816
2959
  this.attachUsageAndCostAttributes(generateSpan, modelName, result?.usage);
2960
+ // Pipe through TTS-of-AI-response when caller asks for it. The
2961
+ // shared `synthesizeAIResponseIfNeeded` no-ops when tts is not
2962
+ // enabled / useAiResponse is false, so the cost is zero on
2963
+ // non-TTS paths.
2964
+ result = await this.synthesizeAIResponseIfNeeded(result, options);
2817
2965
  // Fire onFinish lifecycle callback for the native generate path.
2818
2966
  // Pipeline A providers get this for free via the AI SDK middleware
2819
2967
  // wrapper (LifecycleMiddleware); native @google/genai bypasses
@@ -3053,6 +3201,14 @@ export class GoogleVertexProvider extends BaseProvider {
3053
3201
  ? { error: error instanceof Error ? error.message : String(error) }
3054
3202
  : {}),
3055
3203
  });
3204
+ // Mark on the result so the SDK-level runStandardGenerateRequest knows
3205
+ // this provider already emitted `generation:end` itself and skips its
3206
+ // own duplicate emission. Without this flag the public event listener
3207
+ // (and the observability test) would see two events per generate call.
3208
+ if (result && typeof result === "object") {
3209
+ result._generationEndEmitted =
3210
+ true;
3211
+ }
3056
3212
  }
3057
3213
  formatProviderError(error) {
3058
3214
  const errorRecord = error;
@@ -0,0 +1,33 @@
1
+ import { type LanguageModel } from "ai";
2
+ import type { AIProviderName } from "../constants/enums.js";
3
+ import { BaseProvider } from "../core/baseProvider.js";
4
+ import type { NeurolinkCredentials, StreamOptions, StreamResult, ValidationSchema } from "../types/index.js";
5
+ /**
6
+ * Groq Provider
7
+ *
8
+ * Sub-100ms inference of Llama / Mistral / Gemma at api.groq.com/openai/v1
9
+ * (OpenAI-compatible). Best for low-latency tier; trade-off vs other open
10
+ * model hosts is throughput latency, not quality.
11
+ *
12
+ * @see https://console.groq.com/docs/quickstart
13
+ */
14
+ export declare class GroqProvider extends BaseProvider {
15
+ private model;
16
+ private apiKey;
17
+ private baseURL;
18
+ constructor(modelName?: string, sdk?: unknown, _region?: string, credentials?: NeurolinkCredentials["groq"]);
19
+ protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
20
+ private executeStreamInner;
21
+ protected getProviderName(): AIProviderName;
22
+ protected getDefaultModel(): string;
23
+ protected getAISDKModel(): LanguageModel;
24
+ protected formatProviderError(error: unknown): Error;
25
+ validateConfiguration(): Promise<boolean>;
26
+ getConfiguration(): {
27
+ provider: AIProviderName;
28
+ model: string;
29
+ defaultModel: string;
30
+ baseURL: string;
31
+ };
32
+ }
33
+ export default GroqProvider;