@juspay/neurolink 9.51.3 → 9.52.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.
- package/CHANGELOG.md +12 -0
- package/dist/artifacts/artifactStore.d.ts +56 -0
- package/dist/artifacts/artifactStore.js +143 -0
- package/dist/browser/neurolink.min.js +311 -298
- package/dist/cli/commands/mcp.d.ts +6 -0
- package/dist/cli/commands/mcp.js +128 -86
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/core/factory.d.ts +2 -2
- package/dist/core/factory.js +4 -4
- package/dist/core/redisConversationMemoryManager.js +20 -0
- package/dist/factories/providerFactory.d.ts +4 -4
- package/dist/factories/providerFactory.js +20 -7
- package/dist/factories/providerRegistry.d.ts +5 -0
- package/dist/factories/providerRegistry.js +45 -26
- package/dist/lib/artifacts/artifactStore.d.ts +56 -0
- package/dist/lib/artifacts/artifactStore.js +144 -0
- package/dist/lib/core/factory.d.ts +2 -2
- package/dist/lib/core/factory.js +4 -4
- package/dist/lib/core/redisConversationMemoryManager.js +20 -0
- package/dist/lib/factories/providerFactory.d.ts +4 -4
- package/dist/lib/factories/providerFactory.js +20 -7
- package/dist/lib/factories/providerRegistry.d.ts +5 -0
- package/dist/lib/factories/providerRegistry.js +45 -26
- package/dist/lib/mcp/externalServerManager.d.ts +6 -0
- package/dist/lib/mcp/externalServerManager.js +9 -0
- package/dist/lib/mcp/mcpOutputNormalizer.d.ts +49 -0
- package/dist/lib/mcp/mcpOutputNormalizer.js +182 -0
- package/dist/lib/mcp/toolDiscoveryService.d.ts +10 -0
- package/dist/lib/mcp/toolDiscoveryService.js +32 -1
- package/dist/lib/memory/memoryRetrievalTools.d.ts +64 -9
- package/dist/lib/memory/memoryRetrievalTools.js +77 -9
- package/dist/lib/neurolink.d.ts +23 -0
- package/dist/lib/neurolink.js +128 -86
- package/dist/lib/providers/amazonBedrock.d.ts +6 -1
- package/dist/lib/providers/amazonBedrock.js +14 -2
- package/dist/lib/providers/amazonSagemaker.d.ts +7 -1
- package/dist/lib/providers/amazonSagemaker.js +21 -3
- package/dist/lib/providers/anthropic.d.ts +4 -1
- package/dist/lib/providers/anthropic.js +18 -5
- package/dist/lib/providers/azureOpenai.d.ts +2 -1
- package/dist/lib/providers/azureOpenai.js +10 -5
- package/dist/lib/providers/googleAiStudio.d.ts +4 -1
- package/dist/lib/providers/googleAiStudio.js +6 -7
- package/dist/lib/providers/googleVertex.d.ts +3 -1
- package/dist/lib/providers/googleVertex.js +96 -17
- package/dist/lib/providers/huggingFace.d.ts +2 -1
- package/dist/lib/providers/huggingFace.js +4 -4
- package/dist/lib/providers/litellm.d.ts +5 -1
- package/dist/lib/providers/litellm.js +14 -9
- package/dist/lib/providers/mistral.d.ts +2 -1
- package/dist/lib/providers/mistral.js +2 -2
- package/dist/lib/providers/ollama.d.ts +3 -1
- package/dist/lib/providers/ollama.js +2 -2
- package/dist/lib/providers/openAI.d.ts +5 -1
- package/dist/lib/providers/openAI.js +15 -5
- package/dist/lib/providers/openRouter.d.ts +5 -1
- package/dist/lib/providers/openRouter.js +17 -5
- package/dist/lib/providers/openaiCompatible.d.ts +4 -1
- package/dist/lib/providers/openaiCompatible.js +15 -3
- package/dist/lib/session/globalSessionState.js +44 -1
- package/dist/lib/types/artifactTypes.d.ts +63 -0
- package/dist/lib/types/artifactTypes.js +11 -0
- package/dist/lib/types/configTypes.d.ts +39 -0
- package/dist/lib/types/conversation.d.ts +7 -0
- package/dist/lib/types/generateTypes.d.ts +13 -0
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/mcpOutputTypes.d.ts +40 -0
- package/dist/lib/types/mcpOutputTypes.js +9 -0
- package/dist/lib/types/providers.d.ts +75 -0
- package/dist/lib/types/streamTypes.d.ts +7 -1
- package/dist/mcp/externalServerManager.d.ts +6 -0
- package/dist/mcp/externalServerManager.js +9 -0
- package/dist/mcp/mcpOutputNormalizer.d.ts +49 -0
- package/dist/mcp/mcpOutputNormalizer.js +181 -0
- package/dist/mcp/toolDiscoveryService.d.ts +10 -0
- package/dist/mcp/toolDiscoveryService.js +32 -1
- package/dist/memory/memoryRetrievalTools.d.ts +64 -9
- package/dist/memory/memoryRetrievalTools.js +77 -9
- package/dist/neurolink.d.ts +23 -0
- package/dist/neurolink.js +128 -86
- package/dist/providers/amazonBedrock.d.ts +6 -1
- package/dist/providers/amazonBedrock.js +14 -2
- package/dist/providers/amazonSagemaker.d.ts +7 -1
- package/dist/providers/amazonSagemaker.js +21 -3
- package/dist/providers/anthropic.d.ts +4 -1
- package/dist/providers/anthropic.js +18 -5
- package/dist/providers/azureOpenai.d.ts +2 -1
- package/dist/providers/azureOpenai.js +10 -5
- package/dist/providers/googleAiStudio.d.ts +4 -1
- package/dist/providers/googleAiStudio.js +6 -7
- package/dist/providers/googleVertex.d.ts +3 -1
- package/dist/providers/googleVertex.js +96 -17
- package/dist/providers/huggingFace.d.ts +2 -1
- package/dist/providers/huggingFace.js +4 -4
- package/dist/providers/litellm.d.ts +5 -1
- package/dist/providers/litellm.js +14 -9
- package/dist/providers/mistral.d.ts +2 -1
- package/dist/providers/mistral.js +2 -2
- package/dist/providers/ollama.d.ts +3 -1
- package/dist/providers/ollama.js +2 -2
- package/dist/providers/openAI.d.ts +5 -1
- package/dist/providers/openAI.js +15 -5
- package/dist/providers/openRouter.d.ts +5 -1
- package/dist/providers/openRouter.js +17 -5
- package/dist/providers/openaiCompatible.d.ts +4 -1
- package/dist/providers/openaiCompatible.js +15 -3
- package/dist/session/globalSessionState.js +44 -1
- package/dist/types/artifactTypes.d.ts +63 -0
- package/dist/types/artifactTypes.js +10 -0
- package/dist/types/configTypes.d.ts +39 -0
- package/dist/types/conversation.d.ts +7 -0
- package/dist/types/generateTypes.d.ts +13 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/mcpOutputTypes.d.ts +40 -0
- package/dist/types/mcpOutputTypes.js +8 -0
- package/dist/types/providers.d.ts +75 -0
- package/dist/types/streamTypes.d.ts +7 -1
- package/package.json +3 -2
|
@@ -26,9 +26,20 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
26
26
|
toolRegistry = new Map();
|
|
27
27
|
serverTools = new Map();
|
|
28
28
|
discoveryInProgress = new Set();
|
|
29
|
+
/** Optional normalizer applied to every tool output before it is returned. */
|
|
30
|
+
outputNormalizer;
|
|
29
31
|
constructor() {
|
|
30
32
|
super();
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Attach a McpOutputNormalizer.
|
|
36
|
+
* When set, every raw callTool() result is passed through the normalizer
|
|
37
|
+
* before being returned. Oversized outputs are replaced with compact
|
|
38
|
+
* surrogates according to the configured strategy.
|
|
39
|
+
*/
|
|
40
|
+
setOutputNormalizer(normalizer) {
|
|
41
|
+
this.outputNormalizer = normalizer;
|
|
42
|
+
}
|
|
32
43
|
/**
|
|
33
44
|
* Discover tools from an external MCP server
|
|
34
45
|
*/
|
|
@@ -361,6 +372,26 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
361
372
|
arguments: parameters,
|
|
362
373
|
}), timeout, new Error(`Tool execution timeout: ${toolName}`));
|
|
363
374
|
callSpan.setStatus({ code: SpanStatusCode.OK });
|
|
375
|
+
// ── MCP output normalization ──────────────────────────────────
|
|
376
|
+
// Intercept here — after receive, before cache, before memory,
|
|
377
|
+
// before LLM context injection. Returns a compact surrogate when
|
|
378
|
+
// the payload exceeds mcp.outputLimits.maxBytes.
|
|
379
|
+
if (this.outputNormalizer) {
|
|
380
|
+
try {
|
|
381
|
+
const normalized = await this.outputNormalizer.normalize(callResult, { toolName, serverId });
|
|
382
|
+
callSpan.setAttribute("mcp.output.strategy", normalized.isExternalized ? "externalize" : "inline");
|
|
383
|
+
if (normalized.isExternalized) {
|
|
384
|
+
callSpan.setAttribute("mcp.output.original_bytes", normalized.originalBytes);
|
|
385
|
+
}
|
|
386
|
+
return normalized.result;
|
|
387
|
+
}
|
|
388
|
+
catch (normErr) {
|
|
389
|
+
mcpLogger.warn(`[ToolDiscoveryService] McpOutputNormalizer failed for ` +
|
|
390
|
+
`${toolName}: ${normErr instanceof Error ? normErr.message : String(normErr)} ` +
|
|
391
|
+
`— returning raw result`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// ── end normalization ─────────────────────────────────────────
|
|
364
395
|
return callResult;
|
|
365
396
|
}
|
|
366
397
|
catch (err) {
|
|
@@ -385,7 +416,7 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
385
416
|
}
|
|
386
417
|
mcpLogger.debug(`[ToolDiscoveryService] Tool execution completed: ${toolName}`, {
|
|
387
418
|
duration,
|
|
388
|
-
hasContent: !!result
|
|
419
|
+
hasContent: !!result?.content,
|
|
389
420
|
});
|
|
390
421
|
return {
|
|
391
422
|
success: true,
|
|
@@ -7,14 +7,21 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import type { RedisConversationMemoryManager } from "../core/redisConversationMemoryManager.js";
|
|
10
|
+
import type { ArtifactStore } from "../artifacts/artifactStore.js";
|
|
10
11
|
/**
|
|
11
12
|
* Factory function that creates memory retrieval tools bound to a memory manager.
|
|
12
|
-
*
|
|
13
|
+
*
|
|
14
|
+
* @param memoryManager Redis conversation memory manager instance.
|
|
15
|
+
* @param artifactStore Optional artifact store for externalized MCP outputs.
|
|
16
|
+
* When provided, retrieve_context gains an `artifactId`
|
|
17
|
+
* parameter that fetches the full payload written by
|
|
18
|
+
* McpOutputNormalizer under strategy="externalize".
|
|
13
19
|
* @returns Record of tool name to Vercel AI SDK tool definition
|
|
14
20
|
*/
|
|
15
|
-
export declare function createMemoryRetrievalTools(memoryManager: RedisConversationMemoryManager): {
|
|
21
|
+
export declare function createMemoryRetrievalTools(memoryManager: RedisConversationMemoryManager | undefined, artifactStore?: ArtifactStore): {
|
|
16
22
|
retrieve_context: import("ai").Tool<{
|
|
17
|
-
sessionId
|
|
23
|
+
sessionId?: string | undefined;
|
|
24
|
+
artifactId?: string | undefined;
|
|
18
25
|
messageId?: string | undefined;
|
|
19
26
|
role?: "system" | "user" | "assistant" | "tool_call" | "tool_result" | undefined;
|
|
20
27
|
lastN?: number | undefined;
|
|
@@ -22,14 +29,62 @@ export declare function createMemoryRetrievalTools(memoryManager: RedisConversat
|
|
|
22
29
|
limit?: number | undefined;
|
|
23
30
|
search?: string | undefined;
|
|
24
31
|
}, {
|
|
32
|
+
error: string;
|
|
33
|
+
artifactId: string;
|
|
34
|
+
content?: undefined;
|
|
35
|
+
totalSize?: undefined;
|
|
36
|
+
hasMore?: undefined;
|
|
37
|
+
offset?: undefined;
|
|
38
|
+
limit?: undefined;
|
|
39
|
+
sessionId?: undefined;
|
|
40
|
+
messageId?: undefined;
|
|
41
|
+
messages?: undefined;
|
|
42
|
+
totalMessages?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
artifactId: string;
|
|
45
|
+
content: string;
|
|
46
|
+
totalSize: number;
|
|
47
|
+
hasMore: boolean;
|
|
48
|
+
offset: number;
|
|
49
|
+
limit: number;
|
|
50
|
+
error?: undefined;
|
|
51
|
+
sessionId?: undefined;
|
|
52
|
+
messageId?: undefined;
|
|
53
|
+
messages?: undefined;
|
|
54
|
+
totalMessages?: undefined;
|
|
55
|
+
} | {
|
|
56
|
+
error: string;
|
|
57
|
+
artifactId?: undefined;
|
|
58
|
+
content?: undefined;
|
|
59
|
+
totalSize?: undefined;
|
|
60
|
+
hasMore?: undefined;
|
|
61
|
+
offset?: undefined;
|
|
62
|
+
limit?: undefined;
|
|
63
|
+
sessionId?: undefined;
|
|
64
|
+
messageId?: undefined;
|
|
65
|
+
messages?: undefined;
|
|
66
|
+
totalMessages?: undefined;
|
|
67
|
+
} | {
|
|
25
68
|
error: string;
|
|
26
69
|
sessionId: string;
|
|
70
|
+
artifactId?: undefined;
|
|
71
|
+
content?: undefined;
|
|
72
|
+
totalSize?: undefined;
|
|
73
|
+
hasMore?: undefined;
|
|
74
|
+
offset?: undefined;
|
|
75
|
+
limit?: undefined;
|
|
27
76
|
messageId?: undefined;
|
|
28
77
|
messages?: undefined;
|
|
29
78
|
totalMessages?: undefined;
|
|
30
79
|
} | {
|
|
31
80
|
error: string;
|
|
32
81
|
messageId: string;
|
|
82
|
+
artifactId?: undefined;
|
|
83
|
+
content?: undefined;
|
|
84
|
+
totalSize?: undefined;
|
|
85
|
+
hasMore?: undefined;
|
|
86
|
+
offset?: undefined;
|
|
87
|
+
limit?: undefined;
|
|
33
88
|
sessionId?: undefined;
|
|
34
89
|
messages?: undefined;
|
|
35
90
|
totalMessages?: undefined;
|
|
@@ -70,13 +125,13 @@ export declare function createMemoryRetrievalTools(memoryManager: RedisConversat
|
|
|
70
125
|
})[];
|
|
71
126
|
totalMessages: number;
|
|
72
127
|
error?: undefined;
|
|
128
|
+
artifactId?: undefined;
|
|
129
|
+
content?: undefined;
|
|
130
|
+
totalSize?: undefined;
|
|
131
|
+
hasMore?: undefined;
|
|
132
|
+
offset?: undefined;
|
|
133
|
+
limit?: undefined;
|
|
73
134
|
sessionId?: undefined;
|
|
74
135
|
messageId?: undefined;
|
|
75
|
-
} | {
|
|
76
|
-
error: string;
|
|
77
|
-
sessionId?: undefined;
|
|
78
|
-
messageId?: undefined;
|
|
79
|
-
messages?: undefined;
|
|
80
|
-
totalMessages?: undefined;
|
|
81
136
|
}>;
|
|
82
137
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { tool } from "ai";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { logger } from "../utils/logger.js";
|
|
12
|
+
import { withTimeout } from "../utils/errorHandling.js";
|
|
12
13
|
import { SpanSerializer, SpanType, SpanStatus, } from "../observability/index.js";
|
|
13
14
|
import { getMetricsAggregator } from "../observability/index.js";
|
|
14
15
|
/** Maximum characters returned per retrieval request */
|
|
@@ -19,18 +20,36 @@ const MAX_RETRIEVAL_LIMIT = 200_000;
|
|
|
19
20
|
const MAX_SEARCH_MATCHES = 50;
|
|
20
21
|
/**
|
|
21
22
|
* Factory function that creates memory retrieval tools bound to a memory manager.
|
|
22
|
-
*
|
|
23
|
+
*
|
|
24
|
+
* @param memoryManager Redis conversation memory manager instance.
|
|
25
|
+
* @param artifactStore Optional artifact store for externalized MCP outputs.
|
|
26
|
+
* When provided, retrieve_context gains an `artifactId`
|
|
27
|
+
* parameter that fetches the full payload written by
|
|
28
|
+
* McpOutputNormalizer under strategy="externalize".
|
|
23
29
|
* @returns Record of tool name to Vercel AI SDK tool definition
|
|
24
30
|
*/
|
|
25
|
-
export function createMemoryRetrievalTools(memoryManager) {
|
|
31
|
+
export function createMemoryRetrievalTools(memoryManager, artifactStore) {
|
|
26
32
|
return {
|
|
27
33
|
retrieve_context: tool({
|
|
28
|
-
description: "Retrieve messages from conversation memory
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
34
|
+
description: "Retrieve messages from conversation memory, or fetch the full payload of " +
|
|
35
|
+
"an externalized MCP tool output by artifact ID. Use this to:\n" +
|
|
36
|
+
"• Access full tool outputs when a result was truncated or externalized\n" +
|
|
37
|
+
"• Review previous assistant responses\n" +
|
|
38
|
+
"• Search through conversation history\n" +
|
|
39
|
+
"Supports filtering by role, pagination for large content, and regex search.\n" +
|
|
40
|
+
"To fetch an externalized artifact, provide `artifactId` (omit sessionId).",
|
|
32
41
|
inputSchema: z.object({
|
|
33
|
-
sessionId: z
|
|
42
|
+
sessionId: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Session ID for conversation history retrieval. " +
|
|
46
|
+
"Required unless artifactId is provided."),
|
|
47
|
+
artifactId: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Artifact ID from an externalized MCP tool output " +
|
|
51
|
+
"(visible in the tool output as neurolinkArtifactId=<id>). " +
|
|
52
|
+
"When provided, returns the full stored payload directly."),
|
|
34
53
|
messageId: z
|
|
35
54
|
.string()
|
|
36
55
|
.optional()
|
|
@@ -64,19 +83,68 @@ export function createMemoryRetrievalTools(memoryManager) {
|
|
|
64
83
|
"Returns matching lines with line numbers."),
|
|
65
84
|
}),
|
|
66
85
|
execute: async (args) => {
|
|
86
|
+
// ── Artifact resolution path ────────────────────────────────────────
|
|
87
|
+
// When the caller supplies an artifactId we short-circuit to the
|
|
88
|
+
// artifact store (bypassing Redis) and return the full payload with
|
|
89
|
+
// optional offset/limit pagination.
|
|
90
|
+
if (args.artifactId) {
|
|
91
|
+
if (!artifactStore) {
|
|
92
|
+
logger.warn("[MemoryRetrievalTools] retrieve_context called with artifactId " +
|
|
93
|
+
"but no ArtifactStore is configured");
|
|
94
|
+
return {
|
|
95
|
+
error: "Artifact store not configured — " +
|
|
96
|
+
"mcp.outputLimits.strategy must be set to 'externalize' to use artifactId retrieval",
|
|
97
|
+
artifactId: args.artifactId,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const content = await withTimeout(artifactStore.retrieve(args.artifactId), 10_000, new Error(`ArtifactStore.retrieve() timed out for artifact "${args.artifactId}"`));
|
|
101
|
+
if (content === null) {
|
|
102
|
+
return {
|
|
103
|
+
error: "Artifact not found or has expired",
|
|
104
|
+
artifactId: args.artifactId,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const charLimit = Math.min(args.limit ?? DEFAULT_RETRIEVAL_LIMIT, MAX_RETRIEVAL_LIMIT);
|
|
108
|
+
const start = args.offset ?? 0;
|
|
109
|
+
const slice = content.slice(start, start + charLimit);
|
|
110
|
+
return {
|
|
111
|
+
artifactId: args.artifactId,
|
|
112
|
+
content: slice,
|
|
113
|
+
totalSize: content.length,
|
|
114
|
+
hasMore: start + charLimit < content.length,
|
|
115
|
+
offset: start,
|
|
116
|
+
limit: charLimit,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// ── End artifact resolution ─────────────────────────────────────────
|
|
120
|
+
if (!args.sessionId) {
|
|
121
|
+
return {
|
|
122
|
+
error: "sessionId is required when artifactId is not provided",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (!memoryManager) {
|
|
126
|
+
return {
|
|
127
|
+
error: "Session history retrieval requires Redis conversation memory — " +
|
|
128
|
+
"enable mcp.conversationMemory with a Redis backend, or use " +
|
|
129
|
+
"artifactId to retrieve an externalized MCP tool output.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
67
132
|
const span = SpanSerializer.createSpan(SpanType.MEMORY, "memory.retrieve", {
|
|
68
133
|
"memory.operation": "retrieve",
|
|
69
134
|
"memory.store": "redis",
|
|
70
135
|
"memory.query": args.search || args.messageId || `lastN:${args.lastN ?? "all"}`,
|
|
71
136
|
});
|
|
72
137
|
const startTime = Date.now();
|
|
138
|
+
// args.sessionId is guaranteed non-null here — we returned early above
|
|
139
|
+
// when it was missing. Cast via string coercion to satisfy eslint.
|
|
140
|
+
const sessionId = String(args.sessionId);
|
|
73
141
|
try {
|
|
74
|
-
const conversation = await memoryManager.getSessionRaw(
|
|
142
|
+
const conversation = await withTimeout(memoryManager.getSessionRaw(sessionId), 10_000, new Error(`getSessionRaw() timed out for session "${sessionId}"`));
|
|
75
143
|
if (!conversation) {
|
|
76
144
|
span.durationMs = Date.now() - startTime;
|
|
77
145
|
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
|
|
78
146
|
getMetricsAggregator().recordSpan(endedSpan);
|
|
79
|
-
return { error: "Session not found", sessionId
|
|
147
|
+
return { error: "Session not found", sessionId };
|
|
80
148
|
}
|
|
81
149
|
let messages = conversation.messages;
|
|
82
150
|
// Filter by specific messageId
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export declare class NeuroLink {
|
|
|
47
47
|
private mcpToolBatcher?;
|
|
48
48
|
private mcpEnhancedDiscovery?;
|
|
49
49
|
private mcpToolMiddlewares;
|
|
50
|
+
/** Artifact store for externalized MCP tool outputs (set when strategy=externalize). */
|
|
51
|
+
private mcpArtifactStore?;
|
|
50
52
|
private _disableToolCacheForCurrentRequest;
|
|
51
53
|
private mcpEnhancementsConfig?;
|
|
52
54
|
private toolCircuitBreakers;
|
|
@@ -71,6 +73,27 @@ export declare class NeuroLink {
|
|
|
71
73
|
private authProvider?;
|
|
72
74
|
private pendingAuthConfig?;
|
|
73
75
|
private authInitPromise?;
|
|
76
|
+
private credentials?;
|
|
77
|
+
/**
|
|
78
|
+
* Merge instance-level credentials with per-call credentials.
|
|
79
|
+
*
|
|
80
|
+
* Semantics: **deep merge at the provider level.** For each provider key
|
|
81
|
+
* present in both `this.credentials` and `callCredentials`, the per-call
|
|
82
|
+
* fields are merged ON TOP of the instance-level fields, so fields not
|
|
83
|
+
* mentioned in the per-call slice are preserved.
|
|
84
|
+
*
|
|
85
|
+
* Example:
|
|
86
|
+
* ```
|
|
87
|
+
* instance: { openai: { apiKey: "key1", baseURL: "url1" } }
|
|
88
|
+
* per-call: { openai: { apiKey: "key2" } }
|
|
89
|
+
* merged: { openai: { apiKey: "key2", baseURL: "url1" } } // baseURL preserved
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* Providers present only in one source are carried through unchanged.
|
|
93
|
+
* Unrelated providers (not overridden in callCredentials) are carried through
|
|
94
|
+
* from instance credentials unchanged.
|
|
95
|
+
*/
|
|
96
|
+
private resolveCredentials;
|
|
74
97
|
private hitlManager?;
|
|
75
98
|
private _sessionCostUsd;
|
|
76
99
|
private fileRegistry;
|
package/dist/neurolink.js
CHANGED
|
@@ -40,6 +40,8 @@ import { ToolCallBatcher } from "./mcp/batching/index.js";
|
|
|
40
40
|
import { ToolResultCache } from "./mcp/caching/index.js";
|
|
41
41
|
import { EnhancedToolDiscovery } from "./mcp/enhancedToolDiscovery.js";
|
|
42
42
|
import { ExternalServerManager } from "./mcp/externalServerManager.js";
|
|
43
|
+
import { McpOutputNormalizer, DEFAULT_MAX_MCP_OUTPUT_BYTES, DEFAULT_WARN_MCP_OUTPUT_BYTES, } from "./mcp/mcpOutputNormalizer.js";
|
|
44
|
+
import { LocalTempArtifactStore } from "./artifacts/artifactStore.js";
|
|
43
45
|
import { ToolRouter } from "./mcp/routing/index.js";
|
|
44
46
|
// Import direct tools server for automatic registration
|
|
45
47
|
import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
|
|
@@ -216,6 +218,8 @@ export class NeuroLink {
|
|
|
216
218
|
mcpToolBatcher;
|
|
217
219
|
mcpEnhancedDiscovery;
|
|
218
220
|
mcpToolMiddlewares = [];
|
|
221
|
+
/** Artifact store for externalized MCP tool outputs (set when strategy=externalize). */
|
|
222
|
+
mcpArtifactStore;
|
|
219
223
|
_disableToolCacheForCurrentRequest = false;
|
|
220
224
|
mcpEnhancementsConfig;
|
|
221
225
|
// Enhanced error handling support
|
|
@@ -255,6 +259,60 @@ export class NeuroLink {
|
|
|
255
259
|
authProvider;
|
|
256
260
|
pendingAuthConfig;
|
|
257
261
|
authInitPromise;
|
|
262
|
+
// Per-provider credential overrides (instance-level default)
|
|
263
|
+
credentials;
|
|
264
|
+
/**
|
|
265
|
+
* Merge instance-level credentials with per-call credentials.
|
|
266
|
+
*
|
|
267
|
+
* Semantics: **deep merge at the provider level.** For each provider key
|
|
268
|
+
* present in both `this.credentials` and `callCredentials`, the per-call
|
|
269
|
+
* fields are merged ON TOP of the instance-level fields, so fields not
|
|
270
|
+
* mentioned in the per-call slice are preserved.
|
|
271
|
+
*
|
|
272
|
+
* Example:
|
|
273
|
+
* ```
|
|
274
|
+
* instance: { openai: { apiKey: "key1", baseURL: "url1" } }
|
|
275
|
+
* per-call: { openai: { apiKey: "key2" } }
|
|
276
|
+
* merged: { openai: { apiKey: "key2", baseURL: "url1" } } // baseURL preserved
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* Providers present only in one source are carried through unchanged.
|
|
280
|
+
* Unrelated providers (not overridden in callCredentials) are carried through
|
|
281
|
+
* from instance credentials unchanged.
|
|
282
|
+
*/
|
|
283
|
+
resolveCredentials(callCredentials) {
|
|
284
|
+
if (!this.credentials && !callCredentials) {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
if (!this.credentials) {
|
|
288
|
+
return callCredentials;
|
|
289
|
+
}
|
|
290
|
+
if (!callCredentials) {
|
|
291
|
+
return this.credentials;
|
|
292
|
+
}
|
|
293
|
+
// Per-provider deep merge: for each provider key in the per-call
|
|
294
|
+
// override, merge its fields on top of the instance-level slice so
|
|
295
|
+
// individual fields (e.g. baseURL) are preserved when only apiKey
|
|
296
|
+
// is overridden per-call.
|
|
297
|
+
const merged = { ...this.credentials };
|
|
298
|
+
for (const key of Object.keys(callCredentials)) {
|
|
299
|
+
const instanceSlice = this.credentials[key];
|
|
300
|
+
const callSlice = callCredentials[key];
|
|
301
|
+
if (instanceSlice &&
|
|
302
|
+
callSlice &&
|
|
303
|
+
typeof instanceSlice === "object" &&
|
|
304
|
+
typeof callSlice === "object") {
|
|
305
|
+
merged[key] = {
|
|
306
|
+
...instanceSlice,
|
|
307
|
+
...callSlice,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
merged[key] = callSlice ?? instanceSlice;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return merged;
|
|
315
|
+
}
|
|
258
316
|
// HITL (Human-in-the-Loop) support
|
|
259
317
|
hitlManager;
|
|
260
318
|
// Accumulated cost in USD across all generate() calls on this instance
|
|
@@ -595,6 +653,10 @@ export class NeuroLink {
|
|
|
595
653
|
if (config?.auth) {
|
|
596
654
|
this.pendingAuthConfig = config.auth;
|
|
597
655
|
}
|
|
656
|
+
// Store per-provider credential overrides
|
|
657
|
+
if (config?.credentials) {
|
|
658
|
+
this.credentials = config.credentials;
|
|
659
|
+
}
|
|
598
660
|
// Store task config for lazy initialization
|
|
599
661
|
this._taskManagerConfig = config?.tasks;
|
|
600
662
|
// Eagerly create TaskManager and register tools if config is provided
|
|
@@ -818,6 +880,25 @@ export class NeuroLink {
|
|
|
818
880
|
});
|
|
819
881
|
}
|
|
820
882
|
// ToolRouter — lazy-initialized when 2+ external servers exist (see addExternalMCPServer)
|
|
883
|
+
// McpOutputNormalizer — active when mcp.outputLimits is configured
|
|
884
|
+
if (mcpConfig?.outputLimits) {
|
|
885
|
+
const strategy = mcpConfig.outputLimits.strategy ?? "externalize";
|
|
886
|
+
const maxBytes = mcpConfig.outputLimits.maxBytes ?? DEFAULT_MAX_MCP_OUTPUT_BYTES;
|
|
887
|
+
const warnBytes = mcpConfig.outputLimits.warnBytes ?? DEFAULT_WARN_MCP_OUTPUT_BYTES;
|
|
888
|
+
let artifactStore;
|
|
889
|
+
if (strategy === "externalize") {
|
|
890
|
+
artifactStore = new LocalTempArtifactStore();
|
|
891
|
+
this.mcpArtifactStore = artifactStore;
|
|
892
|
+
logger.debug("[NeuroLink] MCP artifact store initialized (local-temp)");
|
|
893
|
+
}
|
|
894
|
+
const normalizer = new McpOutputNormalizer({ strategy, maxBytes, warnBytes }, artifactStore);
|
|
895
|
+
this.externalServerManager.setOutputNormalizer(normalizer);
|
|
896
|
+
logger.debug("[NeuroLink] MCP output normalizer initialized", {
|
|
897
|
+
strategy,
|
|
898
|
+
maxBytes,
|
|
899
|
+
warnBytes,
|
|
900
|
+
});
|
|
901
|
+
}
|
|
821
902
|
}
|
|
822
903
|
/**
|
|
823
904
|
* Register file reference tools with the MCP tool registry.
|
|
@@ -937,90 +1018,46 @@ export class NeuroLink {
|
|
|
937
1018
|
"redis" in memConfig &&
|
|
938
1019
|
!!memConfig.redis) ||
|
|
939
1020
|
process.env.STORAGE_TYPE === "redis";
|
|
940
|
-
|
|
941
|
-
|
|
1021
|
+
const hasArtifactStore = !!this.mcpArtifactStore;
|
|
1022
|
+
// Register when Redis is configured OR when an artifact store exists.
|
|
1023
|
+
// Artifact store alone is sufficient for the artifactId retrieval path —
|
|
1024
|
+
// session history retrieval just returns a clear error when Redis is absent.
|
|
1025
|
+
if ((!memConfig?.enabled || !hasRedisConfig) && !hasArtifactStore) {
|
|
1026
|
+
logger.debug("[NeuroLink] Skipping memory retrieval tools — requires Redis conversation memory or an artifact store");
|
|
942
1027
|
return;
|
|
943
1028
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const errorMsg = hasError
|
|
974
|
-
? result.error
|
|
975
|
-
: undefined;
|
|
976
|
-
return {
|
|
977
|
-
success: !hasError,
|
|
978
|
-
data: result,
|
|
979
|
-
...(errorMsg ? { error: errorMsg } : {}),
|
|
980
|
-
metadata: {
|
|
981
|
-
toolName: "retrieve_context",
|
|
982
|
-
serverId: "direct",
|
|
983
|
-
executionTime: 0,
|
|
984
|
-
},
|
|
985
|
-
};
|
|
986
|
-
},
|
|
1029
|
+
// Extract the canonical tool definition (schema + description) from the
|
|
1030
|
+
// memoryRetrievalTools factory. We pass undefined as the memoryManager here
|
|
1031
|
+
// because we only need the Zod inputSchema and description at registration
|
|
1032
|
+
// time — the actual manager is resolved lazily at execution time.
|
|
1033
|
+
const canonicalTools = createMemoryRetrievalTools(undefined, this.mcpArtifactStore);
|
|
1034
|
+
const retrieveContextDef = canonicalTools.retrieve_context;
|
|
1035
|
+
// Register via this.registerTool() so the tool ends up in the "user-defined"
|
|
1036
|
+
// category inside toolRegistry. getCustomTools() returns that category, which
|
|
1037
|
+
// is what ToolsManager reads to build the tool schema sent to the LLM.
|
|
1038
|
+
// (Tools registered via toolRegistry.registerTool() directly land in the
|
|
1039
|
+
// "built-in" category and are never included in the LLM's tool schema.)
|
|
1040
|
+
this.registerTool("retrieve_context", {
|
|
1041
|
+
name: "retrieve_context",
|
|
1042
|
+
description: retrieveContextDef.description ?? "Retrieve context or artifacts",
|
|
1043
|
+
// Pass the Zod schema so ToolsManager gives the LLM full parameter types.
|
|
1044
|
+
// registerTool() detects isZodSchema on inputSchema and preserves it.
|
|
1045
|
+
inputSchema: retrieveContextDef
|
|
1046
|
+
.inputSchema,
|
|
1047
|
+
execute: async (params) => {
|
|
1048
|
+
// Lazy: conversationMemory is initialized on the first generate() call.
|
|
1049
|
+
// When only an artifact store is present (no Redis), memoryManager is
|
|
1050
|
+
// undefined — createMemoryRetrievalTools handles that via an explicit guard.
|
|
1051
|
+
const memoryManager = this.conversationMemory;
|
|
1052
|
+
const tools = createMemoryRetrievalTools(memoryManager, this.mcpArtifactStore);
|
|
1053
|
+
// Return the result directly so the LLM receives clean output instead
|
|
1054
|
+
// of a nested { success, data, metadata } wrapper.
|
|
1055
|
+
// Bounded by TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS so a stalled Redis or
|
|
1056
|
+
// filesystem backend never hangs the tool call indefinitely.
|
|
1057
|
+
return await withTimeout(tools.retrieve_context.execute(params, { toolCallId: "memory-retrieval", messages: [] }), TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS, ErrorFactory.toolTimeout("retrieve_context", TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS));
|
|
987
1058
|
},
|
|
988
|
-
};
|
|
989
|
-
const registrations = Object.entries(tools).map(async ([toolName, toolDef]) => {
|
|
990
|
-
const toolId = `direct.${toolName}`;
|
|
991
|
-
const toolInfo = {
|
|
992
|
-
name: toolName,
|
|
993
|
-
description: toolDef.description,
|
|
994
|
-
inputSchema: {},
|
|
995
|
-
serverId: "direct",
|
|
996
|
-
category: "built-in",
|
|
997
|
-
};
|
|
998
|
-
await this.toolRegistry.registerTool(toolId, toolInfo, {
|
|
999
|
-
execute: async (params) => {
|
|
1000
|
-
try {
|
|
1001
|
-
return await toolDef.execute(params);
|
|
1002
|
-
}
|
|
1003
|
-
catch (error) {
|
|
1004
|
-
// Known limitation: this non-throwing error path returns
|
|
1005
|
-
// { success: false } without recording errorCategories in
|
|
1006
|
-
// toolExecutionMetrics. These are internal memory-tool failures
|
|
1007
|
-
// (low frequency), so the risk of metric gaps is minimal.
|
|
1008
|
-
// A full fix would require access to the metrics map here,
|
|
1009
|
-
// which is not available in the registration closure.
|
|
1010
|
-
return {
|
|
1011
|
-
success: false,
|
|
1012
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1013
|
-
metadata: { toolName, serverId: "direct", executionTime: 0 },
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
},
|
|
1017
|
-
description: toolDef.description,
|
|
1018
|
-
inputSchema: {},
|
|
1019
|
-
});
|
|
1020
|
-
});
|
|
1021
|
-
void Promise.all(registrations).then(() => {
|
|
1022
|
-
logger.info("[NeuroLink] Memory retrieval tools registered");
|
|
1023
1059
|
});
|
|
1060
|
+
logger.info("[NeuroLink] Memory retrieval tools registered");
|
|
1024
1061
|
}
|
|
1025
1062
|
/** Format memory context for prompt inclusion */
|
|
1026
1063
|
formatMemoryContext(memoryContext, currentInput) {
|
|
@@ -2143,6 +2180,7 @@ Current user's request: ${currentInput}`;
|
|
|
2143
2180
|
}
|
|
2144
2181
|
}
|
|
2145
2182
|
logger.debug("[NeuroLink] Graceful shutdown completed");
|
|
2183
|
+
this.credentials = undefined;
|
|
2146
2184
|
}
|
|
2147
2185
|
catch (error) {
|
|
2148
2186
|
logger.error("[NeuroLink] Shutdown failed:", error);
|
|
@@ -2671,6 +2709,7 @@ Current user's request: ${currentInput}`;
|
|
|
2671
2709
|
skipToolPromptInjection: options.skipToolPromptInjection,
|
|
2672
2710
|
middleware: options.middleware,
|
|
2673
2711
|
conversationMessages: options.conversationMessages,
|
|
2712
|
+
credentials: options.credentials,
|
|
2674
2713
|
};
|
|
2675
2714
|
const extraContext = options;
|
|
2676
2715
|
if (extraContext.sessionId || extraContext.userId) {
|
|
@@ -2805,7 +2844,7 @@ Current user's request: ${currentInput}`;
|
|
|
2805
2844
|
const { extractPPTContext, getEffectivePPTProvider } = await import("./features/ppt/utils.js");
|
|
2806
2845
|
// Get provider instance for content planning
|
|
2807
2846
|
const requestedProvider = (options.provider || "vertex");
|
|
2808
|
-
const provider = await AIProviderFactory.createProvider(requestedProvider, options.model, true, this);
|
|
2847
|
+
const provider = await AIProviderFactory.createProvider(requestedProvider, options.model, true, this, undefined, this.resolveCredentials(options.credentials));
|
|
2809
2848
|
// Resolve effective PPT provider (may auto-select if current is not PPT-compatible)
|
|
2810
2849
|
const effectiveProvider = await getEffectivePPTProvider(provider, requestedProvider, options.model || "default", this);
|
|
2811
2850
|
// Extract PPT context from options
|
|
@@ -3839,7 +3878,7 @@ Current user's request: ${currentInput}`;
|
|
|
3839
3878
|
}
|
|
3840
3879
|
async generateWithMCPProvider(context) {
|
|
3841
3880
|
const { options, requestId, functionTag, tryMCPStartTime, providerName, availableTools, enhancedSystemPrompt, conversationMessages, } = context;
|
|
3842
|
-
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, this, options.region);
|
|
3881
|
+
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, this, options.region, this.resolveCredentials(options.credentials));
|
|
3843
3882
|
provider.setTraceContext(this._metricsTraceContext);
|
|
3844
3883
|
this.emitter.emit("connected");
|
|
3845
3884
|
this.emitter.emit("message", `${providerName} provider initialized successfully`);
|
|
@@ -4035,7 +4074,8 @@ Current user's request: ${currentInput}`;
|
|
|
4035
4074
|
}
|
|
4036
4075
|
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
|
|
4037
4076
|
this, // Pass SDK instance
|
|
4038
|
-
options.region
|
|
4077
|
+
options.region, // Pass region parameter
|
|
4078
|
+
this.resolveCredentials(options.credentials));
|
|
4039
4079
|
// Propagate trace context for parent-child span hierarchy
|
|
4040
4080
|
provider.setTraceContext(this._metricsTraceContext);
|
|
4041
4081
|
// ADD: Emit connection events for successful provider creation (Bedrock-compatible)
|
|
@@ -4783,7 +4823,7 @@ Current user's request: ${currentInput}`;
|
|
|
4783
4823
|
reason: errorMsg,
|
|
4784
4824
|
});
|
|
4785
4825
|
try {
|
|
4786
|
-
const fallbackProvider = await AIProviderFactory.createProvider(fallbackRoute.provider, fallbackRoute.model);
|
|
4826
|
+
const fallbackProvider = await AIProviderFactory.createProvider(fallbackRoute.provider, fallbackRoute.model, true, undefined, undefined, this.resolveCredentials(enhancedOptions.credentials));
|
|
4787
4827
|
// Ensure fallback provider can execute tools
|
|
4788
4828
|
fallbackProvider.setupToolExecutor({
|
|
4789
4829
|
customTools: this.getCustomTools(),
|
|
@@ -4946,7 +4986,8 @@ Current user's request: ${currentInput}`;
|
|
|
4946
4986
|
const providerName = await getBestProvider(options.provider);
|
|
4947
4987
|
const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
|
|
4948
4988
|
this, // Pass SDK instance
|
|
4949
|
-
options.region
|
|
4989
|
+
options.region, // Pass region parameter
|
|
4990
|
+
this.resolveCredentials(options.credentials));
|
|
4950
4991
|
// Propagate trace context for parent-child span hierarchy
|
|
4951
4992
|
provider.setTraceContext(this._metricsTraceContext);
|
|
4952
4993
|
// Enable tool execution for the provider using BaseProvider method
|
|
@@ -5161,7 +5202,7 @@ Current user's request: ${currentInput}`;
|
|
|
5161
5202
|
const originalPrompt = options.input.text;
|
|
5162
5203
|
const responseTime = Date.now() - startTime;
|
|
5163
5204
|
const providerName = await getBestProvider(options.provider);
|
|
5164
|
-
const provider = await AIProviderFactory.createProvider(providerName, options.model);
|
|
5205
|
+
const provider = await AIProviderFactory.createProvider(providerName, options.model, true, undefined, undefined, this.resolveCredentials(options.credentials));
|
|
5165
5206
|
const fallbackStreamResult = await provider.stream({
|
|
5166
5207
|
input: { text: options.input.text },
|
|
5167
5208
|
model: options.model,
|
|
@@ -8523,6 +8564,7 @@ Current user's request: ${currentInput}`;
|
|
|
8523
8564
|
this.mcpInitialized = false;
|
|
8524
8565
|
this.mcpInitPromise = null;
|
|
8525
8566
|
this.conversationMemoryNeedsInit = false;
|
|
8567
|
+
this.credentials = undefined;
|
|
8526
8568
|
logger.debug("[NeuroLink] Initialization state reset successfully");
|
|
8527
8569
|
}
|
|
8528
8570
|
catch (error) {
|