@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.
Files changed (119) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +13 -3
  3. package/dist/adapters/providerImageAdapter.d.ts +1 -1
  4. package/dist/adapters/providerImageAdapter.js +62 -0
  5. package/dist/agent/directTools.d.ts +0 -72
  6. package/dist/agent/directTools.js +3 -74
  7. package/dist/cli/commands/config.d.ts +18 -18
  8. package/dist/cli/factories/commandFactory.js +1 -0
  9. package/dist/cli/loop/conversationSelector.js +4 -0
  10. package/dist/cli/loop/session.js +27 -15
  11. package/dist/constants/enums.d.ts +1 -0
  12. package/dist/constants/enums.js +3 -1
  13. package/dist/constants/tokens.d.ts +3 -0
  14. package/dist/constants/tokens.js +3 -0
  15. package/dist/core/baseProvider.d.ts +56 -53
  16. package/dist/core/baseProvider.js +107 -1095
  17. package/dist/core/constants.d.ts +3 -0
  18. package/dist/core/constants.js +6 -3
  19. package/dist/core/modelConfiguration.js +10 -0
  20. package/dist/core/modules/GenerationHandler.d.ts +63 -0
  21. package/dist/core/modules/GenerationHandler.js +230 -0
  22. package/dist/core/modules/MessageBuilder.d.ts +39 -0
  23. package/dist/core/modules/MessageBuilder.js +179 -0
  24. package/dist/core/modules/StreamHandler.d.ts +52 -0
  25. package/dist/core/modules/StreamHandler.js +103 -0
  26. package/dist/core/modules/TelemetryHandler.d.ts +64 -0
  27. package/dist/core/modules/TelemetryHandler.js +170 -0
  28. package/dist/core/modules/ToolsManager.d.ts +98 -0
  29. package/dist/core/modules/ToolsManager.js +521 -0
  30. package/dist/core/modules/Utilities.d.ts +88 -0
  31. package/dist/core/modules/Utilities.js +329 -0
  32. package/dist/factories/providerRegistry.js +1 -1
  33. package/dist/lib/adapters/providerImageAdapter.d.ts +1 -1
  34. package/dist/lib/adapters/providerImageAdapter.js +62 -0
  35. package/dist/lib/agent/directTools.d.ts +0 -72
  36. package/dist/lib/agent/directTools.js +3 -74
  37. package/dist/lib/constants/enums.d.ts +1 -0
  38. package/dist/lib/constants/enums.js +3 -1
  39. package/dist/lib/constants/tokens.d.ts +3 -0
  40. package/dist/lib/constants/tokens.js +3 -0
  41. package/dist/lib/core/baseProvider.d.ts +56 -53
  42. package/dist/lib/core/baseProvider.js +107 -1095
  43. package/dist/lib/core/constants.d.ts +3 -0
  44. package/dist/lib/core/constants.js +6 -3
  45. package/dist/lib/core/modelConfiguration.js +10 -0
  46. package/dist/lib/core/modules/GenerationHandler.d.ts +63 -0
  47. package/dist/lib/core/modules/GenerationHandler.js +231 -0
  48. package/dist/lib/core/modules/MessageBuilder.d.ts +39 -0
  49. package/dist/lib/core/modules/MessageBuilder.js +180 -0
  50. package/dist/lib/core/modules/StreamHandler.d.ts +52 -0
  51. package/dist/lib/core/modules/StreamHandler.js +104 -0
  52. package/dist/lib/core/modules/TelemetryHandler.d.ts +64 -0
  53. package/dist/lib/core/modules/TelemetryHandler.js +171 -0
  54. package/dist/lib/core/modules/ToolsManager.d.ts +98 -0
  55. package/dist/lib/core/modules/ToolsManager.js +522 -0
  56. package/dist/lib/core/modules/Utilities.d.ts +88 -0
  57. package/dist/lib/core/modules/Utilities.js +330 -0
  58. package/dist/lib/factories/providerRegistry.js +1 -1
  59. package/dist/lib/mcp/servers/agent/directToolsServer.js +0 -1
  60. package/dist/lib/models/modelRegistry.js +44 -0
  61. package/dist/lib/neurolink.js +35 -3
  62. package/dist/lib/providers/amazonBedrock.js +59 -10
  63. package/dist/lib/providers/anthropic.js +2 -30
  64. package/dist/lib/providers/azureOpenai.js +2 -24
  65. package/dist/lib/providers/googleAiStudio.js +2 -24
  66. package/dist/lib/providers/googleVertex.js +2 -45
  67. package/dist/lib/providers/huggingFace.js +3 -31
  68. package/dist/lib/providers/litellm.d.ts +1 -1
  69. package/dist/lib/providers/litellm.js +110 -44
  70. package/dist/lib/providers/mistral.js +5 -32
  71. package/dist/lib/providers/ollama.d.ts +1 -0
  72. package/dist/lib/providers/ollama.js +476 -129
  73. package/dist/lib/providers/openAI.js +2 -28
  74. package/dist/lib/providers/openaiCompatible.js +3 -31
  75. package/dist/lib/types/content.d.ts +16 -113
  76. package/dist/lib/types/content.js +16 -2
  77. package/dist/lib/types/conversation.d.ts +3 -17
  78. package/dist/lib/types/generateTypes.d.ts +2 -2
  79. package/dist/lib/types/index.d.ts +2 -0
  80. package/dist/lib/types/index.js +2 -0
  81. package/dist/lib/types/multimodal.d.ts +282 -0
  82. package/dist/lib/types/multimodal.js +101 -0
  83. package/dist/lib/types/streamTypes.d.ts +2 -2
  84. package/dist/lib/utils/imageProcessor.d.ts +1 -1
  85. package/dist/lib/utils/messageBuilder.js +25 -2
  86. package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -1
  87. package/dist/lib/utils/pdfProcessor.d.ts +9 -0
  88. package/dist/lib/utils/pdfProcessor.js +67 -9
  89. package/dist/mcp/servers/agent/directToolsServer.js +0 -1
  90. package/dist/models/modelRegistry.js +44 -0
  91. package/dist/neurolink.js +35 -3
  92. package/dist/providers/amazonBedrock.js +59 -10
  93. package/dist/providers/anthropic.js +2 -30
  94. package/dist/providers/azureOpenai.js +2 -24
  95. package/dist/providers/googleAiStudio.js +2 -24
  96. package/dist/providers/googleVertex.js +2 -45
  97. package/dist/providers/huggingFace.js +3 -31
  98. package/dist/providers/litellm.d.ts +1 -1
  99. package/dist/providers/litellm.js +110 -44
  100. package/dist/providers/mistral.js +5 -32
  101. package/dist/providers/ollama.d.ts +1 -0
  102. package/dist/providers/ollama.js +476 -129
  103. package/dist/providers/openAI.js +2 -28
  104. package/dist/providers/openaiCompatible.js +3 -31
  105. package/dist/types/content.d.ts +16 -113
  106. package/dist/types/content.js +16 -2
  107. package/dist/types/conversation.d.ts +3 -17
  108. package/dist/types/generateTypes.d.ts +2 -2
  109. package/dist/types/index.d.ts +2 -0
  110. package/dist/types/index.js +2 -0
  111. package/dist/types/multimodal.d.ts +282 -0
  112. package/dist/types/multimodal.js +100 -0
  113. package/dist/types/streamTypes.d.ts +2 -2
  114. package/dist/utils/imageProcessor.d.ts +1 -1
  115. package/dist/utils/messageBuilder.js +25 -2
  116. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -1
  117. package/dist/utils/pdfProcessor.d.ts +9 -0
  118. package/dist/utils/pdfProcessor.js +67 -9
  119. package/package.json +5 -2
@@ -1,21 +1,19 @@
1
- import { z } from "zod";
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
- import { validateStreamOptions as validateStreamOpts, validateTextGenerationOptions, ValidationError, createValidationSummary, } from "../utils/parameterValidation.js";
16
- import { convertJsonSchemaToZod } from "../utils/schemaConversion.js";
17
- import { recordProviderPerformanceFromMetrics, getPerformanceOptimizedProvider, } from "./evaluationProviders.js";
18
- import { modelConfig } from "./modelConfiguration.js";
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
- const hasMultimodalInput = (opts) => {
234
- const input = opts.input;
235
- const hasImages = !!input?.images?.length;
236
- const hasContent = !!input?.content?.length;
237
- const hasCSVFiles = !!input?.csvFiles?.length;
238
- const hasPdfFiles = !!input?.pdfFiles?.length;
239
- const hasFiles = !!input?.files?.length;
240
- return hasImages || hasContent || hasCSVFiles || hasPdfFiles || hasFiles;
241
- };
242
- let messages;
243
- if (hasMultimodalInput(options)) {
244
- if (process.env.NEUROLINK_DEBUG === "true") {
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
- const shouldUseTools = !options.disableTools && this.supportsTools();
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
- logger.debug(`generateText completed`, {
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
- try {
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
- const toolsUsed = [];
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
- // Only access experimental_output if we set a schema
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
- // 🔧 NEUROLINK RAW AI RESPONSE TRACE: Log everything about the raw AI response before parameter extraction
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 from generation options. This is the single
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
- // 1. Determine effective middleware config: per-request overrides global.
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 (typeof schema === "object" &&
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 from MCP format to standard format
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
- // Handle MCP-style results
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
- * Process direct tools with event emission wrapping
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
- const fixedSchema = JSON.parse(JSON.stringify(schema));
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 - direct tools are ALWAYS available
1221
- * MCP tools are added when available (without blocking)
449
+ * Get all available tools - delegated to ToolsManager
1222
450
  */
1223
451
  async getAllTools() {
1224
- // Start with wrapped direct tools that emit events
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 based on token usage and provider configuration
455
+ * Calculate actual cost - delegated to TelemetryHandler
1243
456
  */
1244
457
  async calculateActualCost(usage) {
1245
- try {
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 that accepts all parameters as-is
461
+ * Create a permissive Zod schema - delegated to Utilities
1264
462
  */
1265
463
  createPermissiveZodSchema() {
1266
- // Create a permissive record that accepts any object structure
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 - consolidates validation from 7/10 providers
505
+ * Validate stream options - delegated to StreamHandler
1312
506
  */
1313
507
  validateStreamOptions(options) {
1314
- const validation = validateStreamOpts(options);
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 - consolidates identical logic from 7/10 providers
511
+ * Create text stream transformation - delegated to StreamHandler
1335
512
  */
1336
513
  createTextStream(result) {
1337
- return (async function* () {
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 - consolidates result structure
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 - consolidates analytics from 4/10 providers
523
+ * Create stream analytics - delegated to StreamHandler
1356
524
  */
1357
525
  async createStreamAnalytics(result, startTime, options) {
1358
- try {
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 - consolidates error handling from multiple providers
529
+ * Handle common error patterns - delegated to Utilities
1373
530
  */
1374
531
  handleCommonErrors(error) {
1375
- if (error instanceof TimeoutError) {
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 for a provider to enable actual tool execution
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
- logger.debug(`[${functionTag}] Setting up tool executor for provider`, {
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
- if (typeof optionsOrPrompt === "string") {
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
- if (typeof optionsOrPrompt === "string") {
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
- const { createAnalytics } = await import("./analytics.js");
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
- const { evaluateResponse } = await import("../core/evaluation.js");
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
- const validation = validateTextGenerationOptions(options);
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
- if (!options.timeout) {
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
- // Check if tools are not empty
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