@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.
- package/CHANGELOG.md +6 -0
- package/dist/adapters/video/vertexVideoHandler.js +3 -3
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/core/analytics.js +11 -4
- package/dist/core/baseProvider.d.ts +6 -0
- package/dist/core/baseProvider.js +83 -14
- package/dist/core/conversationMemoryManager.d.ts +13 -0
- package/dist/core/conversationMemoryManager.js +28 -0
- package/dist/core/dynamicModels.js +3 -2
- package/dist/core/modules/GenerationHandler.js +2 -0
- package/dist/core/redisConversationMemoryManager.d.ts +11 -0
- package/dist/core/redisConversationMemoryManager.js +26 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/lib/adapters/video/vertexVideoHandler.js +3 -3
- package/dist/lib/core/analytics.js +11 -4
- package/dist/lib/core/baseProvider.d.ts +6 -0
- package/dist/lib/core/baseProvider.js +83 -14
- package/dist/lib/core/conversationMemoryManager.d.ts +13 -0
- package/dist/lib/core/conversationMemoryManager.js +28 -0
- package/dist/lib/core/dynamicModels.js +3 -2
- package/dist/lib/core/modules/GenerationHandler.js +2 -0
- package/dist/lib/core/redisConversationMemoryManager.d.ts +11 -0
- package/dist/lib/core/redisConversationMemoryManager.js +26 -9
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/mcp/httpRetryHandler.js +6 -2
- package/dist/lib/neurolink.d.ts +5 -0
- package/dist/lib/neurolink.js +160 -10
- package/dist/lib/processors/base/BaseFileProcessor.js +2 -1
- package/dist/lib/processors/errors/errorHelpers.js +12 -4
- package/dist/lib/providers/amazonBedrock.js +2 -1
- package/dist/lib/providers/anthropic.js +2 -2
- package/dist/lib/providers/anthropicBaseProvider.js +10 -4
- package/dist/lib/providers/azureOpenai.js +14 -25
- package/dist/lib/providers/googleAiStudio.d.ts +0 -34
- package/dist/lib/providers/googleAiStudio.js +124 -315
- package/dist/lib/providers/googleNativeGemini3.d.ts +119 -0
- package/dist/lib/providers/googleNativeGemini3.js +264 -0
- package/dist/lib/providers/googleVertex.d.ts +0 -40
- package/dist/lib/providers/googleVertex.js +150 -317
- package/dist/lib/providers/huggingFace.js +20 -5
- package/dist/lib/providers/litellm.js +6 -4
- package/dist/lib/providers/mistral.js +3 -2
- package/dist/lib/providers/openAI.js +2 -2
- package/dist/lib/providers/openRouter.js +8 -7
- package/dist/lib/providers/openaiCompatible.js +10 -4
- package/dist/lib/rag/resilience/RetryHandler.js +6 -2
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +24 -2
- package/dist/lib/services/server/ai/observability/instrumentation.js +12 -1
- package/dist/lib/types/generateTypes.d.ts +28 -0
- package/dist/lib/types/ragTypes.d.ts +9 -1
- package/dist/lib/types/streamTypes.d.ts +13 -0
- package/dist/lib/utils/conversationMemory.js +15 -0
- package/dist/lib/utils/errorHandling.d.ts +5 -0
- package/dist/lib/utils/errorHandling.js +19 -0
- package/dist/lib/utils/pricing.d.ts +12 -0
- package/dist/lib/utils/pricing.js +134 -0
- package/dist/lib/utils/redis.d.ts +17 -0
- package/dist/lib/utils/redis.js +105 -0
- package/dist/lib/utils/timeout.d.ts +10 -0
- package/dist/lib/utils/timeout.js +15 -0
- package/dist/mcp/httpRetryHandler.js +6 -2
- package/dist/neurolink.d.ts +5 -0
- package/dist/neurolink.js +160 -10
- package/dist/processors/base/BaseFileProcessor.js +2 -1
- package/dist/processors/errors/errorHelpers.js +12 -4
- package/dist/providers/amazonBedrock.js +2 -1
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/anthropicBaseProvider.js +10 -4
- package/dist/providers/azureOpenai.js +14 -25
- package/dist/providers/googleAiStudio.d.ts +0 -34
- package/dist/providers/googleAiStudio.js +124 -315
- package/dist/providers/googleNativeGemini3.d.ts +119 -0
- package/dist/providers/googleNativeGemini3.js +263 -0
- package/dist/providers/googleVertex.d.ts +0 -40
- package/dist/providers/googleVertex.js +150 -317
- package/dist/providers/huggingFace.js +20 -5
- package/dist/providers/litellm.js +6 -4
- package/dist/providers/mistral.js +3 -2
- package/dist/providers/openAI.js +2 -2
- package/dist/providers/openRouter.js +8 -7
- package/dist/providers/openaiCompatible.js +10 -4
- package/dist/rag/resilience/RetryHandler.js +6 -2
- package/dist/services/server/ai/observability/instrumentation.d.ts +24 -2
- package/dist/services/server/ai/observability/instrumentation.js +12 -1
- package/dist/types/generateTypes.d.ts +28 -0
- package/dist/types/ragTypes.d.ts +9 -1
- package/dist/types/streamTypes.d.ts +13 -0
- package/dist/utils/conversationMemory.js +15 -0
- package/dist/utils/errorHandling.d.ts +5 -0
- package/dist/utils/errorHandling.js +19 -0
- package/dist/utils/pricing.d.ts +12 -0
- package/dist/utils/pricing.js +133 -0
- package/dist/utils/redis.d.ts +17 -0
- package/dist/utils/redis.js +105 -0
- package/dist/utils/timeout.d.ts +10 -0
- package/dist/utils/timeout.js +15 -0
- 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
|
|
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 {
|
|
12
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
529
|
-
const 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
|
-
//
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
557
|
-
logger.debug(`[GoogleAIStudio]
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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 =
|
|
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 =
|
|
659
|
+
const config = buildNativeConfig(options, toolsConfig);
|
|
646
660
|
const startTime = Date.now();
|
|
647
|
-
const
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
step
|
|
659
|
-
|
|
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
|
-
|
|
676
|
-
logger.debug(`[GoogleAIStudio]
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|