@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.
- package/CHANGELOG.md +12 -0
- package/dist/cli/commands/config.d.ts +18 -18
- package/dist/cli/factories/commandFactory.d.ts +24 -0
- package/dist/cli/factories/commandFactory.js +297 -245
- package/dist/core/baseProvider.d.ts +44 -3
- package/dist/core/baseProvider.js +729 -352
- package/dist/core/constants.d.ts +2 -30
- package/dist/core/constants.js +15 -43
- package/dist/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/core/redisConversationMemoryManager.js +665 -203
- package/dist/factories/providerFactory.js +23 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -3
- package/dist/lib/core/baseProvider.d.ts +44 -3
- package/dist/lib/core/baseProvider.js +729 -352
- package/dist/lib/core/constants.d.ts +2 -30
- package/dist/lib/core/constants.js +15 -43
- package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/lib/core/redisConversationMemoryManager.js +665 -203
- package/dist/lib/factories/providerFactory.js +23 -6
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +4 -3
- package/dist/lib/mcp/externalServerManager.js +2 -2
- package/dist/lib/mcp/registry.js +2 -2
- package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/lib/mcp/toolRegistry.js +4 -8
- package/dist/lib/neurolink.d.ts +95 -28
- package/dist/lib/neurolink.js +479 -719
- package/dist/lib/providers/amazonBedrock.js +2 -2
- package/dist/lib/providers/anthropic.js +8 -0
- package/dist/lib/providers/anthropicBaseProvider.js +8 -0
- package/dist/lib/providers/azureOpenai.js +8 -0
- package/dist/lib/providers/googleAiStudio.js +8 -0
- package/dist/lib/providers/googleVertex.d.ts +3 -23
- package/dist/lib/providers/googleVertex.js +24 -342
- package/dist/lib/providers/huggingFace.js +8 -0
- package/dist/lib/providers/litellm.js +8 -0
- package/dist/lib/providers/mistral.js +8 -0
- package/dist/lib/providers/openAI.d.ts +23 -0
- package/dist/lib/providers/openAI.js +323 -6
- package/dist/lib/providers/openaiCompatible.js +8 -0
- package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/lib/sdk/toolRegistration.js +18 -1
- package/dist/lib/types/common.d.ts +98 -0
- package/dist/lib/types/conversation.d.ts +52 -2
- package/dist/lib/types/streamTypes.d.ts +13 -6
- package/dist/lib/types/typeAliases.d.ts +3 -2
- package/dist/lib/utils/conversationMemory.js +3 -1
- package/dist/lib/utils/messageBuilder.d.ts +10 -2
- package/dist/lib/utils/messageBuilder.js +22 -1
- package/dist/lib/utils/parameterValidation.js +6 -25
- package/dist/lib/utils/promptRedaction.js +4 -4
- package/dist/lib/utils/redis.d.ts +10 -6
- package/dist/lib/utils/redis.js +71 -70
- package/dist/lib/utils/schemaConversion.d.ts +14 -0
- package/dist/lib/utils/schemaConversion.js +140 -0
- package/dist/lib/utils/transformationUtils.js +143 -5
- package/dist/mcp/externalServerManager.js +2 -2
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/mcp/toolRegistry.js +4 -8
- package/dist/neurolink.d.ts +95 -28
- package/dist/neurolink.js +479 -719
- package/dist/providers/amazonBedrock.js +2 -2
- package/dist/providers/anthropic.js +8 -0
- package/dist/providers/anthropicBaseProvider.js +8 -0
- package/dist/providers/azureOpenai.js +8 -0
- package/dist/providers/googleAiStudio.js +8 -0
- package/dist/providers/googleVertex.d.ts +3 -23
- package/dist/providers/googleVertex.js +24 -342
- package/dist/providers/huggingFace.js +8 -0
- package/dist/providers/litellm.js +8 -0
- package/dist/providers/mistral.js +8 -0
- package/dist/providers/openAI.d.ts +23 -0
- package/dist/providers/openAI.js +323 -6
- package/dist/providers/openaiCompatible.js +8 -0
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/sdk/toolRegistration.js +18 -1
- package/dist/types/common.d.ts +98 -0
- package/dist/types/conversation.d.ts +52 -2
- package/dist/types/streamTypes.d.ts +13 -6
- package/dist/types/typeAliases.d.ts +3 -2
- package/dist/utils/conversationMemory.js +3 -1
- package/dist/utils/messageBuilder.d.ts +10 -2
- package/dist/utils/messageBuilder.js +22 -1
- package/dist/utils/parameterValidation.js +6 -25
- package/dist/utils/promptRedaction.js +4 -4
- package/dist/utils/redis.d.ts +10 -6
- package/dist/utils/redis.js +71 -70
- package/dist/utils/schemaConversion.d.ts +14 -0
- package/dist/utils/schemaConversion.js +140 -0
- package/dist/utils/transformationUtils.js +143 -5
- 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:`,
|
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
|
-
*
|
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
|
162
|
-
const
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
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
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
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
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
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
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
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
|
-
|
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:
|
599
|
-
? toolInfo.parameters
|
600
|
-
: z.object({}),
|
763
|
+
parameters: finalSchema,
|
601
764
|
execute: async (params) => {
|
602
|
-
const
|
603
|
-
|
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(`
|
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:
|
972
|
+
parameters: this.createPermissiveZodSchema(),
|
651
973
|
execute: async (params) => {
|
652
|
-
logger.debug(`
|
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
|
-
|
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
|
-
|
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
|
-
|
722
|
-
|
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
|
-
*
|
760
|
-
* Handles common MCP schema patterns safely
|
1167
|
+
* Create a permissive Zod schema that accepts all parameters as-is
|
761
1168
|
*/
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
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
|