@juspay/neurolink 9.54.9 → 9.55.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.
@@ -24,7 +24,7 @@ import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../
24
24
  import { estimateTokens } from "../utils/tokenEstimation.js";
25
25
  import { resolveToolChoice } from "../utils/toolChoice.js";
26
26
  import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
27
- import { buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps as computeMaxStepsShared, createTextChannel, executeNativeToolCalls, extractTextFromParts, handleMaxStepsTermination, normalizeToolsForJsonSchemaProvider, pushModelResponseToHistory, sanitizeToolsForGemini, } from "./googleNativeGemini3.js";
27
+ import { buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps as computeMaxStepsShared, createTextChannel, executeNativeToolCalls, extractTextFromParts, extractThoughtSignature, handleMaxStepsTermination, normalizeToolsForJsonSchemaProvider, pushModelResponseToHistory, sanitizeToolsForGemini, } from "./googleNativeGemini3.js";
28
28
  import { getModelId } from "./providerTypeUtils.js";
29
29
  // Import proper types for multimodal message handling
30
30
  // Keep-alive note: Node.js native fetch and undici (used by createProxyFetch)
@@ -1310,10 +1310,63 @@ export class GoogleVertexProvider extends BaseProvider {
1310
1310
  return currentContents;
1311
1311
  }
1312
1312
  const history = [];
1313
+ // Composite key: "<turnCounter>:<stepIndex>" ensures step 1 of
1314
+ // conversational turn 1 never collides with step 1 of turn 2.
1315
+ const stepMap = new Map();
1316
+ const segments = [];
1317
+ // Incremented each time a regular user/assistant message is encountered,
1318
+ // acting as a logical turn boundary between agentic loop iterations.
1319
+ let turnCounter = 0;
1320
+ const makeKey = (stepIndex) => `${turnCounter}:${stepIndex ?? "undefined"}`;
1321
+ const getOrCreateStep = (stepIndex) => {
1322
+ const key = makeKey(stepIndex);
1323
+ if (stepMap.has(key)) {
1324
+ return stepMap.get(key);
1325
+ }
1326
+ const step = {
1327
+ type: "tool_step",
1328
+ callParts: [],
1329
+ resultParts: [],
1330
+ };
1331
+ stepMap.set(key, step);
1332
+ segments.push(step);
1333
+ return step;
1334
+ };
1313
1335
  for (const msg of conversationMessages) {
1314
- // @google/genai only accepts "user" and "model" roles in contents.
1315
- // Skip system messages (handled via config.systemInstruction).
1316
- // Map "assistant" → "model" (Gemini convention).
1336
+ if (msg.role === "tool_call") {
1337
+ const step = getOrCreateStep(msg.metadata?.stepIndex);
1338
+ const fcPart = {
1339
+ functionCall: {
1340
+ name: msg.tool || "unknown",
1341
+ args: msg.args || {},
1342
+ },
1343
+ };
1344
+ if (msg.metadata?.thoughtSignature) {
1345
+ fcPart.thoughtSignature = msg.metadata.thoughtSignature;
1346
+ }
1347
+ step.callParts.push(fcPart);
1348
+ continue;
1349
+ }
1350
+ if (msg.role === "tool_result") {
1351
+ const step = getOrCreateStep(msg.metadata?.stepIndex);
1352
+ let responsePayload;
1353
+ try {
1354
+ responsePayload = msg.content
1355
+ ? { result: JSON.parse(msg.content) }
1356
+ : { result: "success" };
1357
+ }
1358
+ catch {
1359
+ responsePayload = { result: msg.content || "success" };
1360
+ }
1361
+ step.resultParts.push({
1362
+ functionResponse: {
1363
+ name: msg.tool || "unknown",
1364
+ response: responsePayload,
1365
+ },
1366
+ });
1367
+ continue;
1368
+ }
1369
+ // Regular (user / assistant) message — acts as a turn boundary.
1317
1370
  const role = msg.role === "assistant" ? "model" : msg.role;
1318
1371
  if (role !== "user" && role !== "model") {
1319
1372
  continue;
@@ -1321,9 +1374,29 @@ export class GoogleVertexProvider extends BaseProvider {
1321
1374
  if (!msg.content || msg.content.trim().length === 0) {
1322
1375
  continue;
1323
1376
  }
1324
- history.push({ role, parts: [{ text: msg.content }] });
1377
+ // Increment turn counter BEFORE pushing the segment so that any
1378
+ // tool_calls that follow this message get a fresh namespace.
1379
+ turnCounter++;
1380
+ const textPart = { text: msg.content };
1381
+ if (msg.metadata?.thoughtSignature) {
1382
+ textPart.thoughtSignature = msg.metadata.thoughtSignature;
1383
+ }
1384
+ segments.push({ type: "regular", role, parts: [textPart] });
1385
+ }
1386
+ // Emit in order: each ToolStep → model turn (calls) + user turn (results)
1387
+ for (const seg of segments) {
1388
+ if (seg.type === "regular") {
1389
+ history.push({ role: seg.role, parts: seg.parts });
1390
+ }
1391
+ else {
1392
+ if (seg.callParts.length > 0) {
1393
+ history.push({ role: "model", parts: seg.callParts });
1394
+ }
1395
+ if (seg.resultParts.length > 0) {
1396
+ history.push({ role: "user", parts: seg.resultParts });
1397
+ }
1398
+ }
1325
1399
  }
1326
- // Prepend history before current user message
1327
1400
  return [...history, ...currentContents];
1328
1401
  }
1329
1402
  // ── Shared Gemini 3 helpers are now in ./googleNativeGemini3.ts ──
@@ -1425,6 +1498,7 @@ export class GoogleVertexProvider extends BaseProvider {
1425
1498
  timeoutController,
1426
1499
  composedSignal,
1427
1500
  maxSteps,
1501
+ options,
1428
1502
  });
1429
1503
  loopPromise.catch(() => undefined);
1430
1504
  return {
@@ -1438,11 +1512,13 @@ export class GoogleVertexProvider extends BaseProvider {
1438
1512
  }
1439
1513
  async runNativeGemini3StreamLoop(params) {
1440
1514
  let lastStepText = "";
1515
+ let lastThoughtSignature;
1441
1516
  let totalInputTokens = 0;
1442
1517
  let totalOutputTokens = 0;
1443
1518
  let step = 0;
1444
1519
  let completedWithFinalAnswer = false;
1445
1520
  const failedTools = new Map();
1521
+ const toolExecutions = [];
1446
1522
  try {
1447
1523
  while (step < params.maxSteps) {
1448
1524
  if (params.composedSignal?.aborted) {
@@ -1465,6 +1541,10 @@ export class GoogleVertexProvider extends BaseProvider {
1465
1541
  totalInputTokens += chunkResult.inputTokens;
1466
1542
  totalOutputTokens += chunkResult.outputTokens;
1467
1543
  const stepText = extractTextFromParts(chunkResult.rawResponseParts);
1544
+ const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
1545
+ if (stepThoughtSig) {
1546
+ lastThoughtSignature = stepThoughtSig;
1547
+ }
1468
1548
  if (chunkResult.stepFunctionCalls.length === 0) {
1469
1549
  completedWithFinalAnswer = true;
1470
1550
  break;
@@ -1478,7 +1558,36 @@ export class GoogleVertexProvider extends BaseProvider {
1478
1558
  }
1479
1559
  logger.debug(`[GoogleVertex] Executing ${chunkResult.stepFunctionCalls.length} function calls`);
1480
1560
  pushModelResponseToHistory(params.currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
1481
- const functionResponses = await executeNativeToolCalls("[GoogleVertex]", chunkResult.stepFunctionCalls, params.executeMap, failedTools, params.allToolCalls, { abortSignal: params.composedSignal });
1561
+ // Track array lengths before execution to extract this step's additions
1562
+ const toolCallsBefore = params.allToolCalls.length;
1563
+ const toolExecsBefore = toolExecutions.length;
1564
+ const functionResponses = await executeNativeToolCalls("[GoogleVertex]", chunkResult.stepFunctionCalls, params.executeMap, failedTools, params.allToolCalls, { toolExecutions, abortSignal: params.composedSignal });
1565
+ // Store this step's tool calls/results in conversation memory
1566
+ // (mirrors onStepFinish in the standard GenerationHandler path)
1567
+ const stepToolCalls = params.allToolCalls.slice(toolCallsBefore);
1568
+ const stepToolExecs = toolExecutions.slice(toolExecsBefore);
1569
+ // Tag each tool call with the step's thoughtSignature and stepIndex
1570
+ // so they can be stored on the tool_call ChatMessage metadata in Redis
1571
+ // and reconstructed correctly in prependConversationHistory.
1572
+ const taggedToolCalls = stepToolCalls.map((tc, i) => ({
1573
+ ...tc,
1574
+ ...(i === 0 && stepThoughtSig
1575
+ ? { thoughtSignature: stepThoughtSig }
1576
+ : {}),
1577
+ stepIndex: step,
1578
+ }));
1579
+ const taggedToolResults = stepToolExecs.map((te) => ({
1580
+ toolName: te.name,
1581
+ result: te.output,
1582
+ stepIndex: step,
1583
+ }));
1584
+ if (taggedToolCalls.length > 0 || taggedToolResults.length > 0) {
1585
+ this.handleToolExecutionStorage(taggedToolCalls, taggedToolResults, params.options, new Date()).catch((error) => {
1586
+ logger.warn("[GoogleVertex] Failed to store native stream tool executions", {
1587
+ error: error instanceof Error ? error.message : String(error),
1588
+ });
1589
+ });
1590
+ }
1482
1591
  params.currentContents.push({
1483
1592
  role: "user",
1484
1593
  parts: functionResponses,
@@ -1498,6 +1607,10 @@ export class GoogleVertexProvider extends BaseProvider {
1498
1607
  const responseTime = Date.now() - params.startTime;
1499
1608
  params.metadata.responseTime = responseTime;
1500
1609
  params.metadata.totalToolExecutions = params.allToolCalls.length;
1610
+ if (lastThoughtSignature) {
1611
+ params.metadata.thoughtSignature =
1612
+ lastThoughtSignature;
1613
+ }
1501
1614
  params.span.setAttribute(ATTR.GEN_AI_INPUT_TOKENS, totalInputTokens);
1502
1615
  params.span.setAttribute(ATTR.GEN_AI_OUTPUT_TOKENS, totalOutputTokens);
1503
1616
  params.span.setAttribute(ATTR.GEN_AI_FINISH_REASON, step >= params.maxSteps && !completedWithFinalAnswer
@@ -1603,10 +1716,8 @@ export class GoogleVertexProvider extends BaseProvider {
1603
1716
  // Build config — systemInstruction stays in config for Gemini 3.x.
1604
1717
  // See stream path comment for rationale.
1605
1718
  const config = buildNativeConfig(options, toolsConfig);
1606
- // Note: Schema/JSON output for Gemini 3 native SDK is complex due to $ref resolution issues
1607
- // For now, schemas are handled via the AI SDK fallback path, not native SDK
1608
- // TODO: Implement proper $ref resolution for complex nested schemas
1609
1719
  const startTime = Date.now();
1720
+ // 5-min default: timeout covers ALL agentic steps, 30s is too short.
1610
1721
  const timeout = this.getTimeout(options);
1611
1722
  const timeoutController = createTimeoutController(timeout, this.providerName, "generate");
1612
1723
  const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
@@ -1615,6 +1726,7 @@ export class GoogleVertexProvider extends BaseProvider {
1615
1726
  const currentContents = this.prependConversationHistory([...contents], options.conversationMessages);
1616
1727
  let finalText = "";
1617
1728
  let lastStepText = "";
1729
+ let lastThoughtSignature;
1618
1730
  let totalInputTokens = 0;
1619
1731
  let totalOutputTokens = 0;
1620
1732
  const allToolCalls = [];
@@ -1642,11 +1754,24 @@ export class GoogleVertexProvider extends BaseProvider {
1642
1754
  : {}),
1643
1755
  });
1644
1756
  const chunkResult = await collectStreamChunks(stream);
1757
+ // Silent timeout: abort signal may terminate the stream without
1758
+ // throwing, yielding zero parts. Surface error explicitly.
1759
+ if (composedSignal?.aborted &&
1760
+ chunkResult.rawResponseParts.length === 0) {
1761
+ throw composedSignal.reason instanceof Error
1762
+ ? composedSignal.reason
1763
+ : new Error("Request timed out (no response parts received)");
1764
+ }
1645
1765
  totalInputTokens += chunkResult.inputTokens;
1646
1766
  totalOutputTokens += chunkResult.outputTokens;
1767
+ const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
1768
+ if (stepThoughtSig) {
1769
+ lastThoughtSignature = stepThoughtSig;
1770
+ }
1647
1771
  const stepText = extractTextFromParts(chunkResult.rawResponseParts);
1648
1772
  if (chunkResult.stepFunctionCalls.length === 0) {
1649
- finalText = stepText;
1773
+ // Fall back to lastStepText when final step has no text parts
1774
+ finalText = stepText || lastStepText;
1650
1775
  break;
1651
1776
  }
1652
1777
  lastStepText = stepText;
@@ -1659,7 +1784,36 @@ export class GoogleVertexProvider extends BaseProvider {
1659
1784
  }
1660
1785
  logger.debug(`[GoogleVertex] Generate executing ${chunkResult.stepFunctionCalls.length} function calls`);
1661
1786
  pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
1787
+ // Track array lengths before execution to extract this step's additions
1788
+ const toolCallsBefore = allToolCalls.length;
1789
+ const toolExecsBefore = toolExecutions.length;
1662
1790
  const functionResponses = await executeNativeToolCalls("[GoogleVertex]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, { toolExecutions, abortSignal: composedSignal });
1791
+ // Store this step's tool calls/results in conversation memory
1792
+ // (mirrors onStepFinish in the standard GenerationHandler path)
1793
+ const stepToolCalls = allToolCalls.slice(toolCallsBefore);
1794
+ const stepToolExecs = toolExecutions.slice(toolExecsBefore);
1795
+ // Tag each tool call with the step's thoughtSignature and stepIndex
1796
+ // so they can be stored on the tool_call ChatMessage metadata in Redis
1797
+ // and reconstructed correctly in prependConversationHistory.
1798
+ const taggedToolCalls = stepToolCalls.map((tc, i) => ({
1799
+ ...tc,
1800
+ ...(i === 0 && stepThoughtSig
1801
+ ? { thoughtSignature: stepThoughtSig }
1802
+ : {}),
1803
+ stepIndex: step,
1804
+ }));
1805
+ const taggedToolResults = stepToolExecs.map((te) => ({
1806
+ toolName: te.name,
1807
+ result: te.output,
1808
+ stepIndex: step,
1809
+ }));
1810
+ if (taggedToolCalls.length > 0 || taggedToolResults.length > 0) {
1811
+ this.handleToolExecutionStorage(taggedToolCalls, taggedToolResults, options, new Date()).catch((error) => {
1812
+ logger.warn("[GoogleVertex] Failed to store native tool executions", {
1813
+ error: error instanceof Error ? error.message : String(error),
1814
+ });
1815
+ });
1816
+ }
1663
1817
  // Function/tool responses must use role: "user" — the
1664
1818
  // @google/genai SDK's validateHistory() only accepts "user"
1665
1819
  // and "model" roles (matching automaticFunctionCalling).
@@ -1720,6 +1874,9 @@ export class GoogleVertexProvider extends BaseProvider {
1720
1874
  toolsUsed: allToolCalls.map((tc) => tc.toolName),
1721
1875
  toolExecutions: toolExecutions,
1722
1876
  enhancedWithTools: allToolCalls.length > 0,
1877
+ ...(lastThoughtSignature && {
1878
+ thoughtSignature: lastThoughtSignature,
1879
+ }),
1723
1880
  };
1724
1881
  });
1725
1882
  }
@@ -224,6 +224,8 @@ export type ChatMessageMetadata = {
224
224
  thoughtHash?: string;
225
225
  /** Whether extended thinking was used for this message */
226
226
  thinkingExpanded?: boolean;
227
+ /** Step index for reconstructing parallel vs sequential tool calls */
228
+ stepIndex?: number;
227
229
  /**
228
230
  * Head/tail preview of a large tool output.
229
231
  * Only present on tool_result messages where the output exceeded truncation limits.
@@ -371,6 +373,8 @@ export type StoreConversationTurnOptions = {
371
373
  cacheReadTokens?: number;
372
374
  cacheWriteTokens?: number;
373
375
  };
376
+ /** Gemini 3 thought signature for reasoning continuity across turns */
377
+ thoughtSignature?: string;
374
378
  };
375
379
  /**
376
380
  * Lightweight session metadata for efficient session listing
@@ -999,6 +999,8 @@ export type TextGenerationResult = {
999
999
  imageOutput?: {
1000
1000
  base64: string;
1001
1001
  } | null;
1002
+ /** Gemini 3 thought signature for reasoning continuity across turns */
1003
+ thoughtSignature?: string;
1002
1004
  retries?: {
1003
1005
  count: number;
1004
1006
  errors: Array<{
@@ -1577,3 +1577,18 @@ export type VertexNativePart = {
1577
1577
  data: string;
1578
1578
  };
1579
1579
  };
1580
+ /**
1581
+ * Internal helpers used by the conversation-history builder in
1582
+ * providers/googleVertex.ts to merge interleaved tool call / result turns.
1583
+ */
1584
+ export type VertexToolStep = {
1585
+ type: "tool_step";
1586
+ callParts: unknown[];
1587
+ resultParts: unknown[];
1588
+ };
1589
+ export type VertexRegularSegment = {
1590
+ type: "regular";
1591
+ role: string;
1592
+ parts: unknown[];
1593
+ };
1594
+ export type VertexSegment = VertexToolStep | VertexRegularSegment;
@@ -432,13 +432,17 @@ export type PendingToolExecution = {
432
432
  toolName?: string;
433
433
  args?: Record<string, unknown>;
434
434
  timestamp?: Date;
435
+ thoughtSignature?: string;
436
+ stepIndex?: number;
435
437
  [key: string]: unknown;
436
438
  }>;
437
439
  toolResults: Array<{
438
440
  toolCallId?: string;
441
+ toolName?: string;
439
442
  result?: unknown;
440
443
  error?: string;
441
444
  timestamp?: Date;
445
+ stepIndex?: number;
442
446
  [key: string]: unknown;
443
447
  }>;
444
448
  timestamp: number;
@@ -170,6 +170,7 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
170
170
  cacheWriteTokens: result.usage.cacheCreationTokens,
171
171
  }
172
172
  : undefined,
173
+ thoughtSignature: result.thoughtSignature,
173
174
  });
174
175
  logger.debug("[conversationMemoryUtils] Conversation turn stored successfully", {
175
176
  requestId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.54.9",
3
+ "version": "9.55.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
6
6
  "author": {