@juspay/neurolink 7.12.0 → 7.14.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +90 -25
  3. package/dist/config/conversationMemoryConfig.js +2 -1
  4. package/dist/context/ContextManager.d.ts +28 -0
  5. package/dist/context/ContextManager.js +113 -0
  6. package/dist/context/config.d.ts +5 -0
  7. package/dist/context/config.js +42 -0
  8. package/dist/context/types.d.ts +20 -0
  9. package/dist/context/types.js +1 -0
  10. package/dist/context/utils.d.ts +7 -0
  11. package/dist/context/utils.js +8 -0
  12. package/dist/core/baseProvider.d.ts +16 -1
  13. package/dist/core/baseProvider.js +208 -9
  14. package/dist/core/conversationMemoryManager.js +3 -2
  15. package/dist/core/factory.js +13 -2
  16. package/dist/factories/providerFactory.js +5 -11
  17. package/dist/factories/providerRegistry.js +2 -2
  18. package/dist/lib/config/conversationMemoryConfig.js +2 -1
  19. package/dist/lib/context/ContextManager.d.ts +28 -0
  20. package/dist/lib/context/ContextManager.js +113 -0
  21. package/dist/lib/context/config.d.ts +5 -0
  22. package/dist/lib/context/config.js +42 -0
  23. package/dist/lib/context/types.d.ts +20 -0
  24. package/dist/lib/context/types.js +1 -0
  25. package/dist/lib/context/utils.d.ts +7 -0
  26. package/dist/lib/context/utils.js +8 -0
  27. package/dist/lib/core/baseProvider.d.ts +16 -1
  28. package/dist/lib/core/baseProvider.js +208 -9
  29. package/dist/lib/core/conversationMemoryManager.js +3 -2
  30. package/dist/lib/core/factory.js +13 -2
  31. package/dist/lib/factories/providerFactory.js +5 -11
  32. package/dist/lib/factories/providerRegistry.js +2 -2
  33. package/dist/lib/mcp/externalServerManager.d.ts +115 -0
  34. package/dist/lib/mcp/externalServerManager.js +677 -0
  35. package/dist/lib/mcp/mcpCircuitBreaker.d.ts +184 -0
  36. package/dist/lib/mcp/mcpCircuitBreaker.js +338 -0
  37. package/dist/lib/mcp/mcpClientFactory.d.ts +104 -0
  38. package/dist/lib/mcp/mcpClientFactory.js +416 -0
  39. package/dist/lib/mcp/toolDiscoveryService.d.ts +192 -0
  40. package/dist/lib/mcp/toolDiscoveryService.js +578 -0
  41. package/dist/lib/neurolink.d.ts +128 -16
  42. package/dist/lib/neurolink.js +555 -49
  43. package/dist/lib/providers/googleVertex.d.ts +1 -1
  44. package/dist/lib/providers/googleVertex.js +23 -7
  45. package/dist/lib/types/externalMcp.d.ts +282 -0
  46. package/dist/lib/types/externalMcp.js +6 -0
  47. package/dist/lib/types/generateTypes.d.ts +0 -1
  48. package/dist/lib/types/index.d.ts +1 -0
  49. package/dist/mcp/externalServerManager.d.ts +115 -0
  50. package/dist/mcp/externalServerManager.js +677 -0
  51. package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
  52. package/dist/mcp/mcpCircuitBreaker.js +338 -0
  53. package/dist/mcp/mcpClientFactory.d.ts +104 -0
  54. package/dist/mcp/mcpClientFactory.js +416 -0
  55. package/dist/mcp/toolDiscoveryService.d.ts +192 -0
  56. package/dist/mcp/toolDiscoveryService.js +578 -0
  57. package/dist/neurolink.d.ts +128 -16
  58. package/dist/neurolink.js +555 -49
  59. package/dist/providers/googleVertex.d.ts +1 -1
  60. package/dist/providers/googleVertex.js +23 -7
  61. package/dist/types/externalMcp.d.ts +282 -0
  62. package/dist/types/externalMcp.js +6 -0
  63. package/dist/types/generateTypes.d.ts +0 -1
  64. package/dist/types/index.d.ts +1 -0
  65. package/package.json +1 -1
@@ -157,10 +157,20 @@ export class BaseProvider {
157
157
  try {
158
158
  // Import generateText dynamically to avoid circular dependencies
159
159
  const { generateText } = await import("ai");
160
- // Get ALL available tools (direct + MCP when available)
160
+ // Get ALL available tools (direct + MCP + external from options)
161
161
  const shouldUseTools = !options.disableTools && this.supportsTools();
162
- const tools = shouldUseTools ? await this.getAllTools() : {};
163
- logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}: ${Object.keys(tools).join(", ")}`);
162
+ const baseTools = shouldUseTools ? await this.getAllTools() : {};
163
+ const tools = shouldUseTools
164
+ ? {
165
+ ...baseTools,
166
+ ...(options.tools || {}), // Include external tools passed from NeuroLink
167
+ }
168
+ : {};
169
+ logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}:`, {
170
+ directTools: Object.keys(baseTools),
171
+ externalTools: Object.keys(options.tools || {}),
172
+ totalTools: Object.keys(tools),
173
+ });
164
174
  // EVERY provider uses Vercel AI SDK - no exceptions
165
175
  const model = await this.getAISDKModel(); // This method is now REQUIRED
166
176
  // Build proper message array with conversation history
@@ -201,22 +211,66 @@ export class BaseProvider {
201
211
  const uniqueToolsUsed = [...new Set(toolsUsed)];
202
212
  // ✅ Extract tool executions from AI SDK result
203
213
  const toolExecutions = [];
214
+ // Create a map of tool calls to their arguments for matching with results
215
+ const toolCallArgsMap = new Map();
204
216
  // Extract tool executions from AI SDK result steps
205
- // Extract tool executions from steps (where tool results are stored)
206
217
  if (result.steps &&
207
218
  Array.isArray(result.steps)) {
208
219
  for (const step of result.steps ||
209
220
  []) {
210
- // Focus only on tool results (which have complete execution data)
211
- // Tool calls are just the requests, tool results contain the actual execution data
221
+ // First, collect tool calls and their arguments
222
+ if (step?.toolCalls && Array.isArray(step.toolCalls)) {
223
+ for (const toolCall of step.toolCalls) {
224
+ const tcRecord = toolCall;
225
+ const toolName = tcRecord.toolName ||
226
+ tcRecord.name ||
227
+ "unknown";
228
+ const toolId = tcRecord.toolCallId ||
229
+ tcRecord.id ||
230
+ toolName;
231
+ // Extract arguments from tool call
232
+ let callArgs = {};
233
+ if (tcRecord.args) {
234
+ callArgs = tcRecord.args;
235
+ }
236
+ else if (tcRecord.arguments) {
237
+ callArgs = tcRecord.arguments;
238
+ }
239
+ else if (tcRecord.parameters) {
240
+ callArgs = tcRecord.parameters;
241
+ }
242
+ toolCallArgsMap.set(toolId, callArgs);
243
+ toolCallArgsMap.set(toolName, callArgs); // Also map by name as fallback
244
+ }
245
+ }
246
+ // Then, process tool results and match with call arguments
212
247
  if (step?.toolResults && Array.isArray(step.toolResults)) {
213
248
  for (const toolResult of step.toolResults) {
214
249
  const trRecord = toolResult;
250
+ const toolName = trRecord.toolName || "unknown";
251
+ const toolId = trRecord.toolCallId || trRecord.id;
252
+ // Try to get arguments from the tool result first
253
+ let toolArgs = {};
254
+ if (trRecord.args) {
255
+ toolArgs = trRecord.args;
256
+ }
257
+ else if (trRecord.arguments) {
258
+ toolArgs = trRecord.arguments;
259
+ }
260
+ else if (trRecord.parameters) {
261
+ toolArgs = trRecord.parameters;
262
+ }
263
+ else if (trRecord.input) {
264
+ toolArgs = trRecord.input;
265
+ }
266
+ else {
267
+ // Fallback: get arguments from the corresponding tool call
268
+ toolArgs = toolCallArgsMap.get(toolId || toolName) || {};
269
+ }
215
270
  toolExecutions.push({
216
- name: trRecord.toolName || "unknown",
217
- input: trRecord.args || {},
271
+ name: toolName,
272
+ input: toolArgs,
218
273
  output: trRecord.result || "success",
219
- duration: 0, // AI SDK doesn't track duration
220
274
  });
221
275
  }
222
276
  }
@@ -248,6 +302,12 @@ export class BaseProvider {
248
302
  toolResults: result.toolResults,
249
303
  toolsUsed: uniqueToolsUsed,
250
304
  toolExecutions, // ✅ Add extracted tool executions
305
+ availableTools: Object.keys(tools).map((name) => ({
306
+ name,
307
+ description: tools[name].description || "No description available",
308
+ parameters: tools[name].parameters || {},
309
+ server: tools[name].serverId || "direct",
310
+ })),
251
311
  };
252
312
  // Enhanced result with analytics and evaluation
253
313
  return await this.enhanceResult(enhancedResult, options, startTime);
@@ -339,6 +399,71 @@ export class BaseProvider {
339
399
  // Not an error - custom tools are optional
340
400
  }
341
401
  }
402
+ // ✅ CRITICAL FIX: Add external MCP tools if SDK has external server manager
403
+ if (this.sdk &&
404
+ this.sdk.externalServerManager &&
405
+ typeof this.sdk.externalServerManager.getAllTools === "function") {
406
+ try {
407
+ logger.debug(`[BaseProvider] Loading external MCP tools from SDK via externalServerManager`);
408
+ const externalTools = this.sdk.externalServerManager.getAllTools();
409
+ logger.debug(`[BaseProvider] Found ${externalTools.length} external MCP tools`, {
410
+ tools: externalTools.map((t) => ({
411
+ name: t.name,
412
+ available: t.isAvailable,
413
+ server: t.serverId,
414
+ })),
415
+ });
416
+ for (const externalTool of externalTools) {
417
+ if (externalTool.isAvailable) {
418
+ logger.debug(`[BaseProvider] Converting external MCP tool: ${externalTool.name} from ${externalTool.serverId}`);
419
+ // Convert to AI SDK tool format
420
+ const { tool: createAISDKTool } = await import("ai");
421
+ const { z } = await import("zod");
422
+ tools[externalTool.name] = createAISDKTool({
423
+ description: externalTool.description ||
424
+ `External MCP tool ${externalTool.name}`,
425
+ parameters: await this.convertMCPSchemaToZod(externalTool.inputSchema),
426
+ execute: async (args) => {
427
+ logger.debug(`[BaseProvider] Executing external MCP tool: ${externalTool.name}`, { args });
428
+ // Execute via SDK's external server manager
429
+ if (this.sdk &&
430
+ this.sdk.externalServerManager &&
431
+ typeof this.sdk.externalServerManager.executeTool ===
432
+ "function") {
433
+ return await this.sdk.externalServerManager.executeTool(externalTool.serverId, externalTool.name, args);
434
+ }
435
+ else {
436
+ throw new Error(`Cannot execute external MCP tool: SDK externalServerManager.executeTool not available`);
437
+ }
438
+ },
439
+ });
440
+ logger.debug(`[BaseProvider] Successfully added external MCP tool: ${externalTool.name}`);
441
+ }
442
+ else {
443
+ logger.debug(`[BaseProvider] Skipping unavailable external MCP tool: ${externalTool.name}`);
444
+ }
445
+ }
446
+ logger.debug(`[BaseProvider] External MCP tools loading complete`, {
447
+ totalExternalTools: externalTools.length,
448
+ availableExternalTools: externalTools.filter((t) => t.isAvailable)
449
+ .length,
450
+ addedToTools: externalTools.filter((t) => t.isAvailable).length,
451
+ });
452
+ }
453
+ catch (error) {
454
+ logger.error(`[BaseProvider] Failed to load external MCP tools for ${this.providerName}:`, error);
455
+ // Not an error - external tools are optional
456
+ }
457
+ }
458
+ else {
459
+ logger.debug(`[BaseProvider] No external MCP tools interface available`, {
460
+ hasSDK: !!this.sdk,
461
+ hasExternalServerManager: this.sdk && !!this.sdk.externalServerManager,
462
+ hasGetAllTools: this.sdk &&
463
+ this.sdk.externalServerManager &&
464
+ typeof this.sdk.externalServerManager.getAllTools === "function",
465
+ });
466
+ }
342
467
  // MCP tools loading simplified - removed functionCalling dependency
343
468
  if (!this.mcpTools) {
344
469
  // Set empty tools object - MCP tools are handled at a higher level
@@ -351,6 +476,80 @@ export class BaseProvider {
351
476
  logger.debug(`[BaseProvider] getAllTools returning tools: ${Object.keys(tools).join(", ")}`);
352
477
  return tools;
353
478
  }
479
+ /**
480
+ * Convert MCP JSON Schema to Zod schema for AI SDK tools
481
+ * Handles common MCP schema patterns safely
482
+ */
483
+ async convertMCPSchemaToZod(inputSchema) {
484
+ const { z } = await import("zod");
485
+ if (!inputSchema || typeof inputSchema !== "object") {
486
+ return z.object({});
487
+ }
488
+ try {
489
+ const schema = inputSchema;
490
+ const zodFields = {};
491
+ // Handle JSON Schema properties
492
+ if (schema.properties && typeof schema.properties === "object") {
493
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
494
+ for (const [propName, propDef] of Object.entries(schema.properties)) {
495
+ const prop = propDef;
496
+ let zodType;
497
+ // Convert based on JSON Schema type
498
+ switch (prop.type) {
499
+ case "string":
500
+ zodType = z.string();
501
+ if (prop.description) {
502
+ zodType = zodType.describe(prop.description);
503
+ }
504
+ break;
505
+ case "number":
506
+ case "integer":
507
+ zodType = z.number();
508
+ if (prop.description) {
509
+ zodType = zodType.describe(prop.description);
510
+ }
511
+ break;
512
+ case "boolean":
513
+ zodType = z.boolean();
514
+ if (prop.description) {
515
+ zodType = zodType.describe(prop.description);
516
+ }
517
+ break;
518
+ case "array":
519
+ zodType = z.array(z.unknown());
520
+ if (prop.description) {
521
+ zodType = zodType.describe(prop.description);
522
+ }
523
+ break;
524
+ case "object":
525
+ zodType = z.object({});
526
+ if (prop.description) {
527
+ zodType = zodType.describe(prop.description);
528
+ }
529
+ break;
530
+ default:
531
+ // Unknown type, use string as fallback
532
+ zodType = z.string();
533
+ if (prop.description) {
534
+ zodType = zodType.describe(prop.description);
535
+ }
536
+ }
537
+ // Make optional if not required
538
+ if (!required.has(propName)) {
539
+ zodType = zodType.optional();
540
+ }
541
+ zodFields[propName] = zodType;
542
+ }
543
+ }
544
+ return Object.keys(zodFields).length > 0
545
+ ? z.object(zodFields)
546
+ : z.object({});
547
+ }
548
+ catch (error) {
549
+ logger.warn(`Failed to convert MCP schema to Zod, using empty schema:`, error);
550
+ return z.object({});
551
+ }
552
+ }
354
553
  /**
355
554
  * Set session context for MCP tools
356
555
  */
@@ -3,7 +3,7 @@
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
5
  import { ConversationMemoryError } from "../types/conversationTypes.js";
6
- import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN } from "../config/conversationMemoryConfig.js";
6
+ import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemoryConfig.js";
7
7
  import { logger } from "../utils/logger.js";
8
8
  export class ConversationMemoryManager {
9
9
  sessions = new Map();
@@ -49,7 +49,8 @@ export class ConversationMemoryManager {
49
49
  session.messages.push({ role: "user", content: userMessage }, { role: "assistant", content: aiResponse });
50
50
  session.lastActivity = Date.now();
51
51
  // Enforce per-session turn limit (each turn = MESSAGES_PER_TURN messages: user + assistant)
52
- const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) * MESSAGES_PER_TURN;
52
+ const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) *
53
+ MESSAGES_PER_TURN;
53
54
  if (session.messages.length > maxMessages) {
54
55
  session.messages = session.messages.slice(-maxMessages);
55
56
  }
@@ -109,13 +109,24 @@ export class AIProviderFactory {
109
109
  const finalModelName = resolvedModelName === "default" || resolvedModelName === null
110
110
  ? undefined
111
111
  : resolvedModelName;
112
- const provider = await ProviderFactory.createProvider(normalizedName, finalModelName, sdk);
112
+ // CRITICAL FIX: Pass external MCP tools interface to BaseProvider
113
+ let finalSdk = sdk;
114
+ if (sdk && typeof sdk.getExternalMCPTools === "function") {
115
+ finalSdk = {
116
+ ...sdk,
117
+ externalServerManager: {
118
+ getAllTools: () => sdk.getExternalMCPTools(),
119
+ executeTool: (serverId, toolName, params) => sdk.executeExternalMCPTool(serverId, toolName, params),
120
+ },
121
+ };
122
+ }
123
+ // Create provider with enhanced SDK
124
+ const provider = await ProviderFactory.createProvider(normalizedName, finalModelName, finalSdk);
113
125
  logger.debug(componentIdentifier, "Pure factory pattern provider created", {
114
126
  providerName: normalizedName,
115
127
  modelName: finalModelName,
116
128
  factoryUsed: true,
117
129
  });
118
- // PURE FACTORY PATTERN: All providers handled by ProviderFactory - no switch statements needed
119
130
  // Wrap with MCP if enabled
120
131
  if (enableMCP) {
121
132
  try {
@@ -46,22 +46,16 @@ export class ProviderFactory {
46
46
  model = model || registration.defaultModel;
47
47
  }
48
48
  try {
49
- // Try calling as factory function first, then fallback to constructor
50
49
  let result;
51
50
  try {
52
- // Try as factory function
53
- result = registration.constructor(model, providerName, sdk);
51
+ // Try as async factory function first (most providers are async functions)
52
+ result = await registration.constructor(model, providerName, sdk);
54
53
  }
55
- catch (factoryError) {
56
- // Fallback to constructor
54
+ catch (functionError) {
55
+ // Fallback to constructor - ensure parameters are maintained
57
56
  result = new registration.constructor(model, providerName, sdk);
58
57
  }
59
- // Only await if result is actually a Promise
60
- if (result &&
61
- typeof result === "object" &&
62
- typeof result.then === "function") {
63
- result = await result;
64
- }
58
+ // Return result (no need to await again if already awaited in try block)
65
59
  return result;
66
60
  }
67
61
  catch (error) {
@@ -52,9 +52,9 @@ export class ProviderRegistry {
52
52
  process.env.AZURE_OPENAI_DEPLOYMENT_ID ||
53
53
  "gpt-4o-mini", ["azure", "azureOpenai"]);
54
54
  // Register Google Vertex AI provider
55
- ProviderFactory.registerProvider(AIProviderName.VERTEX, async (modelName) => {
55
+ ProviderFactory.registerProvider(AIProviderName.VERTEX, async (modelName, providerName, sdk) => {
56
56
  const { GoogleVertexProvider } = await import("../providers/googleVertex.js");
57
- return new GoogleVertexProvider(modelName);
57
+ return new GoogleVertexProvider(modelName, providerName, sdk);
58
58
  }, "claude-sonnet-4@20250514", ["vertex", "googleVertex"]);
59
59
  // Register Hugging Face provider (Unified Router implementation)
60
60
  ProviderFactory.registerProvider(AIProviderName.HUGGINGFACE, async (modelName) => {
@@ -34,6 +34,7 @@ export function getConversationMemoryDefaults() {
34
34
  return {
35
35
  enabled: process.env.NEUROLINK_MEMORY_ENABLED === "true",
36
36
  maxSessions: Number(process.env.NEUROLINK_MEMORY_MAX_SESSIONS) || DEFAULT_MAX_SESSIONS,
37
- maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) || DEFAULT_MAX_TURNS_PER_SESSION,
37
+ maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) ||
38
+ DEFAULT_MAX_TURNS_PER_SESSION,
38
39
  };
39
40
  }
@@ -0,0 +1,28 @@
1
+ import type { TextGenerationOptions, TextGenerationResult } from "../core/types.js";
2
+ import type { ContextManagerConfig } from "./types.js";
3
+ type InternalGenerator = (options: TextGenerationOptions) => Promise<TextGenerationResult>;
4
+ /**
5
+ * Manages conversation context, automatically summarizing it when it
6
+ * exceeds a specified word count limit.
7
+ */
8
+ export declare class ContextManager {
9
+ private static readonly SUMMARIZATION_FAILED_WARNING;
10
+ private static readonly SUMMARIZATION_EMPTY_WARNING;
11
+ private history;
12
+ private wordCount;
13
+ private readonly internalGenerator;
14
+ private readonly config;
15
+ constructor(generatorFunction: InternalGenerator, config: ContextManagerConfig, initialContext?: string);
16
+ addTurn(role: "user" | "assistant", message: string): Promise<void>;
17
+ /**
18
+ * Formats the history including the latest user turn for the prompt, without modifying the permanent history.
19
+ */
20
+ getContextForPrompt(role: "user", message: string): string;
21
+ getCurrentContext(): string;
22
+ private _summarize;
23
+ /**
24
+ * Truncates the history to a specific word count, preserving the most recent messages.
25
+ */
26
+ private _truncateHistory;
27
+ }
28
+ export {};
@@ -0,0 +1,113 @@
1
+ import { logger } from "../utils/logger.js";
2
+ import { formatHistoryToString } from "./utils.js";
3
+ /**
4
+ * Manages conversation context, automatically summarizing it when it
5
+ * exceeds a specified word count limit.
6
+ */
7
+ export class ContextManager {
8
+ static SUMMARIZATION_FAILED_WARNING = "[System Warning: Context summarization failed. Conversation history has been truncated.]";
9
+ static SUMMARIZATION_EMPTY_WARNING = "[System Warning: Context summarization failed to return valid content. Conversation history has been truncated.]";
10
+ history;
11
+ wordCount;
12
+ internalGenerator;
13
+ config;
14
+ constructor(generatorFunction, config, initialContext = "This is the start of the conversation.") {
15
+ this.internalGenerator = generatorFunction;
16
+ this.config = config;
17
+ const initialMessage = {
18
+ role: "system",
19
+ content: initialContext,
20
+ };
21
+ initialMessage.wordCount = this.config.estimateWordCount([initialMessage]);
22
+ this.history = [initialMessage];
23
+ this.wordCount = initialMessage.wordCount;
24
+ }
25
+ async addTurn(role, message) {
26
+ const newMessage = { role, content: message };
27
+ newMessage.wordCount = this.config.estimateWordCount([newMessage]);
28
+ this.history.push(newMessage);
29
+ this.wordCount += newMessage.wordCount;
30
+ logger.info(`[ContextManager] Current word count: ${this.wordCount} / ${this.config.highWaterMarkWords}`);
31
+ if (this.wordCount > this.config.highWaterMarkWords) {
32
+ await this._summarize();
33
+ }
34
+ }
35
+ /**
36
+ * Formats the history including the latest user turn for the prompt, without modifying the permanent history.
37
+ */
38
+ getContextForPrompt(role, message) {
39
+ const tempHistory = [...this.history, { role, content: message }];
40
+ return formatHistoryToString(tempHistory);
41
+ }
42
+ getCurrentContext() {
43
+ // Format the history into a single string for the provider prompt
44
+ return formatHistoryToString(this.history);
45
+ }
46
+ async _summarize() {
47
+ try {
48
+ const prompt = this.config.getSummarizationPrompt(this.history, this.config.lowWaterMarkWords);
49
+ // Construct options for the internal method, bypassing the main 'generate' entry point
50
+ const textOptions = {
51
+ prompt,
52
+ provider: this.config.summarizationProvider,
53
+ model: this.config.summarizationModel,
54
+ // Ensure summarization does not trigger more context management or tools
55
+ disableTools: true,
56
+ };
57
+ // Call the internal generation function directly to avoid recursion
58
+ const result = await this.internalGenerator(textOptions);
59
+ if (typeof result.content === "string" && result.content.length > 0) {
60
+ // Replace the history with a single system message containing the summary
61
+ const newHistory = [
62
+ { role: "system", content: result.content },
63
+ ];
64
+ this.history = newHistory;
65
+ this.wordCount = this.config.estimateWordCount(this.history);
66
+ logger.info(`[ContextManager] Summarization complete. New history length: ${this.wordCount} words.`);
67
+ }
68
+ else {
69
+ logger.warn("[ContextManager] Summarization returned empty or non-string content; truncating history as a fallback.");
70
+ this._truncateHistory(this.config.lowWaterMarkWords);
71
+ this.history.unshift({
72
+ role: "system",
73
+ content: ContextManager.SUMMARIZATION_EMPTY_WARNING,
74
+ });
75
+ this.wordCount = this.config.estimateWordCount(this.history);
76
+ }
77
+ logger.debug(`[ContextManager] New history: ${JSON.stringify(this.history)}`);
78
+ }
79
+ catch (error) {
80
+ logger.error("Context summarization failed:", { error });
81
+ // Fallback strategy: truncate the history to the target word count.
82
+ this._truncateHistory(this.config.lowWaterMarkWords);
83
+ this.history.unshift({
84
+ role: "system",
85
+ content: ContextManager.SUMMARIZATION_FAILED_WARNING,
86
+ });
87
+ this.wordCount = this.config.estimateWordCount(this.history);
88
+ }
89
+ }
90
+ /**
91
+ * Truncates the history to a specific word count, preserving the most recent messages.
92
+ */
93
+ _truncateHistory(wordLimit) {
94
+ if (this.wordCount <= wordLimit) {
95
+ return;
96
+ }
97
+ let runningCount = 0;
98
+ let sliceIndex = this.history.length;
99
+ for (let i = this.history.length - 1; i >= 0; i--) {
100
+ let wordCount = this.history[i].wordCount;
101
+ if (wordCount === undefined) {
102
+ logger.warn(`[ContextManager] Word count cache missing for message at index ${i}. Recalculating.`);
103
+ wordCount = this.config.estimateWordCount([this.history[i]]);
104
+ }
105
+ runningCount += wordCount;
106
+ if (runningCount > wordLimit) {
107
+ sliceIndex = i + 1;
108
+ break;
109
+ }
110
+ }
111
+ this.history = this.history.slice(sliceIndex);
112
+ }
113
+ }
@@ -0,0 +1,5 @@
1
+ import type { ContextManagerConfig } from "./types.js";
2
+ /**
3
+ * Default configuration for the ContextManager.
4
+ */
5
+ export declare const defaultContextConfig: ContextManagerConfig;
@@ -0,0 +1,42 @@
1
+ import { formatHistoryToString } from "./utils.js";
2
+ /**
3
+ * Estimates the word count of a conversation history.
4
+ */
5
+ function estimateWordCount(history) {
6
+ if (!history || history.length === 0) {
7
+ return 0;
8
+ }
9
+ return history.reduce((acc, msg) => acc +
10
+ (msg.content
11
+ .trim()
12
+ .split(/\s+/)
13
+ .filter((word) => word.length > 0).length || 0), 0);
14
+ }
15
+ /**
16
+ * Generates the default prompt for summarization.
17
+ */
18
+ function getDefaultSummarizationPrompt(history, wordLimit) {
19
+ const formattedHistory = formatHistoryToString(history);
20
+ return `
21
+ You are a context summarization AI. Your task is to condense the following conversation history for another AI assistant.
22
+ The summary must be a concise, third-person narrative that retains all critical information. Pay special attention to retaining key entities, technical details, decisions made, and any specific dates or times mentioned.
23
+ Ensure the summary flows logically and is ready to be used as context for the next turn in the conversation.
24
+ Please keep the summary under ${wordLimit} words.
25
+
26
+ Conversation History to Summarize:
27
+ ---
28
+ ${formattedHistory}
29
+ ---
30
+ `.trim();
31
+ }
32
+ /**
33
+ * Default configuration for the ContextManager.
34
+ */
35
+ export const defaultContextConfig = {
36
+ highWaterMarkWords: 3000,
37
+ lowWaterMarkWords: 800,
38
+ summarizationModel: "gemini-2.5-flash",
39
+ summarizationProvider: "googlevertex",
40
+ getSummarizationPrompt: getDefaultSummarizationPrompt,
41
+ estimateWordCount: estimateWordCount,
42
+ };
@@ -0,0 +1,20 @@
1
+ export interface ChatMessage {
2
+ /** Role of the message sender */
3
+ role: "user" | "assistant" | "system";
4
+ /** Content of the message */
5
+ content: string;
6
+ /** Cached word count for performance */
7
+ wordCount?: number;
8
+ }
9
+ /**
10
+ * Defines the configuration for the ContextManager.
11
+ * This allows for easy customization of the summarization behavior.
12
+ */
13
+ export interface ContextManagerConfig {
14
+ highWaterMarkWords: number;
15
+ lowWaterMarkWords: number;
16
+ summarizationModel: string;
17
+ summarizationProvider: string;
18
+ getSummarizationPrompt: (history: ChatMessage[], wordLimit: number) => string;
19
+ estimateWordCount: (history: ChatMessage[]) => number;
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { ChatMessage } from "./types.js";
2
+ /**
3
+ * Formats a chat history array into a single string for use in a prompt.
4
+ * @param history The array of ChatMessage objects.
5
+ * @returns A formatted string representing the conversation.
6
+ */
7
+ export declare function formatHistoryToString(history: ChatMessage[]): string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Formats a chat history array into a single string for use in a prompt.
3
+ * @param history The array of ChatMessage objects.
4
+ * @returns A formatted string representing the conversation.
5
+ */
6
+ export function formatHistoryToString(history) {
7
+ return history.map((msg) => `${msg.role}: ${msg.content}`).join("\n\n");
8
+ }
@@ -6,7 +6,7 @@ import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
6
6
  import type { JsonValue, UnknownRecord } from "../types/common.js";
7
7
  import type { ToolResult } from "../types/tools.js";
8
8
  /**
9
- * Interface for SDK with in-memory MCP servers
9
+ * Interface for SDK with in-memory MCP servers and external MCP support
10
10
  */
11
11
  export interface NeuroLinkSDK {
12
12
  getInMemoryServers?: () => Map<string, {
@@ -18,6 +18,16 @@ export interface NeuroLinkSDK {
18
18
  category?: string;
19
19
  metadata?: UnknownRecord;
20
20
  }>;
21
+ externalServerManager?: {
22
+ getAllTools: () => Array<{
23
+ name: string;
24
+ description: string;
25
+ serverId: string;
26
+ isAvailable: boolean;
27
+ inputSchema?: Record<string, unknown>;
28
+ }>;
29
+ executeTool: (serverId: string, toolName: string, params: any) => Promise<any>;
30
+ };
21
31
  }
22
32
  /**
23
33
  * Interface for tool information in MCP servers
@@ -86,6 +96,11 @@ export declare abstract class BaseProvider implements AIProvider {
86
96
  * MCP tools are added when available (without blocking)
87
97
  */
88
98
  protected getAllTools(): Promise<Record<string, Tool>>;
99
+ /**
100
+ * Convert MCP JSON Schema to Zod schema for AI SDK tools
101
+ * Handles common MCP schema patterns safely
102
+ */
103
+ private convertMCPSchemaToZod;
89
104
  /**
90
105
  * Set session context for MCP tools
91
106
  */