@juspay/neurolink 9.54.9 → 9.55.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +17 -15
  3. package/dist/browser/neurolink.min.js +273 -272
  4. package/dist/cli/commands/proxy.js +1 -1
  5. package/dist/core/redisConversationMemoryManager.js +17 -1
  6. package/dist/lib/core/redisConversationMemoryManager.js +17 -1
  7. package/dist/lib/neurolink.js +10 -2
  8. package/dist/lib/providers/googleNativeGemini3.d.ts +5 -0
  9. package/dist/lib/providers/googleNativeGemini3.js +18 -0
  10. package/dist/lib/providers/googleVertex.js +168 -11
  11. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +2 -3
  12. package/dist/lib/services/server/ai/observability/instrumentation.js +22 -18
  13. package/dist/lib/types/conversation.d.ts +4 -0
  14. package/dist/lib/types/generate.d.ts +2 -0
  15. package/dist/lib/types/providers.d.ts +15 -0
  16. package/dist/lib/types/tools.d.ts +4 -0
  17. package/dist/lib/utils/conversationMemory.js +1 -0
  18. package/dist/neurolink.js +10 -2
  19. package/dist/providers/googleNativeGemini3.d.ts +5 -0
  20. package/dist/providers/googleNativeGemini3.js +18 -0
  21. package/dist/providers/googleVertex.js +168 -11
  22. package/dist/services/server/ai/observability/instrumentation.d.ts +2 -3
  23. package/dist/services/server/ai/observability/instrumentation.js +22 -18
  24. package/dist/types/conversation.d.ts +4 -0
  25. package/dist/types/generate.d.ts +2 -0
  26. package/dist/types/providers.d.ts +15 -0
  27. package/dist/types/tools.d.ts +4 -0
  28. package/dist/utils/conversationMemory.js +1 -0
  29. package/package.json +2 -2
  30. package/dist/lib/utils/imageCompressor.d.ts +0 -25
  31. package/dist/lib/utils/imageCompressor.js +0 -141
  32. package/dist/utils/imageCompressor.d.ts +0 -25
  33. package/dist/utils/imageCompressor.js +0 -140
@@ -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/dist/neurolink.js CHANGED
@@ -1472,7 +1472,11 @@ Current user's request: ${currentInput}`;
1472
1472
  message: "Starting Langfuse observability initialization",
1473
1473
  });
1474
1474
  // Initialize OpenTelemetry (sets defaults from config)
1475
- initializeOpenTelemetry(langfuseConfig);
1475
+ void initializeOpenTelemetry(langfuseConfig).catch((err) => {
1476
+ logger.error("[NeuroLink] OpenTelemetry initialization failed", {
1477
+ error: err instanceof Error ? err.message : String(err),
1478
+ });
1479
+ });
1476
1480
  const healthStatus = getLangfuseHealthStatus();
1477
1481
  const langfuseInitDurationNs = process.hrtime.bigint() - langfuseInitStartTime;
1478
1482
  if (healthStatus.initialized &&
@@ -2234,7 +2238,11 @@ Current user's request: ${currentInput}`;
2234
2238
  try {
2235
2239
  const langfuseConfig = this.observabilityConfig?.langfuse;
2236
2240
  if (langfuseConfig?.enabled) {
2237
- initializeOpenTelemetry(langfuseConfig);
2241
+ void initializeOpenTelemetry(langfuseConfig).catch((err) => {
2242
+ logger.error("[NeuroLink] OpenTelemetry initialization failed", {
2243
+ error: err instanceof Error ? err.message : String(err),
2244
+ });
2245
+ });
2238
2246
  logger.debug("[NeuroLink] Langfuse observability initialized via public method");
2239
2247
  }
2240
2248
  else {
@@ -92,6 +92,11 @@ export declare function collectStreamChunksIncremental(stream: AsyncIterable<{
92
92
  functionCalls?: NativeFunctionCall[];
93
93
  [key: string]: unknown;
94
94
  }>, channel: TextChannel): Promise<CollectedChunkResult>;
95
+ /**
96
+ * Extract the thoughtSignature token from raw response parts.
97
+ * Returns the last thoughtSignature found (each step may produce one).
98
+ */
99
+ export declare function extractThoughtSignature(rawResponseParts: unknown[]): string | undefined;
95
100
  /**
96
101
  * Extract text from raw response parts, filtering out non-text parts
97
102
  * (thoughtSignature, functionCall) to avoid SDK warnings.
@@ -437,6 +437,24 @@ export async function collectStreamChunksIncremental(stream, channel) {
437
437
  }
438
438
  return { rawResponseParts, stepFunctionCalls, inputTokens, outputTokens };
439
439
  }
440
+ /**
441
+ * Extract the thoughtSignature token from raw response parts.
442
+ * Returns the last thoughtSignature found (each step may produce one).
443
+ */
444
+ export function extractThoughtSignature(rawResponseParts) {
445
+ for (let i = rawResponseParts.length - 1; i >= 0; i--) {
446
+ const part = rawResponseParts[i];
447
+ if (part !== null &&
448
+ part !== undefined &&
449
+ typeof part === "object" &&
450
+ "thoughtSignature" in part &&
451
+ typeof part.thoughtSignature ===
452
+ "string") {
453
+ return part.thoughtSignature;
454
+ }
455
+ }
456
+ return undefined;
457
+ }
440
458
  /**
441
459
  * Extract text from raw response parts, filtering out non-text parts
442
460
  * (thoughtSignature, functionCall) to avoid SDK warnings.
@@ -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
  }
@@ -6,7 +6,6 @@
6
6
  *
7
7
  * Flow: Vercel AI SDK → OpenTelemetry Spans → LangfuseSpanProcessor → Langfuse Platform
8
8
  */
9
- import { LangfuseSpanProcessor } from "@langfuse/otel";
10
9
  import { trace } from "@opentelemetry/api";
11
10
  import { LoggerProvider } from "@opentelemetry/sdk-logs";
12
11
  import { type SpanProcessor } from "@opentelemetry/sdk-trace-base";
@@ -26,7 +25,7 @@ import type { LangfuseConfig, LangfuseContext } from "../../../../types/index.js
26
25
  *
27
26
  * @param config - Langfuse configuration passed from parent application
28
27
  */
29
- export declare function initializeOpenTelemetry(config: LangfuseConfig): void;
28
+ export declare function initializeOpenTelemetry(config: LangfuseConfig): Promise<void>;
30
29
  /**
31
30
  * Flush all pending spans to Langfuse
32
31
  */
@@ -38,7 +37,7 @@ export declare function shutdownOpenTelemetry(): Promise<void>;
38
37
  /**
39
38
  * Get the Langfuse span processor
40
39
  */
41
- export declare function getLangfuseSpanProcessor(): LangfuseSpanProcessor | null;
40
+ export declare function getLangfuseSpanProcessor(): SpanProcessor | null;
42
41
  /**
43
42
  * Get the tracer provider
44
43
  */
@@ -6,7 +6,6 @@
6
6
  *
7
7
  * Flow: Vercel AI SDK → OpenTelemetry Spans → LangfuseSpanProcessor → Langfuse Platform
8
8
  */
9
- import { LangfuseSpanProcessor } from "@langfuse/otel";
10
9
  import { metrics, SpanStatusCode, trace } from "@opentelemetry/api";
11
10
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
12
11
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
@@ -503,8 +502,19 @@ class ContextEnricher {
503
502
  return Promise.resolve();
504
503
  }
505
504
  }
506
- function createLangfuseProcessor(config) {
507
- return new LangfuseSpanProcessor({
505
+ async function createLangfuseProcessor(config) {
506
+ let mod;
507
+ try {
508
+ mod = await import(/* @vite-ignore */ "@langfuse/otel");
509
+ }
510
+ catch (err) {
511
+ const e = err instanceof Error ? err : null;
512
+ if (e?.code === "ERR_MODULE_NOT_FOUND" && e.message.includes("langfuse")) {
513
+ throw new Error('Langfuse observability requires "@langfuse/otel". Install it with:\n pnpm add @langfuse/otel', { cause: err });
514
+ }
515
+ throw err;
516
+ }
517
+ return new mod.LangfuseSpanProcessor({
508
518
  publicKey: config.publicKey,
509
519
  secretKey: config.secretKey,
510
520
  baseUrl: config.baseUrl || "https://cloud.langfuse.com",
@@ -513,7 +523,7 @@ function createLangfuseProcessor(config) {
513
523
  shouldExportSpan: () => true,
514
524
  });
515
525
  }
516
- function initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds) {
526
+ async function initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds) {
517
527
  if (langfuseRequested && !hasLangfuseCreds) {
518
528
  if (!otlpEndpoint) {
519
529
  logger.warn(`${LOG_PREFIX} External provider mode requested Langfuse but credentials are missing, and no OTLP endpoint is configured; skipping initialization`, {
@@ -535,7 +545,7 @@ function initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, ser
535
545
  isCredentialsValid = hasLangfuseCreds;
536
546
  langfuseProcessor =
537
547
  langfuseRequested && hasLangfuseCreds
538
- ? createLangfuseProcessor(config)
548
+ ? await createLangfuseProcessor(config)
539
549
  : null;
540
550
  usingExternalProvider = true;
541
551
  isInitialized = true;
@@ -548,11 +558,9 @@ function initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, ser
548
558
  // Auto-detect: skip if consumer already registered a LangfuseSpanProcessor.
549
559
  //
550
560
  // Detection strategy (ordered by robustness):
551
- // 1. `instanceof LangfuseSpanProcessor` reliable when both sides use
552
- // the same @langfuse/otel package instance (same module identity).
553
- // 2. Duck-type check for Langfuse-specific public member
561
+ // 1. Duck-type check for Langfuse-specific public member
554
562
  // (`langfuseClient` property) — survives minification.
555
- // 3. `constructor.name === "LangfuseSpanProcessor"` — last resort,
563
+ // 2. `constructor.name === "LangfuseSpanProcessor"` — last resort,
556
564
  // brittle under minification or bundler renaming.
557
565
  //
558
566
  // NOTE: `_registeredSpanProcessors` is an internal OpenTelemetry field.
@@ -566,10 +574,6 @@ function initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, ser
566
574
  if (p === null || p === undefined || typeof p !== "object") {
567
575
  return false;
568
576
  }
569
- // Prefer instanceof — works when same @langfuse/otel package is shared
570
- if (p instanceof LangfuseSpanProcessor) {
571
- return true;
572
- }
573
577
  // Duck-type: Langfuse processor exposes a langfuseClient property
574
578
  if ("langfuseClient" in p) {
575
579
  return true;
@@ -626,7 +630,7 @@ function initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, ser
626
630
  isInitialized = true;
627
631
  }
628
632
  }
629
- function initializeStandaloneOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds) {
633
+ async function initializeStandaloneOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds) {
630
634
  if ((!langfuseRequested || !hasLangfuseCreds) && !otlpEndpoint) {
631
635
  if (langfuseRequested && !hasLangfuseCreds) {
632
636
  logger.warn(`${LOG_PREFIX} Langfuse requested but credentials are missing, and no OTLP endpoint is configured; skipping initialization`, {
@@ -652,7 +656,7 @@ function initializeStandaloneOpenTelemetryMode(config, resource, otlpEndpoint, s
652
656
  isCredentialsValid = hasLangfuseCreds;
653
657
  langfuseProcessor =
654
658
  langfuseRequested && hasLangfuseCreds
655
- ? createLangfuseProcessor(config)
659
+ ? await createLangfuseProcessor(config)
656
660
  : null;
657
661
  logger.debug(`${LOG_PREFIX} Standalone observability mode`, {
658
662
  langfuseEnabled: !!langfuseProcessor,
@@ -741,7 +745,7 @@ function initializeStandaloneOpenTelemetryMode(config, resource, otlpEndpoint, s
741
745
  *
742
746
  * @param config - Langfuse configuration passed from parent application
743
747
  */
744
- export function initializeOpenTelemetry(config) {
748
+ export async function initializeOpenTelemetry(config) {
745
749
  // Guard against multiple initializations — but always update config
746
750
  // so that later NeuroLink instances can change traceNameFormat,
747
751
  // autoDetectOperationName, and other configuration preferences
@@ -771,10 +775,10 @@ export function initializeOpenTelemetry(config) {
771
775
  const serviceName = process.env.OTEL_SERVICE_NAME || "neurolink";
772
776
  const resource = createOtelResource(config, serviceName);
773
777
  if (shouldUseExternal) {
774
- initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds);
778
+ await initializeExternalOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds);
775
779
  return;
776
780
  }
777
- initializeStandaloneOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds);
781
+ await initializeStandaloneOpenTelemetryMode(config, resource, otlpEndpoint, serviceName, langfuseRequested, hasLangfuseCreds);
778
782
  }
779
783
  /**
780
784
  * Flush all pending spans to Langfuse
@@ -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.1",
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": {
@@ -212,7 +212,6 @@
212
212
  "@google/genai": "^1.43.0",
213
213
  "@huggingface/inference": "^4.13.14",
214
214
  "@juspay/hippocampus": "^0.1.4",
215
- "@langfuse/otel": "^5.0.1",
216
215
  "@modelcontextprotocol/sdk": "^1.27.1",
217
216
  "@openrouter/ai-sdk-provider": "^2.2.3",
218
217
  "@opentelemetry/api-logs": "^0.214.0",
@@ -275,6 +274,7 @@
275
274
  }
276
275
  },
277
276
  "optionalDependencies": {
277
+ "@langfuse/otel": "^5.0.1",
278
278
  "@fastify/cors": "^11.2.0",
279
279
  "@fastify/rate-limit": "^10.3.0",
280
280
  "@hono/node-server": "^1.19.9",
@@ -1,25 +0,0 @@
1
- import type { CompressionOptions, CompressionResult, ProviderName } from "../types/index.js";
2
- /**
3
- * Provider-specific image size limits in bytes
4
- */
5
- export declare const PROVIDER_IMAGE_LIMITS: Record<ProviderName, number>;
6
- /**
7
- * Compress an image to meet provider-specific size limits
8
- * @param imageBuffer - Input image buffer
9
- * @param options - Compression options including provider name
10
- * @returns Compressed image buffer with metadata
11
- */
12
- export declare function compressImage(imageBuffer: Buffer, options: CompressionOptions): Promise<CompressionResult>;
13
- /**
14
- * Check if an image needs compression for a specific provider
15
- * @param imageBuffer - Input image buffer
16
- * @param provider - AI provider name
17
- * @returns True if compression is needed
18
- */
19
- export declare function needsCompression(imageBuffer: Buffer, provider: ProviderName): boolean;
20
- /**
21
- * Get the size limit for a specific provider
22
- * @param provider - AI provider name
23
- * @returns Size limit in bytes
24
- */
25
- export declare function getProviderSizeLimit(provider: ProviderName): number;