@juspay/neurolink 9.65.0 → 9.65.1
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/agent/directTools.js +11 -3
- package/dist/browser/neurolink.min.js +352 -352
- package/dist/core/constants.d.ts +1 -0
- package/dist/core/constants.js +3 -0
- package/dist/core/redisConversationMemoryManager.js +0 -6
- package/dist/lib/agent/directTools.js +11 -3
- package/dist/lib/core/constants.d.ts +1 -0
- package/dist/lib/core/constants.js +3 -0
- package/dist/lib/core/redisConversationMemoryManager.js +0 -6
- package/dist/lib/providers/googleAiStudio.js +82 -5
- package/dist/lib/providers/googleNativeGemini3.d.ts +2 -5
- package/dist/lib/providers/googleNativeGemini3.js +103 -8
- package/dist/lib/providers/googleVertex.js +466 -164
- package/dist/lib/types/conversation.d.ts +16 -0
- package/dist/providers/googleAiStudio.js +82 -5
- package/dist/providers/googleNativeGemini3.d.ts +2 -5
- package/dist/providers/googleNativeGemini3.js +103 -8
- package/dist/providers/googleVertex.js +466 -164
- package/dist/types/conversation.d.ts +16 -0
- package/package.json +1 -1
package/dist/core/constants.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare const DEFAULT_TEMPERATURE = 0.7;
|
|
|
21
21
|
export declare const DEFAULT_TIMEOUT = 60000;
|
|
22
22
|
export declare const DEFAULT_MAX_STEPS = 200;
|
|
23
23
|
export declare const DEFAULT_TOOL_MAX_RETRIES = 2;
|
|
24
|
+
export declare const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
24
25
|
export declare const STEP_LIMITS: {
|
|
25
26
|
min: number;
|
|
26
27
|
max: number;
|
package/dist/core/constants.js
CHANGED
|
@@ -107,6 +107,9 @@ export const DEFAULT_TEMPERATURE = 0.7;
|
|
|
107
107
|
export const DEFAULT_TIMEOUT = 60000;
|
|
108
108
|
export const DEFAULT_MAX_STEPS = 200;
|
|
109
109
|
export const DEFAULT_TOOL_MAX_RETRIES = 2; // Maximum retries per tool before permanently failing
|
|
110
|
+
// Fire-and-forget tool storage writes (Redis). 5s is generous for a single
|
|
111
|
+
// Redis write; if breached, the .catch logs a warning.
|
|
112
|
+
export const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
110
113
|
// Step execution limits
|
|
111
114
|
export const STEP_LIMITS = {
|
|
112
115
|
min: 1,
|
|
@@ -951,12 +951,6 @@ User message: "${userMessage}"`;
|
|
|
951
951
|
provider: this.config.summarizationProvider || "vertex",
|
|
952
952
|
model: this.config.summarizationModel || "gemini-2.5-flash",
|
|
953
953
|
disableTools: true, // Title generation doesn't need tools — saves ~600 tokens of tool descriptions
|
|
954
|
-
// A 20-25 letter title is well under 64 tokens; capping prevents
|
|
955
|
-
// Vertex 400 INVALID_ARGUMENT — Gemini 2.5 Flash on the native
|
|
956
|
-
// @google/genai SDK rejects requests with no maxOutputTokens cap
|
|
957
|
-
// when the prompt is short, which silently broke every
|
|
958
|
-
// `conversationMemory.enabled + context` test path.
|
|
959
|
-
maxTokens: 64,
|
|
960
954
|
});
|
|
961
955
|
// Clean up the generated title
|
|
962
956
|
let title = result.content?.trim() || "New Conversation";
|
|
@@ -547,9 +547,16 @@ export const directAgentTools = {
|
|
|
547
547
|
// It is only included in directAgentTools when NEUROLINK_ENABLE_BASH_TOOL=true or
|
|
548
548
|
// toolConfig.enableBashTool is explicitly set to true. See shouldEnableBashTool() in toolUtils.ts.
|
|
549
549
|
websearchGrounding: tool({
|
|
550
|
-
description: "Search
|
|
550
|
+
description: "Performs a Google Search and returns a summarized answer with source citations. Always check the current date before constructing the query. Use whenever the answer depends on time-sensitive facts or requires verification against real-world sources.",
|
|
551
551
|
inputSchema: z.object({
|
|
552
|
-
query: z
|
|
552
|
+
query: z
|
|
553
|
+
.string()
|
|
554
|
+
.trim()
|
|
555
|
+
.min(1, { message: "must be a non-empty search string" })
|
|
556
|
+
.refine((v) => v.toLowerCase() !== "undefined", {
|
|
557
|
+
message: 'must not be the literal string "undefined" — pass a real search query',
|
|
558
|
+
})
|
|
559
|
+
.describe("The search query string to look up on the web."),
|
|
553
560
|
maxResults: z
|
|
554
561
|
.number()
|
|
555
562
|
.optional()
|
|
@@ -581,7 +588,8 @@ export const directAgentTools = {
|
|
|
581
588
|
project: hasProjectId,
|
|
582
589
|
location: projectLocation,
|
|
583
590
|
});
|
|
584
|
-
const websearchModel =
|
|
591
|
+
const websearchModel = process.env.NEUROLINK_WEBSEARCH_MODEL?.trim() ||
|
|
592
|
+
"gemini-2.5-flash-lite";
|
|
585
593
|
const model = vertex_ai.getGenerativeModel({
|
|
586
594
|
model: websearchModel,
|
|
587
595
|
tools: createGoogleSearchTools(),
|
|
@@ -21,6 +21,7 @@ export declare const DEFAULT_TEMPERATURE = 0.7;
|
|
|
21
21
|
export declare const DEFAULT_TIMEOUT = 60000;
|
|
22
22
|
export declare const DEFAULT_MAX_STEPS = 200;
|
|
23
23
|
export declare const DEFAULT_TOOL_MAX_RETRIES = 2;
|
|
24
|
+
export declare const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
24
25
|
export declare const STEP_LIMITS: {
|
|
25
26
|
min: number;
|
|
26
27
|
max: number;
|
|
@@ -107,6 +107,9 @@ export const DEFAULT_TEMPERATURE = 0.7;
|
|
|
107
107
|
export const DEFAULT_TIMEOUT = 60000;
|
|
108
108
|
export const DEFAULT_MAX_STEPS = 200;
|
|
109
109
|
export const DEFAULT_TOOL_MAX_RETRIES = 2; // Maximum retries per tool before permanently failing
|
|
110
|
+
// Fire-and-forget tool storage writes (Redis). 5s is generous for a single
|
|
111
|
+
// Redis write; if breached, the .catch logs a warning.
|
|
112
|
+
export const TOOL_STORAGE_TIMEOUT_MS = 5000;
|
|
110
113
|
// Step execution limits
|
|
111
114
|
export const STEP_LIMITS = {
|
|
112
115
|
min: 1,
|
|
@@ -951,12 +951,6 @@ User message: "${userMessage}"`;
|
|
|
951
951
|
provider: this.config.summarizationProvider || "vertex",
|
|
952
952
|
model: this.config.summarizationModel || "gemini-2.5-flash",
|
|
953
953
|
disableTools: true, // Title generation doesn't need tools — saves ~600 tokens of tool descriptions
|
|
954
|
-
// A 20-25 letter title is well under 64 tokens; capping prevents
|
|
955
|
-
// Vertex 400 INVALID_ARGUMENT — Gemini 2.5 Flash on the native
|
|
956
|
-
// @google/genai SDK rejects requests with no maxOutputTokens cap
|
|
957
|
-
// when the prompt is short, which silently broke every
|
|
958
|
-
// `conversationMemory.enabled + context` test path.
|
|
959
|
-
maxTokens: 64,
|
|
960
954
|
});
|
|
961
955
|
// Clean up the generated title
|
|
962
956
|
let title = result.content?.trim() || "New Conversation";
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {} from "ai";
|
|
2
2
|
import { ErrorCategory, ErrorSeverity, GoogleAIModels, } from "../constants/enums.js";
|
|
3
3
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
4
|
-
import { IMAGE_GENERATION_MODELS } from "../core/constants.js";
|
|
4
|
+
import { IMAGE_GENERATION_MODELS, TOOL_STORAGE_TIMEOUT_MS, } from "../core/constants.js";
|
|
5
5
|
import { processUnifiedFilesArray } from "../utils/messageBuilder.js";
|
|
6
6
|
import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "../telemetry/index.js";
|
|
7
7
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
8
8
|
import { ERROR_CODES, NeuroLinkError } from "../utils/errorHandling.js";
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
10
|
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
11
|
+
import { withTimeout } from "../utils/async/index.js";
|
|
11
12
|
import { estimateTokens } from "../utils/tokenEstimation.js";
|
|
12
|
-
import {
|
|
13
|
+
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
14
|
+
import { buildGeminiResponseSchema, buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps, createTextChannel, buildUserPartsWithMultimodal, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, pushModelResponseToHistory, } from "./googleNativeGemini3.js";
|
|
13
15
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
14
16
|
// Google AI Live API types now imported from ../types/providerSpecific.js
|
|
15
17
|
// Import proper types for multimodal message handling
|
|
@@ -557,6 +559,10 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
557
559
|
const channel = createTextChannel();
|
|
558
560
|
// Shared mutable state updated by the background agentic loop.
|
|
559
561
|
const allToolCalls = [];
|
|
562
|
+
// Mirror the Vertex Gemini stream path: track tool executions so
|
|
563
|
+
// the storage hook can persist real outputs and StreamResult can
|
|
564
|
+
// surface toolsUsed/toolExecutions for tool-bearing turns.
|
|
565
|
+
const toolExecutions = [];
|
|
560
566
|
// analyticsResolvers lets the background loop settle the analytics
|
|
561
567
|
// promise once token counts are known (after the loop completes).
|
|
562
568
|
let analyticsResolve;
|
|
@@ -627,7 +633,40 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
627
633
|
logger.debug(`[GoogleAIStudio] Executing ${chunkResult.stepFunctionCalls.length} function calls`);
|
|
628
634
|
// Add model response with ALL parts (including thoughtSignature) to history
|
|
629
635
|
pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
|
|
630
|
-
const
|
|
636
|
+
const toolCallsBefore = allToolCalls.length;
|
|
637
|
+
const toolExecsBefore = toolExecutions.length;
|
|
638
|
+
const functionResponses = await executeNativeToolCalls("[GoogleAIStudio]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, {
|
|
639
|
+
abortSignal: composedSignal,
|
|
640
|
+
originalNameMap,
|
|
641
|
+
toolExecutions,
|
|
642
|
+
});
|
|
643
|
+
// Persist this step's tool calls/results into conversation
|
|
644
|
+
// memory. Without this, tool_call / tool_result rows never
|
|
645
|
+
// reach Redis and the chat-history UI loses every tool
|
|
646
|
+
// invocation.
|
|
647
|
+
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
648
|
+
const stepToolExecs = toolExecutions.slice(toolExecsBefore);
|
|
649
|
+
if (stepToolCalls.length > 0 || stepToolExecs.length > 0) {
|
|
650
|
+
const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
|
|
651
|
+
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
652
|
+
toolName: tc.toolName,
|
|
653
|
+
args: tc.args,
|
|
654
|
+
...(i === 0 && stepThoughtSig
|
|
655
|
+
? { thoughtSignature: stepThoughtSig }
|
|
656
|
+
: {}),
|
|
657
|
+
stepIndex: step,
|
|
658
|
+
})), stepToolExecs.map((te) => ({
|
|
659
|
+
toolName: te.name,
|
|
660
|
+
output: te.output,
|
|
661
|
+
stepIndex: step,
|
|
662
|
+
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
663
|
+
logger.warn("[GoogleAIStudio] Failed to store native tool executions", {
|
|
664
|
+
error: error instanceof Error
|
|
665
|
+
? error.message
|
|
666
|
+
: String(error),
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
}
|
|
631
670
|
// Add function responses to history — the @google/genai SDK
|
|
632
671
|
// only accepts "user" and "model" as valid roles in contents.
|
|
633
672
|
// Function/tool responses must use role: "user" (matching the
|
|
@@ -684,7 +723,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
684
723
|
// Suppress unhandled-rejection warnings on loopPromise — errors are
|
|
685
724
|
// forwarded to the channel and will surface when the caller iterates.
|
|
686
725
|
loopPromise.catch(() => undefined);
|
|
687
|
-
|
|
726
|
+
const result = {
|
|
688
727
|
stream: channel.iterable,
|
|
689
728
|
provider: this.providerName,
|
|
690
729
|
model: modelName,
|
|
@@ -692,6 +731,20 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
692
731
|
analytics: analyticsPromise,
|
|
693
732
|
metadata,
|
|
694
733
|
};
|
|
734
|
+
// Surface tools-used + executions via getters so they resolve at
|
|
735
|
+
// access time, after the background loop has populated the live
|
|
736
|
+
// arrays. Same lazy pattern used for `structuredOutput` elsewhere.
|
|
737
|
+
Object.defineProperty(result, "toolsUsed", {
|
|
738
|
+
enumerable: true,
|
|
739
|
+
configurable: true,
|
|
740
|
+
get: () => allToolCalls.map((tc) => tc.toolName),
|
|
741
|
+
});
|
|
742
|
+
Object.defineProperty(result, "toolExecutions", {
|
|
743
|
+
enumerable: true,
|
|
744
|
+
configurable: true,
|
|
745
|
+
get: () => transformToolExecutions(toolExecutions),
|
|
746
|
+
});
|
|
747
|
+
return result;
|
|
695
748
|
}
|
|
696
749
|
finally {
|
|
697
750
|
// Timeout controller cleanup is managed inside the background loop
|
|
@@ -822,11 +875,35 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
822
875
|
// Add model response with ALL parts (including thoughtSignature) to history
|
|
823
876
|
// This is critical for Gemini 3 - it requires thought signatures in subsequent turns
|
|
824
877
|
pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
|
|
878
|
+
const toolCallsBefore = allToolCalls.length;
|
|
879
|
+
const toolExecsBefore = toolExecutions.length;
|
|
825
880
|
const functionResponses = await executeNativeToolCalls("[GoogleAIStudio]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, {
|
|
826
881
|
toolExecutions,
|
|
827
882
|
abortSignal: composedSignal,
|
|
828
883
|
originalNameMap,
|
|
829
884
|
});
|
|
885
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
886
|
+
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
887
|
+
const stepToolExecs = toolExecutions.slice(toolExecsBefore);
|
|
888
|
+
if (stepToolCalls.length > 0 || stepToolExecs.length > 0) {
|
|
889
|
+
const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
|
|
890
|
+
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
891
|
+
toolName: tc.toolName,
|
|
892
|
+
args: tc.args,
|
|
893
|
+
...(i === 0 && stepThoughtSig
|
|
894
|
+
? { thoughtSignature: stepThoughtSig }
|
|
895
|
+
: {}),
|
|
896
|
+
stepIndex: step,
|
|
897
|
+
})), stepToolExecs.map((te) => ({
|
|
898
|
+
toolName: te.name,
|
|
899
|
+
output: te.output,
|
|
900
|
+
stepIndex: step,
|
|
901
|
+
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
902
|
+
logger.warn("[GoogleAIStudio] Failed to store native generate tool executions", {
|
|
903
|
+
error: error instanceof Error ? error.message : String(error),
|
|
904
|
+
});
|
|
905
|
+
});
|
|
906
|
+
}
|
|
830
907
|
// Add function responses to history — the @google/genai SDK
|
|
831
908
|
// only accepts "user" and "model" as valid roles in contents.
|
|
832
909
|
// Function/tool responses must use role: "user" (matching the
|
|
@@ -862,7 +939,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
862
939
|
},
|
|
863
940
|
responseTime,
|
|
864
941
|
toolsUsed: allToolCalls.map((tc) => tc.toolName),
|
|
865
|
-
toolExecutions: toolExecutions,
|
|
942
|
+
toolExecutions: transformToolExecutions(toolExecutions),
|
|
866
943
|
enhancedWithTools: allToolCalls.length > 0,
|
|
867
944
|
};
|
|
868
945
|
return this.enhanceResult(baseResult, options, startTime);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* providers so they can share a single implementation.
|
|
10
10
|
*/
|
|
11
11
|
import { type Tool } from "ai";
|
|
12
|
-
import type { ThinkingConfig, CollectedChunkResult, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput } from "../types/index.js";
|
|
12
|
+
import type { ThinkingConfig, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, TextChannel, VertexNativePart, GeminiMultimodalInput } from "../types/index.js";
|
|
13
13
|
export declare function sanitizeForGoogleFunctionName(name: string): string;
|
|
14
14
|
/**
|
|
15
15
|
* Resolve a sanitized Gemini tool name to one that is both unique within
|
|
@@ -218,10 +218,7 @@ export declare function buildGeminiResponseSchema(schema: unknown): Record<strin
|
|
|
218
218
|
export declare function prependConversationMessages(contents: Array<{
|
|
219
219
|
role: string;
|
|
220
220
|
parts: unknown[];
|
|
221
|
-
}>, conversationMessages?: Array<
|
|
222
|
-
role: string;
|
|
223
|
-
content: string;
|
|
224
|
-
}>): void;
|
|
221
|
+
}>, conversationMessages?: Array<ChatMessage | MinimalChatMessage>): void;
|
|
225
222
|
/**
|
|
226
223
|
* Build the `parts` array for the current user turn of a Gemini native
|
|
227
224
|
* `generateContent` request, including inline image + PDF blobs.
|
|
@@ -646,6 +646,10 @@ export async function executeNativeToolCalls(logLabel, stepFunctionCalls, execut
|
|
|
646
646
|
// original name. Falls back to the safe name if the map is missing or
|
|
647
647
|
// doesn't contain the call (e.g. tool added mid-conversation).
|
|
648
648
|
const externalName = (safeName) => options?.originalNameMap?.get(safeName) ?? safeName;
|
|
649
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
650
|
+
// `execute` wrapper (see src/lib/core/modules/ToolsManager.ts:355 and :790)
|
|
651
|
+
// around every tool's execute function. The native paths invoke that same
|
|
652
|
+
// wrapped execute via the executeMap, so emitting here would duplicate.
|
|
649
653
|
for (const call of stepFunctionCalls) {
|
|
650
654
|
const exposedName = externalName(call.name);
|
|
651
655
|
allToolCalls.push({ toolName: exposedName, args: call.args });
|
|
@@ -808,22 +812,113 @@ export function buildGeminiResponseSchema(schema) {
|
|
|
808
812
|
* - The current user input should be appended AFTER calling this helper
|
|
809
813
|
* so the prior turns appear first in chronological order.
|
|
810
814
|
*/
|
|
811
|
-
export function prependConversationMessages(contents,
|
|
815
|
+
export function prependConversationMessages(contents,
|
|
816
|
+
// Accept either the full ChatMessage shape (when callers pass real Redis-
|
|
817
|
+
// backed history) or the reduced MinimalChatMessage shape (tests / synthetic
|
|
818
|
+
// callers). Only role, content, tool, args, and metadata.* are read here.
|
|
819
|
+
conversationMessages) {
|
|
812
820
|
if (!conversationMessages || conversationMessages.length === 0) {
|
|
813
821
|
return;
|
|
814
822
|
}
|
|
823
|
+
// Walk prior turns building ordered segments. Tool_call / tool_result rows
|
|
824
|
+
// get grouped by (turnCounter, stepIndex) so parallel calls within a step
|
|
825
|
+
// stay together and don't bleed across turn boundaries. Regular user/
|
|
826
|
+
// assistant messages act as those boundaries.
|
|
827
|
+
//
|
|
828
|
+
// Without this reconstruction, a text-only mapper would strip tool rows
|
|
829
|
+
// from history — leaving the model unaware of any tools it called in
|
|
830
|
+
// prior turns. The grouped emit (model with functionCall parts → user
|
|
831
|
+
// with functionResponse parts) is what @google/genai's own
|
|
832
|
+
// automaticFunctionCalling produces, so the SDK validates it as a
|
|
833
|
+
// well-formed multi-turn conversation.
|
|
834
|
+
const stepMap = new Map();
|
|
835
|
+
const segments = [];
|
|
836
|
+
let turnCounter = 0;
|
|
837
|
+
const makeKey = (stepIndex) => `${turnCounter}:${stepIndex ?? "undefined"}`;
|
|
838
|
+
const getOrCreateStep = (stepIndex) => {
|
|
839
|
+
const key = makeKey(stepIndex);
|
|
840
|
+
const existing = stepMap.get(key);
|
|
841
|
+
if (existing) {
|
|
842
|
+
return existing;
|
|
843
|
+
}
|
|
844
|
+
const step = {
|
|
845
|
+
type: "tool_step",
|
|
846
|
+
callParts: [],
|
|
847
|
+
resultParts: [],
|
|
848
|
+
};
|
|
849
|
+
stepMap.set(key, step);
|
|
850
|
+
segments.push(step);
|
|
851
|
+
return step;
|
|
852
|
+
};
|
|
815
853
|
for (const msg of conversationMessages) {
|
|
816
|
-
if (msg.role
|
|
854
|
+
if (msg.role === "tool_call") {
|
|
855
|
+
const step = getOrCreateStep(msg.metadata?.stepIndex);
|
|
856
|
+
const fcPart = {
|
|
857
|
+
functionCall: {
|
|
858
|
+
name: msg.tool || "unknown",
|
|
859
|
+
args: msg.args || {},
|
|
860
|
+
},
|
|
861
|
+
};
|
|
862
|
+
if (msg.metadata?.thoughtSignature) {
|
|
863
|
+
fcPart.thoughtSignature = msg.metadata.thoughtSignature;
|
|
864
|
+
}
|
|
865
|
+
step.callParts.push(fcPart);
|
|
817
866
|
continue;
|
|
818
867
|
}
|
|
819
|
-
|
|
820
|
-
|
|
868
|
+
if (msg.role === "tool_result") {
|
|
869
|
+
const step = getOrCreateStep(msg.metadata?.stepIndex);
|
|
870
|
+
let responsePayload;
|
|
871
|
+
try {
|
|
872
|
+
responsePayload =
|
|
873
|
+
msg.content !== undefined && msg.content !== null
|
|
874
|
+
? { result: JSON.parse(msg.content) }
|
|
875
|
+
: { result: "success" };
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
responsePayload = { result: msg.content ?? "success" };
|
|
879
|
+
}
|
|
880
|
+
step.resultParts.push({
|
|
881
|
+
functionResponse: {
|
|
882
|
+
name: msg.tool || "unknown",
|
|
883
|
+
response: responsePayload,
|
|
884
|
+
},
|
|
885
|
+
});
|
|
821
886
|
continue;
|
|
822
887
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
888
|
+
// Regular (user / assistant) message — acts as a turn boundary.
|
|
889
|
+
const role = msg.role === "assistant" ? "model" : msg.role;
|
|
890
|
+
if (role !== "user" && role !== "model") {
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (!msg.content || msg.content.trim().length === 0) {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
// Increment turn counter BEFORE pushing the segment so any tool_calls
|
|
897
|
+
// that follow this message get a fresh (turnCounter, stepIndex) namespace.
|
|
898
|
+
turnCounter++;
|
|
899
|
+
const textPart = { text: msg.content };
|
|
900
|
+
if (msg.metadata?.thoughtSignature) {
|
|
901
|
+
textPart.thoughtSignature = msg.metadata.thoughtSignature;
|
|
902
|
+
}
|
|
903
|
+
segments.push({ type: "regular", role, parts: [textPart] });
|
|
904
|
+
}
|
|
905
|
+
// Emit in order: each ToolStep → model turn (calls) + user turn (results)
|
|
906
|
+
// — same ordering @google/genai's automaticFunctionCalling produces.
|
|
907
|
+
for (const seg of segments) {
|
|
908
|
+
if (seg.type === "regular") {
|
|
909
|
+
contents.push({ role: seg.role, parts: seg.parts });
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (seg.callParts.length === 0) {
|
|
913
|
+
if (seg.resultParts.length > 0) {
|
|
914
|
+
logger.debug("[GoogleNativeGemini3] Dropping orphan tool_result segment with no matching tool_call rows", { resultCount: seg.resultParts.length });
|
|
915
|
+
}
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
contents.push({ role: "model", parts: seg.callParts });
|
|
919
|
+
if (seg.resultParts.length > 0) {
|
|
920
|
+
contents.push({ role: "user", parts: seg.resultParts });
|
|
921
|
+
}
|
|
827
922
|
}
|
|
828
923
|
}
|
|
829
924
|
/**
|