@juspay/neurolink 11.11.2 → 11.11.3
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 +1 -1
- package/dist/browser/neurolink.min.js +378 -378
- package/dist/lib/providers/googleAiStudio/client.js +117 -101
- package/dist/providers/googleAiStudio/client.js +117 -101
- package/package.json +1 -1
|
@@ -6,6 +6,9 @@ import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "
|
|
|
6
6
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
|
|
7
7
|
import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
|
|
8
8
|
import { logger } from "../../utils/logger.js";
|
|
9
|
+
import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
|
|
10
|
+
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
11
|
+
import { DEFAULT_TOOL_MAX_RETRIES } from "../../core/constants.js";
|
|
9
12
|
import { isToolsSchemaExclusionInForce } from "../../core/modules/structuredOutputPolicy.js";
|
|
10
13
|
import { GEMINI_ELISION_NOTE, planGeminiLoopReclaim, previewGeminiToolResponseText, } from "../../context/geminiLoopGuard.js";
|
|
11
14
|
import { getAvailableInputTokens, getContextWindowSize, } from "../../constants/contextWindows.js";
|
|
@@ -14,7 +17,7 @@ import { withTimeout } from "../../utils/async/index.js";
|
|
|
14
17
|
import { estimateTokens } from "../../utils/tokenEstimation.js";
|
|
15
18
|
import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
16
19
|
import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
|
|
17
|
-
import { buildGeminiResponseSchema, buildNativeConfig, collectStreamChunks,
|
|
20
|
+
import { buildGeminiResponseSchema, buildNativeConfig, collectStreamChunks, computeMaxSteps, createContextGuard, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, refreshNativeToolDeclarations, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
|
|
18
21
|
import { createStreamChannel } from "../../core/streamChannel.js";
|
|
19
22
|
import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
|
|
20
23
|
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
@@ -649,8 +652,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
649
652
|
});
|
|
650
653
|
// Convert tools
|
|
651
654
|
let toolsConfig;
|
|
652
|
-
let executeMap = new DedupExecuteMap();
|
|
653
|
-
let originalNameMap = new Map();
|
|
654
655
|
let declarationsResult;
|
|
655
656
|
if (options.tools &&
|
|
656
657
|
Object.keys(options.tools).length > 0 &&
|
|
@@ -658,8 +659,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
658
659
|
const result = toNativeToolDeclarations(options.tools, "functionDeclarations");
|
|
659
660
|
declarationsResult = result;
|
|
660
661
|
toolsConfig = result.toolsConfig;
|
|
661
|
-
executeMap = result.executeMap;
|
|
662
|
-
originalNameMap = result.originalNameMap;
|
|
663
662
|
logger.debug("[GoogleAIStudio] Converted tools for native SDK", {
|
|
664
663
|
toolCount: toolsConfig[0].functionDeclarations.length,
|
|
665
664
|
toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
|
|
@@ -720,8 +719,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
720
719
|
let totalCacheReadTokens = 0;
|
|
721
720
|
let totalReasoningTokens = 0;
|
|
722
721
|
let step = 0;
|
|
723
|
-
let completedWithFinalAnswer = false;
|
|
724
|
-
const failedTools = new Map();
|
|
725
722
|
// Cheap trigger for the in-turn reclaim, mirroring the Vertex twin.
|
|
726
723
|
// Planning serializes the WHOLE accumulated history to estimate it,
|
|
727
724
|
// so running it unconditionally charges that once per step for the
|
|
@@ -731,98 +728,88 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
731
728
|
const contextGuard = createContextGuard(getContextWindowSize("googleAiStudio", modelName));
|
|
732
729
|
try {
|
|
733
730
|
// Agentic loop for tool calling
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
731
|
+
// The turn itself now runs on the shared engine: the step cap,
|
|
732
|
+
// tool dispatch, the failure breaker and usage accumulation are
|
|
733
|
+
// engine-owned. Everything below is the provider half — building
|
|
734
|
+
// one request, and the per-step side effects the old loop
|
|
735
|
+
// performed inline.
|
|
736
|
+
const baseAdapter = createGeminiLoopAdapter({
|
|
737
|
+
providerLabel: "GoogleAIStudio",
|
|
738
|
+
maxSteps,
|
|
739
|
+
// Same threshold the hand-rolled dispatcher used, which is
|
|
740
|
+
// what makes an always-failing tool dispatch exactly twice.
|
|
741
|
+
toolFailureBreaker: { maxRetries: DEFAULT_TOOL_MAX_RETRIES },
|
|
742
|
+
liveTools: options.tools ?? {},
|
|
743
|
+
...(declarationsResult
|
|
744
|
+
? { declarations: declarationsResult }
|
|
745
|
+
: {}),
|
|
746
|
+
buildRequest: (contents) => ({
|
|
747
|
+
model: modelName,
|
|
748
|
+
contents,
|
|
749
|
+
config,
|
|
750
|
+
...(composedSignal
|
|
751
|
+
? { httpOptions: { signal: composedSignal } }
|
|
752
|
+
: {}),
|
|
753
|
+
}),
|
|
754
|
+
sendStep: async (request) => client.models.generateContentStream(request),
|
|
755
|
+
noteUsage: (inputTokens, outputTokens) => {
|
|
756
|
+
contextGuard.noteUsage(inputTokens, outputTokens);
|
|
757
|
+
},
|
|
758
|
+
// Pure: the engine assigns what this returns. The old loop
|
|
759
|
+
// reclaimed in place because it owned `currentContents`.
|
|
760
|
+
planReclaim: (contents, stepIndex) => {
|
|
761
|
+
if (stepIndex !== 0 && !contextGuard.shouldStop()) {
|
|
762
|
+
return undefined;
|
|
744
763
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
? composedSignal.reason
|
|
749
|
-
: new Error("Request aborted");
|
|
750
|
-
}
|
|
751
|
-
step++;
|
|
752
|
-
// Mid-turn discovery sync: advertise tools hydrated into the
|
|
753
|
-
// live record by search_tools during the previous step.
|
|
754
|
-
if (declarationsResult) {
|
|
755
|
-
refreshNativeToolDeclarations(options.tools, declarationsResult);
|
|
756
|
-
}
|
|
757
|
-
logger.debug(`[GoogleAIStudio] Native SDK step ${step}/${maxSteps}`);
|
|
758
|
-
try {
|
|
759
|
-
const rawStream = await client.models.generateContentStream({
|
|
760
|
-
model: modelName,
|
|
761
|
-
contents: currentContents,
|
|
762
|
-
config,
|
|
763
|
-
...(composedSignal
|
|
764
|
-
? { httpOptions: { signal: composedSignal } }
|
|
765
|
-
: {}),
|
|
766
|
-
});
|
|
767
|
-
// For every step, use incremental collection so text parts
|
|
768
|
-
// are pushed to the channel as they arrive. For intermediate
|
|
769
|
-
// steps (those that produce function calls) we still need the
|
|
770
|
-
// complete rawResponseParts for pushModelResponseToHistory,
|
|
771
|
-
// which collectStreamChunksIncremental provides at stream end.
|
|
772
|
-
const chunkResult = await collectStreamChunksIncremental(rawStream, channel);
|
|
773
|
-
totalInputTokens += chunkResult.inputTokens;
|
|
774
|
-
totalOutputTokens += chunkResult.outputTokens;
|
|
775
|
-
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
776
|
-
totalReasoningTokens += chunkResult.reasoningTokens ?? 0;
|
|
777
|
-
// `inputTokens` is this step's promptTokenCount — the FULL
|
|
778
|
-
// prompt size for the request just made, which is what the
|
|
779
|
-
// guard projects the next request from.
|
|
780
|
-
contextGuard.noteUsage(chunkResult.inputTokens, chunkResult.outputTokens);
|
|
781
|
-
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
782
|
-
// If no function calls, this was the final step — channel
|
|
783
|
-
// already received all text parts incrementally.
|
|
784
|
-
if (chunkResult.stepFunctionCalls.length === 0) {
|
|
785
|
-
completedWithFinalAnswer = true;
|
|
786
|
-
break;
|
|
764
|
+
const working = [...contents];
|
|
765
|
+
if (!reclaimAiStudioContext(working, modelName, contextGuard.projectedNextPromptTokens)) {
|
|
766
|
+
return undefined;
|
|
787
767
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
768
|
+
contextGuard.resetAfterReclaim();
|
|
769
|
+
return working;
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
// Wrapped because these fire once PER STEP in the loop this
|
|
773
|
+
// replaces, and buildToolResultMessages is the only hook that
|
|
774
|
+
// runs per step with exactly that step's results. Reading them
|
|
775
|
+
// off the turn's final result would batch every step into one
|
|
776
|
+
// late write and lose the per-step thought signature.
|
|
777
|
+
const adapter = {
|
|
778
|
+
...baseAdapter,
|
|
779
|
+
buildToolResultMessages: (contents, stepResult, toolResults) => {
|
|
780
|
+
step++;
|
|
781
|
+
for (const call of stepResult.toolCalls) {
|
|
791
782
|
span.addEvent("gen_ai.tool_call", {
|
|
792
|
-
"tool.name":
|
|
783
|
+
"tool.name": call.name,
|
|
793
784
|
"tool.step": step,
|
|
794
785
|
});
|
|
795
786
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
toolExecutions
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
|
|
816
|
-
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
817
|
-
toolName: tc.toolName,
|
|
818
|
-
args: tc.args,
|
|
787
|
+
lastStepText = stepResult.text || lastStepText;
|
|
788
|
+
for (const call of stepResult.toolCalls) {
|
|
789
|
+
allToolCalls.push({
|
|
790
|
+
toolName: call.name,
|
|
791
|
+
args: call.args,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
for (const result of toolResults) {
|
|
795
|
+
toolExecutions.push({
|
|
796
|
+
name: result.name,
|
|
797
|
+
input: result.args,
|
|
798
|
+
output: result.output,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
if (toolResults.length > 0) {
|
|
802
|
+
const stepThoughtSig = extractThoughtSignature(stepResult.raw.rawResponseParts);
|
|
803
|
+
withTimeout(this.handleToolExecutionStorage(stepResult.toolCalls.map((call, i) => ({
|
|
804
|
+
toolName: call.name,
|
|
805
|
+
args: call.args,
|
|
819
806
|
...(i === 0 && stepThoughtSig
|
|
820
807
|
? { thoughtSignature: stepThoughtSig }
|
|
821
808
|
: {}),
|
|
822
809
|
stepIndex: step,
|
|
823
|
-
})),
|
|
824
|
-
toolName:
|
|
825
|
-
output:
|
|
810
|
+
})), toolResults.map((result) => ({
|
|
811
|
+
toolName: result.name,
|
|
812
|
+
output: result.output,
|
|
826
813
|
stepIndex: step,
|
|
827
814
|
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
828
815
|
logger.warn("[GoogleAIStudio] Failed to store native tool executions", {
|
|
@@ -832,28 +819,57 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
832
819
|
});
|
|
833
820
|
});
|
|
834
821
|
}
|
|
835
|
-
|
|
836
|
-
// only accepts "user" and "model" as valid roles in contents.
|
|
837
|
-
// Function/tool responses must use role: "user" (matching the
|
|
838
|
-
// SDK's own automaticFunctionCalling implementation).
|
|
839
|
-
currentContents.push({
|
|
840
|
-
role: "user",
|
|
841
|
-
parts: functionResponses,
|
|
842
|
-
});
|
|
822
|
+
const next = baseAdapter.buildToolResultMessages(contents, stepResult, toolResults);
|
|
843
823
|
// Project this step's growth: the appended tool results ride
|
|
844
824
|
// the next prompt, which the provider has not reported on yet.
|
|
845
825
|
try {
|
|
846
|
-
|
|
826
|
+
const appended = next[next.length - 1];
|
|
827
|
+
contextGuard.noteAppendedChars(JSON.stringify(appended?.parts ?? []).length);
|
|
847
828
|
}
|
|
848
829
|
catch {
|
|
849
830
|
/* estimation is best-effort — never break the loop */
|
|
850
831
|
}
|
|
832
|
+
return next;
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
const engineTools = {};
|
|
836
|
+
for (const [name, tool] of Object.entries(options.tools ?? {})) {
|
|
837
|
+
const execute = tool?.execute;
|
|
838
|
+
if (!execute) {
|
|
839
|
+
continue;
|
|
851
840
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
841
|
+
engineTools[name] = {
|
|
842
|
+
execute: async (args, opts) => execute(args, opts),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
const { stream: engineStream, resultPromise } = runAgenticLoop(adapter, currentContents, {
|
|
846
|
+
tools: engineTools,
|
|
847
|
+
...(composedSignal ? { abortSignal: composedSignal } : {}),
|
|
848
|
+
});
|
|
849
|
+
const pump = (async () => {
|
|
850
|
+
for await (const chunk of engineStream) {
|
|
851
|
+
channel.push(chunk);
|
|
855
852
|
}
|
|
853
|
+
})();
|
|
854
|
+
let engineResult;
|
|
855
|
+
try {
|
|
856
|
+
engineResult = await resultPromise;
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
await pump.catch(() => { });
|
|
860
|
+
logger.error("[GoogleAIStudio] Native SDK error", error);
|
|
861
|
+
throw this.handleProviderError(error);
|
|
856
862
|
}
|
|
863
|
+
await pump;
|
|
864
|
+
totalInputTokens += engineResult.usage.inputTokens;
|
|
865
|
+
totalOutputTokens += engineResult.usage.outputTokens;
|
|
866
|
+
totalCacheReadTokens += engineResult.usage.cacheReadTokens ?? 0;
|
|
867
|
+
totalReasoningTokens += engineResult.usage.reasoningTokens ?? 0;
|
|
868
|
+
// The turn produced a final answer when the model stopped
|
|
869
|
+
// calling tools of its own accord, rather than being cut off at
|
|
870
|
+
// the cap.
|
|
871
|
+
const completedWithFinalAnswer = engineResult.toolCalls.length === 0 ||
|
|
872
|
+
engineResult.finishReason !== "tool-calls";
|
|
857
873
|
// Handle max-steps termination: if the model was still calling
|
|
858
874
|
// tools when we hit the limit, push a synthetic final message.
|
|
859
875
|
const hitStepLimitWithoutFinalAnswer = step >= maxSteps && !completedWithFinalAnswer;
|
|
@@ -6,6 +6,9 @@ import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "
|
|
|
6
6
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
|
|
7
7
|
import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
|
|
8
8
|
import { logger } from "../../utils/logger.js";
|
|
9
|
+
import { createGeminiLoopAdapter } from "../../core/geminiLoopAdapter.js";
|
|
10
|
+
import { runAgenticLoop } from "../../core/loopEngine.js";
|
|
11
|
+
import { DEFAULT_TOOL_MAX_RETRIES } from "../../core/constants.js";
|
|
9
12
|
import { isToolsSchemaExclusionInForce } from "../../core/modules/structuredOutputPolicy.js";
|
|
10
13
|
import { GEMINI_ELISION_NOTE, planGeminiLoopReclaim, previewGeminiToolResponseText, } from "../../context/geminiLoopGuard.js";
|
|
11
14
|
import { getAvailableInputTokens, getContextWindowSize, } from "../../constants/contextWindows.js";
|
|
@@ -14,7 +17,7 @@ import { withTimeout } from "../../utils/async/index.js";
|
|
|
14
17
|
import { estimateTokens } from "../../utils/tokenEstimation.js";
|
|
15
18
|
import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
16
19
|
import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
|
|
17
|
-
import { buildGeminiResponseSchema, buildNativeConfig, collectStreamChunks,
|
|
20
|
+
import { buildGeminiResponseSchema, buildNativeConfig, collectStreamChunks, computeMaxSteps, createContextGuard, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, refreshNativeToolDeclarations, DedupExecuteMap, } from "../googleNativeGemini3/index.js";
|
|
18
21
|
import { createStreamChannel } from "../../core/streamChannel.js";
|
|
19
22
|
import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
|
|
20
23
|
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
@@ -649,8 +652,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
649
652
|
});
|
|
650
653
|
// Convert tools
|
|
651
654
|
let toolsConfig;
|
|
652
|
-
let executeMap = new DedupExecuteMap();
|
|
653
|
-
let originalNameMap = new Map();
|
|
654
655
|
let declarationsResult;
|
|
655
656
|
if (options.tools &&
|
|
656
657
|
Object.keys(options.tools).length > 0 &&
|
|
@@ -658,8 +659,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
658
659
|
const result = toNativeToolDeclarations(options.tools, "functionDeclarations");
|
|
659
660
|
declarationsResult = result;
|
|
660
661
|
toolsConfig = result.toolsConfig;
|
|
661
|
-
executeMap = result.executeMap;
|
|
662
|
-
originalNameMap = result.originalNameMap;
|
|
663
662
|
logger.debug("[GoogleAIStudio] Converted tools for native SDK", {
|
|
664
663
|
toolCount: toolsConfig[0].functionDeclarations.length,
|
|
665
664
|
toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
|
|
@@ -720,8 +719,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
720
719
|
let totalCacheReadTokens = 0;
|
|
721
720
|
let totalReasoningTokens = 0;
|
|
722
721
|
let step = 0;
|
|
723
|
-
let completedWithFinalAnswer = false;
|
|
724
|
-
const failedTools = new Map();
|
|
725
722
|
// Cheap trigger for the in-turn reclaim, mirroring the Vertex twin.
|
|
726
723
|
// Planning serializes the WHOLE accumulated history to estimate it,
|
|
727
724
|
// so running it unconditionally charges that once per step for the
|
|
@@ -731,98 +728,88 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
731
728
|
const contextGuard = createContextGuard(getContextWindowSize("googleAiStudio", modelName));
|
|
732
729
|
try {
|
|
733
730
|
// Agentic loop for tool calling
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
731
|
+
// The turn itself now runs on the shared engine: the step cap,
|
|
732
|
+
// tool dispatch, the failure breaker and usage accumulation are
|
|
733
|
+
// engine-owned. Everything below is the provider half — building
|
|
734
|
+
// one request, and the per-step side effects the old loop
|
|
735
|
+
// performed inline.
|
|
736
|
+
const baseAdapter = createGeminiLoopAdapter({
|
|
737
|
+
providerLabel: "GoogleAIStudio",
|
|
738
|
+
maxSteps,
|
|
739
|
+
// Same threshold the hand-rolled dispatcher used, which is
|
|
740
|
+
// what makes an always-failing tool dispatch exactly twice.
|
|
741
|
+
toolFailureBreaker: { maxRetries: DEFAULT_TOOL_MAX_RETRIES },
|
|
742
|
+
liveTools: options.tools ?? {},
|
|
743
|
+
...(declarationsResult
|
|
744
|
+
? { declarations: declarationsResult }
|
|
745
|
+
: {}),
|
|
746
|
+
buildRequest: (contents) => ({
|
|
747
|
+
model: modelName,
|
|
748
|
+
contents,
|
|
749
|
+
config,
|
|
750
|
+
...(composedSignal
|
|
751
|
+
? { httpOptions: { signal: composedSignal } }
|
|
752
|
+
: {}),
|
|
753
|
+
}),
|
|
754
|
+
sendStep: async (request) => client.models.generateContentStream(request),
|
|
755
|
+
noteUsage: (inputTokens, outputTokens) => {
|
|
756
|
+
contextGuard.noteUsage(inputTokens, outputTokens);
|
|
757
|
+
},
|
|
758
|
+
// Pure: the engine assigns what this returns. The old loop
|
|
759
|
+
// reclaimed in place because it owned `currentContents`.
|
|
760
|
+
planReclaim: (contents, stepIndex) => {
|
|
761
|
+
if (stepIndex !== 0 && !contextGuard.shouldStop()) {
|
|
762
|
+
return undefined;
|
|
744
763
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
? composedSignal.reason
|
|
749
|
-
: new Error("Request aborted");
|
|
750
|
-
}
|
|
751
|
-
step++;
|
|
752
|
-
// Mid-turn discovery sync: advertise tools hydrated into the
|
|
753
|
-
// live record by search_tools during the previous step.
|
|
754
|
-
if (declarationsResult) {
|
|
755
|
-
refreshNativeToolDeclarations(options.tools, declarationsResult);
|
|
756
|
-
}
|
|
757
|
-
logger.debug(`[GoogleAIStudio] Native SDK step ${step}/${maxSteps}`);
|
|
758
|
-
try {
|
|
759
|
-
const rawStream = await client.models.generateContentStream({
|
|
760
|
-
model: modelName,
|
|
761
|
-
contents: currentContents,
|
|
762
|
-
config,
|
|
763
|
-
...(composedSignal
|
|
764
|
-
? { httpOptions: { signal: composedSignal } }
|
|
765
|
-
: {}),
|
|
766
|
-
});
|
|
767
|
-
// For every step, use incremental collection so text parts
|
|
768
|
-
// are pushed to the channel as they arrive. For intermediate
|
|
769
|
-
// steps (those that produce function calls) we still need the
|
|
770
|
-
// complete rawResponseParts for pushModelResponseToHistory,
|
|
771
|
-
// which collectStreamChunksIncremental provides at stream end.
|
|
772
|
-
const chunkResult = await collectStreamChunksIncremental(rawStream, channel);
|
|
773
|
-
totalInputTokens += chunkResult.inputTokens;
|
|
774
|
-
totalOutputTokens += chunkResult.outputTokens;
|
|
775
|
-
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
776
|
-
totalReasoningTokens += chunkResult.reasoningTokens ?? 0;
|
|
777
|
-
// `inputTokens` is this step's promptTokenCount — the FULL
|
|
778
|
-
// prompt size for the request just made, which is what the
|
|
779
|
-
// guard projects the next request from.
|
|
780
|
-
contextGuard.noteUsage(chunkResult.inputTokens, chunkResult.outputTokens);
|
|
781
|
-
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
782
|
-
// If no function calls, this was the final step — channel
|
|
783
|
-
// already received all text parts incrementally.
|
|
784
|
-
if (chunkResult.stepFunctionCalls.length === 0) {
|
|
785
|
-
completedWithFinalAnswer = true;
|
|
786
|
-
break;
|
|
764
|
+
const working = [...contents];
|
|
765
|
+
if (!reclaimAiStudioContext(working, modelName, contextGuard.projectedNextPromptTokens)) {
|
|
766
|
+
return undefined;
|
|
787
767
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
768
|
+
contextGuard.resetAfterReclaim();
|
|
769
|
+
return working;
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
// Wrapped because these fire once PER STEP in the loop this
|
|
773
|
+
// replaces, and buildToolResultMessages is the only hook that
|
|
774
|
+
// runs per step with exactly that step's results. Reading them
|
|
775
|
+
// off the turn's final result would batch every step into one
|
|
776
|
+
// late write and lose the per-step thought signature.
|
|
777
|
+
const adapter = {
|
|
778
|
+
...baseAdapter,
|
|
779
|
+
buildToolResultMessages: (contents, stepResult, toolResults) => {
|
|
780
|
+
step++;
|
|
781
|
+
for (const call of stepResult.toolCalls) {
|
|
791
782
|
span.addEvent("gen_ai.tool_call", {
|
|
792
|
-
"tool.name":
|
|
783
|
+
"tool.name": call.name,
|
|
793
784
|
"tool.step": step,
|
|
794
785
|
});
|
|
795
786
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
toolExecutions
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
|
|
816
|
-
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
817
|
-
toolName: tc.toolName,
|
|
818
|
-
args: tc.args,
|
|
787
|
+
lastStepText = stepResult.text || lastStepText;
|
|
788
|
+
for (const call of stepResult.toolCalls) {
|
|
789
|
+
allToolCalls.push({
|
|
790
|
+
toolName: call.name,
|
|
791
|
+
args: call.args,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
for (const result of toolResults) {
|
|
795
|
+
toolExecutions.push({
|
|
796
|
+
name: result.name,
|
|
797
|
+
input: result.args,
|
|
798
|
+
output: result.output,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
if (toolResults.length > 0) {
|
|
802
|
+
const stepThoughtSig = extractThoughtSignature(stepResult.raw.rawResponseParts);
|
|
803
|
+
withTimeout(this.handleToolExecutionStorage(stepResult.toolCalls.map((call, i) => ({
|
|
804
|
+
toolName: call.name,
|
|
805
|
+
args: call.args,
|
|
819
806
|
...(i === 0 && stepThoughtSig
|
|
820
807
|
? { thoughtSignature: stepThoughtSig }
|
|
821
808
|
: {}),
|
|
822
809
|
stepIndex: step,
|
|
823
|
-
})),
|
|
824
|
-
toolName:
|
|
825
|
-
output:
|
|
810
|
+
})), toolResults.map((result) => ({
|
|
811
|
+
toolName: result.name,
|
|
812
|
+
output: result.output,
|
|
826
813
|
stepIndex: step,
|
|
827
814
|
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
828
815
|
logger.warn("[GoogleAIStudio] Failed to store native tool executions", {
|
|
@@ -832,28 +819,57 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
832
819
|
});
|
|
833
820
|
});
|
|
834
821
|
}
|
|
835
|
-
|
|
836
|
-
// only accepts "user" and "model" as valid roles in contents.
|
|
837
|
-
// Function/tool responses must use role: "user" (matching the
|
|
838
|
-
// SDK's own automaticFunctionCalling implementation).
|
|
839
|
-
currentContents.push({
|
|
840
|
-
role: "user",
|
|
841
|
-
parts: functionResponses,
|
|
842
|
-
});
|
|
822
|
+
const next = baseAdapter.buildToolResultMessages(contents, stepResult, toolResults);
|
|
843
823
|
// Project this step's growth: the appended tool results ride
|
|
844
824
|
// the next prompt, which the provider has not reported on yet.
|
|
845
825
|
try {
|
|
846
|
-
|
|
826
|
+
const appended = next[next.length - 1];
|
|
827
|
+
contextGuard.noteAppendedChars(JSON.stringify(appended?.parts ?? []).length);
|
|
847
828
|
}
|
|
848
829
|
catch {
|
|
849
830
|
/* estimation is best-effort — never break the loop */
|
|
850
831
|
}
|
|
832
|
+
return next;
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
const engineTools = {};
|
|
836
|
+
for (const [name, tool] of Object.entries(options.tools ?? {})) {
|
|
837
|
+
const execute = tool?.execute;
|
|
838
|
+
if (!execute) {
|
|
839
|
+
continue;
|
|
851
840
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
841
|
+
engineTools[name] = {
|
|
842
|
+
execute: async (args, opts) => execute(args, opts),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
const { stream: engineStream, resultPromise } = runAgenticLoop(adapter, currentContents, {
|
|
846
|
+
tools: engineTools,
|
|
847
|
+
...(composedSignal ? { abortSignal: composedSignal } : {}),
|
|
848
|
+
});
|
|
849
|
+
const pump = (async () => {
|
|
850
|
+
for await (const chunk of engineStream) {
|
|
851
|
+
channel.push(chunk);
|
|
855
852
|
}
|
|
853
|
+
})();
|
|
854
|
+
let engineResult;
|
|
855
|
+
try {
|
|
856
|
+
engineResult = await resultPromise;
|
|
857
|
+
}
|
|
858
|
+
catch (error) {
|
|
859
|
+
await pump.catch(() => { });
|
|
860
|
+
logger.error("[GoogleAIStudio] Native SDK error", error);
|
|
861
|
+
throw this.handleProviderError(error);
|
|
856
862
|
}
|
|
863
|
+
await pump;
|
|
864
|
+
totalInputTokens += engineResult.usage.inputTokens;
|
|
865
|
+
totalOutputTokens += engineResult.usage.outputTokens;
|
|
866
|
+
totalCacheReadTokens += engineResult.usage.cacheReadTokens ?? 0;
|
|
867
|
+
totalReasoningTokens += engineResult.usage.reasoningTokens ?? 0;
|
|
868
|
+
// The turn produced a final answer when the model stopped
|
|
869
|
+
// calling tools of its own accord, rather than being cut off at
|
|
870
|
+
// the cap.
|
|
871
|
+
const completedWithFinalAnswer = engineResult.toolCalls.length === 0 ||
|
|
872
|
+
engineResult.finishReason !== "tool-calls";
|
|
857
873
|
// Handle max-steps termination: if the model was still calling
|
|
858
874
|
// tools when we hit the limit, push a synthetic final message.
|
|
859
875
|
const hitStepLimitWithoutFinalAnswer = step >= maxSteps && !completedWithFinalAnswer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.11.
|
|
3
|
+
"version": "11.11.3",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|