@juspay/neurolink 8.3.0 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -0
- package/dist/adapters/providerImageAdapter.d.ts +1 -1
- package/dist/adapters/providerImageAdapter.js +62 -0
- package/dist/agent/directTools.d.ts +0 -72
- package/dist/agent/directTools.js +3 -74
- package/dist/cli/commands/config.d.ts +18 -18
- package/dist/cli/factories/commandFactory.js +1 -0
- package/dist/constants/enums.d.ts +1 -0
- package/dist/constants/enums.js +3 -1
- package/dist/constants/tokens.d.ts +3 -0
- package/dist/constants/tokens.js +3 -0
- package/dist/core/baseProvider.d.ts +56 -53
- package/dist/core/baseProvider.js +107 -1095
- package/dist/core/constants.d.ts +3 -0
- package/dist/core/constants.js +6 -3
- package/dist/core/modelConfiguration.js +10 -0
- package/dist/core/modules/GenerationHandler.d.ts +63 -0
- package/dist/core/modules/GenerationHandler.js +230 -0
- package/dist/core/modules/MessageBuilder.d.ts +39 -0
- package/dist/core/modules/MessageBuilder.js +179 -0
- package/dist/core/modules/StreamHandler.d.ts +52 -0
- package/dist/core/modules/StreamHandler.js +103 -0
- package/dist/core/modules/TelemetryHandler.d.ts +64 -0
- package/dist/core/modules/TelemetryHandler.js +170 -0
- package/dist/core/modules/ToolsManager.d.ts +98 -0
- package/dist/core/modules/ToolsManager.js +521 -0
- package/dist/core/modules/Utilities.d.ts +88 -0
- package/dist/core/modules/Utilities.js +329 -0
- package/dist/factories/providerRegistry.js +1 -1
- package/dist/lib/adapters/providerImageAdapter.d.ts +1 -1
- package/dist/lib/adapters/providerImageAdapter.js +62 -0
- package/dist/lib/agent/directTools.d.ts +0 -72
- package/dist/lib/agent/directTools.js +3 -74
- package/dist/lib/constants/enums.d.ts +1 -0
- package/dist/lib/constants/enums.js +3 -1
- package/dist/lib/constants/tokens.d.ts +3 -0
- package/dist/lib/constants/tokens.js +3 -0
- package/dist/lib/core/baseProvider.d.ts +56 -53
- package/dist/lib/core/baseProvider.js +107 -1095
- package/dist/lib/core/constants.d.ts +3 -0
- package/dist/lib/core/constants.js +6 -3
- package/dist/lib/core/modelConfiguration.js +10 -0
- package/dist/lib/core/modules/GenerationHandler.d.ts +63 -0
- package/dist/lib/core/modules/GenerationHandler.js +231 -0
- package/dist/lib/core/modules/MessageBuilder.d.ts +39 -0
- package/dist/lib/core/modules/MessageBuilder.js +180 -0
- package/dist/lib/core/modules/StreamHandler.d.ts +52 -0
- package/dist/lib/core/modules/StreamHandler.js +104 -0
- package/dist/lib/core/modules/TelemetryHandler.d.ts +64 -0
- package/dist/lib/core/modules/TelemetryHandler.js +171 -0
- package/dist/lib/core/modules/ToolsManager.d.ts +98 -0
- package/dist/lib/core/modules/ToolsManager.js +522 -0
- package/dist/lib/core/modules/Utilities.d.ts +88 -0
- package/dist/lib/core/modules/Utilities.js +330 -0
- package/dist/lib/factories/providerRegistry.js +1 -1
- package/dist/lib/mcp/servers/agent/directToolsServer.js +0 -1
- package/dist/lib/models/modelRegistry.js +44 -0
- package/dist/lib/neurolink.js +35 -3
- package/dist/lib/providers/amazonBedrock.js +59 -10
- package/dist/lib/providers/anthropic.js +2 -30
- package/dist/lib/providers/azureOpenai.js +2 -24
- package/dist/lib/providers/googleAiStudio.js +2 -24
- package/dist/lib/providers/googleVertex.js +2 -45
- package/dist/lib/providers/huggingFace.js +3 -31
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +110 -44
- package/dist/lib/providers/mistral.js +5 -32
- package/dist/lib/providers/ollama.d.ts +1 -0
- package/dist/lib/providers/ollama.js +476 -129
- package/dist/lib/providers/openAI.js +2 -28
- package/dist/lib/providers/openaiCompatible.js +3 -31
- package/dist/lib/types/content.d.ts +16 -113
- package/dist/lib/types/content.js +16 -2
- package/dist/lib/types/conversation.d.ts +3 -17
- package/dist/lib/types/generateTypes.d.ts +2 -2
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/multimodal.d.ts +282 -0
- package/dist/lib/types/multimodal.js +101 -0
- package/dist/lib/types/streamTypes.d.ts +2 -2
- package/dist/lib/utils/imageProcessor.d.ts +1 -1
- package/dist/lib/utils/messageBuilder.js +25 -2
- package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -1
- package/dist/lib/utils/pdfProcessor.d.ts +9 -0
- package/dist/lib/utils/pdfProcessor.js +67 -9
- package/dist/mcp/servers/agent/directToolsServer.js +0 -1
- package/dist/models/modelRegistry.js +44 -0
- package/dist/neurolink.js +35 -3
- package/dist/providers/amazonBedrock.js +59 -10
- package/dist/providers/anthropic.js +2 -30
- package/dist/providers/azureOpenai.js +2 -24
- package/dist/providers/googleAiStudio.js +2 -24
- package/dist/providers/googleVertex.js +2 -45
- package/dist/providers/huggingFace.js +3 -31
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +110 -44
- package/dist/providers/mistral.js +5 -32
- package/dist/providers/ollama.d.ts +1 -0
- package/dist/providers/ollama.js +476 -129
- package/dist/providers/openAI.js +2 -28
- package/dist/providers/openaiCompatible.js +3 -31
- package/dist/types/content.d.ts +16 -113
- package/dist/types/content.js +16 -2
- package/dist/types/conversation.d.ts +3 -17
- package/dist/types/generateTypes.d.ts +2 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/multimodal.d.ts +282 -0
- package/dist/types/multimodal.js +100 -0
- package/dist/types/streamTypes.d.ts +2 -2
- package/dist/utils/imageProcessor.d.ts +1 -1
- package/dist/utils/messageBuilder.js +25 -2
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -1
- package/dist/utils/pdfProcessor.d.ts +9 -0
- package/dist/utils/pdfProcessor.js +67 -9
- package/package.json +5 -2
|
@@ -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");
|
|
@@ -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",
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -455,7 +455,7 @@ Current user's request: ${currentInput}`;
|
|
|
455
455
|
try {
|
|
456
456
|
this.externalServerManager = new ExternalServerManager({
|
|
457
457
|
maxServers: 20,
|
|
458
|
-
defaultTimeout:
|
|
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
|
-
//
|
|
2100
|
-
//
|
|
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
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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:
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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,
|