@juspay/neurolink 7.37.0 → 7.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cli/commands/config.d.ts +18 -18
- package/dist/cli/factories/commandFactory.d.ts +24 -0
- package/dist/cli/factories/commandFactory.js +297 -245
- package/dist/core/baseProvider.d.ts +44 -3
- package/dist/core/baseProvider.js +729 -352
- package/dist/core/constants.d.ts +2 -30
- package/dist/core/constants.js +15 -43
- package/dist/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/core/redisConversationMemoryManager.js +665 -203
- package/dist/factories/providerFactory.js +23 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -3
- package/dist/lib/core/baseProvider.d.ts +44 -3
- package/dist/lib/core/baseProvider.js +729 -352
- package/dist/lib/core/constants.d.ts +2 -30
- package/dist/lib/core/constants.js +15 -43
- package/dist/lib/core/redisConversationMemoryManager.d.ts +98 -15
- package/dist/lib/core/redisConversationMemoryManager.js +665 -203
- package/dist/lib/factories/providerFactory.js +23 -6
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +4 -3
- package/dist/lib/mcp/externalServerManager.js +2 -2
- package/dist/lib/mcp/registry.js +2 -2
- package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/lib/mcp/toolRegistry.js +4 -8
- package/dist/lib/neurolink.d.ts +95 -28
- package/dist/lib/neurolink.js +479 -719
- package/dist/lib/providers/amazonBedrock.js +2 -2
- package/dist/lib/providers/anthropic.js +8 -0
- package/dist/lib/providers/anthropicBaseProvider.js +8 -0
- package/dist/lib/providers/azureOpenai.js +8 -0
- package/dist/lib/providers/googleAiStudio.js +8 -0
- package/dist/lib/providers/googleVertex.d.ts +3 -23
- package/dist/lib/providers/googleVertex.js +24 -342
- package/dist/lib/providers/huggingFace.js +8 -0
- package/dist/lib/providers/litellm.js +8 -0
- package/dist/lib/providers/mistral.js +8 -0
- package/dist/lib/providers/openAI.d.ts +23 -0
- package/dist/lib/providers/openAI.js +323 -6
- package/dist/lib/providers/openaiCompatible.js +8 -0
- package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/lib/sdk/toolRegistration.js +18 -1
- package/dist/lib/types/common.d.ts +98 -0
- package/dist/lib/types/conversation.d.ts +52 -2
- package/dist/lib/types/streamTypes.d.ts +13 -6
- package/dist/lib/types/typeAliases.d.ts +3 -2
- package/dist/lib/utils/conversationMemory.js +3 -1
- package/dist/lib/utils/messageBuilder.d.ts +10 -2
- package/dist/lib/utils/messageBuilder.js +22 -1
- package/dist/lib/utils/parameterValidation.js +6 -25
- package/dist/lib/utils/promptRedaction.js +4 -4
- package/dist/lib/utils/redis.d.ts +10 -6
- package/dist/lib/utils/redis.js +71 -70
- package/dist/lib/utils/schemaConversion.d.ts +14 -0
- package/dist/lib/utils/schemaConversion.js +140 -0
- package/dist/lib/utils/transformationUtils.js +143 -5
- package/dist/mcp/externalServerManager.js +2 -2
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/mcp/toolRegistry.js +4 -8
- package/dist/neurolink.d.ts +95 -28
- package/dist/neurolink.js +479 -719
- package/dist/providers/amazonBedrock.js +2 -2
- package/dist/providers/anthropic.js +8 -0
- package/dist/providers/anthropicBaseProvider.js +8 -0
- package/dist/providers/azureOpenai.js +8 -0
- package/dist/providers/googleAiStudio.js +8 -0
- package/dist/providers/googleVertex.d.ts +3 -23
- package/dist/providers/googleVertex.js +24 -342
- package/dist/providers/huggingFace.js +8 -0
- package/dist/providers/litellm.js +8 -0
- package/dist/providers/mistral.js +8 -0
- package/dist/providers/openAI.d.ts +23 -0
- package/dist/providers/openAI.js +323 -6
- package/dist/providers/openaiCompatible.js +8 -0
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/sdk/toolRegistration.js +18 -1
- package/dist/types/common.d.ts +98 -0
- package/dist/types/conversation.d.ts +52 -2
- package/dist/types/streamTypes.d.ts +13 -6
- package/dist/types/typeAliases.d.ts +3 -2
- package/dist/utils/conversationMemory.js +3 -1
- package/dist/utils/messageBuilder.d.ts +10 -2
- package/dist/utils/messageBuilder.js +22 -1
- package/dist/utils/parameterValidation.js +6 -25
- package/dist/utils/promptRedaction.js +4 -4
- package/dist/utils/redis.d.ts +10 -6
- package/dist/utils/redis.js +71 -70
- package/dist/utils/schemaConversion.d.ts +14 -0
- package/dist/utils/schemaConversion.js +140 -0
- package/dist/utils/transformationUtils.js +143 -5
- package/package.json +3 -2
@@ -11,12 +11,35 @@ import type { NeuroLink } from "../neurolink.js";
|
|
11
11
|
export declare class OpenAIProvider extends BaseProvider {
|
12
12
|
private model;
|
13
13
|
constructor(modelName?: string, neurolink?: NeuroLink);
|
14
|
+
/**
|
15
|
+
* Check if this provider supports tool/function calling
|
16
|
+
*/
|
17
|
+
supportsTools(): boolean;
|
14
18
|
getProviderName(): AIProviderName;
|
15
19
|
getDefaultModel(): string;
|
16
20
|
/**
|
17
21
|
* Returns the Vercel AI SDK model instance for OpenAI
|
18
22
|
*/
|
19
23
|
getAISDKModel(): LanguageModelV1;
|
24
|
+
/**
|
25
|
+
* OpenAI-specific tool validation and filtering
|
26
|
+
* Filters out tools that might cause streaming issues
|
27
|
+
*/
|
28
|
+
private validateAndFilterToolsForOpenAI;
|
29
|
+
/**
|
30
|
+
* Validate Zod schema structure
|
31
|
+
*/
|
32
|
+
private validateZodSchema;
|
33
|
+
/**
|
34
|
+
* Validate tool structure for OpenAI compatibility
|
35
|
+
* More lenient validation to avoid filtering out valid tools
|
36
|
+
*/
|
37
|
+
private isValidToolStructure;
|
38
|
+
/**
|
39
|
+
* Validate tool parameters for OpenAI compatibility
|
40
|
+
* Ensures the tool has either valid Zod schema or valid JSON schema
|
41
|
+
*/
|
42
|
+
private isValidToolParameters;
|
20
43
|
handleProviderError(error: unknown): Error;
|
21
44
|
/**
|
22
45
|
* executeGenerate method removed - generation is now handled by BaseProvider.
|
@@ -10,6 +10,7 @@ import { validateApiKey, createOpenAIConfig, getProviderModel, } from "../utils/
|
|
10
10
|
import { streamAnalyticsCollector } from "../core/streamAnalytics.js";
|
11
11
|
import { buildMessagesArray } from "../utils/messageBuilder.js";
|
12
12
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
13
|
+
import { isZodSchema } from "../utils/schemaConversion.js";
|
13
14
|
// Configuration helpers - now using consolidated utility
|
14
15
|
const getOpenAIApiKey = () => {
|
15
16
|
return validateApiKey(createOpenAIConfig());
|
@@ -32,14 +33,22 @@ export class OpenAIProvider extends BaseProvider {
|
|
32
33
|
});
|
33
34
|
// Initialize model
|
34
35
|
this.model = openai(this.modelName);
|
35
|
-
logger.debug("
|
36
|
+
logger.debug("OpenAIProvider constructor called", {
|
36
37
|
model: this.modelName,
|
37
38
|
provider: this.providerName,
|
39
|
+
supportsTools: this.supportsTools(),
|
40
|
+
className: this.constructor.name,
|
38
41
|
});
|
39
42
|
}
|
40
43
|
// ===================
|
41
44
|
// ABSTRACT METHOD IMPLEMENTATIONS
|
42
45
|
// ===================
|
46
|
+
/**
|
47
|
+
* Check if this provider supports tool/function calling
|
48
|
+
*/
|
49
|
+
supportsTools() {
|
50
|
+
return true; // Re-enable tools now that we understand the issue
|
51
|
+
}
|
43
52
|
getProviderName() {
|
44
53
|
return AIProviderName.OPENAI;
|
45
54
|
}
|
@@ -52,6 +61,128 @@ export class OpenAIProvider extends BaseProvider {
|
|
52
61
|
getAISDKModel() {
|
53
62
|
return this.model;
|
54
63
|
}
|
64
|
+
/**
|
65
|
+
* OpenAI-specific tool validation and filtering
|
66
|
+
* Filters out tools that might cause streaming issues
|
67
|
+
*/
|
68
|
+
validateAndFilterToolsForOpenAI(tools) {
|
69
|
+
const validTools = {};
|
70
|
+
for (const [name, tool] of Object.entries(tools)) {
|
71
|
+
try {
|
72
|
+
// Basic validation - ensure tool has required structure
|
73
|
+
if (tool && typeof tool === "object") {
|
74
|
+
// Check if tool has description (required by OpenAI)
|
75
|
+
if (tool.description && typeof tool.description === "string") {
|
76
|
+
// Keep the original tool structure - AI SDK will handle Zod schema conversion internally
|
77
|
+
const processedTool = { ...tool };
|
78
|
+
// Validate that Zod schemas are properly structured for AI SDK processing
|
79
|
+
if (tool.parameters && isZodSchema(tool.parameters)) {
|
80
|
+
logger.debug(`OpenAI: Tool ${name} has Zod schema - AI SDK will handle conversion`);
|
81
|
+
// Basic validation that the Zod schema has the required structure
|
82
|
+
this.validateZodSchema(name, tool.parameters);
|
83
|
+
}
|
84
|
+
// Include the tool with original Zod schema for AI SDK processing
|
85
|
+
if (this.isValidToolStructure(processedTool)) {
|
86
|
+
validTools[name] = processedTool;
|
87
|
+
}
|
88
|
+
else {
|
89
|
+
logger.warn(`OpenAI: Filtering out tool with invalid structure: ${name}`, {
|
90
|
+
parametersType: typeof processedTool.parameters,
|
91
|
+
hasDescription: !!processedTool.description,
|
92
|
+
hasExecute: !!processedTool.execute,
|
93
|
+
});
|
94
|
+
}
|
95
|
+
}
|
96
|
+
else {
|
97
|
+
logger.warn(`OpenAI: Filtering out tool without description: ${name}`);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
else {
|
101
|
+
logger.warn(`OpenAI: Filtering out invalid tool: ${name}`);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
catch (error) {
|
105
|
+
logger.warn(`OpenAI: Error validating tool ${name}:`, error);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
return validTools;
|
109
|
+
}
|
110
|
+
/**
|
111
|
+
* Validate Zod schema structure
|
112
|
+
*/
|
113
|
+
validateZodSchema(toolName, schema) {
|
114
|
+
try {
|
115
|
+
const zodSchema = schema;
|
116
|
+
if (zodSchema._def && zodSchema._def.typeName) {
|
117
|
+
logger.debug(`OpenAI: Zod schema for ${toolName} appears valid`, {
|
118
|
+
typeName: zodSchema._def.typeName,
|
119
|
+
});
|
120
|
+
}
|
121
|
+
else {
|
122
|
+
logger.warn(`OpenAI: Zod schema for ${toolName} missing typeName - may cause issues`);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
catch (zodValidationError) {
|
126
|
+
logger.warn(`OpenAI: Zod schema validation failed for ${toolName}:`, zodValidationError);
|
127
|
+
// Continue anyway - let AI SDK handle it
|
128
|
+
}
|
129
|
+
}
|
130
|
+
/**
|
131
|
+
* Validate tool structure for OpenAI compatibility
|
132
|
+
* More lenient validation to avoid filtering out valid tools
|
133
|
+
*/
|
134
|
+
isValidToolStructure(tool) {
|
135
|
+
if (!tool || typeof tool !== "object") {
|
136
|
+
return false;
|
137
|
+
}
|
138
|
+
const toolObj = tool;
|
139
|
+
// Ensure tool has description and execute function
|
140
|
+
if (!toolObj.description || typeof toolObj.description !== "string") {
|
141
|
+
return false;
|
142
|
+
}
|
143
|
+
if (!toolObj.execute || typeof toolObj.execute !== "function") {
|
144
|
+
return false;
|
145
|
+
}
|
146
|
+
return this.isValidToolParameters(toolObj.parameters);
|
147
|
+
}
|
148
|
+
/**
|
149
|
+
* Validate tool parameters for OpenAI compatibility
|
150
|
+
* Ensures the tool has either valid Zod schema or valid JSON schema
|
151
|
+
*/
|
152
|
+
isValidToolParameters(parameters) {
|
153
|
+
if (!parameters) {
|
154
|
+
// For OpenAI, tools without parameters need an empty object schema
|
155
|
+
return true;
|
156
|
+
}
|
157
|
+
// Check if it's a Zod schema - these are valid
|
158
|
+
if (isZodSchema(parameters)) {
|
159
|
+
return true;
|
160
|
+
}
|
161
|
+
// Check if it's a JSON schema
|
162
|
+
if (typeof parameters !== "object" || parameters === null) {
|
163
|
+
return false;
|
164
|
+
}
|
165
|
+
const params = parameters;
|
166
|
+
// If it's a JSON schema, it should have type "object" for OpenAI
|
167
|
+
if (params.type && params.type !== "object") {
|
168
|
+
return false;
|
169
|
+
}
|
170
|
+
// OpenAI requires schemas to have properties field, even if empty
|
171
|
+
// If there's no properties field, the schema is incomplete
|
172
|
+
if (params.type === "object" && !params.properties) {
|
173
|
+
logger.warn(`Tool parameter schema missing properties field:`, params);
|
174
|
+
return false;
|
175
|
+
}
|
176
|
+
// If properties exist, they should be an object
|
177
|
+
if (params.properties && typeof params.properties !== "object") {
|
178
|
+
return false;
|
179
|
+
}
|
180
|
+
// If required exists, it should be an array
|
181
|
+
if (params.required && !Array.isArray(params.required)) {
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
return true;
|
185
|
+
}
|
55
186
|
handleProviderError(error) {
|
56
187
|
if (error instanceof TimeoutError) {
|
57
188
|
throw new NetworkError(error.message, this.providerName);
|
@@ -90,9 +221,48 @@ export class OpenAIProvider extends BaseProvider {
|
|
90
221
|
try {
|
91
222
|
// Get tools consistently with generate method
|
92
223
|
const shouldUseTools = !options.disableTools && this.supportsTools();
|
93
|
-
const
|
224
|
+
const allTools = shouldUseTools ? await this.getAllTools() : {};
|
225
|
+
// OpenAI-specific fix: Validate tools format and filter out problematic ones
|
226
|
+
let tools = this.validateAndFilterToolsForOpenAI(allTools);
|
227
|
+
// OpenAI has limits on the number of tools per request - limit to 3 tools max for testing
|
228
|
+
const MAX_TOOLS = 3;
|
229
|
+
if (Object.keys(tools).length > MAX_TOOLS) {
|
230
|
+
logger.warn(`OpenAI: Too many tools (${Object.keys(tools).length}), limiting to ${MAX_TOOLS} tools`);
|
231
|
+
const toolEntries = Object.entries(tools);
|
232
|
+
tools = Object.fromEntries(toolEntries.slice(0, MAX_TOOLS));
|
233
|
+
}
|
234
|
+
// Count tools with Zod schemas for debugging
|
235
|
+
const zodToolsCount = Object.values(allTools).filter((tool) => tool &&
|
236
|
+
typeof tool === "object" &&
|
237
|
+
tool.parameters &&
|
238
|
+
isZodSchema(tool.parameters)).length;
|
239
|
+
logger.info("OpenAI streaming tools", {
|
240
|
+
shouldUseTools,
|
241
|
+
allToolsCount: Object.keys(allTools).length,
|
242
|
+
filteredToolsCount: Object.keys(tools).length,
|
243
|
+
zodToolsCount,
|
244
|
+
toolNames: Object.keys(tools),
|
245
|
+
filteredOutTools: Object.keys(allTools).filter((name) => !tools[name]),
|
246
|
+
});
|
94
247
|
// Build message array from options
|
95
248
|
const messages = buildMessagesArray(options);
|
249
|
+
// Debug the actual request being sent to OpenAI
|
250
|
+
logger.debug(`OpenAI: streamText request parameters:`, {
|
251
|
+
modelName: this.modelName,
|
252
|
+
messagesCount: messages.length,
|
253
|
+
temperature: options.temperature,
|
254
|
+
maxTokens: options.maxTokens,
|
255
|
+
toolsCount: Object.keys(tools).length,
|
256
|
+
toolChoice: shouldUseTools && Object.keys(tools).length > 0 ? "auto" : "none",
|
257
|
+
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
258
|
+
firstToolExample: Object.keys(tools).length > 0
|
259
|
+
? {
|
260
|
+
name: Object.keys(tools)[0],
|
261
|
+
description: tools[Object.keys(tools)[0]]?.description,
|
262
|
+
parametersType: typeof tools[Object.keys(tools)[0]]?.parameters,
|
263
|
+
}
|
264
|
+
: "no-tools",
|
265
|
+
});
|
96
266
|
const result = await streamText({
|
97
267
|
model: this.model,
|
98
268
|
messages: messages,
|
@@ -100,19 +270,166 @@ export class OpenAIProvider extends BaseProvider {
|
|
100
270
|
maxTokens: options.maxTokens, // No default limit - unlimited unless specified
|
101
271
|
tools,
|
102
272
|
maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
|
103
|
-
toolChoice: shouldUseTools ? "auto" : "none",
|
273
|
+
toolChoice: shouldUseTools && Object.keys(tools).length > 0 ? "auto" : "none",
|
104
274
|
abortSignal: timeoutController?.controller.signal,
|
275
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
276
|
+
logger.info("Tool execution completed", { toolResults, toolCalls });
|
277
|
+
// Handle tool execution storage
|
278
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
279
|
+
logger.warn("[OpenAIProvider] Failed to store tool executions", {
|
280
|
+
provider: this.providerName,
|
281
|
+
error: error instanceof Error ? error.message : String(error),
|
282
|
+
});
|
283
|
+
});
|
284
|
+
},
|
105
285
|
});
|
106
286
|
timeoutController?.cleanup();
|
107
|
-
//
|
108
|
-
|
287
|
+
// Debug the actual result structure
|
288
|
+
logger.debug(`OpenAI: streamText result structure:`, {
|
289
|
+
resultKeys: Object.keys(result),
|
290
|
+
hasTextStream: !!result.textStream,
|
291
|
+
hasToolCalls: !!result.toolCalls,
|
292
|
+
hasToolResults: !!result.toolResults,
|
293
|
+
resultType: typeof result,
|
294
|
+
});
|
295
|
+
// Transform string stream to content object stream using fullStream
|
296
|
+
const transformedStream = async function* () {
|
297
|
+
try {
|
298
|
+
logger.debug(`OpenAI: Starting stream transformation`, {
|
299
|
+
hasTextStream: !!result.textStream,
|
300
|
+
hasFullStream: !!result.fullStream,
|
301
|
+
resultKeys: Object.keys(result),
|
302
|
+
toolsEnabled: shouldUseTools,
|
303
|
+
toolsCount: Object.keys(tools).length,
|
304
|
+
});
|
305
|
+
let chunkCount = 0;
|
306
|
+
let contentYielded = 0;
|
307
|
+
// Try fullStream first (handles both text and tool calls), fallback to textStream
|
308
|
+
const streamToUse = result.fullStream || result.textStream;
|
309
|
+
if (!streamToUse) {
|
310
|
+
logger.error("OpenAI: No stream available in result", {
|
311
|
+
resultKeys: Object.keys(result),
|
312
|
+
});
|
313
|
+
return;
|
314
|
+
}
|
315
|
+
logger.debug(`OpenAI: Stream source selected:`, {
|
316
|
+
usingFullStream: !!result.fullStream,
|
317
|
+
usingTextStream: !!result.textStream && !result.fullStream,
|
318
|
+
streamSourceType: result.fullStream ? "fullStream" : "textStream",
|
319
|
+
});
|
320
|
+
for await (const chunk of streamToUse) {
|
321
|
+
chunkCount++;
|
322
|
+
logger.debug(`OpenAI: Processing chunk ${chunkCount}:`, {
|
323
|
+
chunkType: typeof chunk,
|
324
|
+
chunkValue: typeof chunk === "string"
|
325
|
+
? chunk.substring(0, 50)
|
326
|
+
: "not-string",
|
327
|
+
chunkKeys: chunk && typeof chunk === "object"
|
328
|
+
? Object.keys(chunk)
|
329
|
+
: "not-object",
|
330
|
+
hasText: chunk && typeof chunk === "object" && "text" in chunk,
|
331
|
+
hasTextDelta: chunk && typeof chunk === "object" && "textDelta" in chunk,
|
332
|
+
hasType: chunk && typeof chunk === "object" && "type" in chunk,
|
333
|
+
chunkTypeValue: chunk && typeof chunk === "object" && "type" in chunk
|
334
|
+
? chunk.type
|
335
|
+
: "no-type",
|
336
|
+
});
|
337
|
+
let contentToYield = null;
|
338
|
+
// Handle different chunk types from fullStream
|
339
|
+
if (chunk && typeof chunk === "object") {
|
340
|
+
// Log the full chunk structure for debugging (debug mode only)
|
341
|
+
if (process.env.NEUROLINK_DEBUG === "true") {
|
342
|
+
logger.debug(`OpenAI: Full chunk structure:`, {
|
343
|
+
chunkKeys: Object.keys(chunk),
|
344
|
+
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
345
|
+
});
|
346
|
+
}
|
347
|
+
if ("type" in chunk && chunk.type === "error") {
|
348
|
+
// Handle error chunks when tools are enabled
|
349
|
+
const errorChunk = chunk;
|
350
|
+
logger.error(`OpenAI: Error chunk received:`, {
|
351
|
+
errorType: errorChunk.type,
|
352
|
+
errorDetails: errorChunk.error,
|
353
|
+
fullChunk: JSON.stringify(chunk),
|
354
|
+
});
|
355
|
+
// Throw a more descriptive error for tool-related issues
|
356
|
+
const errorMessage = errorChunk.error &&
|
357
|
+
typeof errorChunk.error === "object" &&
|
358
|
+
"message" in errorChunk.error
|
359
|
+
? String(errorChunk.error.message)
|
360
|
+
: "OpenAI API error when tools are enabled";
|
361
|
+
throw new Error(`OpenAI streaming error with tools: ${errorMessage}. Try disabling tools with --disableTools`);
|
362
|
+
}
|
363
|
+
else if ("type" in chunk &&
|
364
|
+
chunk.type === "text-delta" &&
|
365
|
+
"textDelta" in chunk) {
|
366
|
+
// Text delta from fullStream
|
367
|
+
contentToYield = chunk.textDelta;
|
368
|
+
logger.debug(`OpenAI: Found text-delta:`, {
|
369
|
+
textDelta: contentToYield,
|
370
|
+
});
|
371
|
+
}
|
372
|
+
else if ("text" in chunk) {
|
373
|
+
// Direct text chunk
|
374
|
+
contentToYield = chunk.text;
|
375
|
+
logger.debug(`OpenAI: Found direct text:`, {
|
376
|
+
text: contentToYield,
|
377
|
+
});
|
378
|
+
}
|
379
|
+
else {
|
380
|
+
// Log unhandled chunks in debug mode only
|
381
|
+
if (process.env.NEUROLINK_DEBUG === "true") {
|
382
|
+
logger.debug(`OpenAI: Unhandled object chunk:`, {
|
383
|
+
chunkKeys: Object.keys(chunk),
|
384
|
+
chunkType: chunk.type || "no-type",
|
385
|
+
fullChunk: JSON.stringify(chunk).substring(0, 500),
|
386
|
+
});
|
387
|
+
}
|
388
|
+
}
|
389
|
+
}
|
390
|
+
else if (typeof chunk === "string") {
|
391
|
+
// Direct string chunk from textStream
|
392
|
+
contentToYield = chunk;
|
393
|
+
logger.debug(`OpenAI: Found string chunk:`, {
|
394
|
+
content: contentToYield,
|
395
|
+
});
|
396
|
+
}
|
397
|
+
else {
|
398
|
+
logger.warn(`OpenAI: Unhandled chunk type:`, {
|
399
|
+
type: typeof chunk,
|
400
|
+
value: String(chunk).substring(0, 100),
|
401
|
+
});
|
402
|
+
}
|
403
|
+
if (contentToYield) {
|
404
|
+
contentYielded++;
|
405
|
+
logger.debug(`OpenAI: Yielding content ${contentYielded}:`, {
|
406
|
+
content: contentToYield.substring(0, 50),
|
407
|
+
length: contentToYield.length,
|
408
|
+
});
|
409
|
+
yield { content: contentToYield };
|
410
|
+
}
|
411
|
+
}
|
412
|
+
logger.debug(`OpenAI: Stream transformation completed`, {
|
413
|
+
totalChunks: chunkCount,
|
414
|
+
contentYielded,
|
415
|
+
success: contentYielded > 0,
|
416
|
+
});
|
417
|
+
if (contentYielded === 0) {
|
418
|
+
logger.warn(`OpenAI: No content was yielded from stream despite processing ${chunkCount} chunks`);
|
419
|
+
}
|
420
|
+
}
|
421
|
+
catch (streamError) {
|
422
|
+
logger.error(`OpenAI: Stream transformation error:`, streamError);
|
423
|
+
throw streamError;
|
424
|
+
}
|
425
|
+
};
|
109
426
|
// Create analytics promise that resolves after stream completion
|
110
427
|
const analyticsPromise = streamAnalyticsCollector.createAnalytics(this.providerName, this.modelName, result, Date.now() - startTime, {
|
111
428
|
requestId: `openai-stream-${Date.now()}`,
|
112
429
|
streamingMode: true,
|
113
430
|
});
|
114
431
|
return {
|
115
|
-
stream: transformedStream,
|
432
|
+
stream: transformedStream(),
|
116
433
|
provider: this.providerName,
|
117
434
|
model: this.modelName,
|
118
435
|
analytics: analyticsPromise,
|
@@ -166,6 +166,14 @@ export class OpenAICompatibleProvider extends BaseProvider {
|
|
166
166
|
tools: options.tools,
|
167
167
|
toolChoice: "auto",
|
168
168
|
abortSignal: timeoutController?.controller.signal,
|
169
|
+
onStepFinish: ({ toolCalls, toolResults }) => {
|
170
|
+
this.handleToolExecutionStorage(toolCalls, toolResults, options).catch((error) => {
|
171
|
+
logger.warn("[OpenAiCompatibleProvider] Failed to store tool executions", {
|
172
|
+
provider: this.providerName,
|
173
|
+
error: error instanceof Error ? error.message : String(error),
|
174
|
+
});
|
175
|
+
});
|
176
|
+
},
|
169
177
|
});
|
170
178
|
timeoutController?.cleanup();
|
171
179
|
// Transform stream to match StreamResult interface
|
@@ -131,7 +131,7 @@ export declare class SageMakerLanguageModel implements LanguageModelV1 {
|
|
131
131
|
provider: string;
|
132
132
|
specificationVersion: string;
|
133
133
|
endpointName: string;
|
134
|
-
modelType: "
|
134
|
+
modelType: "custom" | "huggingface" | "mistral" | "claude" | "llama" | "jumpstart" | undefined;
|
135
135
|
region: string;
|
136
136
|
};
|
137
137
|
/**
|
@@ -178,7 +178,7 @@ export declare class SageMakerLanguageModel implements LanguageModelV1 {
|
|
178
178
|
provider: string;
|
179
179
|
specificationVersion: string;
|
180
180
|
endpointName: string;
|
181
|
-
modelType: "
|
181
|
+
modelType: "custom" | "huggingface" | "mistral" | "claude" | "llama" | "jumpstart" | undefined;
|
182
182
|
region: string;
|
183
183
|
};
|
184
184
|
}
|
@@ -5,6 +5,7 @@
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
6
6
|
import { createMCPServerInfo } from "../utils/mcpDefaults.js";
|
7
7
|
import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
|
8
|
+
import { convertZodToJsonSchema, isZodSchema, } from "../utils/schemaConversion.js";
|
8
9
|
/**
|
9
10
|
* Configuration constants for tool validation
|
10
11
|
*/
|
@@ -73,7 +74,9 @@ export function createMCPServerFromTools(serverId, tools, metadata) {
|
|
73
74
|
mcpTools.push({
|
74
75
|
name,
|
75
76
|
description: tool.description || name,
|
76
|
-
inputSchema:
|
77
|
+
inputSchema: tool.parameters
|
78
|
+
? convertSchemaToJsonSchema(tool.parameters)
|
79
|
+
: {},
|
77
80
|
});
|
78
81
|
}
|
79
82
|
// SMART DEFAULTS: Use utility to eliminate manual MCPServerInfo creation
|
@@ -89,6 +92,20 @@ export function createMCPServerFromTools(serverId, tools, metadata) {
|
|
89
92
|
isCustomTool: false,
|
90
93
|
});
|
91
94
|
}
|
95
|
+
/**
|
96
|
+
* Helper to convert schema parameters to JSON Schema format
|
97
|
+
*/
|
98
|
+
function convertSchemaToJsonSchema(schema) {
|
99
|
+
if (isZodSchema(schema)) {
|
100
|
+
return convertZodToJsonSchema(schema);
|
101
|
+
}
|
102
|
+
// If it's already a JSON Schema object, return as-is
|
103
|
+
if (schema && typeof schema === "object") {
|
104
|
+
return schema;
|
105
|
+
}
|
106
|
+
// Return empty object if no schema
|
107
|
+
return {};
|
108
|
+
}
|
92
109
|
/**
|
93
110
|
* Helper to create a tool with type safety
|
94
111
|
*/
|
@@ -73,3 +73,101 @@ export declare function getErrorMessage(error: unknown): string;
|
|
73
73
|
* Safe error conversion
|
74
74
|
*/
|
75
75
|
export declare function toErrorInfo(error: unknown): ErrorInfo;
|
76
|
+
/**
|
77
|
+
* NeuroLink Native Event System Types
|
78
|
+
*/
|
79
|
+
/**
|
80
|
+
* Tool execution event for real-time streaming
|
81
|
+
*/
|
82
|
+
export interface ToolExecutionEvent {
|
83
|
+
type: "tool:start" | "tool:end";
|
84
|
+
tool: string;
|
85
|
+
input?: unknown;
|
86
|
+
result?: unknown;
|
87
|
+
error?: string;
|
88
|
+
timestamp: number;
|
89
|
+
duration?: number;
|
90
|
+
executionId: string;
|
91
|
+
}
|
92
|
+
/**
|
93
|
+
* Tool execution summary for completed executions
|
94
|
+
*/
|
95
|
+
export interface ToolExecutionSummary {
|
96
|
+
tool: string;
|
97
|
+
startTime: number;
|
98
|
+
endTime: number;
|
99
|
+
duration: number;
|
100
|
+
success: boolean;
|
101
|
+
result?: unknown;
|
102
|
+
error?: string;
|
103
|
+
executionId: string;
|
104
|
+
metadata?: {
|
105
|
+
serverId?: string;
|
106
|
+
toolCategory?: "direct" | "custom" | "mcp";
|
107
|
+
isExternal?: boolean;
|
108
|
+
};
|
109
|
+
}
|
110
|
+
/**
|
111
|
+
* Stream event types for real-time communication
|
112
|
+
*/
|
113
|
+
export interface StreamEvent {
|
114
|
+
type: "stream:chunk" | "stream:complete" | "stream:error";
|
115
|
+
content?: string;
|
116
|
+
metadata?: JsonObject;
|
117
|
+
timestamp: number;
|
118
|
+
}
|
119
|
+
/**
|
120
|
+
* Enhanced NeuroLink event types
|
121
|
+
* Flexible interface to support both typed and legacy event patterns
|
122
|
+
*/
|
123
|
+
export interface NeuroLinkEvents {
|
124
|
+
"tool:start": unknown;
|
125
|
+
"tool:end": unknown;
|
126
|
+
"stream:start": unknown;
|
127
|
+
"stream:end": unknown;
|
128
|
+
"stream:chunk": unknown;
|
129
|
+
"stream:complete": unknown;
|
130
|
+
"stream:error": unknown;
|
131
|
+
"generation:start": unknown;
|
132
|
+
"generation:end": unknown;
|
133
|
+
"response:start": unknown;
|
134
|
+
"response:end": unknown;
|
135
|
+
"externalMCP:serverConnected": unknown;
|
136
|
+
"externalMCP:serverDisconnected": unknown;
|
137
|
+
"externalMCP:serverFailed": unknown;
|
138
|
+
"externalMCP:toolDiscovered": unknown;
|
139
|
+
"externalMCP:toolRemoved": unknown;
|
140
|
+
"externalMCP:serverAdded": unknown;
|
141
|
+
"externalMCP:serverRemoved": unknown;
|
142
|
+
"tools-register:start": unknown;
|
143
|
+
"tools-register:end": unknown;
|
144
|
+
connected: unknown;
|
145
|
+
message: unknown;
|
146
|
+
error: unknown;
|
147
|
+
log: unknown;
|
148
|
+
[key: string]: unknown;
|
149
|
+
}
|
150
|
+
/**
|
151
|
+
* TypeScript utility for typed EventEmitter
|
152
|
+
* Flexible interface to support both typed and legacy event patterns
|
153
|
+
*/
|
154
|
+
export interface TypedEventEmitter<TEvents extends Record<string, unknown>> {
|
155
|
+
on<K extends keyof TEvents>(event: K, listener: (...args: unknown[]) => void): this;
|
156
|
+
emit<K extends keyof TEvents>(event: K, ...args: unknown[]): boolean;
|
157
|
+
off<K extends keyof TEvents>(event: K, listener: (...args: unknown[]) => void): this;
|
158
|
+
removeAllListeners<K extends keyof TEvents>(event?: K): this;
|
159
|
+
listenerCount<K extends keyof TEvents>(event: K): number;
|
160
|
+
listeners<K extends keyof TEvents>(event: K): Array<(...args: unknown[]) => void>;
|
161
|
+
}
|
162
|
+
/**
|
163
|
+
* Tool execution context for tracking
|
164
|
+
*/
|
165
|
+
export interface ToolExecutionContext {
|
166
|
+
executionId: string;
|
167
|
+
tool: string;
|
168
|
+
startTime: number;
|
169
|
+
endTime?: number;
|
170
|
+
result?: unknown;
|
171
|
+
error?: string;
|
172
|
+
metadata?: JsonObject;
|
173
|
+
}
|
@@ -32,6 +32,8 @@ export interface SessionMemory {
|
|
32
32
|
sessionId: string;
|
33
33
|
/** User identifier (optional) */
|
34
34
|
userId?: string;
|
35
|
+
/** Auto-generated conversation title (created on first user message) */
|
36
|
+
title?: string;
|
35
37
|
/** Direct message storage - ready for immediate AI consumption */
|
36
38
|
messages: ChatMessage[];
|
37
39
|
/** When this session was created */
|
@@ -61,10 +63,26 @@ export interface ConversationMemoryStats {
|
|
61
63
|
* Chat message format for conversation history
|
62
64
|
*/
|
63
65
|
export interface ChatMessage {
|
64
|
-
/** Role of the message
|
65
|
-
role: "user" | "assistant" | "system";
|
66
|
+
/** Role/type of the message */
|
67
|
+
role: "user" | "assistant" | "system" | "tool_call" | "tool_result";
|
66
68
|
/** Content of the message */
|
67
69
|
content: string;
|
70
|
+
/** Message ID (optional) - for new format */
|
71
|
+
id?: string;
|
72
|
+
/** Timestamp (optional) - for new format */
|
73
|
+
timestamp?: string;
|
74
|
+
/** Tool name (optional) - for tool_call/tool_result messages */
|
75
|
+
tool?: string;
|
76
|
+
/** Tool arguments (optional) - for tool_call messages */
|
77
|
+
args?: Record<string, unknown>;
|
78
|
+
/** Tool result (optional) - for tool_result messages */
|
79
|
+
result?: {
|
80
|
+
success?: boolean;
|
81
|
+
expression?: string;
|
82
|
+
result?: unknown;
|
83
|
+
type?: string;
|
84
|
+
error?: string;
|
85
|
+
};
|
68
86
|
}
|
69
87
|
/**
|
70
88
|
* Content format for multimodal messages (used internally)
|
@@ -129,6 +147,36 @@ export type SessionIdentifier = {
|
|
129
147
|
sessionId: string;
|
130
148
|
userId?: string;
|
131
149
|
};
|
150
|
+
/**
|
151
|
+
* Lightweight session metadata for efficient session listing
|
152
|
+
* Contains only essential information without heavy message arrays
|
153
|
+
*/
|
154
|
+
export interface SessionMetadata {
|
155
|
+
id: string;
|
156
|
+
title: string;
|
157
|
+
createdAt: string;
|
158
|
+
updatedAt: string;
|
159
|
+
}
|
160
|
+
/**
|
161
|
+
* New Redis conversation storage object format
|
162
|
+
* Contains conversation metadata and history in a single object
|
163
|
+
*/
|
164
|
+
export type RedisConversationObject = {
|
165
|
+
/** Unique conversation identifier (UUID v4) */
|
166
|
+
id: string;
|
167
|
+
/** Auto-generated conversation title */
|
168
|
+
title: string;
|
169
|
+
/** Session identifier */
|
170
|
+
sessionId: string;
|
171
|
+
/** User identifier */
|
172
|
+
userId: string;
|
173
|
+
/** When this conversation was first created */
|
174
|
+
createdAt: string;
|
175
|
+
/** When this conversation was last updated */
|
176
|
+
updatedAt: string;
|
177
|
+
/** Array of conversation messages */
|
178
|
+
messages: ChatMessage[];
|
179
|
+
};
|
132
180
|
/**
|
133
181
|
* Redis storage configuration
|
134
182
|
*/
|
@@ -143,6 +191,8 @@ export type RedisStorageConfig = {
|
|
143
191
|
db?: number;
|
144
192
|
/** Key prefix for Redis keys (default: 'neurolink:conversation:') */
|
145
193
|
keyPrefix?: string;
|
194
|
+
/** Key prefix for user sessions mapping (default: derived from keyPrefix) */
|
195
|
+
userSessionsKeyPrefix?: string;
|
146
196
|
/** Time-to-live in seconds (default: 86400, 24 hours) */
|
147
197
|
ttl?: number;
|
148
198
|
/** Additional Redis connection options */
|