@juspay/neurolink 8.2.0 → 8.4.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/README.md +13 -3
- package/dist/adapters/providerImageAdapter.d.ts +1 -1
- package/dist/adapters/providerImageAdapter.js +62 -0
- package/dist/agent/directTools.d.ts +0 -72
- package/dist/agent/directTools.js +3 -74
- package/dist/cli/commands/config.d.ts +18 -18
- package/dist/cli/factories/commandFactory.js +1 -0
- package/dist/cli/loop/conversationSelector.js +4 -0
- package/dist/cli/loop/session.js +27 -15
- package/dist/constants/enums.d.ts +1 -0
- package/dist/constants/enums.js +3 -1
- package/dist/constants/tokens.d.ts +3 -0
- package/dist/constants/tokens.js +3 -0
- package/dist/core/baseProvider.d.ts +56 -53
- package/dist/core/baseProvider.js +107 -1095
- package/dist/core/constants.d.ts +3 -0
- package/dist/core/constants.js +6 -3
- package/dist/core/modelConfiguration.js +10 -0
- package/dist/core/modules/GenerationHandler.d.ts +63 -0
- package/dist/core/modules/GenerationHandler.js +230 -0
- package/dist/core/modules/MessageBuilder.d.ts +39 -0
- package/dist/core/modules/MessageBuilder.js +179 -0
- package/dist/core/modules/StreamHandler.d.ts +52 -0
- package/dist/core/modules/StreamHandler.js +103 -0
- package/dist/core/modules/TelemetryHandler.d.ts +64 -0
- package/dist/core/modules/TelemetryHandler.js +170 -0
- package/dist/core/modules/ToolsManager.d.ts +98 -0
- package/dist/core/modules/ToolsManager.js +521 -0
- package/dist/core/modules/Utilities.d.ts +88 -0
- package/dist/core/modules/Utilities.js +329 -0
- package/dist/factories/providerRegistry.js +1 -1
- package/dist/lib/adapters/providerImageAdapter.d.ts +1 -1
- package/dist/lib/adapters/providerImageAdapter.js +62 -0
- package/dist/lib/agent/directTools.d.ts +0 -72
- package/dist/lib/agent/directTools.js +3 -74
- package/dist/lib/constants/enums.d.ts +1 -0
- package/dist/lib/constants/enums.js +3 -1
- package/dist/lib/constants/tokens.d.ts +3 -0
- package/dist/lib/constants/tokens.js +3 -0
- package/dist/lib/core/baseProvider.d.ts +56 -53
- package/dist/lib/core/baseProvider.js +107 -1095
- package/dist/lib/core/constants.d.ts +3 -0
- package/dist/lib/core/constants.js +6 -3
- package/dist/lib/core/modelConfiguration.js +10 -0
- package/dist/lib/core/modules/GenerationHandler.d.ts +63 -0
- package/dist/lib/core/modules/GenerationHandler.js +231 -0
- package/dist/lib/core/modules/MessageBuilder.d.ts +39 -0
- package/dist/lib/core/modules/MessageBuilder.js +180 -0
- package/dist/lib/core/modules/StreamHandler.d.ts +52 -0
- package/dist/lib/core/modules/StreamHandler.js +104 -0
- package/dist/lib/core/modules/TelemetryHandler.d.ts +64 -0
- package/dist/lib/core/modules/TelemetryHandler.js +171 -0
- package/dist/lib/core/modules/ToolsManager.d.ts +98 -0
- package/dist/lib/core/modules/ToolsManager.js +522 -0
- package/dist/lib/core/modules/Utilities.d.ts +88 -0
- package/dist/lib/core/modules/Utilities.js +330 -0
- package/dist/lib/factories/providerRegistry.js +1 -1
- package/dist/lib/mcp/servers/agent/directToolsServer.js +0 -1
- package/dist/lib/models/modelRegistry.js +44 -0
- package/dist/lib/neurolink.js +35 -3
- package/dist/lib/providers/amazonBedrock.js +59 -10
- package/dist/lib/providers/anthropic.js +2 -30
- package/dist/lib/providers/azureOpenai.js +2 -24
- package/dist/lib/providers/googleAiStudio.js +2 -24
- package/dist/lib/providers/googleVertex.js +2 -45
- package/dist/lib/providers/huggingFace.js +3 -31
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +110 -44
- package/dist/lib/providers/mistral.js +5 -32
- package/dist/lib/providers/ollama.d.ts +1 -0
- package/dist/lib/providers/ollama.js +476 -129
- package/dist/lib/providers/openAI.js +2 -28
- package/dist/lib/providers/openaiCompatible.js +3 -31
- package/dist/lib/types/content.d.ts +16 -113
- package/dist/lib/types/content.js +16 -2
- package/dist/lib/types/conversation.d.ts +3 -17
- package/dist/lib/types/generateTypes.d.ts +2 -2
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/multimodal.d.ts +282 -0
- package/dist/lib/types/multimodal.js +101 -0
- package/dist/lib/types/streamTypes.d.ts +2 -2
- package/dist/lib/utils/imageProcessor.d.ts +1 -1
- package/dist/lib/utils/messageBuilder.js +25 -2
- package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -1
- package/dist/lib/utils/pdfProcessor.d.ts +9 -0
- package/dist/lib/utils/pdfProcessor.js +67 -9
- package/dist/mcp/servers/agent/directToolsServer.js +0 -1
- package/dist/models/modelRegistry.js +44 -0
- package/dist/neurolink.js +35 -3
- package/dist/providers/amazonBedrock.js +59 -10
- package/dist/providers/anthropic.js +2 -30
- package/dist/providers/azureOpenai.js +2 -24
- package/dist/providers/googleAiStudio.js +2 -24
- package/dist/providers/googleVertex.js +2 -45
- package/dist/providers/huggingFace.js +3 -31
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +110 -44
- package/dist/providers/mistral.js +5 -32
- package/dist/providers/ollama.d.ts +1 -0
- package/dist/providers/ollama.js +476 -129
- package/dist/providers/openAI.js +2 -28
- package/dist/providers/openaiCompatible.js +3 -31
- package/dist/types/content.d.ts +16 -113
- package/dist/types/content.js +16 -2
- package/dist/types/conversation.d.ts +3 -17
- package/dist/types/generateTypes.d.ts +2 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/multimodal.d.ts +282 -0
- package/dist/types/multimodal.js +100 -0
- package/dist/types/streamTypes.d.ts +2 -2
- package/dist/utils/imageProcessor.d.ts +1 -1
- package/dist/utils/messageBuilder.js +25 -2
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -1
- package/dist/utils/pdfProcessor.d.ts +9 -0
- package/dist/utils/pdfProcessor.js +67 -9
- package/package.json +5 -2
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { generateText, tool as createAISDKTool, jsonSchema, Output } from "ai";
|
|
1
|
+
import { generateText } from "ai";
|
|
3
2
|
import { AIProviderName } from "../constants/enums.js";
|
|
4
3
|
import { MiddlewareFactory } from "../middleware/factory.js";
|
|
5
4
|
import { logger } from "../utils/logger.js";
|
|
6
|
-
import { DEFAULT_MAX_STEPS, STEP_LIMITS } from "../core/constants.js";
|
|
7
5
|
import { directAgentTools } from "../agent/directTools.js";
|
|
8
|
-
import { getSafeMaxTokens } from "../utils/tokenLimits.js";
|
|
9
6
|
import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
|
|
10
7
|
import { nanoid } from "nanoid";
|
|
11
|
-
import { createAnalytics } from "./analytics.js";
|
|
12
8
|
import { shouldDisableBuiltinTools } from "../utils/toolUtils.js";
|
|
13
|
-
import { buildMessagesArray, buildMultimodalMessagesArray, } from "../utils/messageBuilder.js";
|
|
14
9
|
import { getKeysAsString, getKeyCount } from "../utils/transformationUtils.js";
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
10
|
+
// Import modules for composition
|
|
11
|
+
import { MessageBuilder } from "./modules/MessageBuilder.js";
|
|
12
|
+
import { StreamHandler } from "./modules/StreamHandler.js";
|
|
13
|
+
import { GenerationHandler } from "./modules/GenerationHandler.js";
|
|
14
|
+
import { TelemetryHandler } from "./modules/TelemetryHandler.js";
|
|
15
|
+
import { Utilities } from "./modules/Utilities.js";
|
|
16
|
+
import { ToolsManager } from "./modules/ToolsManager.js";
|
|
19
17
|
/**
|
|
20
18
|
* Abstract base class for all AI providers
|
|
21
19
|
* Tools are integrated as first-class citizens - always available by default
|
|
@@ -35,11 +33,30 @@ export class BaseProvider {
|
|
|
35
33
|
sessionId;
|
|
36
34
|
userId;
|
|
37
35
|
neurolink; // Reference to actual NeuroLink instance for MCP tools
|
|
36
|
+
// Composition modules - Single Responsibility Principle
|
|
37
|
+
messageBuilder;
|
|
38
|
+
streamHandler;
|
|
39
|
+
generationHandler;
|
|
40
|
+
telemetryHandler;
|
|
41
|
+
utilities;
|
|
42
|
+
toolsManager;
|
|
38
43
|
constructor(modelName, providerName, neurolink, middleware) {
|
|
39
44
|
this.modelName = modelName || this.getDefaultModel();
|
|
40
45
|
this.providerName = providerName || this.getProviderName();
|
|
41
46
|
this.neurolink = neurolink;
|
|
42
47
|
this.middlewareOptions = middleware;
|
|
48
|
+
// Initialize composition modules
|
|
49
|
+
this.messageBuilder = new MessageBuilder(this.providerName, this.modelName);
|
|
50
|
+
this.streamHandler = new StreamHandler(this.providerName, this.modelName);
|
|
51
|
+
this.generationHandler = new GenerationHandler(this.providerName, this.modelName, () => this.supportsTools(), (options, type) => this.getStreamTelemetryConfig(options, type), (toolCalls, toolResults, options, timestamp) => this.handleToolExecutionStorage(toolCalls, toolResults, options, timestamp));
|
|
52
|
+
this.telemetryHandler = new TelemetryHandler(this.providerName, this.modelName, this.neurolink);
|
|
53
|
+
this.utilities = new Utilities(this.providerName, this.modelName, this.defaultTimeout, this.middlewareOptions);
|
|
54
|
+
this.toolsManager = new ToolsManager(this.providerName, this.directTools, this.neurolink, {
|
|
55
|
+
isZodSchema: (schema) => this.isZodSchema(schema),
|
|
56
|
+
convertToolResult: (result) => this.convertToolResult(result),
|
|
57
|
+
createPermissiveZodSchema: () => this.createPermissiveZodSchema(),
|
|
58
|
+
fixSchemaForOpenAIStrictMode: (schema) => this.fixSchemaForOpenAIStrictMode(schema),
|
|
59
|
+
});
|
|
43
60
|
}
|
|
44
61
|
/**
|
|
45
62
|
* Check if this provider supports tool/function calling
|
|
@@ -227,353 +244,57 @@ export class BaseProvider {
|
|
|
227
244
|
return { tools, model };
|
|
228
245
|
}
|
|
229
246
|
/**
|
|
230
|
-
* Build messages array for generation
|
|
247
|
+
* Build messages array for generation - delegated to MessageBuilder
|
|
231
248
|
*/
|
|
232
249
|
async buildMessages(options) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
logger.debug("Detected multimodal input, using multimodal message builder");
|
|
246
|
-
}
|
|
247
|
-
const input = options.input;
|
|
248
|
-
const multimodalOptions = {
|
|
249
|
-
input: {
|
|
250
|
-
text: options.prompt || options.input?.text || "",
|
|
251
|
-
images: input?.images,
|
|
252
|
-
content: input?.content,
|
|
253
|
-
csvFiles: input?.csvFiles,
|
|
254
|
-
pdfFiles: input?.pdfFiles,
|
|
255
|
-
files: input?.files,
|
|
256
|
-
},
|
|
257
|
-
csvOptions: options.csvOptions,
|
|
258
|
-
provider: options.provider,
|
|
259
|
-
model: options.model,
|
|
260
|
-
temperature: options.temperature,
|
|
261
|
-
maxTokens: options.maxTokens,
|
|
262
|
-
systemPrompt: options.systemPrompt,
|
|
263
|
-
enableAnalytics: options.enableAnalytics,
|
|
264
|
-
enableEvaluation: options.enableEvaluation,
|
|
265
|
-
context: options.context,
|
|
266
|
-
};
|
|
267
|
-
messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
if (process.env.NEUROLINK_DEBUG === "true") {
|
|
271
|
-
logger.debug("No multimodal input detected, using standard message builder");
|
|
272
|
-
}
|
|
273
|
-
messages = await buildMessagesArray(options);
|
|
274
|
-
}
|
|
275
|
-
// Convert messages to Vercel AI SDK format
|
|
276
|
-
return messages.map((msg) => {
|
|
277
|
-
if (typeof msg.content === "string") {
|
|
278
|
-
return {
|
|
279
|
-
role: msg.role,
|
|
280
|
-
content: msg.content,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
return {
|
|
285
|
-
role: msg.role,
|
|
286
|
-
content: msg.content.map((item) => {
|
|
287
|
-
if (item.type === "text") {
|
|
288
|
-
return { type: "text", text: item.text || "" };
|
|
289
|
-
}
|
|
290
|
-
else if (item.type === "image") {
|
|
291
|
-
return { type: "image", image: item.image || "" };
|
|
292
|
-
}
|
|
293
|
-
return item;
|
|
294
|
-
}),
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
});
|
|
250
|
+
return this.messageBuilder.buildMessages(options);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build messages array for streaming operations - delegated to MessageBuilder
|
|
254
|
+
* This is a protected helper method that providers can use to build messages
|
|
255
|
+
* with automatic multimodal detection, eliminating code duplication
|
|
256
|
+
*
|
|
257
|
+
* @param options - Stream options or text generation options
|
|
258
|
+
* @returns Promise resolving to CoreMessage array ready for AI SDK
|
|
259
|
+
*/
|
|
260
|
+
async buildMessagesForStream(options) {
|
|
261
|
+
return this.messageBuilder.buildMessagesForStream(options);
|
|
298
262
|
}
|
|
299
263
|
/**
|
|
300
|
-
* Execute the generation with AI SDK
|
|
264
|
+
* Execute the generation with AI SDK - delegated to GenerationHandler
|
|
301
265
|
*/
|
|
302
266
|
async executeGeneration(model, messages, tools, options) {
|
|
303
|
-
|
|
304
|
-
const useStructuredOutput = !!options.schema &&
|
|
305
|
-
(options.output?.format === "json" ||
|
|
306
|
-
options.output?.format === "structured");
|
|
307
|
-
return await generateText({
|
|
308
|
-
model,
|
|
309
|
-
messages,
|
|
310
|
-
tools,
|
|
311
|
-
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
|
312
|
-
toolChoice: shouldUseTools ? "auto" : "none",
|
|
313
|
-
temperature: options.temperature,
|
|
314
|
-
maxTokens: options.maxTokens,
|
|
315
|
-
...(useStructuredOutput &&
|
|
316
|
-
options.schema && {
|
|
317
|
-
experimental_output: Output.object({ schema: options.schema }),
|
|
318
|
-
}),
|
|
319
|
-
experimental_telemetry: this.getStreamTelemetryConfig(options, "generate"),
|
|
320
|
-
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
321
|
-
logger.info("Tool execution completed", { toolResults, toolCalls });
|
|
322
|
-
// Handle tool execution storage
|
|
323
|
-
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
324
|
-
logger.warn("[BaseProvider] Failed to store tool executions", {
|
|
325
|
-
provider: this.providerName,
|
|
326
|
-
error: error instanceof Error ? error.message : String(error),
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
},
|
|
330
|
-
});
|
|
267
|
+
return this.generationHandler.executeGeneration(model, messages, tools, options);
|
|
331
268
|
}
|
|
332
269
|
/**
|
|
333
|
-
* Log generation completion information
|
|
270
|
+
* Log generation completion information - delegated to GenerationHandler
|
|
334
271
|
*/
|
|
335
272
|
logGenerationComplete(generateResult) {
|
|
336
|
-
|
|
337
|
-
provider: this.providerName,
|
|
338
|
-
model: this.modelName,
|
|
339
|
-
responseLength: generateResult.text?.length || 0,
|
|
340
|
-
toolResultsCount: generateResult.toolResults?.length || 0,
|
|
341
|
-
finishReason: generateResult.finishReason,
|
|
342
|
-
usage: generateResult.usage,
|
|
343
|
-
timestamp: Date.now(),
|
|
344
|
-
});
|
|
273
|
+
this.generationHandler.logGenerationComplete(generateResult);
|
|
345
274
|
}
|
|
346
275
|
/**
|
|
347
|
-
* Record performance metrics
|
|
276
|
+
* Record performance metrics - delegated to TelemetryHandler
|
|
348
277
|
*/
|
|
349
278
|
async recordPerformanceMetrics(usage, responseTime) {
|
|
350
|
-
|
|
351
|
-
const actualCost = await this.calculateActualCost(usage || { promptTokens: 0, completionTokens: 0, totalTokens: 0 });
|
|
352
|
-
recordProviderPerformanceFromMetrics(this.providerName, {
|
|
353
|
-
responseTime,
|
|
354
|
-
tokensGenerated: usage?.totalTokens || 0,
|
|
355
|
-
cost: actualCost,
|
|
356
|
-
success: true,
|
|
357
|
-
});
|
|
358
|
-
const optimizedProvider = getPerformanceOptimizedProvider("speed");
|
|
359
|
-
logger.debug(`🚀 Performance recorded for ${this.providerName}:`, {
|
|
360
|
-
responseTime: `${responseTime}ms`,
|
|
361
|
-
tokens: usage?.totalTokens || 0,
|
|
362
|
-
estimatedCost: `$${actualCost.toFixed(6)}`,
|
|
363
|
-
recommendedSpeedProvider: optimizedProvider?.provider || "none",
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
catch (perfError) {
|
|
367
|
-
logger.warn("⚠️ Performance recording failed:", perfError);
|
|
368
|
-
}
|
|
279
|
+
await this.telemetryHandler.recordPerformanceMetrics(usage, responseTime);
|
|
369
280
|
}
|
|
370
281
|
/**
|
|
371
|
-
* Extract tool information from generation result
|
|
282
|
+
* Extract tool information from generation result - delegated to GenerationHandler
|
|
372
283
|
*/
|
|
373
284
|
extractToolInformation(generateResult) {
|
|
374
|
-
|
|
375
|
-
const toolExecutions = [];
|
|
376
|
-
// Extract tool names from tool calls
|
|
377
|
-
if (generateResult.toolCalls && generateResult.toolCalls.length > 0) {
|
|
378
|
-
toolsUsed.push(...generateResult.toolCalls.map((tc) => {
|
|
379
|
-
return tc.toolName || tc.name || "unknown";
|
|
380
|
-
}));
|
|
381
|
-
}
|
|
382
|
-
// Extract from steps
|
|
383
|
-
if (generateResult.steps &&
|
|
384
|
-
Array.isArray(generateResult.steps)) {
|
|
385
|
-
const toolCallArgsMap = new Map();
|
|
386
|
-
for (const step of generateResult
|
|
387
|
-
.steps || []) {
|
|
388
|
-
// Collect tool calls and their arguments
|
|
389
|
-
if (step?.toolCalls && Array.isArray(step.toolCalls)) {
|
|
390
|
-
for (const toolCall of step.toolCalls) {
|
|
391
|
-
const tcRecord = toolCall;
|
|
392
|
-
const toolName = tcRecord.toolName ||
|
|
393
|
-
tcRecord.name ||
|
|
394
|
-
"unknown";
|
|
395
|
-
const toolId = tcRecord.toolCallId ||
|
|
396
|
-
tcRecord.id ||
|
|
397
|
-
toolName;
|
|
398
|
-
toolsUsed.push(toolName);
|
|
399
|
-
let callArgs = {};
|
|
400
|
-
if (tcRecord.args) {
|
|
401
|
-
callArgs = tcRecord.args;
|
|
402
|
-
}
|
|
403
|
-
else if (tcRecord.arguments) {
|
|
404
|
-
callArgs = tcRecord.arguments;
|
|
405
|
-
}
|
|
406
|
-
else if (tcRecord.parameters) {
|
|
407
|
-
callArgs = tcRecord.parameters;
|
|
408
|
-
}
|
|
409
|
-
toolCallArgsMap.set(toolId, callArgs);
|
|
410
|
-
toolCallArgsMap.set(toolName, callArgs);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// Process tool results
|
|
414
|
-
if (step?.toolResults && Array.isArray(step.toolResults)) {
|
|
415
|
-
for (const toolResult of step.toolResults) {
|
|
416
|
-
const trRecord = toolResult;
|
|
417
|
-
const toolName = trRecord.toolName || "unknown";
|
|
418
|
-
const toolId = trRecord.toolCallId || trRecord.id;
|
|
419
|
-
let toolArgs = {};
|
|
420
|
-
if (trRecord.args) {
|
|
421
|
-
toolArgs = trRecord.args;
|
|
422
|
-
}
|
|
423
|
-
else if (trRecord.arguments) {
|
|
424
|
-
toolArgs = trRecord.arguments;
|
|
425
|
-
}
|
|
426
|
-
else if (trRecord.parameters) {
|
|
427
|
-
toolArgs = trRecord.parameters;
|
|
428
|
-
}
|
|
429
|
-
else if (trRecord.input) {
|
|
430
|
-
toolArgs = trRecord.input;
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
toolArgs = toolCallArgsMap.get(toolId || toolName) || {};
|
|
434
|
-
}
|
|
435
|
-
toolExecutions.push({
|
|
436
|
-
name: toolName,
|
|
437
|
-
input: toolArgs,
|
|
438
|
-
output: trRecord.result || "success",
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return { toolsUsed: [...new Set(toolsUsed)], toolExecutions };
|
|
285
|
+
return this.generationHandler.extractToolInformation(generateResult);
|
|
445
286
|
}
|
|
446
287
|
/**
|
|
447
|
-
* Format the enhanced result
|
|
288
|
+
* Format the enhanced result - delegated to GenerationHandler
|
|
448
289
|
*/
|
|
449
290
|
formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options) {
|
|
450
|
-
|
|
451
|
-
// (accessing it when not set throws an error)
|
|
452
|
-
const useStructuredOutput = !!options.schema &&
|
|
453
|
-
(options.output?.format === "json" ||
|
|
454
|
-
options.output?.format === "structured");
|
|
455
|
-
const content = useStructuredOutput
|
|
456
|
-
? JSON.stringify(generateResult.experimental_output)
|
|
457
|
-
: generateResult.text;
|
|
458
|
-
return {
|
|
459
|
-
content,
|
|
460
|
-
usage: {
|
|
461
|
-
input: generateResult.usage?.promptTokens || 0,
|
|
462
|
-
output: generateResult.usage?.completionTokens || 0,
|
|
463
|
-
total: generateResult.usage?.totalTokens || 0,
|
|
464
|
-
},
|
|
465
|
-
provider: this.providerName,
|
|
466
|
-
model: this.modelName,
|
|
467
|
-
toolCalls: generateResult.toolCalls
|
|
468
|
-
? generateResult.toolCalls.map((tc) => ({
|
|
469
|
-
toolCallId: tc.toolCallId || "unknown",
|
|
470
|
-
toolName: tc.toolName || "unknown",
|
|
471
|
-
args: tc.args || {},
|
|
472
|
-
}))
|
|
473
|
-
: [],
|
|
474
|
-
toolResults: generateResult.toolResults || [],
|
|
475
|
-
toolsUsed,
|
|
476
|
-
toolExecutions,
|
|
477
|
-
availableTools: Object.keys(tools).map((name) => {
|
|
478
|
-
const tool = tools[name];
|
|
479
|
-
return {
|
|
480
|
-
name,
|
|
481
|
-
description: tool.description || "No description available",
|
|
482
|
-
parameters: tool.parameters || {},
|
|
483
|
-
server: tool.serverId || "direct",
|
|
484
|
-
};
|
|
485
|
-
}),
|
|
486
|
-
};
|
|
291
|
+
return this.generationHandler.formatEnhancedResult(generateResult, tools, toolsUsed, toolExecutions, options);
|
|
487
292
|
}
|
|
488
293
|
/**
|
|
489
|
-
* Analyze AI response structure and log detailed debugging information
|
|
490
|
-
* Extracted from generate method to reduce complexity
|
|
294
|
+
* Analyze AI response structure and log detailed debugging information - delegated to GenerationHandler
|
|
491
295
|
*/
|
|
492
296
|
analyzeAIResponse(result) {
|
|
493
|
-
|
|
494
|
-
logger.debug("NeuroLink Raw AI Response Analysis", {
|
|
495
|
-
provider: this.providerName,
|
|
496
|
-
model: this.modelName,
|
|
497
|
-
responseTextLength: result.text?.length || 0,
|
|
498
|
-
responsePreview: result.text?.substring(0, 500) + "...",
|
|
499
|
-
finishReason: result.finishReason,
|
|
500
|
-
usage: result.usage,
|
|
501
|
-
});
|
|
502
|
-
// 🔧 NEUROLINK TOOL CALLS ANALYSIS: Analyze raw tool calls structure
|
|
503
|
-
const toolCallsAnalysis = {
|
|
504
|
-
hasToolCalls: !!result.toolCalls,
|
|
505
|
-
toolCallsLength: result.toolCalls?.length || 0,
|
|
506
|
-
toolCalls: result.toolCalls?.map((toolCall, index) => {
|
|
507
|
-
const tcRecord = toolCall;
|
|
508
|
-
const toolName = tcRecord.toolName || tcRecord.name || "unknown";
|
|
509
|
-
const isTargetTool = toolName.toString().includes("SuccessRateSRByTime") ||
|
|
510
|
-
toolName.toString().includes("juspay-analytics");
|
|
511
|
-
return {
|
|
512
|
-
index: index + 1,
|
|
513
|
-
toolName,
|
|
514
|
-
toolId: tcRecord.toolCallId || tcRecord.id || "none",
|
|
515
|
-
hasArgs: !!tcRecord.args,
|
|
516
|
-
argsKeys: tcRecord.args && typeof tcRecord.args === "object"
|
|
517
|
-
? Object.keys(tcRecord.args)
|
|
518
|
-
: [],
|
|
519
|
-
isTargetTool,
|
|
520
|
-
...(isTargetTool && {
|
|
521
|
-
targetToolDetails: {
|
|
522
|
-
argsType: typeof tcRecord.args,
|
|
523
|
-
startTime: tcRecord.args?.startTime ||
|
|
524
|
-
"MISSING",
|
|
525
|
-
endTime: tcRecord.args?.endTime ||
|
|
526
|
-
"MISSING",
|
|
527
|
-
},
|
|
528
|
-
}),
|
|
529
|
-
};
|
|
530
|
-
}) || [],
|
|
531
|
-
};
|
|
532
|
-
logger.debug("Tool Calls Analysis", toolCallsAnalysis);
|
|
533
|
-
// 🔧 NEUROLINK STEPS ANALYSIS: Analyze steps structure (AI SDK multi-step format)
|
|
534
|
-
const steps = result.steps;
|
|
535
|
-
const stepsAnalysis = {
|
|
536
|
-
hasSteps: !!steps,
|
|
537
|
-
stepsLength: Array.isArray(steps) ? steps.length : 0,
|
|
538
|
-
steps: Array.isArray(steps)
|
|
539
|
-
? steps.map((step, stepIndex) => ({
|
|
540
|
-
stepIndex: stepIndex + 1,
|
|
541
|
-
hasToolCalls: !!step.toolCalls,
|
|
542
|
-
toolCallsLength: step.toolCalls?.length || 0,
|
|
543
|
-
hasToolResults: !!step.toolResults,
|
|
544
|
-
toolResultsLength: step.toolResults?.length || 0,
|
|
545
|
-
targetToolsInStep: step.toolCalls
|
|
546
|
-
?.filter((tc) => {
|
|
547
|
-
const toolName = tc.toolName || tc.name || "unknown";
|
|
548
|
-
return (toolName.toString().includes("SuccessRateSRByTime") ||
|
|
549
|
-
toolName.toString().includes("juspay-analytics"));
|
|
550
|
-
})
|
|
551
|
-
.map((tc) => ({
|
|
552
|
-
toolName: tc.toolName || tc.name,
|
|
553
|
-
hasArgs: !!tc.args,
|
|
554
|
-
argsKeys: tc.args && typeof tc.args === "object"
|
|
555
|
-
? Object.keys(tc.args)
|
|
556
|
-
: [],
|
|
557
|
-
startTime: tc.args?.startTime,
|
|
558
|
-
endTime: tc.args?.endTime,
|
|
559
|
-
})) || [],
|
|
560
|
-
}))
|
|
561
|
-
: [],
|
|
562
|
-
};
|
|
563
|
-
logger.debug("[BaseProvider] Steps Analysis", stepsAnalysis);
|
|
564
|
-
// 🔧 NEUROLINK TOOL RESULTS ANALYSIS: Analyze top-level tool results
|
|
565
|
-
const toolResultsAnalysis = {
|
|
566
|
-
hasToolResults: !!result.toolResults,
|
|
567
|
-
toolResultsLength: result.toolResults?.length || 0,
|
|
568
|
-
toolResults: result.toolResults?.map((toolResult, index) => ({
|
|
569
|
-
index: index + 1,
|
|
570
|
-
toolName: toolResult.toolName || "unknown",
|
|
571
|
-
hasResult: !!toolResult.result,
|
|
572
|
-
hasError: !!toolResult.error,
|
|
573
|
-
})) || [],
|
|
574
|
-
};
|
|
575
|
-
logger.debug("[BaseProvider] Tool Results Analysis", toolResultsAnalysis);
|
|
576
|
-
logger.debug("[BaseProvider] NeuroLink Raw AI Response Analysis Complete");
|
|
297
|
+
this.generationHandler.analyzeAIResponse(result);
|
|
577
298
|
}
|
|
578
299
|
/**
|
|
579
300
|
* Text generation method - implements AIProvider interface
|
|
@@ -698,584 +419,57 @@ export class BaseProvider {
|
|
|
698
419
|
}
|
|
699
420
|
}
|
|
700
421
|
/**
|
|
701
|
-
* Extract middleware options
|
|
702
|
-
* source of truth for deciding if middleware should be applied.
|
|
422
|
+
* Extract middleware options - delegated to Utilities
|
|
703
423
|
*/
|
|
704
424
|
extractMiddlewareOptions(options) {
|
|
705
|
-
|
|
706
|
-
const middlewareOpts = options.middleware ??
|
|
707
|
-
this.middlewareOptions;
|
|
708
|
-
if (!middlewareOpts) {
|
|
709
|
-
return null;
|
|
710
|
-
}
|
|
711
|
-
// 2. The middleware property must be an object with configuration.
|
|
712
|
-
if (typeof middlewareOpts !== "object" || middlewareOpts === null) {
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
|
-
// 3. Check if the middleware object has any actual configuration keys.
|
|
716
|
-
const fullOpts = middlewareOpts;
|
|
717
|
-
const hasArray = (arr) => Array.isArray(arr) && arr.length > 0;
|
|
718
|
-
const hasConfig = !!fullOpts.middlewareConfig ||
|
|
719
|
-
hasArray(fullOpts.enabledMiddleware) ||
|
|
720
|
-
hasArray(fullOpts.disabledMiddleware) ||
|
|
721
|
-
!!fullOpts.preset ||
|
|
722
|
-
hasArray(fullOpts.middleware);
|
|
723
|
-
if (!hasConfig) {
|
|
724
|
-
return null;
|
|
725
|
-
}
|
|
726
|
-
// 4. Return the formatted options if configuration is present.
|
|
727
|
-
return {
|
|
728
|
-
...fullOpts,
|
|
729
|
-
global: {
|
|
730
|
-
collectStats: true,
|
|
731
|
-
continueOnError: true,
|
|
732
|
-
...(fullOpts.global || {}),
|
|
733
|
-
},
|
|
734
|
-
};
|
|
425
|
+
return this.utilities.extractMiddlewareOptions(options);
|
|
735
426
|
}
|
|
736
427
|
// ===================
|
|
737
428
|
// TOOL MANAGEMENT
|
|
738
429
|
// ===================
|
|
739
430
|
/**
|
|
740
|
-
* Check if a schema is a Zod schema
|
|
431
|
+
* Check if a schema is a Zod schema - delegated to Utilities
|
|
741
432
|
*/
|
|
742
433
|
isZodSchema(schema) {
|
|
743
|
-
return (
|
|
744
|
-
schema !== null &&
|
|
745
|
-
// Most Zod schemas have an internal _def and a parse method
|
|
746
|
-
typeof schema.parse === "function");
|
|
434
|
+
return this.utilities.isZodSchema(schema);
|
|
747
435
|
}
|
|
748
436
|
/**
|
|
749
|
-
* Convert tool execution result
|
|
750
|
-
* Handles tool failures gracefully to prevent stream termination
|
|
437
|
+
* Convert tool execution result - delegated to Utilities
|
|
751
438
|
*/
|
|
752
439
|
async convertToolResult(result) {
|
|
753
|
-
|
|
754
|
-
if (result && typeof result === "object" && "success" in result) {
|
|
755
|
-
const mcpResult = result;
|
|
756
|
-
if (mcpResult.success) {
|
|
757
|
-
return mcpResult.data;
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
// Instead of throwing, return a structured error result
|
|
761
|
-
// This prevents tool failures from terminating streams
|
|
762
|
-
const errorMsg = typeof mcpResult.error === "string"
|
|
763
|
-
? mcpResult.error
|
|
764
|
-
: "Tool execution failed";
|
|
765
|
-
// Log the error for debugging but don't throw
|
|
766
|
-
logger.warn(`Tool execution failed: ${errorMsg}`);
|
|
767
|
-
// Return error as structured data that can be processed by the AI
|
|
768
|
-
return {
|
|
769
|
-
isError: true,
|
|
770
|
-
error: errorMsg,
|
|
771
|
-
content: [
|
|
772
|
-
{
|
|
773
|
-
type: "text",
|
|
774
|
-
text: `Tool execution failed: ${errorMsg}`,
|
|
775
|
-
},
|
|
776
|
-
],
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return result;
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Create a custom tool from tool definition
|
|
784
|
-
*/
|
|
785
|
-
async createCustomToolFromDefinition(toolName, toolInfo) {
|
|
786
|
-
try {
|
|
787
|
-
logger.debug(`[BaseProvider] Converting custom tool: ${toolName}`);
|
|
788
|
-
let finalSchema;
|
|
789
|
-
let originalInputSchema;
|
|
790
|
-
// Prioritize parameters (Zod), then inputSchema (Zod or JSON Schema)
|
|
791
|
-
if (toolInfo.parameters && this.isZodSchema(toolInfo.parameters)) {
|
|
792
|
-
finalSchema = toolInfo.parameters;
|
|
793
|
-
}
|
|
794
|
-
else if (toolInfo.inputSchema &&
|
|
795
|
-
this.isZodSchema(toolInfo.inputSchema)) {
|
|
796
|
-
finalSchema = toolInfo.inputSchema;
|
|
797
|
-
}
|
|
798
|
-
else if (toolInfo.inputSchema &&
|
|
799
|
-
typeof toolInfo.inputSchema === "object") {
|
|
800
|
-
// Use original JSON Schema with jsonSchema() wrapper - NO CONVERSION!
|
|
801
|
-
originalInputSchema = toolInfo.inputSchema;
|
|
802
|
-
finalSchema = jsonSchema(originalInputSchema);
|
|
803
|
-
}
|
|
804
|
-
else if (toolInfo.parameters &&
|
|
805
|
-
typeof toolInfo.parameters === "object") {
|
|
806
|
-
finalSchema = convertJsonSchemaToZod(toolInfo.parameters);
|
|
807
|
-
}
|
|
808
|
-
else {
|
|
809
|
-
finalSchema = z.object({});
|
|
810
|
-
}
|
|
811
|
-
return createAISDKTool({
|
|
812
|
-
description: toolInfo.description || `Tool ${toolName}`,
|
|
813
|
-
parameters: finalSchema,
|
|
814
|
-
execute: async (params) => {
|
|
815
|
-
const startTime = Date.now();
|
|
816
|
-
let executionId;
|
|
817
|
-
if (this.neurolink?.emitToolStart) {
|
|
818
|
-
executionId = this.neurolink.emitToolStart(toolName, params, startTime);
|
|
819
|
-
logger.debug(`Custom tool:start emitted via NeuroLink for ${toolName}`, {
|
|
820
|
-
toolName,
|
|
821
|
-
executionId,
|
|
822
|
-
input: params,
|
|
823
|
-
hasNativeEmission: true,
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
try {
|
|
827
|
-
// 🔧 PARAMETER FLOW TRACING - Before NeuroLink executeTool call
|
|
828
|
-
logger.debug(`About to call NeuroLink executeTool for ${toolName}`, {
|
|
829
|
-
toolName,
|
|
830
|
-
paramsBeforeExecution: {
|
|
831
|
-
type: typeof params,
|
|
832
|
-
isNull: params === null,
|
|
833
|
-
isUndefined: params === undefined,
|
|
834
|
-
isEmpty: params &&
|
|
835
|
-
typeof params === "object" &&
|
|
836
|
-
Object.keys(params).length === 0,
|
|
837
|
-
keys: params && typeof params === "object"
|
|
838
|
-
? Object.keys(params)
|
|
839
|
-
: "NOT_OBJECT",
|
|
840
|
-
keysLength: params && typeof params === "object"
|
|
841
|
-
? Object.keys(params).length
|
|
842
|
-
: 0,
|
|
843
|
-
},
|
|
844
|
-
executorInfo: {
|
|
845
|
-
hasExecutor: typeof toolInfo.execute === "function",
|
|
846
|
-
executorType: typeof toolInfo.execute,
|
|
847
|
-
},
|
|
848
|
-
timestamp: Date.now(),
|
|
849
|
-
phase: "BEFORE_NEUROLINK_EXECUTE",
|
|
850
|
-
});
|
|
851
|
-
const result = await toolInfo.execute(params);
|
|
852
|
-
// 🔧 PARAMETER FLOW TRACING - After NeuroLink executeTool call
|
|
853
|
-
logger.debug(`NeuroLink executeTool completed for ${toolName}`, {
|
|
854
|
-
toolName,
|
|
855
|
-
resultInfo: {
|
|
856
|
-
type: typeof result,
|
|
857
|
-
isNull: result === null,
|
|
858
|
-
isUndefined: result === undefined,
|
|
859
|
-
hasError: result && typeof result === "object" && "error" in result,
|
|
860
|
-
},
|
|
861
|
-
timestamp: Date.now(),
|
|
862
|
-
phase: "AFTER_NEUROLINK_EXECUTE",
|
|
863
|
-
});
|
|
864
|
-
const convertedResult = await this.convertToolResult(result);
|
|
865
|
-
const endTime = Date.now();
|
|
866
|
-
// 🔧 NATIVE NEUROLINK EVENT EMISSION - Tool End (Success)
|
|
867
|
-
if (this.neurolink?.emitToolEnd) {
|
|
868
|
-
this.neurolink.emitToolEnd(toolName, convertedResult, undefined, // no error
|
|
869
|
-
startTime, endTime, executionId);
|
|
870
|
-
logger.debug(`Custom tool:end emitted via NeuroLink for ${toolName}`, {
|
|
871
|
-
toolName,
|
|
872
|
-
executionId,
|
|
873
|
-
duration: endTime - startTime,
|
|
874
|
-
hasResult: convertedResult !== undefined,
|
|
875
|
-
hasNativeEmission: true,
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
return convertedResult;
|
|
879
|
-
}
|
|
880
|
-
catch (error) {
|
|
881
|
-
const endTime = Date.now();
|
|
882
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
883
|
-
// 🔧 NATIVE NEUROLINK EVENT EMISSION - Tool End (Error)
|
|
884
|
-
if (this.neurolink?.emitToolEnd) {
|
|
885
|
-
this.neurolink.emitToolEnd(toolName, undefined, // no result
|
|
886
|
-
errorMsg, startTime, endTime, executionId);
|
|
887
|
-
logger.info(`Custom tool:end error emitted via NeuroLink for ${toolName}`, {
|
|
888
|
-
toolName,
|
|
889
|
-
executionId,
|
|
890
|
-
duration: endTime - startTime,
|
|
891
|
-
error: errorMsg,
|
|
892
|
-
hasNativeEmission: true,
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
throw error;
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
catch (toolCreationError) {
|
|
901
|
-
logger.error(`Failed to create tool: ${toolName}`, toolCreationError);
|
|
902
|
-
return null;
|
|
903
|
-
}
|
|
440
|
+
return this.utilities.convertToolResult(result);
|
|
904
441
|
}
|
|
905
442
|
/**
|
|
906
|
-
*
|
|
907
|
-
*/
|
|
908
|
-
async processDirectTools(tools) {
|
|
909
|
-
if (!this.directTools || Object.keys(this.directTools).length === 0) {
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
logger.debug(`Loading ${Object.keys(this.directTools).length} direct tools with event emission`);
|
|
913
|
-
for (const [toolName, directTool] of Object.entries(this.directTools)) {
|
|
914
|
-
logger.debug(`Processing direct tool: ${toolName}`, {
|
|
915
|
-
toolName,
|
|
916
|
-
hasExecute: directTool &&
|
|
917
|
-
typeof directTool === "object" &&
|
|
918
|
-
"execute" in directTool,
|
|
919
|
-
hasDescription: directTool &&
|
|
920
|
-
typeof directTool === "object" &&
|
|
921
|
-
"description" in directTool,
|
|
922
|
-
});
|
|
923
|
-
// Wrap the direct tool's execute function with event emission
|
|
924
|
-
if (directTool &&
|
|
925
|
-
typeof directTool === "object" &&
|
|
926
|
-
"execute" in directTool) {
|
|
927
|
-
const originalExecute = directTool.execute;
|
|
928
|
-
// Create a new tool with wrapped execute function
|
|
929
|
-
tools[toolName] = {
|
|
930
|
-
...directTool,
|
|
931
|
-
execute: async (params) => {
|
|
932
|
-
// 🔧 EMIT TOOL START EVENT - Bedrock-compatible format
|
|
933
|
-
if (this.neurolink?.getEventEmitter) {
|
|
934
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
935
|
-
emitter.emit("tool:start", { tool: toolName, input: params });
|
|
936
|
-
logger.debug(`Direct tool:start event emitted for ${toolName}`, {
|
|
937
|
-
toolName,
|
|
938
|
-
input: params,
|
|
939
|
-
hasEmitter: !!emitter,
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
try {
|
|
943
|
-
const result = await originalExecute(params);
|
|
944
|
-
// 🔧 EMIT TOOL END EVENT - Bedrock-compatible format
|
|
945
|
-
if (this.neurolink?.getEventEmitter) {
|
|
946
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
947
|
-
emitter.emit("tool:end", { tool: toolName, result });
|
|
948
|
-
logger.debug(`Direct tool:end event emitted for ${toolName}`, {
|
|
949
|
-
toolName,
|
|
950
|
-
result: typeof result === "string"
|
|
951
|
-
? result.substring(0, 100)
|
|
952
|
-
: JSON.stringify(result).substring(0, 100),
|
|
953
|
-
hasEmitter: !!emitter,
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
return result;
|
|
957
|
-
}
|
|
958
|
-
catch (error) {
|
|
959
|
-
// 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
|
|
960
|
-
if (this.neurolink?.getEventEmitter) {
|
|
961
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
962
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
963
|
-
emitter.emit("tool:end", { tool: toolName, error: errorMsg });
|
|
964
|
-
logger.debug(`Direct tool:end error event emitted for ${toolName}`, {
|
|
965
|
-
toolName,
|
|
966
|
-
error: errorMsg,
|
|
967
|
-
hasEmitter: !!emitter,
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
throw error;
|
|
971
|
-
}
|
|
972
|
-
},
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
976
|
-
// Fallback: include tool as-is if it doesn't have execute function
|
|
977
|
-
tools[toolName] = directTool;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
logger.debug(`Direct tools processing complete`, {
|
|
981
|
-
directToolsProcessed: Object.keys(this.directTools).length,
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Process custom tools from setupToolExecutor
|
|
986
|
-
*/
|
|
987
|
-
async processCustomTools(tools) {
|
|
988
|
-
if (!this.customTools || this.customTools.size === 0) {
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
logger.debug(`[BaseProvider] Loading ${this.customTools.size} custom tools from setupToolExecutor`);
|
|
992
|
-
for (const [toolName, toolDef] of this.customTools.entries()) {
|
|
993
|
-
logger.debug(`Processing custom tool: ${toolName}`, {
|
|
994
|
-
toolDef: typeof toolDef,
|
|
995
|
-
hasExecute: toolDef && typeof toolDef === "object" && "execute" in toolDef,
|
|
996
|
-
hasName: toolDef && typeof toolDef === "object" && "name" in toolDef,
|
|
997
|
-
});
|
|
998
|
-
// Validate tool definition has required execute function
|
|
999
|
-
const toolInfo = toolDef ||
|
|
1000
|
-
{};
|
|
1001
|
-
if (toolInfo && typeof toolInfo.execute === "function") {
|
|
1002
|
-
const tool = await this.createCustomToolFromDefinition(toolName, toolInfo);
|
|
1003
|
-
if (tool && !tools[toolName]) {
|
|
1004
|
-
tools[toolName] = tool;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
logger.debug(`[BaseProvider] Custom tools processing complete`, {
|
|
1009
|
-
customToolsProcessed: this.customTools.size,
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Recursively fix JSON Schema for OpenAI strict mode compatibility
|
|
1014
|
-
* OpenAI requires additionalProperties: false at ALL levels and preserves required array
|
|
443
|
+
* Fix JSON Schema for OpenAI strict mode - delegated to Utilities
|
|
1015
444
|
*/
|
|
1016
445
|
fixSchemaForOpenAIStrictMode(schema) {
|
|
1017
|
-
|
|
1018
|
-
if (fixedSchema.type === "object" &&
|
|
1019
|
-
fixedSchema.properties &&
|
|
1020
|
-
typeof fixedSchema.properties === "object") {
|
|
1021
|
-
const allPropertyNames = Object.keys(fixedSchema.properties);
|
|
1022
|
-
if (!fixedSchema.required || !Array.isArray(fixedSchema.required)) {
|
|
1023
|
-
fixedSchema.required = [];
|
|
1024
|
-
}
|
|
1025
|
-
fixedSchema.additionalProperties = false;
|
|
1026
|
-
for (const propName of allPropertyNames) {
|
|
1027
|
-
const propValue = fixedSchema.properties[propName];
|
|
1028
|
-
if (propValue && typeof propValue === "object") {
|
|
1029
|
-
if (propValue.type === "object") {
|
|
1030
|
-
fixedSchema.properties[propName] =
|
|
1031
|
-
this.fixSchemaForOpenAIStrictMode(propValue);
|
|
1032
|
-
}
|
|
1033
|
-
else if (propValue.type === "array" &&
|
|
1034
|
-
propValue.items &&
|
|
1035
|
-
typeof propValue.items === "object") {
|
|
1036
|
-
fixedSchema.properties[propName].items =
|
|
1037
|
-
this.fixSchemaForOpenAIStrictMode(propValue.items);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
return fixedSchema;
|
|
1043
|
-
}
|
|
1044
|
-
/**
|
|
1045
|
-
* Create an external MCP tool
|
|
1046
|
-
*/
|
|
1047
|
-
async createExternalMCPTool(tool) {
|
|
1048
|
-
try {
|
|
1049
|
-
logger.debug(`[BaseProvider] Converting external MCP tool: ${tool.name}`);
|
|
1050
|
-
// Use original JSON Schema from MCP tool if available, otherwise use permissive schema
|
|
1051
|
-
let finalSchema;
|
|
1052
|
-
if (tool.inputSchema && typeof tool.inputSchema === "object") {
|
|
1053
|
-
// Clone and fix the schema for OpenAI strict mode compatibility
|
|
1054
|
-
const originalSchema = tool.inputSchema;
|
|
1055
|
-
const fixedSchema = this.fixSchemaForOpenAIStrictMode(originalSchema);
|
|
1056
|
-
finalSchema = jsonSchema(fixedSchema);
|
|
1057
|
-
}
|
|
1058
|
-
else {
|
|
1059
|
-
finalSchema = this.createPermissiveZodSchema();
|
|
1060
|
-
}
|
|
1061
|
-
return createAISDKTool({
|
|
1062
|
-
description: tool.description || `External MCP tool ${tool.name}`,
|
|
1063
|
-
parameters: finalSchema,
|
|
1064
|
-
execute: async (params) => {
|
|
1065
|
-
logger.debug(`Executing external MCP tool: ${tool.name}`, {
|
|
1066
|
-
toolName: tool.name,
|
|
1067
|
-
serverId: tool.serverId,
|
|
1068
|
-
params: JSON.stringify(params),
|
|
1069
|
-
paramsType: typeof params,
|
|
1070
|
-
hasNeurolink: !!this.neurolink,
|
|
1071
|
-
hasExecuteFunction: this.neurolink &&
|
|
1072
|
-
typeof this.neurolink.executeExternalMCPTool === "function",
|
|
1073
|
-
timestamp: Date.now(),
|
|
1074
|
-
});
|
|
1075
|
-
// 🔧 EMIT TOOL START EVENT - Bedrock-compatible format
|
|
1076
|
-
if (this.neurolink?.getEventEmitter) {
|
|
1077
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
1078
|
-
emitter.emit("tool:start", { tool: tool.name, input: params });
|
|
1079
|
-
logger.debug(`tool:start event emitted for ${tool.name}`, {
|
|
1080
|
-
toolName: tool.name,
|
|
1081
|
-
input: params,
|
|
1082
|
-
hasEmitter: !!emitter,
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
// Execute via NeuroLink's direct tool execution
|
|
1086
|
-
if (this.neurolink &&
|
|
1087
|
-
typeof this.neurolink.executeExternalMCPTool === "function") {
|
|
1088
|
-
try {
|
|
1089
|
-
const result = await this.neurolink.executeExternalMCPTool(tool.serverId || "unknown", tool.name, params);
|
|
1090
|
-
// 🔧 EMIT TOOL END EVENT - Bedrock-compatible format
|
|
1091
|
-
if (this.neurolink?.getEventEmitter) {
|
|
1092
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
1093
|
-
emitter.emit("tool:end", { tool: tool.name, result });
|
|
1094
|
-
logger.debug(`tool:end event emitted for ${tool.name}`, {
|
|
1095
|
-
toolName: tool.name,
|
|
1096
|
-
result: typeof result === "string"
|
|
1097
|
-
? result.substring(0, 100)
|
|
1098
|
-
: JSON.stringify(result).substring(0, 100),
|
|
1099
|
-
hasEmitter: !!emitter,
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
logger.debug(`External MCP tool executed: ${tool.name}`, {
|
|
1103
|
-
toolName: tool.name,
|
|
1104
|
-
result: typeof result === "string"
|
|
1105
|
-
? result.substring(0, 200)
|
|
1106
|
-
: JSON.stringify(result).substring(0, 200),
|
|
1107
|
-
resultType: typeof result,
|
|
1108
|
-
timestamp: Date.now(),
|
|
1109
|
-
});
|
|
1110
|
-
return result;
|
|
1111
|
-
}
|
|
1112
|
-
catch (mcpError) {
|
|
1113
|
-
// 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
|
|
1114
|
-
if (this.neurolink?.getEventEmitter) {
|
|
1115
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
1116
|
-
const errorMsg = mcpError instanceof Error
|
|
1117
|
-
? mcpError.message
|
|
1118
|
-
: String(mcpError);
|
|
1119
|
-
emitter.emit("tool:end", { tool: tool.name, error: errorMsg });
|
|
1120
|
-
logger.debug(`tool:end error event emitted for ${tool.name}`, {
|
|
1121
|
-
toolName: tool.name,
|
|
1122
|
-
error: errorMsg,
|
|
1123
|
-
hasEmitter: !!emitter,
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
logger.error(`External MCP tool failed: ${tool.name}`, {
|
|
1127
|
-
toolName: tool.name,
|
|
1128
|
-
serverId: tool.serverId,
|
|
1129
|
-
error: mcpError instanceof Error
|
|
1130
|
-
? mcpError.message
|
|
1131
|
-
: String(mcpError),
|
|
1132
|
-
errorStack: mcpError instanceof Error ? mcpError.stack : undefined,
|
|
1133
|
-
params: JSON.stringify(params),
|
|
1134
|
-
timestamp: Date.now(),
|
|
1135
|
-
});
|
|
1136
|
-
throw mcpError;
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
else {
|
|
1140
|
-
const error = `Cannot execute external MCP tool: NeuroLink executeExternalMCPTool not available`;
|
|
1141
|
-
// 🔧 EMIT TOOL END EVENT FOR ERROR - Bedrock-compatible format
|
|
1142
|
-
if (this.neurolink?.getEventEmitter) {
|
|
1143
|
-
const emitter = this.neurolink.getEventEmitter();
|
|
1144
|
-
emitter.emit("tool:end", { tool: tool.name, error });
|
|
1145
|
-
logger.debug(`tool:end error event emitted for ${tool.name}`, {
|
|
1146
|
-
toolName: tool.name,
|
|
1147
|
-
error,
|
|
1148
|
-
hasEmitter: !!emitter,
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
logger.error(`${error}`, {
|
|
1152
|
-
toolName: tool.name,
|
|
1153
|
-
hasNeurolink: !!this.neurolink,
|
|
1154
|
-
neurolinkType: typeof this.neurolink,
|
|
1155
|
-
timestamp: Date.now(),
|
|
1156
|
-
});
|
|
1157
|
-
throw new Error(error);
|
|
1158
|
-
}
|
|
1159
|
-
},
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
catch (toolCreationError) {
|
|
1163
|
-
logger.error(`Failed to create external MCP tool: ${tool.name}`, toolCreationError);
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Process external MCP tools
|
|
1169
|
-
*/
|
|
1170
|
-
async processExternalMCPTools(tools) {
|
|
1171
|
-
if (!this.neurolink ||
|
|
1172
|
-
typeof this.neurolink.getExternalMCPTools !== "function") {
|
|
1173
|
-
logger.debug(`[BaseProvider] No external MCP tool interface available`, {
|
|
1174
|
-
hasNeuroLink: !!this.neurolink,
|
|
1175
|
-
hasGetExternalMCPTools: this.neurolink &&
|
|
1176
|
-
typeof this.neurolink.getExternalMCPTools === "function",
|
|
1177
|
-
});
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
try {
|
|
1181
|
-
logger.debug(`[BaseProvider] Loading external MCP tools for ${this.providerName}`);
|
|
1182
|
-
const externalTools = await this.neurolink.getExternalMCPTools();
|
|
1183
|
-
logger.debug(`[BaseProvider] Found ${externalTools.length} external MCP tools`);
|
|
1184
|
-
for (const tool of externalTools) {
|
|
1185
|
-
const mcpTool = await this.createExternalMCPTool(tool);
|
|
1186
|
-
if (mcpTool && !tools[tool.name]) {
|
|
1187
|
-
tools[tool.name] = mcpTool;
|
|
1188
|
-
logger.debug(`[BaseProvider] Successfully added external MCP tool: ${tool.name}`);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
logger.debug(`[BaseProvider] External MCP tools loading complete`, {
|
|
1192
|
-
totalToolsAdded: externalTools.length,
|
|
1193
|
-
});
|
|
1194
|
-
}
|
|
1195
|
-
catch (error) {
|
|
1196
|
-
logger.error(`[BaseProvider] Failed to load external MCP tools for ${this.providerName}:`, error);
|
|
1197
|
-
// Not an error - external tools are optional
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Process MCP tools integration
|
|
1202
|
-
*/
|
|
1203
|
-
async processMCPTools(tools) {
|
|
1204
|
-
// MCP tools loading simplified - removed functionCalling dependency
|
|
1205
|
-
if (!this.mcpTools) {
|
|
1206
|
-
// Set empty tools object - MCP tools are handled at a higher level
|
|
1207
|
-
this.mcpTools = {};
|
|
1208
|
-
}
|
|
1209
|
-
// Add MCP tools if available, but don't overwrite existing direct tools
|
|
1210
|
-
// Direct tools (Zod-based) take precedence over MCP tools (JSON Schema)
|
|
1211
|
-
if (this.mcpTools) {
|
|
1212
|
-
for (const [name, tool] of Object.entries(this.mcpTools)) {
|
|
1213
|
-
if (!tools[name]) {
|
|
1214
|
-
tools[name] = tool;
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
446
|
+
return this.utilities.fixSchemaForOpenAIStrictMode(schema);
|
|
1218
447
|
}
|
|
1219
448
|
/**
|
|
1220
|
-
* Get all available tools -
|
|
1221
|
-
* MCP tools are added when available (without blocking)
|
|
449
|
+
* Get all available tools - delegated to ToolsManager
|
|
1222
450
|
*/
|
|
1223
451
|
async getAllTools() {
|
|
1224
|
-
|
|
1225
|
-
const tools = {};
|
|
1226
|
-
// Wrap direct tools with event emission
|
|
1227
|
-
await this.processDirectTools(tools);
|
|
1228
|
-
logger.debug(`[BaseProvider] getAllTools called for ${this.providerName}`, {
|
|
1229
|
-
neurolinkAvailable: !!this.neurolink,
|
|
1230
|
-
neurolinkType: typeof this.neurolink,
|
|
1231
|
-
directToolsCount: getKeyCount(this.directTools),
|
|
1232
|
-
});
|
|
1233
|
-
logger.debug(`[BaseProvider] Direct tools: ${getKeysAsString(this.directTools)}`);
|
|
1234
|
-
// Process all tool types using dedicated helper methods
|
|
1235
|
-
await this.processCustomTools(tools);
|
|
1236
|
-
await this.processExternalMCPTools(tools);
|
|
1237
|
-
await this.processMCPTools(tools);
|
|
1238
|
-
logger.debug(`[BaseProvider] getAllTools returning tools: ${getKeysAsString(tools)}`);
|
|
1239
|
-
return tools;
|
|
452
|
+
return this.toolsManager.getAllTools();
|
|
1240
453
|
}
|
|
1241
454
|
/**
|
|
1242
|
-
* Calculate actual cost
|
|
455
|
+
* Calculate actual cost - delegated to TelemetryHandler
|
|
1243
456
|
*/
|
|
1244
457
|
async calculateActualCost(usage) {
|
|
1245
|
-
|
|
1246
|
-
const costInfo = modelConfig.getCostInfo(this.providerName, this.modelName);
|
|
1247
|
-
if (!costInfo) {
|
|
1248
|
-
return 0; // No cost info available
|
|
1249
|
-
}
|
|
1250
|
-
const promptTokens = usage?.promptTokens || 0;
|
|
1251
|
-
const completionTokens = usage?.completionTokens || 0;
|
|
1252
|
-
// Calculate cost per 1K tokens
|
|
1253
|
-
const inputCost = (promptTokens / 1000) * costInfo.input;
|
|
1254
|
-
const outputCost = (completionTokens / 1000) * costInfo.output;
|
|
1255
|
-
return inputCost + outputCost;
|
|
1256
|
-
}
|
|
1257
|
-
catch (error) {
|
|
1258
|
-
logger.debug(`Cost calculation failed for ${this.providerName}:`, error);
|
|
1259
|
-
return 0; // Fallback to 0 on any error
|
|
1260
|
-
}
|
|
458
|
+
return this.telemetryHandler.calculateActualCost(usage);
|
|
1261
459
|
}
|
|
1262
460
|
/**
|
|
1263
|
-
* Create a permissive Zod schema
|
|
461
|
+
* Create a permissive Zod schema - delegated to Utilities
|
|
1264
462
|
*/
|
|
1265
463
|
createPermissiveZodSchema() {
|
|
1266
|
-
|
|
1267
|
-
// This allows all parameters to pass through without validation issues
|
|
1268
|
-
return z.record(z.unknown()).transform((data) => {
|
|
1269
|
-
// Return the data as-is to preserve all parameter information
|
|
1270
|
-
return data;
|
|
1271
|
-
});
|
|
464
|
+
return this.utilities.createPermissiveZodSchema();
|
|
1272
465
|
}
|
|
1273
466
|
/**
|
|
1274
|
-
* Set session context for MCP tools
|
|
467
|
+
* Set session context for MCP tools - delegated to ToolsManager
|
|
1275
468
|
*/
|
|
1276
469
|
setSessionContext(sessionId, userId) {
|
|
1277
470
|
this.sessionId = sessionId;
|
|
1278
471
|
this.userId = userId;
|
|
472
|
+
this.toolsManager.setSessionContext(sessionId, userId);
|
|
1279
473
|
}
|
|
1280
474
|
// ===================
|
|
1281
475
|
// CONSOLIDATED PROVIDER METHODS - MOVED FROM INDIVIDUAL PROVIDERS
|
|
@@ -1308,165 +502,59 @@ export class BaseProvider {
|
|
|
1308
502
|
}
|
|
1309
503
|
}
|
|
1310
504
|
/**
|
|
1311
|
-
* Validate stream options -
|
|
505
|
+
* Validate stream options - delegated to StreamHandler
|
|
1312
506
|
*/
|
|
1313
507
|
validateStreamOptions(options) {
|
|
1314
|
-
|
|
1315
|
-
if (!validation.isValid) {
|
|
1316
|
-
const summary = createValidationSummary(validation);
|
|
1317
|
-
throw new ValidationError(`Stream options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
|
|
1318
|
-
}
|
|
1319
|
-
// Log warnings if any
|
|
1320
|
-
if (validation.warnings.length > 0) {
|
|
1321
|
-
logger.warn("Stream options validation warnings:", validation.warnings);
|
|
1322
|
-
}
|
|
1323
|
-
// Additional BaseProvider-specific validation
|
|
1324
|
-
if (options.maxSteps !== undefined) {
|
|
1325
|
-
if (options.maxSteps < STEP_LIMITS.min ||
|
|
1326
|
-
options.maxSteps > STEP_LIMITS.max) {
|
|
1327
|
-
throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
|
|
1328
|
-
`Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
|
|
1329
|
-
]);
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
508
|
+
this.streamHandler.validateStreamOptions(options);
|
|
1332
509
|
}
|
|
1333
510
|
/**
|
|
1334
|
-
* Create text stream transformation -
|
|
511
|
+
* Create text stream transformation - delegated to StreamHandler
|
|
1335
512
|
*/
|
|
1336
513
|
createTextStream(result) {
|
|
1337
|
-
return (
|
|
1338
|
-
for await (const chunk of result.textStream) {
|
|
1339
|
-
yield { content: chunk };
|
|
1340
|
-
}
|
|
1341
|
-
})();
|
|
514
|
+
return this.streamHandler.createTextStream(result);
|
|
1342
515
|
}
|
|
1343
516
|
/**
|
|
1344
|
-
* Create standardized stream result -
|
|
517
|
+
* Create standardized stream result - delegated to StreamHandler
|
|
1345
518
|
*/
|
|
1346
519
|
createStreamResult(stream, additionalProps = {}) {
|
|
1347
|
-
return
|
|
1348
|
-
stream,
|
|
1349
|
-
provider: this.providerName,
|
|
1350
|
-
model: this.modelName,
|
|
1351
|
-
...additionalProps,
|
|
1352
|
-
};
|
|
520
|
+
return this.streamHandler.createStreamResult(stream, additionalProps);
|
|
1353
521
|
}
|
|
1354
522
|
/**
|
|
1355
|
-
* Create stream analytics -
|
|
523
|
+
* Create stream analytics - delegated to StreamHandler
|
|
1356
524
|
*/
|
|
1357
525
|
async createStreamAnalytics(result, startTime, options) {
|
|
1358
|
-
|
|
1359
|
-
const analytics = createAnalytics(this.providerName, this.modelName, result, Date.now() - startTime, {
|
|
1360
|
-
requestId: `${this.providerName}-stream-${nanoid()}`,
|
|
1361
|
-
streamingMode: true,
|
|
1362
|
-
...options.context,
|
|
1363
|
-
});
|
|
1364
|
-
return analytics;
|
|
1365
|
-
}
|
|
1366
|
-
catch (error) {
|
|
1367
|
-
logger.warn(`Analytics creation failed for ${this.providerName}:`, error);
|
|
1368
|
-
return undefined;
|
|
1369
|
-
}
|
|
526
|
+
return this.streamHandler.createStreamAnalytics(result, startTime, options);
|
|
1370
527
|
}
|
|
1371
528
|
/**
|
|
1372
|
-
* Handle common error patterns -
|
|
529
|
+
* Handle common error patterns - delegated to Utilities
|
|
1373
530
|
*/
|
|
1374
531
|
handleCommonErrors(error) {
|
|
1375
|
-
|
|
1376
|
-
return new Error(`${this.providerName} request timed out after ${error.timeout}ms. Consider increasing timeout or using a lighter model.`);
|
|
1377
|
-
}
|
|
1378
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1379
|
-
// Common API key errors
|
|
1380
|
-
if (message.includes("API_KEY_INVALID") ||
|
|
1381
|
-
message.includes("Invalid API key") ||
|
|
1382
|
-
message.includes("authentication") ||
|
|
1383
|
-
message.includes("unauthorized")) {
|
|
1384
|
-
return new Error(`Invalid API key for ${this.providerName}. Please check your API key environment variable.`);
|
|
1385
|
-
}
|
|
1386
|
-
// Common rate limit errors
|
|
1387
|
-
if (message.includes("rate limit") ||
|
|
1388
|
-
message.includes("quota") ||
|
|
1389
|
-
message.includes("429")) {
|
|
1390
|
-
return new Error(`Rate limit exceeded for ${this.providerName}. Please wait before making more requests.`);
|
|
1391
|
-
}
|
|
1392
|
-
return null; // Not a common error, let provider handle it
|
|
532
|
+
return this.utilities.handleCommonErrors(error);
|
|
1393
533
|
}
|
|
1394
534
|
/**
|
|
1395
|
-
* Set up tool executor
|
|
1396
|
-
* Consolidates identical setupToolExecutor logic from neurolink.ts (used in 4 places)
|
|
535
|
+
* Set up tool executor - delegated to ToolsManager
|
|
1397
536
|
* @param sdk - The NeuroLinkSDK instance for tool execution
|
|
1398
537
|
* @param functionTag - Function name for logging
|
|
1399
538
|
*/
|
|
1400
539
|
setupToolExecutor(sdk, functionTag) {
|
|
1401
|
-
// Store custom tools for use in getAllTools()
|
|
1402
540
|
this.customTools = sdk.customTools;
|
|
1403
541
|
this.toolExecutor = sdk.executeTool;
|
|
1404
|
-
|
|
1405
|
-
providerType: this.constructor.name,
|
|
1406
|
-
availableCustomTools: sdk.customTools.size,
|
|
1407
|
-
customToolsStored: !!this.customTools,
|
|
1408
|
-
toolExecutorStored: !!this.toolExecutor,
|
|
1409
|
-
});
|
|
1410
|
-
// Note: Tool execution will be handled through getAllTools() -> AI SDK tools
|
|
1411
|
-
// The custom tools are converted to AI SDK format in getAllTools() method
|
|
542
|
+
this.toolsManager.setupToolExecutor(sdk, functionTag);
|
|
1412
543
|
}
|
|
1413
544
|
// ===================
|
|
1414
545
|
// TEMPLATE METHODS - COMMON FUNCTIONALITY
|
|
1415
546
|
// ===================
|
|
547
|
+
/**
|
|
548
|
+
* Normalize text generation options - delegated to Utilities
|
|
549
|
+
*/
|
|
1416
550
|
normalizeTextOptions(optionsOrPrompt) {
|
|
1417
|
-
|
|
1418
|
-
const safeMaxTokens = getSafeMaxTokens(this.providerName, this.modelName);
|
|
1419
|
-
return {
|
|
1420
|
-
prompt: optionsOrPrompt,
|
|
1421
|
-
provider: this.providerName,
|
|
1422
|
-
model: this.modelName,
|
|
1423
|
-
maxTokens: safeMaxTokens,
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
// Handle both prompt and input.text formats
|
|
1427
|
-
const prompt = optionsOrPrompt.prompt || optionsOrPrompt.input?.text || "";
|
|
1428
|
-
const modelName = optionsOrPrompt.model || this.modelName;
|
|
1429
|
-
const providerName = optionsOrPrompt.provider || this.providerName;
|
|
1430
|
-
// Apply safe maxTokens based on provider and model
|
|
1431
|
-
const safeMaxTokens = getSafeMaxTokens(providerName, modelName, optionsOrPrompt.maxTokens);
|
|
1432
|
-
// CRITICAL FIX: Preserve the entire input object for multimodal support
|
|
1433
|
-
// This ensures images and content arrays are not lost during normalization
|
|
1434
|
-
const normalizedOptions = {
|
|
1435
|
-
...optionsOrPrompt,
|
|
1436
|
-
prompt,
|
|
1437
|
-
provider: providerName,
|
|
1438
|
-
model: modelName,
|
|
1439
|
-
maxTokens: safeMaxTokens,
|
|
1440
|
-
};
|
|
1441
|
-
// Ensure input object is preserved if it exists (for multimodal support)
|
|
1442
|
-
if (optionsOrPrompt.input) {
|
|
1443
|
-
normalizedOptions.input = {
|
|
1444
|
-
...optionsOrPrompt.input,
|
|
1445
|
-
text: prompt, // Ensure text is consistent
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
return normalizedOptions;
|
|
551
|
+
return this.utilities.normalizeTextOptions(optionsOrPrompt);
|
|
1449
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Normalize stream options - delegated to Utilities
|
|
555
|
+
*/
|
|
1450
556
|
normalizeStreamOptions(optionsOrPrompt) {
|
|
1451
|
-
|
|
1452
|
-
const safeMaxTokens = getSafeMaxTokens(this.providerName, this.modelName);
|
|
1453
|
-
return {
|
|
1454
|
-
input: { text: optionsOrPrompt },
|
|
1455
|
-
provider: this.providerName,
|
|
1456
|
-
model: this.modelName,
|
|
1457
|
-
maxTokens: safeMaxTokens,
|
|
1458
|
-
};
|
|
1459
|
-
}
|
|
1460
|
-
const modelName = optionsOrPrompt.model || this.modelName;
|
|
1461
|
-
const providerName = optionsOrPrompt.provider || this.providerName;
|
|
1462
|
-
// Apply safe maxTokens based on provider and model
|
|
1463
|
-
const safeMaxTokens = getSafeMaxTokens(providerName, modelName, optionsOrPrompt.maxTokens);
|
|
1464
|
-
return {
|
|
1465
|
-
...optionsOrPrompt,
|
|
1466
|
-
provider: providerName,
|
|
1467
|
-
model: modelName,
|
|
1468
|
-
maxTokens: safeMaxTokens,
|
|
1469
|
-
};
|
|
557
|
+
return this.utilities.normalizeStreamOptions(optionsOrPrompt);
|
|
1470
558
|
}
|
|
1471
559
|
async enhanceResult(result, options, startTime) {
|
|
1472
560
|
const responseTime = Date.now() - startTime;
|
|
@@ -1493,117 +581,41 @@ export class BaseProvider {
|
|
|
1493
581
|
}
|
|
1494
582
|
return enhancedResult;
|
|
1495
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Create analytics - delegated to TelemetryHandler
|
|
586
|
+
*/
|
|
1496
587
|
async createAnalytics(result, responseTime, options) {
|
|
1497
|
-
|
|
1498
|
-
return createAnalytics(this.providerName, this.modelName, result, responseTime, options.context);
|
|
588
|
+
return this.telemetryHandler.createAnalytics(result, responseTime, options.context);
|
|
1499
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Create evaluation - delegated to TelemetryHandler
|
|
592
|
+
*/
|
|
1500
593
|
async createEvaluation(result, options) {
|
|
1501
|
-
|
|
1502
|
-
const context = {
|
|
1503
|
-
userQuery: options.prompt || options.input?.text || "Generated response",
|
|
1504
|
-
aiResponse: result.content,
|
|
1505
|
-
context: options.context,
|
|
1506
|
-
primaryDomain: options.evaluationDomain,
|
|
1507
|
-
assistantRole: "AI assistant",
|
|
1508
|
-
conversationHistory: options.conversationHistory?.map((msg) => ({
|
|
1509
|
-
role: msg.role,
|
|
1510
|
-
content: msg.content,
|
|
1511
|
-
})),
|
|
1512
|
-
toolUsage: options.toolUsageContext
|
|
1513
|
-
? [
|
|
1514
|
-
{
|
|
1515
|
-
toolName: options.toolUsageContext,
|
|
1516
|
-
input: {},
|
|
1517
|
-
output: {},
|
|
1518
|
-
executionTime: 0,
|
|
1519
|
-
},
|
|
1520
|
-
]
|
|
1521
|
-
: undefined,
|
|
1522
|
-
expectedOutcome: options.expectedOutcome,
|
|
1523
|
-
evaluationCriteria: options.evaluationCriteria,
|
|
1524
|
-
};
|
|
1525
|
-
const evaluation = await evaluateResponse(context);
|
|
1526
|
-
return evaluation;
|
|
594
|
+
return this.telemetryHandler.createEvaluation(result, options);
|
|
1527
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Validate text generation options - delegated to Utilities
|
|
598
|
+
*/
|
|
1528
599
|
validateOptions(options) {
|
|
1529
|
-
|
|
1530
|
-
if (!validation.isValid) {
|
|
1531
|
-
const summary = createValidationSummary(validation);
|
|
1532
|
-
throw new ValidationError(`Text generation options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
|
|
1533
|
-
}
|
|
1534
|
-
// Log warnings if any
|
|
1535
|
-
if (validation.warnings.length > 0) {
|
|
1536
|
-
logger.warn("Text generation options validation warnings:", validation.warnings);
|
|
1537
|
-
}
|
|
1538
|
-
// Additional BaseProvider-specific validation
|
|
1539
|
-
if (options.maxSteps !== undefined) {
|
|
1540
|
-
if (options.maxSteps < STEP_LIMITS.min ||
|
|
1541
|
-
options.maxSteps > STEP_LIMITS.max) {
|
|
1542
|
-
throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
|
|
1543
|
-
`Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
|
|
1544
|
-
]);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
600
|
+
this.utilities.validateOptions(options);
|
|
1547
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* Get provider information - delegated to Utilities
|
|
604
|
+
*/
|
|
1548
605
|
getProviderInfo() {
|
|
1549
|
-
return
|
|
1550
|
-
provider: this.providerName,
|
|
1551
|
-
model: this.modelName,
|
|
1552
|
-
};
|
|
606
|
+
return this.utilities.getProviderInfo();
|
|
1553
607
|
}
|
|
1554
608
|
/**
|
|
1555
|
-
* Get timeout value in milliseconds
|
|
609
|
+
* Get timeout value in milliseconds - delegated to Utilities
|
|
1556
610
|
*/
|
|
1557
611
|
getTimeout(options) {
|
|
1558
|
-
|
|
1559
|
-
return this.defaultTimeout;
|
|
1560
|
-
}
|
|
1561
|
-
if (typeof options.timeout === "number") {
|
|
1562
|
-
return options.timeout;
|
|
1563
|
-
}
|
|
1564
|
-
// Parse string timeout (e.g., '30s', '2m', '1h')
|
|
1565
|
-
const timeoutStr = options.timeout.toLowerCase();
|
|
1566
|
-
const value = parseInt(timeoutStr);
|
|
1567
|
-
if (timeoutStr.includes("h")) {
|
|
1568
|
-
return value * 60 * 60 * 1000;
|
|
1569
|
-
}
|
|
1570
|
-
else if (timeoutStr.includes("m")) {
|
|
1571
|
-
return value * 60 * 1000;
|
|
1572
|
-
}
|
|
1573
|
-
else if (timeoutStr.includes("s")) {
|
|
1574
|
-
return value * 1000;
|
|
1575
|
-
}
|
|
1576
|
-
return this.defaultTimeout;
|
|
612
|
+
return this.utilities.getTimeout(options);
|
|
1577
613
|
}
|
|
1578
614
|
/**
|
|
1579
615
|
* Check if tool executions should be stored and handle storage
|
|
1580
616
|
*/
|
|
1581
617
|
async handleToolExecutionStorage(toolCalls, toolResults, options, currentTime) {
|
|
1582
|
-
|
|
1583
|
-
const hasToolData = (toolCalls && toolCalls.length > 0) ||
|
|
1584
|
-
(toolResults && toolResults.length > 0);
|
|
1585
|
-
// Check if NeuroLink instance is available and has tool execution storage
|
|
1586
|
-
const hasStorageAvailable = this.neurolink?.isToolExecutionStorageAvailable();
|
|
1587
|
-
// Early return if storage is not available or no tool data
|
|
1588
|
-
if (!hasStorageAvailable || !hasToolData || !this.neurolink) {
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1591
|
-
const sessionId = options.context?.sessionId ||
|
|
1592
|
-
options.sessionId ||
|
|
1593
|
-
`session-${nanoid()}`;
|
|
1594
|
-
const userId = options.context?.userId ||
|
|
1595
|
-
options.userId;
|
|
1596
|
-
try {
|
|
1597
|
-
await this.neurolink.storeToolExecutions(sessionId, userId, toolCalls, toolResults, currentTime);
|
|
1598
|
-
}
|
|
1599
|
-
catch (error) {
|
|
1600
|
-
logger.warn("[BaseProvider] Failed to store tool executions", {
|
|
1601
|
-
provider: this.providerName,
|
|
1602
|
-
sessionId,
|
|
1603
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1604
|
-
});
|
|
1605
|
-
// Don't throw - tool storage failures shouldn't break generation
|
|
1606
|
-
}
|
|
618
|
+
return this.telemetryHandler.handleToolExecutionStorage(toolCalls, toolResults, options, currentTime);
|
|
1607
619
|
}
|
|
1608
620
|
/**
|
|
1609
621
|
* Utility method to chunk large prompts into smaller pieces
|