@juspay/neurolink 9.6.0 → 9.7.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 (100) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/adapters/video/vertexVideoHandler.js +3 -3
  3. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  4. package/dist/cli/loop/optionsSchema.js +4 -0
  5. package/dist/core/analytics.js +11 -4
  6. package/dist/core/baseProvider.d.ts +6 -0
  7. package/dist/core/baseProvider.js +83 -14
  8. package/dist/core/conversationMemoryManager.d.ts +13 -0
  9. package/dist/core/conversationMemoryManager.js +28 -0
  10. package/dist/core/dynamicModels.js +3 -2
  11. package/dist/core/modules/GenerationHandler.js +2 -0
  12. package/dist/core/redisConversationMemoryManager.d.ts +11 -0
  13. package/dist/core/redisConversationMemoryManager.js +26 -9
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.js +5 -0
  16. package/dist/lib/adapters/video/vertexVideoHandler.js +3 -3
  17. package/dist/lib/core/analytics.js +11 -4
  18. package/dist/lib/core/baseProvider.d.ts +6 -0
  19. package/dist/lib/core/baseProvider.js +83 -14
  20. package/dist/lib/core/conversationMemoryManager.d.ts +13 -0
  21. package/dist/lib/core/conversationMemoryManager.js +28 -0
  22. package/dist/lib/core/dynamicModels.js +3 -2
  23. package/dist/lib/core/modules/GenerationHandler.js +2 -0
  24. package/dist/lib/core/redisConversationMemoryManager.d.ts +11 -0
  25. package/dist/lib/core/redisConversationMemoryManager.js +26 -9
  26. package/dist/lib/index.d.ts +4 -0
  27. package/dist/lib/index.js +5 -0
  28. package/dist/lib/mcp/httpRetryHandler.js +6 -2
  29. package/dist/lib/neurolink.d.ts +5 -0
  30. package/dist/lib/neurolink.js +160 -10
  31. package/dist/lib/processors/base/BaseFileProcessor.js +2 -1
  32. package/dist/lib/processors/errors/errorHelpers.js +12 -4
  33. package/dist/lib/providers/amazonBedrock.js +2 -1
  34. package/dist/lib/providers/anthropic.js +2 -2
  35. package/dist/lib/providers/anthropicBaseProvider.js +10 -4
  36. package/dist/lib/providers/azureOpenai.js +14 -25
  37. package/dist/lib/providers/googleAiStudio.d.ts +0 -34
  38. package/dist/lib/providers/googleAiStudio.js +124 -315
  39. package/dist/lib/providers/googleNativeGemini3.d.ts +119 -0
  40. package/dist/lib/providers/googleNativeGemini3.js +264 -0
  41. package/dist/lib/providers/googleVertex.d.ts +0 -40
  42. package/dist/lib/providers/googleVertex.js +150 -317
  43. package/dist/lib/providers/huggingFace.js +20 -5
  44. package/dist/lib/providers/litellm.js +6 -4
  45. package/dist/lib/providers/mistral.js +3 -2
  46. package/dist/lib/providers/openAI.js +2 -2
  47. package/dist/lib/providers/openRouter.js +8 -7
  48. package/dist/lib/providers/openaiCompatible.js +10 -4
  49. package/dist/lib/rag/resilience/RetryHandler.js +6 -2
  50. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +24 -2
  51. package/dist/lib/services/server/ai/observability/instrumentation.js +12 -1
  52. package/dist/lib/types/generateTypes.d.ts +28 -0
  53. package/dist/lib/types/ragTypes.d.ts +9 -1
  54. package/dist/lib/types/streamTypes.d.ts +13 -0
  55. package/dist/lib/utils/conversationMemory.js +15 -0
  56. package/dist/lib/utils/errorHandling.d.ts +5 -0
  57. package/dist/lib/utils/errorHandling.js +19 -0
  58. package/dist/lib/utils/pricing.d.ts +12 -0
  59. package/dist/lib/utils/pricing.js +134 -0
  60. package/dist/lib/utils/redis.d.ts +17 -0
  61. package/dist/lib/utils/redis.js +105 -0
  62. package/dist/lib/utils/timeout.d.ts +10 -0
  63. package/dist/lib/utils/timeout.js +15 -0
  64. package/dist/mcp/httpRetryHandler.js +6 -2
  65. package/dist/neurolink.d.ts +5 -0
  66. package/dist/neurolink.js +160 -10
  67. package/dist/processors/base/BaseFileProcessor.js +2 -1
  68. package/dist/processors/errors/errorHelpers.js +12 -4
  69. package/dist/providers/amazonBedrock.js +2 -1
  70. package/dist/providers/anthropic.js +2 -2
  71. package/dist/providers/anthropicBaseProvider.js +10 -4
  72. package/dist/providers/azureOpenai.js +14 -25
  73. package/dist/providers/googleAiStudio.d.ts +0 -34
  74. package/dist/providers/googleAiStudio.js +124 -315
  75. package/dist/providers/googleNativeGemini3.d.ts +119 -0
  76. package/dist/providers/googleNativeGemini3.js +263 -0
  77. package/dist/providers/googleVertex.d.ts +0 -40
  78. package/dist/providers/googleVertex.js +150 -317
  79. package/dist/providers/huggingFace.js +20 -5
  80. package/dist/providers/litellm.js +6 -4
  81. package/dist/providers/mistral.js +3 -2
  82. package/dist/providers/openAI.js +2 -2
  83. package/dist/providers/openRouter.js +8 -7
  84. package/dist/providers/openaiCompatible.js +10 -4
  85. package/dist/rag/resilience/RetryHandler.js +6 -2
  86. package/dist/services/server/ai/observability/instrumentation.d.ts +24 -2
  87. package/dist/services/server/ai/observability/instrumentation.js +12 -1
  88. package/dist/types/generateTypes.d.ts +28 -0
  89. package/dist/types/ragTypes.d.ts +9 -1
  90. package/dist/types/streamTypes.d.ts +13 -0
  91. package/dist/utils/conversationMemory.js +15 -0
  92. package/dist/utils/errorHandling.d.ts +5 -0
  93. package/dist/utils/errorHandling.js +19 -0
  94. package/dist/utils/pricing.d.ts +12 -0
  95. package/dist/utils/pricing.js +133 -0
  96. package/dist/utils/redis.d.ts +17 -0
  97. package/dist/utils/redis.js +105 -0
  98. package/dist/utils/timeout.d.ts +10 -0
  99. package/dist/utils/timeout.js +15 -0
  100. package/package.json +1 -1
@@ -2,15 +2,14 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google";
2
2
  import { streamText } from "ai";
3
3
  import { ErrorCategory, ErrorSeverity, GoogleAIModels, } from "../constants/enums.js";
4
4
  import { BaseProvider } from "../core/baseProvider.js";
5
- import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, } from "../core/constants.js";
5
+ import { DEFAULT_MAX_STEPS } from "../core/constants.js";
6
6
  import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
7
7
  import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/errors.js";
8
8
  import { ERROR_CODES, NeuroLinkError } from "../utils/errorHandling.js";
9
9
  import { logger } from "../utils/logger.js";
10
10
  import { isGemini3Model } from "../utils/modelDetection.js";
11
- import { convertZodToJsonSchema, inlineJsonSchema, isZodSchema, } from "../utils/schemaConversion.js";
12
- import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
13
- import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
11
+ import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
12
+ import { buildNativeToolDeclarations, buildNativeConfig, computeMaxSteps, collectStreamChunks, extractTextFromParts, executeNativeToolCalls, handleMaxStepsTermination, pushModelResponseToHistory, } from "./googleNativeGemini3.js";
14
13
  // Google AI Live API types now imported from ../types/providerSpecific.js
15
14
  // Import proper types for multimodal message handling
16
15
  // Create Google GenAI client
@@ -397,10 +396,19 @@ export class GoogleAIStudioProvider extends BaseProvider {
397
396
  const hasTools = gemini3CheckShouldUseTools && combinedToolCount > 0;
398
397
  if (isGemini3Model(gemini3CheckModelName) && hasTools) {
399
398
  // Merge SDK tools into options for native SDK path
400
- const mergedOptions = {
399
+ let mergedOptions = {
401
400
  ...options,
402
401
  tools: { ...sdkTools, ...optionTools },
403
402
  };
403
+ // Check for tools + JSON schema conflict (Gemini limitation)
404
+ const wantsJsonOutput = options.output?.format === "json" || options.schema;
405
+ if (wantsJsonOutput &&
406
+ mergedOptions.tools &&
407
+ Object.keys(mergedOptions.tools).length > 0 &&
408
+ !mergedOptions.disableTools) {
409
+ logger.warn("[GoogleAIStudio] Gemini does not support tools and JSON schema output simultaneously. Disabling tools for this request.");
410
+ mergedOptions = { ...mergedOptions, disableTools: true, tools: {} };
411
+ }
404
412
  logger.info("[GoogleAIStudio] Routing Gemini 3 to native SDK for tool calling", {
405
413
  model: gemini3CheckModelName,
406
414
  optionToolCount: Object.keys(optionTools).length,
@@ -441,7 +449,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
441
449
  tools,
442
450
  maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
443
451
  toolChoice: shouldUseTools ? "auto" : "none",
444
- abortSignal: timeoutController?.controller.signal,
452
+ abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
445
453
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
446
454
  // Gemini 3: use thinkingLevel via providerOptions
447
455
  // Gemini 2.5: use thinkingBudget via providerOptions
@@ -470,7 +478,8 @@ export class GoogleAIStudioProvider extends BaseProvider {
470
478
  });
471
479
  },
472
480
  });
473
- timeoutController?.cleanup();
481
+ // Defer timeout cleanup until the stream completes or errors
482
+ result.text.finally(() => timeoutController?.cleanup());
474
483
  // Transform string stream to content object stream using BaseProvider method
475
484
  const transformedStream = this.createTextStream(result);
476
485
  // Create analytics promise that resolves after stream completion
@@ -517,7 +526,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
517
526
  if (options.tools &&
518
527
  Object.keys(options.tools).length > 0 &&
519
528
  !options.disableTools) {
520
- const result = this.buildNativeToolDeclarations(options.tools);
529
+ const result = buildNativeToolDeclarations(options.tools);
521
530
  toolsConfig = result.toolsConfig;
522
531
  executeMap = result.executeMap;
523
532
  logger.debug("[GoogleAIStudio] Converted tools for native SDK", {
@@ -525,8 +534,8 @@ export class GoogleAIStudioProvider extends BaseProvider {
525
534
  toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
526
535
  });
527
536
  }
528
- const config = this.buildNativeConfig(options, toolsConfig);
529
- const maxSteps = this.computeMaxSteps(options.maxSteps);
537
+ const config = buildNativeConfig(options, toolsConfig);
538
+ const maxSteps = computeMaxSteps(options.maxSteps);
530
539
  let finalText = "";
531
540
  let lastStepText = "";
532
541
  let totalInputTokens = 0;
@@ -534,50 +543,55 @@ export class GoogleAIStudioProvider extends BaseProvider {
534
543
  const allToolCalls = [];
535
544
  let step = 0;
536
545
  const failedTools = new Map();
537
- // Agentic loop for tool calling
538
- while (step < maxSteps) {
539
- step++;
540
- logger.debug(`[GoogleAIStudio] Native SDK step ${step}/${maxSteps}`);
541
- try {
542
- const stream = await client.models.generateContentStream({
543
- model: modelName,
544
- contents: currentContents,
545
- config,
546
- });
547
- const chunkResult = await this.collectStreamChunks(stream);
548
- totalInputTokens = Math.max(totalInputTokens, chunkResult.inputTokens);
549
- totalOutputTokens = Math.max(totalOutputTokens, chunkResult.outputTokens);
550
- const stepText = this.extractTextFromParts(chunkResult.rawResponseParts);
551
- // If no function calls, we're done
552
- if (chunkResult.stepFunctionCalls.length === 0) {
553
- finalText = stepText;
546
+ // Compose abort signal from user signal + timeout
547
+ const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
548
+ try {
549
+ // Agentic loop for tool calling
550
+ while (step < maxSteps) {
551
+ if (composedSignal?.aborted) {
554
552
  break;
555
553
  }
556
- lastStepText = stepText;
557
- logger.debug(`[GoogleAIStudio] Executing ${chunkResult.stepFunctionCalls.length} function calls`);
558
- // Add model response with ALL parts (including thoughtSignature) to history
559
- currentContents.push({
560
- role: "model",
561
- parts: chunkResult.rawResponseParts.length > 0
562
- ? chunkResult.rawResponseParts
563
- : chunkResult.stepFunctionCalls.map((fc) => ({
564
- functionCall: fc,
565
- })),
566
- });
567
- const functionResponses = await this.executeNativeToolCalls(chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls);
568
- // Add function responses to history
569
- currentContents.push({
570
- role: "function",
571
- parts: functionResponses,
572
- });
573
- }
574
- catch (error) {
575
- logger.error("[GoogleAIStudio] Native SDK error", error);
576
- throw this.handleProviderError(error);
554
+ step++;
555
+ logger.debug(`[GoogleAIStudio] Native SDK step ${step}/${maxSteps}`);
556
+ try {
557
+ const stream = await client.models.generateContentStream({
558
+ model: modelName,
559
+ contents: currentContents,
560
+ config,
561
+ ...(composedSignal
562
+ ? { httpOptions: { signal: composedSignal } }
563
+ : {}),
564
+ });
565
+ const chunkResult = await collectStreamChunks(stream);
566
+ totalInputTokens += chunkResult.inputTokens;
567
+ totalOutputTokens += chunkResult.outputTokens;
568
+ const stepText = extractTextFromParts(chunkResult.rawResponseParts);
569
+ // If no function calls, we're done
570
+ if (chunkResult.stepFunctionCalls.length === 0) {
571
+ finalText = stepText;
572
+ break;
573
+ }
574
+ lastStepText = stepText;
575
+ logger.debug(`[GoogleAIStudio] Executing ${chunkResult.stepFunctionCalls.length} function calls`);
576
+ // Add model response with ALL parts (including thoughtSignature) to history
577
+ pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
578
+ const functionResponses = await executeNativeToolCalls("[GoogleAIStudio]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, { abortSignal: composedSignal });
579
+ // Add function responses to history
580
+ currentContents.push({
581
+ role: "function",
582
+ parts: functionResponses,
583
+ });
584
+ }
585
+ catch (error) {
586
+ logger.error("[GoogleAIStudio] Native SDK error", error);
587
+ throw this.handleProviderError(error);
588
+ }
577
589
  }
578
590
  }
579
- timeoutController?.cleanup();
580
- finalText = this.handleMaxStepsTermination(step, maxSteps, finalText, lastStepText);
591
+ finally {
592
+ timeoutController?.cleanup();
593
+ }
594
+ finalText = handleMaxStepsTermination("[GoogleAIStudio]", step, maxSteps, finalText, lastStepText);
581
595
  const responseTime = Date.now() - startTime;
582
596
  // Create async iterable for streaming result
583
597
  async function* createTextStream() {
@@ -633,7 +647,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
633
647
  const sdkTools = await this.getAllTools();
634
648
  const mergedTools = { ...sdkTools, ...(options.tools || {}) };
635
649
  if (Object.keys(mergedTools).length > 0) {
636
- const result = this.buildNativeToolDeclarations(mergedTools);
650
+ const result = buildNativeToolDeclarations(mergedTools);
637
651
  toolsConfig = result.toolsConfig;
638
652
  executeMap = result.executeMap;
639
653
  logger.debug("[GoogleAIStudio] Converted tools for native SDK generate", {
@@ -642,9 +656,12 @@ export class GoogleAIStudioProvider extends BaseProvider {
642
656
  });
643
657
  }
644
658
  }
645
- const config = this.buildNativeConfig(options, toolsConfig);
659
+ const config = buildNativeConfig(options, toolsConfig);
646
660
  const startTime = Date.now();
647
- const maxSteps = this.computeMaxSteps(options.maxSteps);
661
+ const timeout = this.getTimeout(options);
662
+ const timeoutController = createTimeoutController(timeout, this.providerName, "generate");
663
+ const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
664
+ const maxSteps = computeMaxSteps(options.maxSteps);
648
665
  let finalText = "";
649
666
  let lastStepText = "";
650
667
  let totalInputTokens = 0;
@@ -653,50 +670,54 @@ export class GoogleAIStudioProvider extends BaseProvider {
653
670
  const toolExecutions = [];
654
671
  let step = 0;
655
672
  const failedTools = new Map();
656
- // Agentic loop for tool calling
657
- while (step < maxSteps) {
658
- step++;
659
- logger.debug(`[GoogleAIStudio] Native SDK generate step ${step}/${maxSteps}`);
660
- try {
661
- const stream = await client.models.generateContentStream({
662
- model: modelName,
663
- contents: currentContents,
664
- config,
665
- });
666
- const chunkResult = await this.collectStreamChunks(stream);
667
- totalInputTokens = Math.max(totalInputTokens, chunkResult.inputTokens);
668
- totalOutputTokens = Math.max(totalOutputTokens, chunkResult.outputTokens);
669
- const stepText = this.extractTextFromParts(chunkResult.rawResponseParts);
670
- // If no function calls, we're done
671
- if (chunkResult.stepFunctionCalls.length === 0) {
672
- finalText = stepText;
673
+ try {
674
+ // Agentic loop for tool calling
675
+ while (step < maxSteps) {
676
+ if (composedSignal?.aborted) {
673
677
  break;
674
678
  }
675
- lastStepText = stepText;
676
- logger.debug(`[GoogleAIStudio] Executing ${chunkResult.stepFunctionCalls.length} function calls in generate`);
677
- // Add model response with ALL parts (including thoughtSignature) to history
678
- // This is critical for Gemini 3 - it requires thought signatures in subsequent turns
679
- currentContents.push({
680
- role: "model",
681
- parts: chunkResult.rawResponseParts.length > 0
682
- ? chunkResult.rawResponseParts
683
- : chunkResult.stepFunctionCalls.map((fc) => ({
684
- functionCall: fc,
685
- })),
686
- });
687
- const functionResponses = await this.executeNativeToolCalls(chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, toolExecutions);
688
- // Add function responses to history
689
- currentContents.push({
690
- role: "function",
691
- parts: functionResponses,
692
- });
693
- }
694
- catch (error) {
695
- logger.error("[GoogleAIStudio] Native SDK generate error", error);
696
- throw this.handleProviderError(error);
679
+ step++;
680
+ logger.debug(`[GoogleAIStudio] Native SDK generate step ${step}/${maxSteps}`);
681
+ try {
682
+ const stream = await client.models.generateContentStream({
683
+ model: modelName,
684
+ contents: currentContents,
685
+ config,
686
+ ...(composedSignal
687
+ ? { httpOptions: { signal: composedSignal } }
688
+ : {}),
689
+ });
690
+ const chunkResult = await collectStreamChunks(stream);
691
+ totalInputTokens += chunkResult.inputTokens;
692
+ totalOutputTokens += chunkResult.outputTokens;
693
+ const stepText = extractTextFromParts(chunkResult.rawResponseParts);
694
+ // If no function calls, we're done
695
+ if (chunkResult.stepFunctionCalls.length === 0) {
696
+ finalText = stepText;
697
+ break;
698
+ }
699
+ lastStepText = stepText;
700
+ logger.debug(`[GoogleAIStudio] Executing ${chunkResult.stepFunctionCalls.length} function calls in generate`);
701
+ // Add model response with ALL parts (including thoughtSignature) to history
702
+ // This is critical for Gemini 3 - it requires thought signatures in subsequent turns
703
+ pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
704
+ const functionResponses = await executeNativeToolCalls("[GoogleAIStudio]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, { toolExecutions, abortSignal: composedSignal });
705
+ // Add function responses to history
706
+ currentContents.push({
707
+ role: "function",
708
+ parts: functionResponses,
709
+ });
710
+ }
711
+ catch (error) {
712
+ logger.error("[GoogleAIStudio] Native SDK generate error", error);
713
+ throw this.handleProviderError(error);
714
+ }
697
715
  }
698
716
  }
699
- finalText = this.handleMaxStepsTermination(step, maxSteps, finalText, lastStepText);
717
+ finally {
718
+ timeoutController?.cleanup();
719
+ }
720
+ finalText = handleMaxStepsTermination("[GoogleAIStudio]", step, maxSteps, finalText, lastStepText);
700
721
  const responseTime = Date.now() - startTime;
701
722
  // Build EnhancedGenerateResult
702
723
  return {
@@ -731,10 +752,19 @@ export class GoogleAIStudioProvider extends BaseProvider {
731
752
  (options.tools && Object.keys(options.tools).length > 0));
732
753
  if (isGemini3Model(modelName) && hasTools) {
733
754
  // Merge SDK tools into options for native SDK path
734
- const mergedOptions = {
755
+ let mergedOptions = {
735
756
  ...options,
736
757
  tools: { ...sdkTools, ...(options.tools || {}) },
737
758
  };
759
+ // Check for tools + JSON schema conflict (Gemini limitation)
760
+ const wantsJsonOutput = options.output?.format === "json" || options.schema;
761
+ if (wantsJsonOutput &&
762
+ mergedOptions.tools &&
763
+ Object.keys(mergedOptions.tools).length > 0 &&
764
+ !mergedOptions.disableTools) {
765
+ logger.warn("[GoogleAIStudio] Gemini does not support tools and JSON schema output simultaneously. Disabling tools for this request.");
766
+ mergedOptions = { ...mergedOptions, disableTools: true, tools: {} };
767
+ }
738
768
  logger.info("[GoogleAIStudio] Routing Gemini 3 generate to native SDK for tool calling", {
739
769
  model: modelName,
740
770
  sdkToolCount: Object.keys(sdkTools).length,
@@ -748,227 +778,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
748
778
  return super.generate(optionsOrPrompt);
749
779
  }
750
780
  // ===================
751
- // NATIVE GEMINI 3 HELPER METHODS
752
- // ===================
753
- /**
754
- * Convert Vercel AI SDK tools to @google/genai FunctionDeclarations and an execute map.
755
- * Shared by executeNativeGemini3Stream and executeNativeGemini3Generate.
756
- */
757
- buildNativeToolDeclarations(tools) {
758
- const functionDeclarations = [];
759
- const executeMap = new Map();
760
- for (const [name, tool] of Object.entries(tools)) {
761
- const decl = {
762
- name,
763
- description: tool.description || `Tool: ${name}`,
764
- };
765
- if (tool.parameters) {
766
- let rawSchema;
767
- if (isZodSchema(tool.parameters)) {
768
- rawSchema = convertZodToJsonSchema(tool.parameters);
769
- }
770
- else if (typeof tool.parameters === "object") {
771
- rawSchema = tool.parameters;
772
- }
773
- else {
774
- rawSchema = { type: "object", properties: {} };
775
- }
776
- decl.parametersJsonSchema = inlineJsonSchema(rawSchema);
777
- if (decl.parametersJsonSchema.$schema) {
778
- delete decl.parametersJsonSchema.$schema;
779
- }
780
- }
781
- functionDeclarations.push(decl);
782
- if (tool.execute) {
783
- executeMap.set(name, tool.execute);
784
- }
785
- }
786
- return { toolsConfig: [{ functionDeclarations }], executeMap };
787
- }
788
- /**
789
- * Build the native @google/genai config object shared by stream and generate.
790
- */
791
- buildNativeConfig(options, toolsConfig) {
792
- const config = {
793
- temperature: options.temperature ?? 1.0, // Gemini 3 requires 1.0 for tool calling
794
- maxOutputTokens: options.maxTokens,
795
- };
796
- if (toolsConfig) {
797
- config.tools = toolsConfig;
798
- }
799
- if (options.systemPrompt) {
800
- config.systemInstruction = options.systemPrompt;
801
- }
802
- // Add thinking config for Gemini 3
803
- const nativeThinkingConfig = createNativeThinkingConfig(options.thinkingConfig);
804
- if (nativeThinkingConfig) {
805
- config.thinkingConfig = nativeThinkingConfig;
806
- }
807
- return config;
808
- }
809
- /**
810
- * Compute a safe, clamped maxSteps value.
811
- */
812
- computeMaxSteps(rawMaxSteps) {
813
- const value = rawMaxSteps || DEFAULT_MAX_STEPS;
814
- return Number.isFinite(value) && value > 0
815
- ? Math.min(Math.floor(value), 100)
816
- : Math.min(DEFAULT_MAX_STEPS, 100);
817
- }
818
- /**
819
- * Process stream chunks to extract raw response parts, function calls, and usage metadata.
820
- * Shared by executeNativeGemini3Stream and executeNativeGemini3Generate.
821
- */
822
- async collectStreamChunks(stream) {
823
- const rawResponseParts = [];
824
- const stepFunctionCalls = [];
825
- let inputTokens = 0;
826
- let outputTokens = 0;
827
- for await (const chunk of stream) {
828
- // Extract raw parts from candidates FIRST
829
- // This avoids using chunk.text which triggers SDK warning when
830
- // non-text parts (thoughtSignature, functionCall) are present
831
- const chunkRecord = chunk;
832
- const candidates = chunkRecord.candidates;
833
- const firstCandidate = candidates?.[0];
834
- const chunkContent = firstCandidate?.content;
835
- if (chunkContent && Array.isArray(chunkContent.parts)) {
836
- rawResponseParts.push(...chunkContent.parts);
837
- }
838
- if (chunk.functionCalls) {
839
- stepFunctionCalls.push(...chunk.functionCalls);
840
- }
841
- // Accumulate usage metadata from chunks
842
- const usage = chunkRecord.usageMetadata;
843
- if (usage) {
844
- inputTokens = Math.max(inputTokens, usage.promptTokenCount || 0);
845
- outputTokens = Math.max(outputTokens, usage.candidatesTokenCount || 0);
846
- }
847
- }
848
- return { rawResponseParts, stepFunctionCalls, inputTokens, outputTokens };
849
- }
850
- /**
851
- * Extract text from raw response parts. Used after collectStreamChunks.
852
- */
853
- extractTextFromParts(rawResponseParts) {
854
- return rawResponseParts
855
- .filter((part) => typeof part.text === "string")
856
- .map((part) => part.text)
857
- .join("");
858
- }
859
- /**
860
- * Execute a batch of function calls with retry tracking and permanent failure detection.
861
- * Shared by executeNativeGemini3Stream and executeNativeGemini3Generate.
862
- *
863
- * Returns function responses for history and optional tool execution records for generate.
864
- */
865
- async executeNativeToolCalls(stepFunctionCalls, executeMap, failedTools, allToolCalls, toolExecutions) {
866
- const functionResponses = [];
867
- for (const call of stepFunctionCalls) {
868
- allToolCalls.push({ toolName: call.name, args: call.args });
869
- // Check if this tool has already exceeded retry limit
870
- const failedInfo = failedTools.get(call.name);
871
- if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
872
- logger.warn(`[GoogleAIStudio] Tool "${call.name}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
873
- const errorOutput = {
874
- 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.`,
875
- status: "permanently_failed",
876
- do_not_retry: true,
877
- };
878
- functionResponses.push({
879
- functionResponse: { name: call.name, response: errorOutput },
880
- });
881
- toolExecutions?.push({
882
- name: call.name,
883
- input: call.args,
884
- output: errorOutput,
885
- });
886
- continue;
887
- }
888
- const execute = executeMap.get(call.name);
889
- if (execute) {
890
- try {
891
- // AI SDK Tool execute requires (args, options) - provide minimal options
892
- const toolOptions = {
893
- toolCallId: `${call.name}-${Date.now()}`,
894
- messages: [],
895
- abortSignal: undefined,
896
- };
897
- const result = await execute(call.args, toolOptions);
898
- functionResponses.push({
899
- functionResponse: { name: call.name, response: { result } },
900
- });
901
- toolExecutions?.push({
902
- name: call.name,
903
- input: call.args,
904
- output: result,
905
- });
906
- }
907
- catch (error) {
908
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
909
- // Track this failure
910
- const currentFailInfo = failedTools.get(call.name) || {
911
- count: 0,
912
- lastError: "",
913
- };
914
- currentFailInfo.count++;
915
- currentFailInfo.lastError = errorMessage;
916
- failedTools.set(call.name, currentFailInfo);
917
- logger.warn(`[GoogleAIStudio] Tool "${call.name}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
918
- // Determine if this is a permanent failure
919
- const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
920
- const errorOutput = {
921
- error: isPermanentFailure
922
- ? `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.`
923
- : `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
924
- status: isPermanentFailure ? "permanently_failed" : "failed",
925
- do_not_retry: isPermanentFailure,
926
- retry_count: currentFailInfo.count,
927
- max_retries: DEFAULT_TOOL_MAX_RETRIES,
928
- };
929
- functionResponses.push({
930
- functionResponse: { name: call.name, response: errorOutput },
931
- });
932
- toolExecutions?.push({
933
- name: call.name,
934
- input: call.args,
935
- output: errorOutput,
936
- });
937
- }
938
- }
939
- else {
940
- // Tool not found is a permanent error
941
- const errorOutput = {
942
- error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
943
- status: "permanently_failed",
944
- do_not_retry: true,
945
- };
946
- functionResponses.push({
947
- functionResponse: { name: call.name, response: errorOutput },
948
- });
949
- toolExecutions?.push({
950
- name: call.name,
951
- input: call.args,
952
- output: errorOutput,
953
- });
954
- }
955
- }
956
- return functionResponses;
957
- }
958
- /**
959
- * Handle maxSteps termination by producing a final text when the model
960
- * was still calling tools when the step limit was reached.
961
- */
962
- handleMaxStepsTermination(step, maxSteps, finalText, lastStepText) {
963
- if (step >= maxSteps && !finalText) {
964
- logger.warn(`[GoogleAIStudio] Tool call loop terminated after reaching maxSteps (${maxSteps}). ` +
965
- `Model was still calling tools. Using accumulated text from last step.`);
966
- return (lastStepText ||
967
- `[Tool execution limit reached after ${maxSteps} steps. The model continued requesting tool calls beyond the limit.]`);
968
- }
969
- return finalText;
970
- }
971
- // ===================
972
781
  // HELPER METHODS
973
782
  // ===================
974
783
  async executeAudioStreamViaGeminiLive(options) {
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Shared utilities for Gemini 3 native SDK support.
3
+ *
4
+ * Both GoogleAIStudioProvider and GoogleVertexProvider route Gemini 3 models
5
+ * with tools to the native @google/genai SDK (bypassing the Vercel AI SDK)
6
+ * in order to properly handle thought_signature in multi-turn tool calling.
7
+ *
8
+ * This module extracts the functions that are duplicated between the two
9
+ * providers so they can share a single implementation.
10
+ */
11
+ import type { Tool } from "ai";
12
+ import type { ThinkingConfig } from "../utils/thinkingConfig.js";
13
+ /** A single native @google/genai function declaration. */
14
+ export type NativeFunctionDeclaration = {
15
+ name: string;
16
+ description: string;
17
+ parametersJsonSchema?: Record<string, unknown>;
18
+ };
19
+ /** The tools config array expected by the @google/genai SDK. */
20
+ export type NativeToolsConfig = Array<{
21
+ functionDeclarations: NativeFunctionDeclaration[];
22
+ }>;
23
+ /** Return value of buildNativeToolDeclarations. */
24
+ export type NativeToolDeclarationsResult = {
25
+ toolsConfig: NativeToolsConfig;
26
+ executeMap: Map<string, Tool["execute"]>;
27
+ };
28
+ /** A single function call returned by the Gemini model. */
29
+ export type NativeFunctionCall = {
30
+ name: string;
31
+ args: Record<string, unknown>;
32
+ };
33
+ /** A single function response to feed back into the conversation. */
34
+ export type NativeFunctionResponse = {
35
+ functionResponse: {
36
+ name: string;
37
+ response: unknown;
38
+ };
39
+ };
40
+ /** Result from collectStreamChunks. */
41
+ export type CollectedChunkResult = {
42
+ rawResponseParts: unknown[];
43
+ stepFunctionCalls: NativeFunctionCall[];
44
+ inputTokens: number;
45
+ outputTokens: number;
46
+ };
47
+ /**
48
+ * Convert Vercel AI SDK tools to @google/genai FunctionDeclarations and an execute map.
49
+ *
50
+ * This handles both Zod schemas and plain JSON Schema objects for tool parameters.
51
+ */
52
+ export declare function buildNativeToolDeclarations(tools: Record<string, Tool>): NativeToolDeclarationsResult;
53
+ /**
54
+ * Build the native @google/genai config object shared by stream and generate.
55
+ */
56
+ export declare function buildNativeConfig(options: {
57
+ temperature?: number;
58
+ maxTokens?: number;
59
+ systemPrompt?: string;
60
+ thinkingConfig?: ThinkingConfig;
61
+ }, toolsConfig?: NativeToolsConfig): Record<string, unknown>;
62
+ /**
63
+ * Compute a safe, clamped maxSteps value.
64
+ */
65
+ export declare function computeMaxSteps(rawMaxSteps?: number): number;
66
+ /**
67
+ * Process stream chunks to extract raw response parts, function calls, and usage metadata.
68
+ *
69
+ * Consumes the full async iterable and returns all collected data.
70
+ */
71
+ export declare function collectStreamChunks(stream: AsyncIterable<{
72
+ functionCalls?: NativeFunctionCall[];
73
+ [key: string]: unknown;
74
+ }>): Promise<CollectedChunkResult>;
75
+ /**
76
+ * Extract text from raw response parts, filtering out non-text parts
77
+ * (thoughtSignature, functionCall) to avoid SDK warnings.
78
+ */
79
+ export declare function extractTextFromParts(rawResponseParts: unknown[]): string;
80
+ /**
81
+ * Execute a batch of native function calls with retry tracking and permanent failure detection.
82
+ *
83
+ * @param logLabel - Label for log messages (e.g. "[GoogleAIStudio]" or "[GoogleVertex]")
84
+ * @param stepFunctionCalls - The function calls from the model
85
+ * @param executeMap - Map of tool name to execute function
86
+ * @param failedTools - Mutable map tracking per-tool failure counts
87
+ * @param allToolCalls - Mutable array accumulating all tool call records
88
+ * @param options - Optional settings for execution tracking and cancellation
89
+ * @returns Array of function responses for conversation history
90
+ */
91
+ export declare function executeNativeToolCalls(logLabel: string, stepFunctionCalls: NativeFunctionCall[], executeMap: Map<string, Tool["execute"]>, failedTools: Map<string, {
92
+ count: number;
93
+ lastError: string;
94
+ }>, allToolCalls: Array<{
95
+ toolName: string;
96
+ args: Record<string, unknown>;
97
+ }>, options?: {
98
+ toolExecutions?: Array<{
99
+ name: string;
100
+ input: Record<string, unknown>;
101
+ output: unknown;
102
+ }>;
103
+ abortSignal?: AbortSignal;
104
+ }): Promise<NativeFunctionResponse[]>;
105
+ /**
106
+ * Handle maxSteps termination by producing a final text when the model
107
+ * was still calling tools when the step limit was reached.
108
+ *
109
+ * @param logLabel - Label for log messages (e.g. "[GoogleAIStudio]" or "[GoogleVertex]")
110
+ */
111
+ export declare function handleMaxStepsTermination(logLabel: string, step: number, maxSteps: number, finalText: string, lastStepText: string): string;
112
+ /**
113
+ * Push model response parts to conversation history, preserving thoughtSignature
114
+ * for Gemini 3 multi-turn tool calling.
115
+ */
116
+ export declare function pushModelResponseToHistory(currentContents: Array<{
117
+ role: string;
118
+ parts: unknown[];
119
+ }>, rawResponseParts: unknown[], stepFunctionCalls: NativeFunctionCall[]): void;