@juspay/neurolink 7.37.0 → 7.38.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 (93) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/config.d.ts +18 -18
  3. package/dist/cli/factories/commandFactory.d.ts +24 -0
  4. package/dist/cli/factories/commandFactory.js +297 -245
  5. package/dist/core/baseProvider.d.ts +44 -3
  6. package/dist/core/baseProvider.js +729 -352
  7. package/dist/core/constants.d.ts +2 -30
  8. package/dist/core/constants.js +15 -43
  9. package/dist/core/redisConversationMemoryManager.d.ts +98 -15
  10. package/dist/core/redisConversationMemoryManager.js +665 -203
  11. package/dist/factories/providerFactory.js +23 -6
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.js +4 -3
  14. package/dist/lib/core/baseProvider.d.ts +44 -3
  15. package/dist/lib/core/baseProvider.js +729 -352
  16. package/dist/lib/core/constants.d.ts +2 -30
  17. package/dist/lib/core/constants.js +15 -43
  18. package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
  19. package/dist/lib/core/redisConversationMemoryManager.js +665 -203
  20. package/dist/lib/factories/providerFactory.js +23 -6
  21. package/dist/lib/index.d.ts +3 -2
  22. package/dist/lib/index.js +4 -3
  23. package/dist/lib/mcp/externalServerManager.js +2 -2
  24. package/dist/lib/mcp/registry.js +2 -2
  25. package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
  26. package/dist/lib/mcp/toolRegistry.js +4 -8
  27. package/dist/lib/neurolink.d.ts +95 -28
  28. package/dist/lib/neurolink.js +479 -719
  29. package/dist/lib/providers/amazonBedrock.js +2 -2
  30. package/dist/lib/providers/anthropic.js +8 -0
  31. package/dist/lib/providers/anthropicBaseProvider.js +8 -0
  32. package/dist/lib/providers/azureOpenai.js +8 -0
  33. package/dist/lib/providers/googleAiStudio.js +8 -0
  34. package/dist/lib/providers/googleVertex.d.ts +3 -23
  35. package/dist/lib/providers/googleVertex.js +24 -342
  36. package/dist/lib/providers/huggingFace.js +8 -0
  37. package/dist/lib/providers/litellm.js +8 -0
  38. package/dist/lib/providers/mistral.js +8 -0
  39. package/dist/lib/providers/openAI.d.ts +23 -0
  40. package/dist/lib/providers/openAI.js +323 -6
  41. package/dist/lib/providers/openaiCompatible.js +8 -0
  42. package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
  43. package/dist/lib/sdk/toolRegistration.js +18 -1
  44. package/dist/lib/types/common.d.ts +98 -0
  45. package/dist/lib/types/conversation.d.ts +52 -2
  46. package/dist/lib/types/streamTypes.d.ts +13 -6
  47. package/dist/lib/types/typeAliases.d.ts +3 -2
  48. package/dist/lib/utils/conversationMemory.js +3 -1
  49. package/dist/lib/utils/messageBuilder.d.ts +10 -2
  50. package/dist/lib/utils/messageBuilder.js +22 -1
  51. package/dist/lib/utils/parameterValidation.js +6 -25
  52. package/dist/lib/utils/promptRedaction.js +4 -4
  53. package/dist/lib/utils/redis.d.ts +10 -6
  54. package/dist/lib/utils/redis.js +71 -70
  55. package/dist/lib/utils/schemaConversion.d.ts +14 -0
  56. package/dist/lib/utils/schemaConversion.js +140 -0
  57. package/dist/lib/utils/transformationUtils.js +143 -5
  58. package/dist/mcp/externalServerManager.js +2 -2
  59. package/dist/mcp/registry.js +2 -2
  60. package/dist/mcp/servers/agent/directToolsServer.js +19 -10
  61. package/dist/mcp/toolRegistry.js +4 -8
  62. package/dist/neurolink.d.ts +95 -28
  63. package/dist/neurolink.js +479 -719
  64. package/dist/providers/amazonBedrock.js +2 -2
  65. package/dist/providers/anthropic.js +8 -0
  66. package/dist/providers/anthropicBaseProvider.js +8 -0
  67. package/dist/providers/azureOpenai.js +8 -0
  68. package/dist/providers/googleAiStudio.js +8 -0
  69. package/dist/providers/googleVertex.d.ts +3 -23
  70. package/dist/providers/googleVertex.js +24 -342
  71. package/dist/providers/huggingFace.js +8 -0
  72. package/dist/providers/litellm.js +8 -0
  73. package/dist/providers/mistral.js +8 -0
  74. package/dist/providers/openAI.d.ts +23 -0
  75. package/dist/providers/openAI.js +323 -6
  76. package/dist/providers/openaiCompatible.js +8 -0
  77. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  78. package/dist/sdk/toolRegistration.js +18 -1
  79. package/dist/types/common.d.ts +98 -0
  80. package/dist/types/conversation.d.ts +52 -2
  81. package/dist/types/streamTypes.d.ts +13 -6
  82. package/dist/types/typeAliases.d.ts +3 -2
  83. package/dist/utils/conversationMemory.js +3 -1
  84. package/dist/utils/messageBuilder.d.ts +10 -2
  85. package/dist/utils/messageBuilder.js +22 -1
  86. package/dist/utils/parameterValidation.js +6 -25
  87. package/dist/utils/promptRedaction.js +4 -4
  88. package/dist/utils/redis.d.ts +10 -6
  89. package/dist/utils/redis.js +71 -70
  90. package/dist/utils/schemaConversion.d.ts +14 -0
  91. package/dist/utils/schemaConversion.js +140 -0
  92. package/dist/utils/transformationUtils.js +143 -5
  93. package/package.json +3 -2
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { generateText } from "ai";
2
3
  import { MiddlewareFactory } from "../middleware/factory.js";
3
4
  import { logger } from "../utils/logger.js";
@@ -9,6 +10,7 @@ import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
9
10
  import { buildMessagesArray, buildMultimodalMessagesArray, } from "../utils/messageBuilder.js";
10
11
  import { getKeysAsString, getKeyCount } from "../utils/transformationUtils.js";
11
12
  import { validateStreamOptions as validateStreamOpts, validateTextGenerationOptions, ValidationError, createValidationSummary, } from "../utils/parameterValidation.js";
13
+ import { convertJsonSchemaToZod } from "../utils/schemaConversion.js";
12
14
  import { recordProviderPerformanceFromMetrics, getPerformanceOptimizedProvider, } from "./evaluationProviders.js";
13
15
  import { modelConfig } from "./modelConfiguration.js";
14
16
  /**
@@ -53,18 +55,46 @@ export class BaseProvider {
53
55
  */
54
56
  async stream(optionsOrPrompt, analysisSchema) {
55
57
  const options = this.normalizeStreamOptions(optionsOrPrompt);
58
+ logger.info(`Starting stream`, {
59
+ provider: this.providerName,
60
+ hasTools: !options.disableTools && this.supportsTools(),
61
+ disableTools: !!options.disableTools,
62
+ supportsTools: this.supportsTools(),
63
+ inputLength: options.input?.text?.length || 0,
64
+ maxTokens: options.maxTokens,
65
+ temperature: options.temperature,
66
+ timestamp: Date.now(),
67
+ });
56
68
  // CRITICAL FIX: Always prefer real streaming over fake streaming
57
69
  // Try real streaming first, use fake streaming only as fallback
58
70
  try {
71
+ logger.debug(`Attempting real streaming`, {
72
+ provider: this.providerName,
73
+ timestamp: Date.now(),
74
+ });
59
75
  const realStreamResult = await this.executeStream(options, analysisSchema);
76
+ logger.info(`Real streaming succeeded`, {
77
+ provider: this.providerName,
78
+ timestamp: Date.now(),
79
+ });
60
80
  // If real streaming succeeds, return it (with tools support via Vercel AI SDK)
61
81
  return realStreamResult;
62
82
  }
63
83
  catch (realStreamError) {
64
- logger.warn(`Real streaming failed for ${this.providerName}, falling back to fake streaming:`, realStreamError);
84
+ logger.warn(`Real streaming failed for ${this.providerName}, falling back to fake streaming:`, {
85
+ error: realStreamError instanceof Error
86
+ ? realStreamError.message
87
+ : String(realStreamError),
88
+ timestamp: Date.now(),
89
+ });
65
90
  // Fallback to fake streaming only if real streaming fails AND tools are enabled
66
91
  if (!options.disableTools && this.supportsTools()) {
67
92
  try {
93
+ logger.info(`Starting fake streaming with tools`, {
94
+ provider: this.providerName,
95
+ supportsTools: this.supportsTools(),
96
+ timestamp: Date.now(),
97
+ });
68
98
  // Convert stream options to text generation options
69
99
  const textOptions = {
70
100
  prompt: options.input?.text || "",
@@ -82,7 +112,20 @@ export class BaseProvider {
82
112
  toolUsageContext: options.toolUsageContext,
83
113
  context: options.context,
84
114
  };
115
+ logger.debug(`Calling generate for fake streaming`, {
116
+ provider: this.providerName,
117
+ maxSteps: textOptions.maxSteps,
118
+ disableTools: textOptions.disableTools,
119
+ timestamp: Date.now(),
120
+ });
85
121
  const result = await this.generate(textOptions, analysisSchema);
122
+ logger.info(`Generate completed for fake streaming`, {
123
+ provider: this.providerName,
124
+ hasContent: !!result?.content,
125
+ contentLength: result?.content?.length || 0,
126
+ toolsUsed: result?.toolsUsed?.length || 0,
127
+ timestamp: Date.now(),
128
+ });
86
129
  // Create a synthetic stream from the generate result that simulates progressive delivery
87
130
  return {
88
131
  stream: (async function* () {
@@ -153,285 +196,377 @@ export class BaseProvider {
153
196
  }
154
197
  }
155
198
  /**
156
- * Text generation method - implements AIProvider interface
157
- * Tools are always available unless explicitly disabled
158
- * IMPLEMENTATION NOTE: Uses streamText() under the hood and accumulates results
159
- * for consistency and better performance
199
+ * Prepare generation context including tools and model
160
200
  */
161
- async generate(optionsOrPrompt, _analysisSchema) {
162
- const options = this.normalizeTextOptions(optionsOrPrompt);
163
- // Validate options before proceeding
164
- this.validateOptions(options);
165
- const startTime = Date.now();
166
- try {
167
- // Import streamText dynamically to avoid circular dependencies
168
- // Using streamText instead of generateText for unified implementation
169
- // const { streamText } = await import("ai");
170
- // Get ALL available tools (direct + MCP + external from options)
171
- const shouldUseTools = !options.disableTools && this.supportsTools();
172
- const baseTools = shouldUseTools ? await this.getAllTools() : {};
173
- const tools = shouldUseTools
174
- ? {
175
- ...baseTools,
176
- ...(options.tools || {}), // Include external tools passed from NeuroLink
177
- }
178
- : {};
179
- // DEBUG: Log detailed tool information for generate
180
- logger.debug("BaseProvider Generate - Tool Loading Debug", {
181
- provider: this.providerName,
182
- shouldUseTools,
183
- baseToolsProvided: !!baseTools,
184
- baseToolCount: baseTools ? Object.keys(baseTools).length : 0,
185
- finalToolCount: tools ? Object.keys(tools).length : 0,
186
- toolNames: tools ? Object.keys(tools).slice(0, 10) : [],
187
- disableTools: options.disableTools,
188
- supportsTools: this.supportsTools(),
189
- externalToolsCount: options.tools
190
- ? Object.keys(options.tools).length
191
- : 0,
192
- });
193
- if (tools && Object.keys(tools).length > 0) {
194
- logger.debug("BaseProvider Generate - First 5 Tools Detail", {
195
- provider: this.providerName,
196
- tools: Object.keys(tools)
197
- .slice(0, 5)
198
- .map((name) => ({
199
- name,
200
- description: tools[name]?.description?.substring(0, 100),
201
- })),
202
- });
201
+ async prepareGenerationContext(options) {
202
+ const shouldUseTools = !options.disableTools && this.supportsTools();
203
+ const baseTools = shouldUseTools ? await this.getAllTools() : {};
204
+ const tools = shouldUseTools
205
+ ? {
206
+ ...baseTools,
207
+ ...(options.tools || {}),
203
208
  }
204
- logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}:`, {
205
- directTools: getKeyCount(baseTools),
206
- directToolNames: getKeysAsString(baseTools),
207
- externalTools: getKeyCount(options.tools || {}),
208
- externalToolNames: getKeysAsString(options.tools || {}),
209
- totalTools: getKeyCount(tools),
210
- totalToolNames: getKeysAsString(tools),
211
- });
212
- const model = await this.getAISDKModelWithMiddleware(options);
213
- // Build proper message array with conversation history
214
- // Check if this is a multimodal request (images or content present)
215
- let messages;
216
- // Type guard to check if options has multimodal input
217
- const hasMultimodalInput = (opts) => {
218
- const input = opts.input;
219
- const hasImages = !!input?.images?.length;
220
- const hasContent = !!input?.content?.length;
221
- return hasImages || hasContent;
209
+ : {};
210
+ logger.debug(`Final tools prepared for AI`, {
211
+ provider: this.providerName,
212
+ directTools: getKeyCount(baseTools),
213
+ directToolNames: getKeysAsString(baseTools),
214
+ externalTools: getKeyCount(options.tools || {}),
215
+ externalToolNames: getKeysAsString(options.tools || {}),
216
+ totalTools: getKeyCount(tools),
217
+ totalToolNames: getKeysAsString(tools),
218
+ shouldUseTools,
219
+ timestamp: Date.now(),
220
+ });
221
+ const model = await this.getAISDKModelWithMiddleware(options);
222
+ return { tools, model };
223
+ }
224
+ /**
225
+ * Build messages array for generation
226
+ */
227
+ async buildMessages(options) {
228
+ const hasMultimodalInput = (opts) => {
229
+ const input = opts.input;
230
+ const hasImages = !!input?.images?.length;
231
+ const hasContent = !!input?.content?.length;
232
+ return hasImages || hasContent;
233
+ };
234
+ let messages;
235
+ if (hasMultimodalInput(options)) {
236
+ if (process.env.NEUROLINK_DEBUG === "true") {
237
+ logger.debug("Detected multimodal input, using multimodal message builder");
238
+ }
239
+ const input = options.input;
240
+ const multimodalOptions = {
241
+ input: {
242
+ text: options.prompt || options.input?.text || "",
243
+ images: input?.images,
244
+ content: input?.content,
245
+ },
246
+ provider: options.provider,
247
+ model: options.model,
248
+ temperature: options.temperature,
249
+ maxTokens: options.maxTokens,
250
+ systemPrompt: options.systemPrompt,
251
+ enableAnalytics: options.enableAnalytics,
252
+ enableEvaluation: options.enableEvaluation,
253
+ context: options.context,
222
254
  };
223
- if (hasMultimodalInput(options)) {
224
- if (process.env.NEUROLINK_DEBUG === "true") {
225
- logger.info("🖼️ [MULTIMODAL-REQUEST] Detected multimodal input, using multimodal message builder");
226
- }
227
- // This is a multimodal request - use multimodal message builder
228
- // Convert TextGenerationOptions to GenerateOptions format for multimodal processing
229
- const input = options.input;
230
- const multimodalOptions = {
231
- input: {
232
- text: options.prompt || options.input?.text || "",
233
- images: input?.images,
234
- content: input?.content,
235
- },
236
- provider: options.provider,
237
- model: options.model,
238
- temperature: options.temperature,
239
- maxTokens: options.maxTokens,
240
- systemPrompt: options.systemPrompt,
241
- enableAnalytics: options.enableAnalytics,
242
- enableEvaluation: options.enableEvaluation,
243
- context: options.context,
255
+ messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
256
+ }
257
+ else {
258
+ if (process.env.NEUROLINK_DEBUG === "true") {
259
+ logger.debug("No multimodal input detected, using standard message builder");
260
+ }
261
+ messages = buildMessagesArray(options);
262
+ }
263
+ // Convert messages to Vercel AI SDK format
264
+ return messages.map((msg) => {
265
+ if (typeof msg.content === "string") {
266
+ return {
267
+ role: msg.role,
268
+ content: msg.content,
244
269
  };
245
- messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
246
270
  }
247
271
  else {
248
- if (process.env.NEUROLINK_DEBUG === "true") {
249
- logger.info("📝 [TEXT-ONLY-REQUEST] No multimodal input detected, using standard message builder");
250
- }
251
- // Standard text-only request
252
- messages = buildMessagesArray(options);
272
+ return {
273
+ role: msg.role,
274
+ content: msg.content.map((item) => {
275
+ if (item.type === "text") {
276
+ return { type: "text", text: item.text || "" };
277
+ }
278
+ else if (item.type === "image") {
279
+ return { type: "image", image: item.image || "" };
280
+ }
281
+ return item;
282
+ }),
283
+ };
253
284
  }
254
- // Convert messages to Vercel AI SDK format
255
- const aiSDKMessages = messages.map((msg) => {
256
- if (typeof msg.content === "string") {
257
- // Simple text content
258
- return {
259
- role: msg.role,
260
- content: msg.content,
261
- };
262
- }
263
- else {
264
- // Multimodal content array - convert to Vercel AI SDK format
265
- // The Vercel AI SDK expects content to be in a specific format
266
- return {
267
- role: msg.role,
268
- content: msg.content.map((item) => {
269
- if (item.type === "text") {
270
- return { type: "text", text: item.text || "" };
271
- }
272
- else if (item.type === "image") {
273
- return { type: "image", image: item.image || "" };
274
- }
275
- return item;
276
- }),
277
- };
278
- }
285
+ });
286
+ }
287
+ /**
288
+ * Execute the generation with AI SDK
289
+ */
290
+ async executeGeneration(model, messages, tools, options) {
291
+ const shouldUseTools = !options.disableTools && this.supportsTools();
292
+ return await generateText({
293
+ model,
294
+ messages,
295
+ tools,
296
+ maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
297
+ toolChoice: shouldUseTools ? "auto" : "none",
298
+ temperature: options.temperature,
299
+ maxTokens: options.maxTokens,
300
+ onStepFinish: ({ toolCalls, toolResults }) => {
301
+ logger.info("Tool execution completed", { toolResults, toolCalls });
302
+ // Handle tool execution storage
303
+ this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
304
+ logger.warn("[BaseProvider] Failed to store tool executions", {
305
+ provider: this.providerName,
306
+ error: error instanceof Error ? error.message : String(error),
307
+ });
308
+ });
309
+ },
310
+ });
311
+ }
312
+ /**
313
+ * Log generation completion information
314
+ */
315
+ logGenerationComplete(generateResult) {
316
+ logger.debug(`generateText completed`, {
317
+ provider: this.providerName,
318
+ model: this.modelName,
319
+ responseLength: generateResult.text?.length || 0,
320
+ toolResultsCount: generateResult.toolResults?.length || 0,
321
+ finishReason: generateResult.finishReason,
322
+ usage: generateResult.usage,
323
+ timestamp: Date.now(),
324
+ });
325
+ }
326
+ /**
327
+ * Record performance metrics
328
+ */
329
+ async recordPerformanceMetrics(usage, responseTime) {
330
+ try {
331
+ const actualCost = await this.calculateActualCost(usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 });
332
+ recordProviderPerformanceFromMetrics(this.providerName, {
333
+ responseTime,
334
+ tokensGenerated: usage?.totalTokens || 0,
335
+ cost: actualCost,
336
+ success: true,
279
337
  });
280
- const generateResult = await generateText({
281
- model,
282
- messages: aiSDKMessages,
283
- tools,
284
- maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
285
- toolChoice: shouldUseTools ? "auto" : "none",
286
- temperature: options.temperature,
287
- maxTokens: options.maxTokens, // No default limit - unlimited unless specified
338
+ const optimizedProvider = getPerformanceOptimizedProvider("speed");
339
+ logger.debug(`🚀 Performance recorded for ${this.providerName}:`, {
340
+ responseTime: `${responseTime}ms`,
341
+ tokens: usage?.totalTokens || 0,
342
+ estimatedCost: `$${actualCost.toFixed(6)}`,
343
+ recommendedSpeedProvider: optimizedProvider?.provider || "none",
288
344
  });
289
- const responseTime = Date.now() - startTime;
290
- // Extract properties from generateResult
291
- const usage = generateResult.usage;
292
- const toolCalls = generateResult.toolCalls;
293
- const toolResults = generateResult.toolResults;
294
- try {
295
- const actualCost = await this.calculateActualCost(usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 });
296
- recordProviderPerformanceFromMetrics(this.providerName, {
297
- responseTime,
298
- tokensGenerated: usage?.totalTokens || 0,
299
- cost: actualCost,
300
- success: true,
301
- });
302
- // Show what the system learned (updated to include cost)
303
- const optimizedProvider = getPerformanceOptimizedProvider("speed");
304
- logger.debug(`🚀 Performance recorded for ${this.providerName}:`, {
305
- responseTime: `${responseTime}ms`,
306
- tokens: usage?.totalTokens || 0,
307
- estimatedCost: `$${actualCost.toFixed(6)}`,
308
- recommendedSpeedProvider: optimizedProvider?.provider || "none",
309
- });
310
- }
311
- catch (perfError) {
312
- logger.warn("⚠️ Performance recording failed:", perfError);
313
- }
314
- // Extract tool names from tool calls for tracking
315
- // AI SDK puts tool calls in steps array for multi-step generation
316
- const toolsUsed = [];
317
- // First check direct tool calls (fallback)
318
- if (toolCalls && toolCalls.length > 0) {
319
- toolsUsed.push(...toolCalls.map((tc) => {
320
- return tc.toolName || tc.name || "unknown";
321
- }));
322
- }
323
- // Then check steps for tool calls (primary source for multi-step)
324
- if (generateResult.steps &&
325
- Array.isArray(generateResult.steps)) {
326
- for (const step of generateResult
327
- .steps || []) {
328
- if (step?.toolCalls && Array.isArray(step.toolCalls)) {
329
- toolsUsed.push(...step.toolCalls.map((tc) => {
330
- return tc.toolName || tc.name || "unknown";
331
- }));
332
- }
333
- }
334
- }
335
- // Remove duplicates
336
- const uniqueToolsUsed = [...new Set(toolsUsed)];
337
- // ✅ Extract tool executions from AI SDK result
338
- const toolExecutions = [];
339
- // Create a map of tool calls to their arguments for matching with results
345
+ }
346
+ catch (perfError) {
347
+ logger.warn("⚠️ Performance recording failed:", perfError);
348
+ }
349
+ }
350
+ /**
351
+ * Extract tool information from generation result
352
+ */
353
+ extractToolInformation(generateResult) {
354
+ const toolsUsed = [];
355
+ const toolExecutions = [];
356
+ // Extract tool names from tool calls
357
+ if (generateResult.toolCalls && generateResult.toolCalls.length > 0) {
358
+ toolsUsed.push(...generateResult.toolCalls.map((tc) => {
359
+ return tc.toolName || tc.name || "unknown";
360
+ }));
361
+ }
362
+ // Extract from steps
363
+ if (generateResult.steps &&
364
+ Array.isArray(generateResult.steps)) {
340
365
  const toolCallArgsMap = new Map();
341
- // Extract tool executions from AI SDK result steps
342
- if (generateResult.steps &&
343
- Array.isArray(generateResult.steps)) {
344
- for (const step of generateResult
345
- .steps || []) {
346
- // First, collect tool calls and their arguments
347
- if (step?.toolCalls && Array.isArray(step.toolCalls)) {
348
- for (const toolCall of step.toolCalls) {
349
- const tcRecord = toolCall;
350
- const toolName = tcRecord.toolName ||
351
- tcRecord.name ||
352
- "unknown";
353
- const toolId = tcRecord.toolCallId ||
354
- tcRecord.id ||
355
- toolName;
356
- // Extract arguments from tool call
357
- let callArgs = {};
358
- if (tcRecord.args) {
359
- callArgs = tcRecord.args;
360
- }
361
- else if (tcRecord.arguments) {
362
- callArgs = tcRecord.arguments;
363
- }
364
- else if (tcRecord.parameters) {
365
- callArgs = tcRecord.parameters;
366
- }
367
- toolCallArgsMap.set(toolId, callArgs);
368
- toolCallArgsMap.set(toolName, callArgs); // Also map by name as fallback
366
+ for (const step of generateResult
367
+ .steps || []) {
368
+ // Collect tool calls and their arguments
369
+ if (step?.toolCalls && Array.isArray(step.toolCalls)) {
370
+ for (const toolCall of step.toolCalls) {
371
+ const tcRecord = toolCall;
372
+ const toolName = tcRecord.toolName ||
373
+ tcRecord.name ||
374
+ "unknown";
375
+ const toolId = tcRecord.toolCallId ||
376
+ tcRecord.id ||
377
+ toolName;
378
+ toolsUsed.push(toolName);
379
+ let callArgs = {};
380
+ if (tcRecord.args) {
381
+ callArgs = tcRecord.args;
369
382
  }
383
+ else if (tcRecord.arguments) {
384
+ callArgs = tcRecord.arguments;
385
+ }
386
+ else if (tcRecord.parameters) {
387
+ callArgs = tcRecord.parameters;
388
+ }
389
+ toolCallArgsMap.set(toolId, callArgs);
390
+ toolCallArgsMap.set(toolName, callArgs);
370
391
  }
371
- // Then, process tool results and match with call arguments
372
- if (step?.toolResults && Array.isArray(step.toolResults)) {
373
- for (const toolResult of step.toolResults) {
374
- const trRecord = toolResult;
375
- const toolName = trRecord.toolName || "unknown";
376
- const toolId = trRecord.toolCallId || trRecord.id;
377
- // Try to get arguments from the tool result first
378
- let toolArgs = {};
379
- if (trRecord.args) {
380
- toolArgs = trRecord.args;
381
- }
382
- else if (trRecord.arguments) {
383
- toolArgs = trRecord.arguments;
384
- }
385
- else if (trRecord.parameters) {
386
- toolArgs = trRecord.parameters;
387
- }
388
- else if (trRecord.input) {
389
- toolArgs = trRecord.input;
390
- }
391
- else {
392
- // Fallback: get arguments from the corresponding tool call
393
- toolArgs = toolCallArgsMap.get(toolId || toolName) || {};
394
- }
395
- toolExecutions.push({
396
- name: toolName,
397
- input: toolArgs,
398
- output: trRecord.result || "success",
399
- });
392
+ }
393
+ // Process tool results
394
+ if (step?.toolResults && Array.isArray(step.toolResults)) {
395
+ for (const toolResult of step.toolResults) {
396
+ const trRecord = toolResult;
397
+ const toolName = trRecord.toolName || "unknown";
398
+ const toolId = trRecord.toolCallId || trRecord.id;
399
+ let toolArgs = {};
400
+ if (trRecord.args) {
401
+ toolArgs = trRecord.args;
400
402
  }
403
+ else if (trRecord.arguments) {
404
+ toolArgs = trRecord.arguments;
405
+ }
406
+ else if (trRecord.parameters) {
407
+ toolArgs = trRecord.parameters;
408
+ }
409
+ else if (trRecord.input) {
410
+ toolArgs = trRecord.input;
411
+ }
412
+ else {
413
+ toolArgs = toolCallArgsMap.get(toolId || toolName) || {};
414
+ }
415
+ toolExecutions.push({
416
+ name: toolName,
417
+ input: toolArgs,
418
+ output: trRecord.result || "success",
419
+ });
401
420
  }
402
421
  }
403
422
  }
404
- // Format the result with tool executions included
405
- const enhancedResult = {
406
- content: generateResult.text,
407
- usage: {
408
- input: generateResult.usage?.promptTokens || 0,
409
- output: generateResult.usage?.completionTokens || 0,
410
- total: generateResult.usage?.totalTokens || 0,
411
- },
412
- provider: this.providerName,
413
- model: this.modelName,
414
- toolCalls: toolCalls
415
- ? toolCalls.map((tc) => ({
416
- toolCallId: tc.toolCallId || "unknown",
417
- toolName: tc.toolName || "unknown",
418
- args: tc.args || {},
419
- }))
420
- : [],
421
- toolResults: toolResults || [],
422
- toolsUsed: uniqueToolsUsed,
423
- toolExecutions, // Add extracted tool executions
424
- availableTools: Object.keys(tools).map((name) => {
425
- const tool = tools[name];
426
- return {
427
- name,
428
- description: tool.description || "No description available",
429
- parameters: tool.parameters || {},
430
- server: tool.serverId || "direct",
431
- };
432
- }),
433
- };
434
- // Enhanced result with analytics and evaluation
423
+ }
424
+ return { toolsUsed: [...new Set(toolsUsed)], toolExecutions };
425
+ }
426
+ /**
427
+ * Format the enhanced result
428
+ */
429
+ formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions) {
430
+ return {
431
+ content: generateResult.text,
432
+ usage: {
433
+ input: generateResult.usage?.promptTokens || 0,
434
+ output: generateResult.usage?.completionTokens || 0,
435
+ total: generateResult.usage?.totalTokens || 0,
436
+ },
437
+ provider: this.providerName,
438
+ model: this.modelName,
439
+ toolCalls: generateResult.toolCalls
440
+ ? generateResult.toolCalls.map((tc) => ({
441
+ toolCallId: tc.toolCallId || "unknown",
442
+ toolName: tc.toolName || "unknown",
443
+ args: tc.args || {},
444
+ }))
445
+ : [],
446
+ toolResults: generateResult.toolResults || [],
447
+ toolsUsed,
448
+ toolExecutions,
449
+ availableTools: Object.keys(tools).map((name) => {
450
+ const tool = tools[name];
451
+ return {
452
+ name,
453
+ description: tool.description || "No description available",
454
+ parameters: tool.parameters || {},
455
+ server: tool.serverId || "direct",
456
+ };
457
+ }),
458
+ };
459
+ }
460
+ /**
461
+ * Analyze AI response structure and log detailed debugging information
462
+ * Extracted from generate method to reduce complexity
463
+ */
464
+ analyzeAIResponse(result) {
465
+ // 🔧 NEUROLINK RAW AI RESPONSE TRACE: Log everything about the raw AI response before parameter extraction
466
+ logger.debug("NeuroLink Raw AI Response Analysis", {
467
+ provider: this.providerName,
468
+ model: this.modelName,
469
+ responseTextLength: result.text?.length || 0,
470
+ responsePreview: result.text?.substring(0, 500) + "...",
471
+ finishReason: result.finishReason,
472
+ usage: result.usage,
473
+ });
474
+ // 🔧 NEUROLINK TOOL CALLS ANALYSIS: Analyze raw tool calls structure
475
+ const toolCallsAnalysis = {
476
+ hasToolCalls: !!result.toolCalls,
477
+ toolCallsLength: result.toolCalls?.length || 0,
478
+ toolCalls: result.toolCalls?.map((toolCall, index) => {
479
+ const tcRecord = toolCall;
480
+ const toolName = tcRecord.toolName || tcRecord.name || "unknown";
481
+ const isTargetTool = toolName.toString().includes("SuccessRateSRByTime") ||
482
+ toolName.toString().includes("juspay-analytics");
483
+ return {
484
+ index: index + 1,
485
+ toolName,
486
+ toolId: tcRecord.toolCallId || tcRecord.id || "none",
487
+ hasArgs: !!tcRecord.args,
488
+ argsKeys: tcRecord.args && typeof tcRecord.args === "object"
489
+ ? Object.keys(tcRecord.args)
490
+ : [],
491
+ isTargetTool,
492
+ ...(isTargetTool && {
493
+ targetToolDetails: {
494
+ argsType: typeof tcRecord.args,
495
+ startTime: tcRecord.args?.startTime ||
496
+ "MISSING",
497
+ endTime: tcRecord.args?.endTime ||
498
+ "MISSING",
499
+ },
500
+ }),
501
+ };
502
+ }) || [],
503
+ };
504
+ logger.debug("Tool Calls Analysis", toolCallsAnalysis);
505
+ // 🔧 NEUROLINK STEPS ANALYSIS: Analyze steps structure (AI SDK multi-step format)
506
+ const steps = result.steps;
507
+ const stepsAnalysis = {
508
+ hasSteps: !!steps,
509
+ stepsLength: Array.isArray(steps) ? steps.length : 0,
510
+ steps: Array.isArray(steps)
511
+ ? steps.map((step, stepIndex) => ({
512
+ stepIndex: stepIndex + 1,
513
+ hasToolCalls: !!step.toolCalls,
514
+ toolCallsLength: step.toolCalls?.length || 0,
515
+ hasToolResults: !!step.toolResults,
516
+ toolResultsLength: step.toolResults?.length || 0,
517
+ targetToolsInStep: step.toolCalls
518
+ ?.filter((tc) => {
519
+ const toolName = tc.toolName || tc.name || "unknown";
520
+ return (toolName.toString().includes("SuccessRateSRByTime") ||
521
+ toolName.toString().includes("juspay-analytics"));
522
+ })
523
+ .map((tc) => ({
524
+ toolName: tc.toolName || tc.name,
525
+ hasArgs: !!tc.args,
526
+ argsKeys: tc.args && typeof tc.args === "object"
527
+ ? Object.keys(tc.args)
528
+ : [],
529
+ startTime: tc.args?.startTime,
530
+ endTime: tc.args?.endTime,
531
+ })) || [],
532
+ }))
533
+ : [],
534
+ };
535
+ logger.debug("[BaseProvider] Steps Analysis", stepsAnalysis);
536
+ // 🔧 NEUROLINK TOOL RESULTS ANALYSIS: Analyze top-level tool results
537
+ const toolResultsAnalysis = {
538
+ hasToolResults: !!result.toolResults,
539
+ toolResultsLength: result.toolResults?.length || 0,
540
+ toolResults: result.toolResults?.map((toolResult, index) => ({
541
+ index: index + 1,
542
+ toolName: toolResult.toolName || "unknown",
543
+ hasResult: !!toolResult.result,
544
+ hasError: !!toolResult.error,
545
+ })) || [],
546
+ };
547
+ logger.debug("[BaseProvider] Tool Results Analysis", toolResultsAnalysis);
548
+ logger.debug("[BaseProvider] NeuroLink Raw AI Response Analysis Complete");
549
+ }
550
+ /**
551
+ * Text generation method - implements AIProvider interface
552
+ * Tools are always available unless explicitly disabled
553
+ * IMPLEMENTATION NOTE: Uses streamText() under the hood and accumulates results
554
+ * for consistency and better performance
555
+ */
556
+ async generate(optionsOrPrompt, _analysisSchema) {
557
+ const options = this.normalizeTextOptions(optionsOrPrompt);
558
+ this.validateOptions(options);
559
+ const startTime = Date.now();
560
+ try {
561
+ const { tools, model } = await this.prepareGenerationContext(options);
562
+ const messages = await this.buildMessages(options);
563
+ const generateResult = await this.executeGeneration(model, messages, tools, options);
564
+ this.analyzeAIResponse(generateResult);
565
+ this.logGenerationComplete(generateResult);
566
+ const responseTime = Date.now() - startTime;
567
+ await this.recordPerformanceMetrics(generateResult.usage, responseTime);
568
+ const { toolsUsed, toolExecutions } = this.extractToolInformation(generateResult);
569
+ const enhancedResult = this.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions);
435
570
  return await this.enhanceResult(enhancedResult, options, startTime);
436
571
  }
437
572
  catch (error) {
@@ -451,11 +586,12 @@ export class BaseProvider {
451
586
  * Ensures existing scripts using createAIProvider().generateText() continue to work
452
587
  */
453
588
  async generateText(options) {
454
- // Validate required parameters for backward compatibility
455
- if (!options.prompt ||
456
- typeof options.prompt !== "string" ||
457
- options.prompt.trim() === "") {
458
- throw new Error("GenerateText options must include prompt as a non-empty string");
589
+ // Validate required parameters for backward compatibility - support both prompt and input.text
590
+ const promptText = options.prompt || options.input?.text;
591
+ if (!promptText ||
592
+ typeof promptText !== "string" ||
593
+ promptText.trim() === "") {
594
+ throw new Error("GenerateText options must include prompt or input.text as a non-empty string");
459
595
  }
460
596
  // Call the main generate method
461
597
  const result = await this.generate(options);
@@ -567,6 +703,7 @@ export class BaseProvider {
567
703
  }
568
704
  /**
569
705
  * Convert tool execution result from MCP format to standard format
706
+ * Handles tool failures gracefully to prevent stream termination
570
707
  */
571
708
  async convertToolResult(result) {
572
709
  // Handle MCP-style results
@@ -576,10 +713,24 @@ export class BaseProvider {
576
713
  return mcpResult.data;
577
714
  }
578
715
  else {
716
+ // Instead of throwing, return a structured error result
717
+ // This prevents tool failures from terminating streams
579
718
  const errorMsg = typeof mcpResult.error === "string"
580
719
  ? mcpResult.error
581
720
  : "Tool execution failed";
582
- throw new Error(errorMsg);
721
+ // Log the error for debugging but don't throw
722
+ logger.warn(`Tool execution failed: ${errorMsg}`);
723
+ // Return error as structured data that can be processed by the AI
724
+ return {
725
+ isError: true,
726
+ error: errorMsg,
727
+ content: [
728
+ {
729
+ type: "text",
730
+ text: `Tool execution failed: ${errorMsg}`,
731
+ },
732
+ ],
733
+ };
583
734
  }
584
735
  }
585
736
  return result;
@@ -593,14 +744,106 @@ export class BaseProvider {
593
744
  // Convert to AI SDK tool format
594
745
  const { tool: createAISDKTool } = await import("ai");
595
746
  const { z } = await import("zod");
747
+ let finalSchema;
748
+ const schemaSource = toolInfo.parameters || toolInfo.inputSchema;
749
+ if (this.isZodSchema(schemaSource)) {
750
+ finalSchema = schemaSource;
751
+ logger.debug(`[BaseProvider] ${toolName}: Using existing Zod schema from ${toolInfo.parameters ? "parameters" : "inputSchema"} field`);
752
+ }
753
+ else if (schemaSource && typeof schemaSource === "object") {
754
+ logger.debug(`[BaseProvider] ${toolName}: Converting JSON Schema to Zod from ${toolInfo.parameters ? "parameters" : "inputSchema"} field`);
755
+ finalSchema = convertJsonSchemaToZod(schemaSource);
756
+ }
757
+ else {
758
+ finalSchema = z.object({});
759
+ logger.debug(`[BaseProvider] ${toolName}: No schema found, using empty object`);
760
+ }
596
761
  return createAISDKTool({
597
762
  description: toolInfo.description || `Tool ${toolName}`,
598
- parameters: this.isZodSchema(toolInfo.parameters)
599
- ? toolInfo.parameters
600
- : z.object({}),
763
+ parameters: finalSchema,
601
764
  execute: async (params) => {
602
- const result = await toolInfo.execute(params);
603
- return await this.convertToolResult(result);
765
+ const startTime = Date.now();
766
+ let executionId;
767
+ if (this.neurolink?.emitToolStart) {
768
+ executionId = this.neurolink.emitToolStart(toolName, params, startTime);
769
+ logger.debug(`Custom tool:start emitted via NeuroLink for ${toolName}`, {
770
+ toolName,
771
+ executionId,
772
+ input: params,
773
+ hasNativeEmission: true,
774
+ });
775
+ }
776
+ try {
777
+ // 🔧 PARAMETER FLOW TRACING - Before NeuroLink executeTool call
778
+ logger.debug(`About to call NeuroLink executeTool for ${toolName}`, {
779
+ toolName,
780
+ paramsBeforeExecution: {
781
+ type: typeof params,
782
+ isNull: params === null,
783
+ isUndefined: params === undefined,
784
+ isEmpty: params &&
785
+ typeof params === "object" &&
786
+ Object.keys(params).length === 0,
787
+ keys: params && typeof params === "object"
788
+ ? Object.keys(params)
789
+ : "NOT_OBJECT",
790
+ keysLength: params && typeof params === "object"
791
+ ? Object.keys(params).length
792
+ : 0,
793
+ },
794
+ executorInfo: {
795
+ hasExecutor: typeof toolInfo.execute === "function",
796
+ executorType: typeof toolInfo.execute,
797
+ },
798
+ timestamp: Date.now(),
799
+ phase: "BEFORE_NEUROLINK_EXECUTE",
800
+ });
801
+ const result = await toolInfo.execute(params);
802
+ // 🔧 PARAMETER FLOW TRACING - After NeuroLink executeTool call
803
+ logger.debug(`NeuroLink executeTool completed for ${toolName}`, {
804
+ toolName,
805
+ resultInfo: {
806
+ type: typeof result,
807
+ isNull: result === null,
808
+ isUndefined: result === undefined,
809
+ hasError: result && typeof result === "object" && "error" in result,
810
+ },
811
+ timestamp: Date.now(),
812
+ phase: "AFTER_NEUROLINK_EXECUTE",
813
+ });
814
+ const convertedResult = await this.convertToolResult(result);
815
+ const endTime = Date.now();
816
+ // 🔧 NATIVE NEUROLINK EVENT EMISSION - Tool End (Success)
817
+ if (this.neurolink?.emitToolEnd) {
818
+ this.neurolink.emitToolEnd(toolName, convertedResult, undefined, // no error
819
+ startTime, endTime, executionId);
820
+ logger.debug(`Custom tool:end emitted via NeuroLink for ${toolName}`, {
821
+ toolName,
822
+ executionId,
823
+ duration: endTime - startTime,
824
+ hasResult: convertedResult !== undefined,
825
+ hasNativeEmission: true,
826
+ });
827
+ }
828
+ return convertedResult;
829
+ }
830
+ catch (error) {
831
+ const endTime = Date.now();
832
+ const errorMsg = error instanceof Error ? error.message : String(error);
833
+ // 🔧 NATIVE NEUROLINK EVENT EMISSION - Tool End (Error)
834
+ if (this.neurolink?.emitToolEnd) {
835
+ this.neurolink.emitToolEnd(toolName, undefined, // no result
836
+ errorMsg, startTime, endTime, executionId);
837
+ logger.info(`Custom tool:end error emitted via NeuroLink for ${toolName}`, {
838
+ toolName,
839
+ executionId,
840
+ duration: endTime - startTime,
841
+ error: errorMsg,
842
+ hasNativeEmission: true,
843
+ });
844
+ }
845
+ throw error;
846
+ }
604
847
  },
605
848
  });
606
849
  }
@@ -609,6 +852,85 @@ export class BaseProvider {
609
852
  return null;
610
853
  }
611
854
  }
855
+ /**
856
+ * Process direct tools with event emission wrapping
857
+ */
858
+ async processDirectTools(tools) {
859
+ if (!this.directTools || Object.keys(this.directTools).length === 0) {
860
+ return;
861
+ }
862
+ logger.debug(`Loading ${Object.keys(this.directTools).length} direct tools with event emission`);
863
+ for (const [toolName, directTool] of Object.entries(this.directTools)) {
864
+ logger.debug(`Processing direct tool: ${toolName}`, {
865
+ toolName,
866
+ hasExecute: directTool &&
867
+ typeof directTool === "object" &&
868
+ "execute" in directTool,
869
+ hasDescription: directTool &&
870
+ typeof directTool === "object" &&
871
+ "description" in directTool,
872
+ });
873
+ // Wrap the direct tool's execute function with event emission
874
+ if (directTool &&
875
+ typeof directTool === "object" &&
876
+ "execute" in directTool) {
877
+ const originalExecute = directTool.execute;
878
+ // Create a new tool with wrapped execute function
879
+ tools[toolName] = {
880
+ ...directTool,
881
+ execute: async (params) => {
882
+ // 🔧 EMIT TOOL START EVENT - Bedrock-compatible format
883
+ if (this.neurolink?.getEventEmitter) {
884
+ const emitter = this.neurolink.getEventEmitter();
885
+ emitter.emit("tool:start", { tool: toolName, input: params });
886
+ logger.debug(`Direct tool:start event emitted for ${toolName}`, {
887
+ toolName,
888
+ input: params,
889
+ hasEmitter: !!emitter,
890
+ });
891
+ }
892
+ try {
893
+ const result = await originalExecute(params);
894
+ // 🔧 EMIT TOOL END EVENT - Bedrock-compatible format
895
+ if (this.neurolink?.getEventEmitter) {
896
+ const emitter = this.neurolink.getEventEmitter();
897
+ emitter.emit("tool:end", { tool: toolName, result });
898
+ logger.debug(`Direct tool:end event emitted for ${toolName}`, {
899
+ toolName,
900
+ result: typeof result === "string"
901
+ ? result.substring(0, 100)
902
+ : JSON.stringify(result).substring(0, 100),
903
+ hasEmitter: !!emitter,
904
+ });
905
+ }
906
+ return result;
907
+ }
908
+ catch (error) {
909
+ // 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
910
+ if (this.neurolink?.getEventEmitter) {
911
+ const emitter = this.neurolink.getEventEmitter();
912
+ const errorMsg = error instanceof Error ? error.message : String(error);
913
+ emitter.emit("tool:end", { tool: toolName, error: errorMsg });
914
+ logger.debug(`Direct tool:end error event emitted for ${toolName}`, {
915
+ toolName,
916
+ error: errorMsg,
917
+ hasEmitter: !!emitter,
918
+ });
919
+ }
920
+ throw error;
921
+ }
922
+ },
923
+ };
924
+ }
925
+ else {
926
+ // Fallback: include tool as-is if it doesn't have execute function
927
+ tools[toolName] = directTool;
928
+ }
929
+ }
930
+ logger.debug(`Direct tools processing complete`, {
931
+ directToolsProcessed: Object.keys(this.directTools).length,
932
+ });
933
+ }
612
934
  /**
613
935
  * Process custom tools from setupToolExecutor
614
936
  */
@@ -618,7 +940,7 @@ export class BaseProvider {
618
940
  }
619
941
  logger.debug(`[BaseProvider] Loading ${this.customTools.size} custom tools from setupToolExecutor`);
620
942
  for (const [toolName, toolDef] of this.customTools.entries()) {
621
- logger.debug(`[BaseProvider] Processing custom tool: ${toolName}`, {
943
+ logger.debug(`Processing custom tool: ${toolName}`, {
622
944
  toolDef: typeof toolDef,
623
945
  hasExecute: toolDef && typeof toolDef === "object" && "execute" in toolDef,
624
946
  hasName: toolDef && typeof toolDef === "object" && "name" in toolDef,
@@ -647,16 +969,101 @@ export class BaseProvider {
647
969
  const { tool: createAISDKTool } = await import("ai");
648
970
  return createAISDKTool({
649
971
  description: tool.description || `External MCP tool ${tool.name}`,
650
- parameters: await this.convertMCPSchemaToZod(tool.inputSchema),
972
+ parameters: this.createPermissiveZodSchema(),
651
973
  execute: async (params) => {
652
- logger.debug(`[BaseProvider] Executing external MCP tool: ${tool.name}`, { params });
974
+ logger.debug(`Executing external MCP tool: ${tool.name}`, {
975
+ toolName: tool.name,
976
+ serverId: tool.serverId,
977
+ params: JSON.stringify(params),
978
+ paramsType: typeof params,
979
+ hasNeurolink: !!this.neurolink,
980
+ hasExecuteFunction: this.neurolink &&
981
+ typeof this.neurolink.executeExternalMCPTool === "function",
982
+ timestamp: Date.now(),
983
+ });
984
+ // 🔧 EMIT TOOL START EVENT - Bedrock-compatible format
985
+ if (this.neurolink?.getEventEmitter) {
986
+ const emitter = this.neurolink.getEventEmitter();
987
+ emitter.emit("tool:start", { tool: tool.name, input: params });
988
+ logger.debug(`tool:start event emitted for ${tool.name}`, {
989
+ toolName: tool.name,
990
+ input: params,
991
+ hasEmitter: !!emitter,
992
+ });
993
+ }
653
994
  // Execute via NeuroLink's direct tool execution
654
995
  if (this.neurolink &&
655
996
  typeof this.neurolink.executeExternalMCPTool === "function") {
656
- return await this.neurolink.executeExternalMCPTool(tool.serverId || "unknown", tool.name, params);
997
+ try {
998
+ const result = await this.neurolink.executeExternalMCPTool(tool.serverId || "unknown", tool.name, params);
999
+ // 🔧 EMIT TOOL END EVENT - Bedrock-compatible format
1000
+ if (this.neurolink?.getEventEmitter) {
1001
+ const emitter = this.neurolink.getEventEmitter();
1002
+ emitter.emit("tool:end", { tool: tool.name, result });
1003
+ logger.debug(`tool:end event emitted for ${tool.name}`, {
1004
+ toolName: tool.name,
1005
+ result: typeof result === "string"
1006
+ ? result.substring(0, 100)
1007
+ : JSON.stringify(result).substring(0, 100),
1008
+ hasEmitter: !!emitter,
1009
+ });
1010
+ }
1011
+ logger.debug(`External MCP tool executed: ${tool.name}`, {
1012
+ toolName: tool.name,
1013
+ result: typeof result === "string"
1014
+ ? result.substring(0, 200)
1015
+ : JSON.stringify(result).substring(0, 200),
1016
+ resultType: typeof result,
1017
+ timestamp: Date.now(),
1018
+ });
1019
+ return result;
1020
+ }
1021
+ catch (mcpError) {
1022
+ // 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
1023
+ if (this.neurolink?.getEventEmitter) {
1024
+ const emitter = this.neurolink.getEventEmitter();
1025
+ const errorMsg = mcpError instanceof Error
1026
+ ? mcpError.message
1027
+ : String(mcpError);
1028
+ emitter.emit("tool:end", { tool: tool.name, error: errorMsg });
1029
+ logger.debug(`tool:end error event emitted for ${tool.name}`, {
1030
+ toolName: tool.name,
1031
+ error: errorMsg,
1032
+ hasEmitter: !!emitter,
1033
+ });
1034
+ }
1035
+ logger.error(`External MCP tool failed: ${tool.name}`, {
1036
+ toolName: tool.name,
1037
+ serverId: tool.serverId,
1038
+ error: mcpError instanceof Error
1039
+ ? mcpError.message
1040
+ : String(mcpError),
1041
+ errorStack: mcpError instanceof Error ? mcpError.stack : undefined,
1042
+ params: JSON.stringify(params),
1043
+ timestamp: Date.now(),
1044
+ });
1045
+ throw mcpError;
1046
+ }
657
1047
  }
658
1048
  else {
659
- throw new Error(`Cannot execute external MCP tool: NeuroLink executeExternalMCPTool not available`);
1049
+ const error = `Cannot execute external MCP tool: NeuroLink executeExternalMCPTool not available`;
1050
+ // 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
1051
+ if (this.neurolink?.getEventEmitter) {
1052
+ const emitter = this.neurolink.getEventEmitter();
1053
+ emitter.emit("tool:end", { tool: tool.name, error });
1054
+ logger.debug(`tool:end error event emitted for ${tool.name}`, {
1055
+ toolName: tool.name,
1056
+ error,
1057
+ hasEmitter: !!emitter,
1058
+ });
1059
+ }
1060
+ logger.error(`${error}`, {
1061
+ toolName: tool.name,
1062
+ hasNeurolink: !!this.neurolink,
1063
+ neurolinkType: typeof this.neurolink,
1064
+ timestamp: Date.now(),
1065
+ });
1066
+ throw new Error(error);
660
1067
  }
661
1068
  },
662
1069
  });
@@ -718,9 +1125,10 @@ export class BaseProvider {
718
1125
  * MCP tools are added when available (without blocking)
719
1126
  */
720
1127
  async getAllTools() {
721
- const tools = {
722
- ...this.directTools, // Always include direct tools
723
- };
1128
+ // Start with wrapped direct tools that emit events
1129
+ const tools = {};
1130
+ // Wrap direct tools with event emission
1131
+ await this.processDirectTools(tools);
724
1132
  logger.debug(`[BaseProvider] getAllTools called for ${this.providerName}`, {
725
1133
  neurolinkAvailable: !!this.neurolink,
726
1134
  neurolinkType: typeof this.neurolink,
@@ -756,76 +1164,15 @@ export class BaseProvider {
756
1164
  }
757
1165
  }
758
1166
  /**
759
- * Convert MCP JSON Schema to Zod schema for AI SDK tools
760
- * Handles common MCP schema patterns safely
1167
+ * Create a permissive Zod schema that accepts all parameters as-is
761
1168
  */
762
- async convertMCPSchemaToZod(inputSchema) {
763
- const { z } = await import("zod");
764
- if (!inputSchema || typeof inputSchema !== "object") {
765
- return z.object({});
766
- }
767
- try {
768
- const schema = inputSchema;
769
- const zodFields = {};
770
- // Handle JSON Schema properties
771
- if (schema.properties && typeof schema.properties === "object") {
772
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
773
- for (const [propName, propDef] of Object.entries(schema.properties)) {
774
- const prop = propDef;
775
- let zodType;
776
- // Convert based on JSON Schema type
777
- switch (prop.type) {
778
- case "string":
779
- zodType = z.string();
780
- if (prop.description && typeof prop.description === "string") {
781
- zodType = zodType.describe(prop.description);
782
- }
783
- break;
784
- case "number":
785
- case "integer":
786
- zodType = z.number();
787
- if (prop.description && typeof prop.description === "string") {
788
- zodType = zodType.describe(prop.description);
789
- }
790
- break;
791
- case "boolean":
792
- zodType = z.boolean();
793
- if (prop.description && typeof prop.description === "string") {
794
- zodType = zodType.describe(prop.description);
795
- }
796
- break;
797
- case "array":
798
- zodType = z.array(z.unknown());
799
- if (prop.description && typeof prop.description === "string") {
800
- zodType = zodType.describe(prop.description);
801
- }
802
- break;
803
- case "object":
804
- zodType = z.object({});
805
- if (prop.description && typeof prop.description === "string") {
806
- zodType = zodType.describe(prop.description);
807
- }
808
- break;
809
- default:
810
- // Unknown type, use string as fallback
811
- zodType = z.string();
812
- if (prop.description && typeof prop.description === "string") {
813
- zodType = zodType.describe(prop.description);
814
- }
815
- }
816
- // Make optional if not required
817
- if (!required.has(propName)) {
818
- zodType = zodType.optional();
819
- }
820
- zodFields[propName] = zodType;
821
- }
822
- }
823
- return getKeyCount(zodFields) > 0 ? z.object(zodFields) : z.object({});
824
- }
825
- catch (error) {
826
- logger.warn(`Failed to convert MCP schema to Zod, using empty schema:`, error);
827
- return z.object({});
828
- }
1169
+ createPermissiveZodSchema() {
1170
+ // Create a permissive record that accepts any object structure
1171
+ // This allows all parameters to pass through without validation issues
1172
+ return z.record(z.unknown()).transform((data) => {
1173
+ // Return the data as-is to preserve all parameter information
1174
+ return data;
1175
+ });
829
1176
  }
830
1177
  /**
831
1178
  * Set session context for MCP tools
@@ -1133,6 +1480,36 @@ export class BaseProvider {
1133
1480
  }
1134
1481
  return this.defaultTimeout;
1135
1482
  }
1483
+ /**
1484
+ * Check if tool executions should be stored and handle storage
1485
+ */
1486
+ async handleToolExecutionStorage(toolCalls, toolResults, options) {
1487
+ // Check if tools are not empty
1488
+ const hasToolData = (toolCalls && toolCalls.length > 0) ||
1489
+ (toolResults && toolResults.length > 0);
1490
+ // Check if NeuroLink instance is available and has tool execution storage
1491
+ const hasStorageAvailable = this.neurolink?.isToolExecutionStorageAvailable();
1492
+ // Early return if storage is not available or no tool data
1493
+ if (!hasStorageAvailable || !hasToolData || !this.neurolink) {
1494
+ return;
1495
+ }
1496
+ const sessionId = options.context?.sessionId ||
1497
+ options.sessionId ||
1498
+ `session-${Date.now()}`;
1499
+ const userId = options.context?.userId ||
1500
+ options.userId;
1501
+ try {
1502
+ await this.neurolink.storeToolExecutions(sessionId, userId, toolCalls, toolResults);
1503
+ }
1504
+ catch (error) {
1505
+ logger.warn("[BaseProvider] Failed to store tool executions", {
1506
+ provider: this.providerName,
1507
+ sessionId,
1508
+ error: error instanceof Error ? error.message : String(error),
1509
+ });
1510
+ // Don't throw - tool storage failures shouldn't break generation
1511
+ }
1512
+ }
1136
1513
  /**
1137
1514
  * Utility method to chunk large prompts into smaller pieces
1138
1515
  * @param prompt The prompt to chunk