@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
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Utilities Module
3
+ *
4
+ * Handles validation, normalization, schema conversion, and error handling utilities.
5
+ * Extracted from BaseProvider to follow Single Responsibility Principle.
6
+ *
7
+ * Responsibilities:
8
+ * - Options validation (text generation and stream options)
9
+ * - Options normalization (string to object conversion)
10
+ * - Provider information formatting
11
+ * - Timeout parsing and calculation
12
+ * - Schema utilities (Zod detection, permissive schema creation, OpenAI strict mode fixes)
13
+ * - Tool result conversion
14
+ * - Middleware options extraction
15
+ * - Common error pattern handling
16
+ *
17
+ * @module core/modules/Utilities
18
+ */
19
+ import { z } from "zod";
20
+ import { logger } from "../../utils/logger.js";
21
+ import { getSafeMaxTokens } from "../../utils/tokenLimits.js";
22
+ import { TimeoutError } from "../../utils/timeout.js";
23
+ import { validateStreamOptions as validateStreamOpts, validateTextGenerationOptions, ValidationError, createValidationSummary, } from "../../utils/parameterValidation.js";
24
+ import { STEP_LIMITS } from "../constants.js";
25
+ /**
26
+ * Utilities class - Provides validation, normalization, and utility methods
27
+ */
28
+ export class Utilities {
29
+ providerName;
30
+ modelName;
31
+ defaultTimeout;
32
+ middlewareOptions;
33
+ constructor(providerName, modelName, defaultTimeout = 30000, middlewareOptions) {
34
+ this.providerName = providerName;
35
+ this.modelName = modelName;
36
+ this.defaultTimeout = defaultTimeout;
37
+ this.middlewareOptions = middlewareOptions;
38
+ }
39
+ /**
40
+ * Validate text generation options
41
+ */
42
+ validateOptions(options) {
43
+ const validation = validateTextGenerationOptions(options);
44
+ if (!validation.isValid) {
45
+ const summary = createValidationSummary(validation);
46
+ throw new ValidationError(`Text generation options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
47
+ }
48
+ // Log warnings if any
49
+ if (validation.warnings.length > 0) {
50
+ logger.warn("Text generation options validation warnings:", validation.warnings);
51
+ }
52
+ // Additional BaseProvider-specific validation
53
+ if (options.maxSteps !== undefined) {
54
+ if (options.maxSteps < STEP_LIMITS.min ||
55
+ options.maxSteps > STEP_LIMITS.max) {
56
+ throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
57
+ `Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
58
+ ]);
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Validate stream options
64
+ */
65
+ validateStreamOptions(options) {
66
+ const validation = validateStreamOpts(options);
67
+ if (!validation.isValid) {
68
+ const summary = createValidationSummary(validation);
69
+ throw new ValidationError(`Stream options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
70
+ }
71
+ // Log warnings if any
72
+ if (validation.warnings.length > 0) {
73
+ logger.warn("Stream options validation warnings:", validation.warnings);
74
+ }
75
+ // Additional BaseProvider-specific validation
76
+ if (options.maxSteps !== undefined) {
77
+ if (options.maxSteps < STEP_LIMITS.min ||
78
+ options.maxSteps > STEP_LIMITS.max) {
79
+ throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
80
+ `Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
81
+ ]);
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Normalize text generation options from string or object
87
+ */
88
+ normalizeTextOptions(optionsOrPrompt) {
89
+ if (typeof optionsOrPrompt === "string") {
90
+ const safeMaxTokens = getSafeMaxTokens(this.providerName, this.modelName);
91
+ return {
92
+ prompt: optionsOrPrompt,
93
+ provider: this.providerName,
94
+ model: this.modelName,
95
+ maxTokens: safeMaxTokens,
96
+ };
97
+ }
98
+ // Handle both prompt and input.text formats
99
+ const prompt = optionsOrPrompt.prompt || optionsOrPrompt.input?.text || "";
100
+ const modelName = optionsOrPrompt.model || this.modelName;
101
+ const providerName = optionsOrPrompt.provider || this.providerName;
102
+ // Apply safe maxTokens based on provider and model
103
+ const safeMaxTokens = getSafeMaxTokens(providerName, modelName, optionsOrPrompt.maxTokens);
104
+ // CRITICAL FIX: Preserve the entire input object for multimodal support
105
+ // This ensures images and content arrays are not lost during normalization
106
+ const normalizedOptions = {
107
+ ...optionsOrPrompt,
108
+ prompt,
109
+ provider: providerName,
110
+ model: modelName,
111
+ maxTokens: safeMaxTokens,
112
+ };
113
+ // Ensure input object is preserved if it exists (for multimodal support)
114
+ if (optionsOrPrompt.input) {
115
+ normalizedOptions.input = {
116
+ ...optionsOrPrompt.input,
117
+ text: prompt, // Ensure text is consistent
118
+ };
119
+ }
120
+ return normalizedOptions;
121
+ }
122
+ /**
123
+ * Normalize stream options from string or object
124
+ */
125
+ normalizeStreamOptions(optionsOrPrompt) {
126
+ if (typeof optionsOrPrompt === "string") {
127
+ const safeMaxTokens = getSafeMaxTokens(this.providerName, this.modelName);
128
+ return {
129
+ input: { text: optionsOrPrompt },
130
+ provider: this.providerName,
131
+ model: this.modelName,
132
+ maxTokens: safeMaxTokens,
133
+ };
134
+ }
135
+ const modelName = optionsOrPrompt.model || this.modelName;
136
+ const providerName = optionsOrPrompt.provider || this.providerName;
137
+ // Apply safe maxTokens based on provider and model
138
+ const safeMaxTokens = getSafeMaxTokens(providerName, modelName, optionsOrPrompt.maxTokens);
139
+ return {
140
+ ...optionsOrPrompt,
141
+ provider: providerName,
142
+ model: modelName,
143
+ maxTokens: safeMaxTokens,
144
+ };
145
+ }
146
+ /**
147
+ * Get provider information
148
+ */
149
+ getProviderInfo() {
150
+ return {
151
+ provider: this.providerName,
152
+ model: this.modelName,
153
+ };
154
+ }
155
+ /**
156
+ * Get timeout value in milliseconds from options
157
+ * Supports number or string formats (e.g., '30s', '2m', '1h')
158
+ */
159
+ getTimeout(options) {
160
+ if (!options.timeout) {
161
+ return this.defaultTimeout;
162
+ }
163
+ if (typeof options.timeout === "number") {
164
+ return options.timeout;
165
+ }
166
+ // Parse string timeout (e.g., '30s', '2m', '1h')
167
+ const timeoutStr = options.timeout.toLowerCase();
168
+ const value = parseInt(timeoutStr);
169
+ if (timeoutStr.includes("h")) {
170
+ return value * 60 * 60 * 1000;
171
+ }
172
+ else if (timeoutStr.includes("m")) {
173
+ return value * 60 * 1000;
174
+ }
175
+ else if (timeoutStr.includes("s")) {
176
+ return value * 1000;
177
+ }
178
+ return this.defaultTimeout;
179
+ }
180
+ /**
181
+ * Check if a schema is a Zod schema
182
+ */
183
+ isZodSchema(schema) {
184
+ return (typeof schema === "object" &&
185
+ schema !== null &&
186
+ // Most Zod schemas have an internal _def and a parse method
187
+ typeof schema.parse === "function");
188
+ }
189
+ /**
190
+ * Convert tool execution result from MCP format to standard format
191
+ * Handles tool failures gracefully to prevent stream termination
192
+ */
193
+ async convertToolResult(result) {
194
+ // Handle MCP-style results
195
+ if (result && typeof result === "object" && "success" in result) {
196
+ const mcpResult = result;
197
+ if (mcpResult.success) {
198
+ return mcpResult.data;
199
+ }
200
+ else {
201
+ // Instead of throwing, return a structured error result
202
+ // This prevents tool failures from terminating streams
203
+ const errorMsg = typeof mcpResult.error === "string"
204
+ ? mcpResult.error
205
+ : "Tool execution failed";
206
+ // Log the error for debugging but don't throw
207
+ logger.warn(`Tool execution failed: ${errorMsg}`);
208
+ // Return error as structured data that can be processed by the AI
209
+ return {
210
+ isError: true,
211
+ error: errorMsg,
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: `Tool execution failed: ${errorMsg}`,
216
+ },
217
+ ],
218
+ };
219
+ }
220
+ }
221
+ return result;
222
+ }
223
+ /**
224
+ * Create a permissive Zod schema that accepts all parameters as-is
225
+ */
226
+ createPermissiveZodSchema() {
227
+ // Create a permissive record that accepts any object structure
228
+ // This allows all parameters to pass through without validation issues
229
+ return z.record(z.unknown()).transform((data) => {
230
+ // Return the data as-is to preserve all parameter information
231
+ return data;
232
+ });
233
+ }
234
+ /**
235
+ * Recursively fix JSON Schema for OpenAI strict mode compatibility
236
+ * OpenAI requires additionalProperties: false at ALL levels and preserves required array
237
+ */
238
+ fixSchemaForOpenAIStrictMode(schema) {
239
+ const fixedSchema = JSON.parse(JSON.stringify(schema));
240
+ if (fixedSchema.type === "object" &&
241
+ fixedSchema.properties &&
242
+ typeof fixedSchema.properties === "object") {
243
+ const allPropertyNames = Object.keys(fixedSchema.properties);
244
+ if (!fixedSchema.required || !Array.isArray(fixedSchema.required)) {
245
+ fixedSchema.required = [];
246
+ }
247
+ fixedSchema.additionalProperties = false;
248
+ for (const propName of allPropertyNames) {
249
+ const propValue = fixedSchema.properties[propName];
250
+ if (propValue && typeof propValue === "object") {
251
+ if (propValue.type === "object") {
252
+ fixedSchema.properties[propName] =
253
+ this.fixSchemaForOpenAIStrictMode(propValue);
254
+ }
255
+ else if (propValue.type === "array" &&
256
+ propValue.items &&
257
+ typeof propValue.items === "object") {
258
+ fixedSchema.properties[propName].items =
259
+ this.fixSchemaForOpenAIStrictMode(propValue.items);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ return fixedSchema;
265
+ }
266
+ /**
267
+ * Extract middleware options from generation/stream options
268
+ * This is the single source of truth for deciding if middleware should be applied
269
+ */
270
+ extractMiddlewareOptions(options) {
271
+ // 1. Determine effective middleware config: per-request overrides global.
272
+ const middlewareOpts = options.middleware ??
273
+ this.middlewareOptions;
274
+ if (!middlewareOpts) {
275
+ return null;
276
+ }
277
+ // 2. The middleware property must be an object with configuration.
278
+ if (typeof middlewareOpts !== "object" ||
279
+ middlewareOpts === null ||
280
+ middlewareOpts instanceof Date ||
281
+ middlewareOpts instanceof RegExp) {
282
+ return null;
283
+ }
284
+ // 3. Check if the middleware object has any actual configuration keys.
285
+ const fullOpts = middlewareOpts;
286
+ const hasArray = (arr) => Array.isArray(arr) && arr.length > 0;
287
+ const hasConfig = !!fullOpts.middlewareConfig ||
288
+ hasArray(fullOpts.enabledMiddleware) ||
289
+ hasArray(fullOpts.disabledMiddleware) ||
290
+ !!fullOpts.preset ||
291
+ hasArray(fullOpts.middleware);
292
+ if (!hasConfig) {
293
+ return null;
294
+ }
295
+ // 4. Return the formatted options if configuration is present.
296
+ return {
297
+ ...fullOpts,
298
+ global: {
299
+ collectStats: true,
300
+ continueOnError: true,
301
+ ...(fullOpts.global || {}),
302
+ },
303
+ };
304
+ }
305
+ /**
306
+ * Handle common error patterns across providers
307
+ * Returns transformed error or null if not a common pattern
308
+ */
309
+ handleCommonErrors(error) {
310
+ if (error instanceof TimeoutError) {
311
+ return new Error(`${this.providerName} request timed out after ${error.timeout}ms. Consider increasing timeout or using a lighter model.`);
312
+ }
313
+ const message = error instanceof Error ? error.message : String(error);
314
+ // Common API key errors
315
+ if (message.includes("API_KEY_INVALID") ||
316
+ message.includes("Invalid API key") ||
317
+ message.includes("authentication") ||
318
+ message.includes("unauthorized")) {
319
+ return new Error(`Invalid API key for ${this.providerName}. Please check your API key environment variable.`);
320
+ }
321
+ // Common rate limit errors
322
+ if (message.includes("rate limit") ||
323
+ message.includes("quota") ||
324
+ message.includes("429")) {
325
+ return new Error(`Rate limit exceeded for ${this.providerName}. Please wait before making more requests.`);
326
+ }
327
+ return null; // Not a common error, let provider handle it
328
+ }
329
+ }
330
+ //# sourceMappingURL=Utilities.js.map
@@ -24,7 +24,7 @@ export class ProviderRegistry {
24
24
  ProviderFactory.registerProvider(AIProviderName.GOOGLE_AI, async (modelName, _providerName, sdk) => {
25
25
  const { GoogleAIStudioProvider } = await import("../providers/googleAiStudio.js");
26
26
  return new GoogleAIStudioProvider(modelName, sdk);
27
- }, GoogleAIModels.GEMINI_2_5_FLASH, ["googleAiStudio", "google", "gemini", "google-ai"]);
27
+ }, GoogleAIModels.GEMINI_2_5_FLASH, ["googleAiStudio", "google", "gemini", "google-ai", "google-ai-studio"]);
28
28
  // Register OpenAI provider
29
29
  ProviderFactory.registerProvider(AIProviderName.OPENAI, async (modelName, _providerName, sdk) => {
30
30
  const { OpenAIProvider } = await import("../providers/openAI.js");
@@ -135,7 +135,6 @@ function getToolCategory(toolName) {
135
135
  case "readFile":
136
136
  case "writeFile":
137
137
  case "listDirectory":
138
- case "searchFiles":
139
138
  return "filesystem";
140
139
  case "websearchGrounding":
141
140
  return "search";
@@ -188,6 +188,50 @@ export const MODEL_REGISTRY = {
188
188
  category: "general",
189
189
  },
190
190
  // Anthropic Models
191
+ [AnthropicModels.CLAUDE_4_5_HAIKU]: {
192
+ id: AnthropicModels.CLAUDE_4_5_HAIKU,
193
+ name: "Claude 4.5 Haiku",
194
+ provider: AIProviderName.ANTHROPIC,
195
+ description: "Latest fast and efficient Claude model with vision support",
196
+ capabilities: {
197
+ vision: true,
198
+ functionCalling: true,
199
+ codeGeneration: true,
200
+ reasoning: true,
201
+ multimodal: true,
202
+ streaming: true,
203
+ jsonMode: false,
204
+ },
205
+ pricing: {
206
+ inputCostPer1K: 0.001,
207
+ outputCostPer1K: 0.005,
208
+ currency: "USD",
209
+ },
210
+ performance: {
211
+ speed: "fast",
212
+ quality: "high",
213
+ accuracy: "high",
214
+ },
215
+ limits: {
216
+ maxContextTokens: 200000,
217
+ maxOutputTokens: 64000,
218
+ maxRequestsPerMinute: 100,
219
+ },
220
+ useCases: {
221
+ coding: 8,
222
+ creative: 8,
223
+ analysis: 8,
224
+ conversation: 9,
225
+ reasoning: 8,
226
+ translation: 8,
227
+ summarization: 9,
228
+ },
229
+ aliases: ["claude-4.5-haiku", "claude-haiku-latest", "haiku-4.5"],
230
+ deprecated: false,
231
+ isLocal: false,
232
+ releaseDate: "2025-10-15",
233
+ category: "general",
234
+ },
191
235
  [AnthropicModels.CLAUDE_3_5_SONNET]: {
192
236
  id: AnthropicModels.CLAUDE_3_5_SONNET,
193
237
  name: "Claude 3.5 Sonnet",
@@ -455,7 +455,7 @@ Current user's request: ${currentInput}`;
455
455
  try {
456
456
  this.externalServerManager = new ExternalServerManager({
457
457
  maxServers: 20,
458
- defaultTimeout: 15000,
458
+ defaultTimeout: 30000, // Increased from 15s to 30s for proxy latency (e.g., LiteLLM)
459
459
  enableAutoRestart: true,
460
460
  enablePerformanceMonitoring: true,
461
461
  }, {
@@ -1963,6 +1963,29 @@ Current user's request: ${currentInput}`;
1963
1963
  // Continue with original options if orchestration fails
1964
1964
  }
1965
1965
  }
1966
+ // 🔧 AUTO-DISABLE TOOLS: For Ollama models that don't support tools (same logic as generate())
1967
+ // This prevents overwhelming smaller models with massive tool descriptions in the system message
1968
+ if ((options.provider === "ollama" ||
1969
+ options.provider?.toLowerCase().includes("ollama")) &&
1970
+ !options.disableTools) {
1971
+ const { ModelConfigurationManager } = await import("./core/modelConfiguration.js");
1972
+ const modelConfig = ModelConfigurationManager.getInstance();
1973
+ const ollamaConfig = modelConfig.getProviderConfiguration("ollama");
1974
+ const toolCapableModels = ollamaConfig?.modelBehavior?.toolCapableModels || [];
1975
+ // Only disable tools if we have explicit evidence the model doesn't support them
1976
+ // If toolCapableModels is empty or model is not specified, don't make assumptions
1977
+ const modelName = options.model;
1978
+ if (toolCapableModels.length > 0 && modelName) {
1979
+ const modelSupportsTools = toolCapableModels.some((capableModel) => modelName.toLowerCase().includes(capableModel.toLowerCase()));
1980
+ if (!modelSupportsTools) {
1981
+ options.disableTools = true;
1982
+ logger.debug("Auto-disabled tools for Ollama model that doesn't support them (stream)", {
1983
+ model: options.model,
1984
+ toolCapableModels: toolCapableModels.slice(0, 3), // Show first 3 for brevity
1985
+ });
1986
+ }
1987
+ }
1988
+ }
1966
1989
  factoryResult = processStreamingFactoryOptions(options);
1967
1990
  enhancedOptions = createCleanStreamOptions(options);
1968
1991
  if (options.input?.text) {
@@ -2091,17 +2114,26 @@ Current user's request: ${currentInput}`;
2091
2114
  customTools: this.getCustomTools(),
2092
2115
  executeTool: this.executeTool.bind(this),
2093
2116
  }, "NeuroLink.createMCPStream");
2117
+ // 🔧 FIX: Get available tools and create tool-aware system prompt
2118
+ // Use SAME pattern as tryMCPGeneration (generate mode)
2119
+ const availableTools = await this.getAllAvailableTools();
2120
+ const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
2094
2121
  // Get conversation messages for context
2095
2122
  const conversationMessages = await getConversationMessages(this.conversationMemory, {
2096
2123
  prompt: options.input.text,
2097
2124
  context: options.context,
2098
2125
  });
2099
- // Let provider handle tools and system prompt automatically via Vercel AI SDK
2100
- // This ensures proper tool integration in stream mode
2126
+ // 🔧 FIX: Pass enhanced system prompt to real streaming
2127
+ // Tools will be accessed through the streamText call in executeStream
2101
2128
  const streamResult = await provider.stream({
2102
2129
  ...options,
2130
+ systemPrompt: enhancedSystemPrompt, // Use enhanced prompt with tool descriptions
2103
2131
  conversationMessages,
2104
2132
  });
2133
+ logger.debug("[createMCPStream] Stream created successfully", {
2134
+ provider: providerName,
2135
+ systemPromptPassedLength: enhancedSystemPrompt.length,
2136
+ });
2105
2137
  return { stream: streamResult.stream, provider: providerName };
2106
2138
  }
2107
2139
  /**
@@ -8,6 +8,7 @@ import { buildMultimodalMessagesArray } from "../utils/messageBuilder.js";
8
8
  import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
9
9
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
10
10
  import { createAnalytics } from "../core/analytics.js";
11
+ import path from "path";
11
12
  // Bedrock-specific types now imported from ../types/providerSpecific.js
12
13
  export class AmazonBedrockProvider extends BaseProvider {
13
14
  bedrockClient;
@@ -95,13 +96,45 @@ export class AmazonBedrockProvider extends BaseProvider {
95
96
  : optionsOrPrompt;
96
97
  // Clear conversation history for new generation
97
98
  this.conversationHistory = [];
98
- // Add user message to conversation
99
- const userMessage = {
100
- role: "user",
101
- content: [{ text: options.prompt }],
102
- };
103
- this.conversationHistory.push(userMessage);
104
- logger.debug(`[AmazonBedrockProvider] Starting conversation with prompt: ${options.prompt}`);
99
+ // Check for multimodal input (images, PDFs, CSVs, files)
100
+ // Cast to any to access multimodal properties (runtime check is safe)
101
+ const input = options.input;
102
+ const hasMultimodalInput = !!(input?.images?.length ||
103
+ input?.content?.length ||
104
+ input?.files?.length ||
105
+ input?.csvFiles?.length ||
106
+ input?.pdfFiles?.length);
107
+ if (hasMultimodalInput) {
108
+ logger.debug(`[AmazonBedrockProvider] Detected multimodal input in generate(), using multimodal message builder`, {
109
+ hasImages: !!input?.images?.length,
110
+ imageCount: input?.images?.length || 0,
111
+ hasContent: !!input?.content?.length,
112
+ contentCount: input?.content?.length || 0,
113
+ hasFiles: !!input?.files?.length,
114
+ fileCount: input?.files?.length || 0,
115
+ hasCSVFiles: !!input?.csvFiles?.length,
116
+ csvFileCount: input?.csvFiles?.length || 0,
117
+ hasPDFFiles: !!input?.pdfFiles?.length,
118
+ pdfFileCount: input?.pdfFiles?.length || 0,
119
+ });
120
+ // Cast options to StreamOptions for multimodal processing
121
+ const streamOptions = options;
122
+ const multimodalOptions = buildMultimodalOptions(streamOptions, this.providerName, this.modelName);
123
+ const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
124
+ // Convert to Bedrock format
125
+ this.conversationHistory =
126
+ this.convertToBedrockMessages(multimodalMessages);
127
+ }
128
+ else {
129
+ logger.debug(`[AmazonBedrockProvider] Text-only input in generate(), using simple message builder`);
130
+ // Add user message to conversation - simple text-only case
131
+ const userMessage = {
132
+ role: "user",
133
+ content: [{ text: options.prompt }],
134
+ };
135
+ this.conversationHistory.push(userMessage);
136
+ }
137
+ logger.debug(`[AmazonBedrockProvider] Starting conversation with ${this.conversationHistory.length} message(s)`);
105
138
  // Start conversation loop and return enhanced result
106
139
  const text = await this.conversationLoop(options);
107
140
  return {
@@ -585,12 +618,28 @@ export class AmazonBedrockProvider extends BaseProvider {
585
618
  else {
586
619
  docData = contentItem.data;
587
620
  }
621
+ // Extract basename and sanitize for Bedrock's filename requirements
622
+ // Bedrock only allows: alphanumeric, whitespace, hyphens, parentheses, brackets
623
+ // NOTE: Periods (.) are NOT allowed, so we remove the extension
624
+ let filename = typeof contentItem.name === "string" && contentItem.name
625
+ ? path.basename(contentItem.name)
626
+ : "document-pdf";
627
+ // Remove file extension
628
+ filename = filename.replace(/\.[^.]+$/, "");
629
+ // Replace all disallowed characters with hyphens
630
+ // Bedrock constraint: only alphanumeric, whitespace, hyphens, parentheses, brackets allowed
631
+ filename = filename.replace(/[^a-zA-Z0-9\s\-()[\]]/g, "-");
632
+ // Clean up: remove multiple consecutive hyphens and trim
633
+ filename = filename
634
+ .replace(/-+/g, "-")
635
+ .trim()
636
+ .replace(/^-+|-+$/g, "");
637
+ // Fallback if filename becomes empty after sanitization
638
+ filename = filename || "document";
588
639
  bedrockMessage.content.push({
589
640
  document: {
590
641
  format: "pdf",
591
- name: typeof contentItem.name === "string" && contentItem.name
592
- ? contentItem.name
593
- : "document.pdf",
642
+ name: filename,
594
643
  source: {
595
644
  bytes: docData,
596
645
  },
@@ -7,8 +7,6 @@ import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
7
7
  import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/errors.js";
8
8
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
9
9
  import { validateApiKey, createAnthropicConfig, getProviderModel, } from "../utils/providerConfig.js";
10
- import { buildMessagesArray, buildMultimodalMessagesArray, convertToCoreMessages, } from "../utils/messageBuilder.js";
11
- import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
12
10
  import { createProxyFetch } from "../proxy/proxyFetch.js";
13
11
  // Configuration helpers - now using consolidated utility
14
12
  const getAnthropicApiKey = () => {
@@ -94,34 +92,8 @@ export class AnthropicProvider extends BaseProvider {
94
92
  const shouldUseTools = !options.disableTools && this.supportsTools();
95
93
  const tools = shouldUseTools ? await this.getAllTools() : {};
96
94
  // Build message array from options with multimodal support
97
- const hasMultimodalInput = !!(options.input?.images?.length ||
98
- options.input?.content?.length ||
99
- options.input?.files?.length ||
100
- options.input?.csvFiles?.length ||
101
- options.input?.pdfFiles?.length);
102
- let messages;
103
- if (hasMultimodalInput) {
104
- logger.debug(`Anthropic: Detected multimodal input, using multimodal message builder`, {
105
- hasImages: !!options.input?.images?.length,
106
- imageCount: options.input?.images?.length || 0,
107
- hasContent: !!options.input?.content?.length,
108
- contentCount: options.input?.content?.length || 0,
109
- hasFiles: !!options.input?.files?.length,
110
- fileCount: options.input?.files?.length || 0,
111
- hasCSVFiles: !!options.input?.csvFiles?.length,
112
- csvFileCount: options.input?.csvFiles?.length || 0,
113
- hasPDFFiles: !!options.input?.pdfFiles?.length,
114
- pdfFileCount: options.input?.pdfFiles?.length || 0,
115
- });
116
- const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
117
- const mm = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
118
- // Convert multimodal messages to Vercel AI SDK format (CoreMessage[])
119
- messages = convertToCoreMessages(mm);
120
- }
121
- else {
122
- logger.debug(`Anthropic: Text-only input, using standard message builder`);
123
- messages = await buildMessagesArray(options);
124
- }
95
+ // Using protected helper from BaseProvider to eliminate code duplication
96
+ const messages = await this.buildMessagesForStream(options);
125
97
  const model = await this.getAISDKModelWithMiddleware(options);
126
98
  const result = await streamText({
127
99
  model: model,
@@ -4,8 +4,6 @@ import { BaseProvider } from "../core/baseProvider.js";
4
4
  import { AIProviderName, APIVersions } from "../constants/enums.js";
5
5
  import { validateApiKey, createAzureAPIKeyConfig, createAzureEndpointConfig, } from "../utils/providerConfig.js";
6
6
  import { logger } from "../utils/logger.js";
7
- import { buildMessagesArray, buildMultimodalMessagesArray, convertToCoreMessages, } from "../utils/messageBuilder.js";
8
- import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
9
7
  import { createProxyFetch } from "../proxy/proxyFetch.js";
10
8
  import { DEFAULT_MAX_STEPS } from "../core/constants.js";
11
9
  export class AzureOpenAIProvider extends BaseProvider {
@@ -111,28 +109,8 @@ export class AzureOpenAIProvider extends BaseProvider {
111
109
  });
112
110
  }
113
111
  // Build message array from options with multimodal support
114
- const hasMultimodalInput = !!(options.input?.images?.length ||
115
- options.input?.content?.length ||
116
- options.input?.files?.length ||
117
- options.input?.csvFiles?.length ||
118
- options.input?.pdfFiles?.length);
119
- let messages;
120
- if (hasMultimodalInput) {
121
- logger.debug(`Azure OpenAI: Detected multimodal input, using multimodal message builder`, {
122
- hasImages: !!options.input?.images?.length,
123
- imageCount: options.input?.images?.length || 0,
124
- hasContent: !!options.input?.content?.length,
125
- contentCount: options.input?.content?.length || 0,
126
- });
127
- const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
128
- const mm = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
129
- // Convert multimodal messages to Vercel AI SDK format (CoreMessage[])
130
- messages = convertToCoreMessages(mm);
131
- }
132
- else {
133
- logger.debug(`Azure OpenAI: Text-only input, using standard message builder`);
134
- messages = await buildMessagesArray(options);
135
- }
112
+ // Using protected helper from BaseProvider to eliminate code duplication
113
+ const messages = await this.buildMessagesForStream(options);
136
114
  const model = await this.getAISDKModelWithMiddleware(options);
137
115
  const stream = await streamText({
138
116
  model,