@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.
- package/CHANGELOG.md +8 -0
- package/README.md +17 -15
- package/dist/browser/neurolink.min.js +273 -272
- package/dist/cli/commands/proxy.js +1 -1
- package/dist/core/redisConversationMemoryManager.js +17 -1
- package/dist/lib/core/redisConversationMemoryManager.js +17 -1
- package/dist/lib/neurolink.js +10 -2
- package/dist/lib/providers/googleNativeGemini3.d.ts +5 -0
- package/dist/lib/providers/googleNativeGemini3.js +18 -0
- package/dist/lib/providers/googleVertex.js +168 -11
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +2 -3
- package/dist/lib/services/server/ai/observability/instrumentation.js +22 -18
- package/dist/lib/types/conversation.d.ts +4 -0
- package/dist/lib/types/generate.d.ts +2 -0
- package/dist/lib/types/providers.d.ts +15 -0
- package/dist/lib/types/tools.d.ts +4 -0
- package/dist/lib/utils/conversationMemory.js +1 -0
- package/dist/neurolink.js +10 -2
- package/dist/providers/googleNativeGemini3.d.ts +5 -0
- package/dist/providers/googleNativeGemini3.js +18 -0
- package/dist/providers/googleVertex.js +168 -11
- package/dist/services/server/ai/observability/instrumentation.d.ts +2 -3
- package/dist/services/server/ai/observability/instrumentation.js +22 -18
- package/dist/types/conversation.d.ts +4 -0
- package/dist/types/generate.d.ts +2 -0
- package/dist/types/providers.d.ts +15 -0
- package/dist/types/tools.d.ts +4 -0
- package/dist/utils/conversationMemory.js +1 -0
- package/package.json +2 -2
- package/dist/lib/utils/imageCompressor.d.ts +0 -25
- package/dist/lib/utils/imageCompressor.js +0 -141
- package/dist/utils/imageCompressor.d.ts +0 -25
- 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
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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():
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
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
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/tools.d.ts
CHANGED
|
@@ -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.
|
|
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;
|