@juspay/neurolink 5.1.0 → 5.3.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 +21 -9
- package/README.md +123 -126
- package/dist/agent/direct-tools.d.ts +6 -6
- package/dist/cli/commands/config.d.ts +3 -3
- package/dist/cli/commands/mcp.js +8 -7
- package/dist/cli/factories/command-factory.d.ts +4 -0
- package/dist/cli/factories/command-factory.js +63 -8
- package/dist/cli/index.js +87 -140
- package/dist/core/base-provider.d.ts +423 -0
- package/dist/core/base-provider.js +376 -0
- package/dist/core/constants.d.ts +2 -1
- package/dist/core/constants.js +2 -1
- package/dist/core/dynamic-models.d.ts +6 -6
- package/dist/core/evaluation.d.ts +19 -80
- package/dist/core/evaluation.js +185 -484
- package/dist/core/factory.d.ts +3 -3
- package/dist/core/factory.js +31 -91
- package/dist/core/service-registry.d.ts +47 -0
- package/dist/core/service-registry.js +112 -0
- package/dist/core/types.d.ts +8 -1
- package/dist/factories/compatibility-factory.js +1 -1
- package/dist/factories/provider-factory.d.ts +72 -0
- package/dist/factories/provider-factory.js +144 -0
- package/dist/factories/provider-registry.d.ts +38 -0
- package/dist/factories/provider-registry.js +107 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -4
- package/dist/lib/agent/direct-tools.d.ts +6 -6
- package/dist/lib/core/base-provider.d.ts +423 -0
- package/dist/lib/core/base-provider.js +376 -0
- package/dist/lib/core/constants.d.ts +2 -1
- package/dist/lib/core/constants.js +2 -1
- package/dist/lib/core/dynamic-models.d.ts +6 -6
- package/dist/lib/core/evaluation.d.ts +19 -80
- package/dist/lib/core/evaluation.js +185 -484
- package/dist/lib/core/factory.d.ts +3 -3
- package/dist/lib/core/factory.js +30 -91
- package/dist/lib/core/service-registry.d.ts +47 -0
- package/dist/lib/core/service-registry.js +112 -0
- package/dist/lib/core/types.d.ts +8 -1
- package/dist/lib/factories/compatibility-factory.js +1 -1
- package/dist/lib/factories/provider-factory.d.ts +72 -0
- package/dist/lib/factories/provider-factory.js +144 -0
- package/dist/lib/factories/provider-registry.d.ts +38 -0
- package/dist/lib/factories/provider-registry.js +107 -0
- package/dist/lib/index.d.ts +4 -3
- package/dist/lib/index.js +2 -4
- package/dist/lib/mcp/client.d.ts +1 -0
- package/dist/lib/mcp/client.js +1 -0
- package/dist/lib/mcp/config.js +28 -3
- package/dist/lib/mcp/context-manager.d.ts +1 -0
- package/dist/lib/mcp/context-manager.js +8 -4
- package/dist/lib/mcp/function-calling.d.ts +13 -0
- package/dist/lib/mcp/function-calling.js +134 -35
- package/dist/lib/mcp/initialize-tools.d.ts +1 -1
- package/dist/lib/mcp/initialize-tools.js +45 -1
- package/dist/lib/mcp/initialize.js +16 -6
- package/dist/lib/mcp/neurolink-mcp-client.d.ts +1 -0
- package/dist/lib/mcp/neurolink-mcp-client.js +21 -5
- package/dist/lib/mcp/servers/agent/direct-tools-server.d.ts +8 -0
- package/dist/lib/mcp/servers/agent/direct-tools-server.js +109 -0
- package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +3 -1
- package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/lib/mcp/unified-registry.d.ts +4 -0
- package/dist/lib/mcp/unified-registry.js +42 -9
- package/dist/lib/neurolink.d.ts +156 -117
- package/dist/lib/neurolink.js +619 -404
- package/dist/lib/providers/amazon-bedrock.d.ts +32 -0
- package/dist/lib/providers/amazon-bedrock.js +143 -0
- package/dist/lib/providers/analytics-helper.js +7 -4
- package/dist/lib/providers/anthropic-baseprovider.d.ts +23 -0
- package/dist/lib/providers/anthropic-baseprovider.js +114 -0
- package/dist/lib/providers/anthropic.d.ts +19 -43
- package/dist/lib/providers/anthropic.js +82 -306
- package/dist/lib/providers/azure-openai.d.ts +20 -0
- package/dist/lib/providers/azure-openai.js +89 -0
- package/dist/lib/providers/function-calling-provider.d.ts +64 -2
- package/dist/lib/providers/function-calling-provider.js +208 -9
- package/dist/lib/providers/google-ai-studio.d.ts +23 -0
- package/dist/lib/providers/google-ai-studio.js +107 -0
- package/dist/lib/providers/google-vertex.d.ts +47 -0
- package/dist/lib/providers/google-vertex.js +205 -0
- package/dist/lib/providers/huggingFace.d.ts +32 -25
- package/dist/lib/providers/huggingFace.js +97 -431
- package/dist/lib/providers/index.d.ts +9 -9
- package/dist/lib/providers/index.js +9 -9
- package/dist/lib/providers/mcp-provider.js +24 -5
- package/dist/lib/providers/mistral.d.ts +42 -0
- package/dist/lib/providers/mistral.js +160 -0
- package/dist/lib/providers/ollama.d.ts +52 -36
- package/dist/lib/providers/ollama.js +297 -520
- package/dist/lib/providers/openAI.d.ts +19 -18
- package/dist/lib/providers/openAI.js +76 -275
- package/dist/lib/sdk/tool-extension.d.ts +181 -0
- package/dist/lib/sdk/tool-extension.js +283 -0
- package/dist/lib/sdk/tool-registration.d.ts +95 -0
- package/dist/lib/sdk/tool-registration.js +167 -0
- package/dist/lib/services/streaming/streaming-manager.js +11 -10
- package/dist/lib/services/websocket/websocket-server.js +12 -11
- package/dist/lib/telemetry/telemetry-service.js +8 -7
- package/dist/lib/types/generate-types.d.ts +1 -0
- package/dist/lib/types/mcp-types.d.ts +116 -0
- package/dist/lib/types/mcp-types.js +5 -0
- package/dist/lib/types/stream-types.d.ts +30 -18
- package/dist/lib/types/universal-provider-options.d.ts +87 -0
- package/dist/lib/types/universal-provider-options.js +53 -0
- package/dist/mcp/client.d.ts +1 -0
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/config.js +28 -3
- package/dist/mcp/context-manager.d.ts +1 -0
- package/dist/mcp/context-manager.js +8 -4
- package/dist/mcp/function-calling.d.ts +13 -0
- package/dist/mcp/function-calling.js +134 -35
- package/dist/mcp/initialize-tools.d.ts +1 -1
- package/dist/mcp/initialize-tools.js +45 -1
- package/dist/mcp/initialize.js +16 -6
- package/dist/mcp/neurolink-mcp-client.d.ts +1 -0
- package/dist/mcp/neurolink-mcp-client.js +21 -5
- package/dist/mcp/servers/agent/direct-tools-server.d.ts +8 -0
- package/dist/mcp/servers/agent/direct-tools-server.js +109 -0
- package/dist/mcp/servers/ai-providers/ai-core-server.js +3 -1
- package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
- package/dist/mcp/unified-registry.d.ts +4 -0
- package/dist/mcp/unified-registry.js +42 -9
- package/dist/neurolink.d.ts +156 -117
- package/dist/neurolink.js +619 -404
- package/dist/providers/amazon-bedrock.d.ts +32 -0
- package/dist/providers/amazon-bedrock.js +143 -0
- package/dist/providers/analytics-helper.js +7 -4
- package/dist/providers/anthropic-baseprovider.d.ts +23 -0
- package/dist/providers/anthropic-baseprovider.js +114 -0
- package/dist/providers/anthropic.d.ts +19 -43
- package/dist/providers/anthropic.js +81 -305
- package/dist/providers/azure-openai.d.ts +20 -0
- package/dist/providers/azure-openai.js +89 -0
- package/dist/providers/function-calling-provider.d.ts +64 -2
- package/dist/providers/function-calling-provider.js +208 -9
- package/dist/providers/google-ai-studio.d.ts +23 -0
- package/dist/providers/google-ai-studio.js +108 -0
- package/dist/providers/google-vertex.d.ts +47 -0
- package/dist/providers/google-vertex.js +205 -0
- package/dist/providers/huggingFace.d.ts +32 -25
- package/dist/providers/huggingFace.js +96 -430
- package/dist/providers/index.d.ts +9 -9
- package/dist/providers/index.js +9 -9
- package/dist/providers/mcp-provider.js +24 -5
- package/dist/providers/mistral.d.ts +42 -0
- package/dist/providers/mistral.js +160 -0
- package/dist/providers/ollama.d.ts +52 -36
- package/dist/providers/ollama.js +297 -519
- package/dist/providers/openAI.d.ts +19 -18
- package/dist/providers/openAI.js +76 -276
- package/dist/sdk/tool-extension.d.ts +181 -0
- package/dist/sdk/tool-extension.js +283 -0
- package/dist/sdk/tool-registration.d.ts +95 -0
- package/dist/sdk/tool-registration.js +168 -0
- package/dist/services/streaming/streaming-manager.js +11 -10
- package/dist/services/websocket/websocket-server.js +12 -11
- package/dist/telemetry/telemetry-service.js +8 -7
- package/dist/types/generate-types.d.ts +1 -0
- package/dist/types/mcp-types.d.ts +116 -0
- package/dist/types/mcp-types.js +5 -0
- package/dist/types/stream-types.d.ts +30 -18
- package/dist/types/universal-provider-options.d.ts +87 -0
- package/dist/types/universal-provider-options.js +53 -0
- package/package.json +12 -5
- package/dist/lib/providers/agent-enhanced-provider.d.ts +0 -93
- package/dist/lib/providers/agent-enhanced-provider.js +0 -605
- package/dist/lib/providers/amazonBedrock.d.ts +0 -28
- package/dist/lib/providers/amazonBedrock.js +0 -364
- package/dist/lib/providers/azureOpenAI.d.ts +0 -42
- package/dist/lib/providers/azureOpenAI.js +0 -347
- package/dist/lib/providers/googleAIStudio.d.ts +0 -42
- package/dist/lib/providers/googleAIStudio.js +0 -364
- package/dist/lib/providers/googleVertexAI.d.ts +0 -34
- package/dist/lib/providers/googleVertexAI.js +0 -547
- package/dist/lib/providers/mistralAI.d.ts +0 -37
- package/dist/lib/providers/mistralAI.js +0 -325
- package/dist/providers/agent-enhanced-provider.d.ts +0 -93
- package/dist/providers/agent-enhanced-provider.js +0 -606
- package/dist/providers/amazonBedrock.d.ts +0 -28
- package/dist/providers/amazonBedrock.js +0 -364
- package/dist/providers/azureOpenAI.d.ts +0 -42
- package/dist/providers/azureOpenAI.js +0 -348
- package/dist/providers/googleAIStudio.d.ts +0 -42
- package/dist/providers/googleAIStudio.js +0 -364
- package/dist/providers/googleVertexAI.d.ts +0 -34
- package/dist/providers/googleVertexAI.js +0 -547
- package/dist/providers/mistralAI.d.ts +0 -37
- package/dist/providers/mistralAI.js +0 -325
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuroLink SDK Tool Extension System
|
|
3
|
+
* Allows developers to register custom tools that integrate with AI providers
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { tool as createAISDKTool } from "ai";
|
|
7
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
8
|
+
import { logger } from "../utils/logger.js";
|
|
9
|
+
/**
|
|
10
|
+
* Converts a custom tool to Vercel AI SDK format
|
|
11
|
+
*/
|
|
12
|
+
export function convertToAISDKTool(name, customTool) {
|
|
13
|
+
// Convert parameters to JSON schema if needed
|
|
14
|
+
let parametersSchema = {};
|
|
15
|
+
let zodSchema;
|
|
16
|
+
if (customTool.parameters) {
|
|
17
|
+
if ("parse" in customTool.parameters && "_def" in customTool.parameters) {
|
|
18
|
+
// It's a Zod schema
|
|
19
|
+
zodSchema = customTool.parameters;
|
|
20
|
+
parametersSchema = zodToJsonSchema(zodSchema);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// It's already a JSON schema - convert to Zod
|
|
24
|
+
parametersSchema = customTool.parameters;
|
|
25
|
+
zodSchema = z.object({}).passthrough(); // Allow any properties
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
zodSchema = z.object({});
|
|
30
|
+
}
|
|
31
|
+
return createAISDKTool({
|
|
32
|
+
description: customTool.description,
|
|
33
|
+
parameters: zodSchema,
|
|
34
|
+
execute: async (args) => {
|
|
35
|
+
try {
|
|
36
|
+
// Apply timeout if configured
|
|
37
|
+
if (customTool.config?.timeout) {
|
|
38
|
+
return await Promise.race([
|
|
39
|
+
customTool.execute(args),
|
|
40
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Tool ${name} timed out`)), customTool.config.timeout)),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
return await customTool.execute(args);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error(`Tool ${name} execution failed:`, error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Tool registry for managing custom tools
|
|
54
|
+
*/
|
|
55
|
+
export class ToolRegistry {
|
|
56
|
+
tools = new Map();
|
|
57
|
+
middleware = [];
|
|
58
|
+
permissions = {};
|
|
59
|
+
rateLimits = new Map();
|
|
60
|
+
/**
|
|
61
|
+
* Simple rate limiting check with automatic cleanup
|
|
62
|
+
*/
|
|
63
|
+
checkRateLimit(name, rateLimit) {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
// Clean up expired entries to prevent memory leaks
|
|
66
|
+
for (const [key, limit] of this.rateLimits.entries()) {
|
|
67
|
+
if (limit.resetTime <= now) {
|
|
68
|
+
this.rateLimits.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const limit = this.rateLimits.get(name);
|
|
72
|
+
if (limit && limit.resetTime > now) {
|
|
73
|
+
if (limit.count >= rateLimit.requests) {
|
|
74
|
+
throw new Error(`Tool ${name} rate limit exceeded`);
|
|
75
|
+
}
|
|
76
|
+
limit.count++;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.rateLimits.set(name, {
|
|
80
|
+
count: 1,
|
|
81
|
+
resetTime: now + rateLimit.window,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Register a custom tool
|
|
87
|
+
*/
|
|
88
|
+
register(name, tool) {
|
|
89
|
+
if (this.tools.has(name)) {
|
|
90
|
+
logger.warn(`Tool ${name} already registered, overwriting`);
|
|
91
|
+
}
|
|
92
|
+
this.tools.set(name, tool);
|
|
93
|
+
logger.info(`Registered custom tool: ${name}`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register multiple tools at once
|
|
97
|
+
*/
|
|
98
|
+
registerMany(tools) {
|
|
99
|
+
Object.entries(tools).forEach(([name, tool]) => {
|
|
100
|
+
this.register(name, tool);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Unregister a tool
|
|
105
|
+
*/
|
|
106
|
+
unregister(name) {
|
|
107
|
+
const result = this.tools.delete(name);
|
|
108
|
+
if (result) {
|
|
109
|
+
logger.info(`Unregistered tool: ${name}`);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get a tool by name
|
|
115
|
+
*/
|
|
116
|
+
get(name) {
|
|
117
|
+
return this.tools.get(name);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get all registered tools
|
|
121
|
+
*/
|
|
122
|
+
getAll() {
|
|
123
|
+
return new Map(this.tools);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Convert all tools to AI SDK format
|
|
127
|
+
*/
|
|
128
|
+
toAISDKTools() {
|
|
129
|
+
const aiTools = {};
|
|
130
|
+
for (const [name, tool] of this.tools) {
|
|
131
|
+
if (this.isToolAllowed(name)) {
|
|
132
|
+
aiTools[name] = convertToAISDKTool(name, tool);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return aiTools;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Add middleware
|
|
139
|
+
*/
|
|
140
|
+
use(middleware) {
|
|
141
|
+
this.middleware.push(middleware);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Set permissions
|
|
145
|
+
*/
|
|
146
|
+
setPermissions(permissions) {
|
|
147
|
+
this.permissions = permissions;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Check if a tool is allowed
|
|
151
|
+
*/
|
|
152
|
+
isToolAllowed(name) {
|
|
153
|
+
// Check denylist first
|
|
154
|
+
if (this.permissions.denylist?.includes(name)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// Check allowlist if specified
|
|
158
|
+
if (this.permissions.allowlist &&
|
|
159
|
+
!this.permissions.allowlist.includes(name)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Execute a tool with middleware
|
|
166
|
+
*/
|
|
167
|
+
async execute(name, args, context) {
|
|
168
|
+
const tool = this.tools.get(name);
|
|
169
|
+
if (!tool) {
|
|
170
|
+
throw new Error(`Tool ${name} not found`);
|
|
171
|
+
}
|
|
172
|
+
// Check permissions
|
|
173
|
+
if (!this.isToolAllowed(name)) {
|
|
174
|
+
throw new Error(`Tool ${name} is not allowed`);
|
|
175
|
+
}
|
|
176
|
+
// Check custom validator
|
|
177
|
+
if (this.permissions.customValidator) {
|
|
178
|
+
const allowed = await this.permissions.customValidator(name, args);
|
|
179
|
+
if (!allowed) {
|
|
180
|
+
throw new Error(`Tool ${name} execution denied by custom validator`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Check rate limit
|
|
184
|
+
if (tool.config?.rateLimit) {
|
|
185
|
+
this.checkRateLimit(name, tool.config.rateLimit);
|
|
186
|
+
}
|
|
187
|
+
// Build middleware chain
|
|
188
|
+
let index = 0;
|
|
189
|
+
const next = async () => {
|
|
190
|
+
if (index < this.middleware.length) {
|
|
191
|
+
const middleware = this.middleware[index++];
|
|
192
|
+
return middleware(name, args, next, context);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Execute the actual tool
|
|
196
|
+
return tool.execute(args, context);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
return next();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Create a simple tool helper
|
|
204
|
+
*/
|
|
205
|
+
export function createTool(config) {
|
|
206
|
+
return config;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Create an async tool helper
|
|
210
|
+
*/
|
|
211
|
+
export function createAsyncTool(config) {
|
|
212
|
+
return config;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Create a batch tool that processes multiple items
|
|
216
|
+
*/
|
|
217
|
+
export function createBatchTool(config) {
|
|
218
|
+
return {
|
|
219
|
+
...config,
|
|
220
|
+
execute: async ({ items }, context) => {
|
|
221
|
+
const batchSize = config.batchSize || 10;
|
|
222
|
+
const results = [];
|
|
223
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
224
|
+
const batch = items.slice(i, i + batchSize);
|
|
225
|
+
const batchResults = await Promise.all(batch.map((item) => config.processItem(item, context)));
|
|
226
|
+
results.push(...batchResults);
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Tool testing utilities
|
|
234
|
+
*/
|
|
235
|
+
export const TestUtils = {
|
|
236
|
+
/**
|
|
237
|
+
* Create a mock tool context
|
|
238
|
+
*/
|
|
239
|
+
mockContext(overrides) {
|
|
240
|
+
return {
|
|
241
|
+
callTool: async (name, args) => {
|
|
242
|
+
logger.debug(`Mock tool call: ${name}`, args);
|
|
243
|
+
return {};
|
|
244
|
+
},
|
|
245
|
+
session: {
|
|
246
|
+
id: "test-session",
|
|
247
|
+
userId: "test-user",
|
|
248
|
+
provider: "test-provider",
|
|
249
|
+
model: "test-model",
|
|
250
|
+
},
|
|
251
|
+
logger,
|
|
252
|
+
...overrides,
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
/**
|
|
256
|
+
* Test a tool with mock data
|
|
257
|
+
*/
|
|
258
|
+
async testTool(tool, testCases) {
|
|
259
|
+
const context = TestUtils.mockContext();
|
|
260
|
+
const results = [];
|
|
261
|
+
for (const testCase of testCases) {
|
|
262
|
+
try {
|
|
263
|
+
const result = await tool.execute(testCase.input, context);
|
|
264
|
+
results.push({
|
|
265
|
+
input: testCase.input,
|
|
266
|
+
output: result,
|
|
267
|
+
success: true,
|
|
268
|
+
matches: testCase.expected
|
|
269
|
+
? JSON.stringify(result) === JSON.stringify(testCase.expected)
|
|
270
|
+
: undefined,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
results.push({
|
|
275
|
+
input: testCase.input,
|
|
276
|
+
error: error instanceof Error ? error.message : String(error),
|
|
277
|
+
success: false,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return results;
|
|
282
|
+
},
|
|
283
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuroLink SDK Tool Registration API
|
|
3
|
+
* Simple interface for developers to register custom tools
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { Tool } from "ai";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
import type { InMemoryMCPServerConfig, InMemoryToolInfo } from "../types/mcp-types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Context provided to tools during execution
|
|
11
|
+
*/
|
|
12
|
+
export interface ToolContext {
|
|
13
|
+
/**
|
|
14
|
+
* Current session ID
|
|
15
|
+
*/
|
|
16
|
+
sessionId: string;
|
|
17
|
+
/**
|
|
18
|
+
* User ID if available
|
|
19
|
+
*/
|
|
20
|
+
userId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* AI provider being used
|
|
23
|
+
*/
|
|
24
|
+
provider?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Model being used
|
|
27
|
+
*/
|
|
28
|
+
model?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Call another tool
|
|
31
|
+
*/
|
|
32
|
+
callTool?: (name: string, args: any) => Promise<any>;
|
|
33
|
+
/**
|
|
34
|
+
* Logger instance
|
|
35
|
+
*/
|
|
36
|
+
logger: typeof logger;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Simple tool interface for SDK users
|
|
40
|
+
*/
|
|
41
|
+
export interface SimpleTool {
|
|
42
|
+
/**
|
|
43
|
+
* Tool description that helps AI understand when to use it
|
|
44
|
+
*/
|
|
45
|
+
description: string;
|
|
46
|
+
/**
|
|
47
|
+
* Parameters schema using Zod (optional)
|
|
48
|
+
*/
|
|
49
|
+
parameters?: z.ZodSchema;
|
|
50
|
+
/**
|
|
51
|
+
* Tool execution function
|
|
52
|
+
*/
|
|
53
|
+
execute: (args: any, context?: ToolContext) => Promise<any> | any;
|
|
54
|
+
/**
|
|
55
|
+
* Optional metadata
|
|
56
|
+
*/
|
|
57
|
+
metadata?: {
|
|
58
|
+
category?: string;
|
|
59
|
+
version?: string;
|
|
60
|
+
author?: string;
|
|
61
|
+
[key: string]: any;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Converts a SimpleTool to Vercel AI SDK format
|
|
66
|
+
*/
|
|
67
|
+
export declare function convertToAISDKTool(name: string, simpleTool: SimpleTool): Tool;
|
|
68
|
+
/**
|
|
69
|
+
* Converts a SimpleTool to MCP tool format
|
|
70
|
+
*/
|
|
71
|
+
export declare function convertToMCPTool(simpleTool: SimpleTool): InMemoryToolInfo;
|
|
72
|
+
/**
|
|
73
|
+
* Creates an in-memory MCP server configuration from a set of tools
|
|
74
|
+
*/
|
|
75
|
+
export declare function createMCPServerFromTools(serverId: string, tools: Record<string, SimpleTool>, metadata?: {
|
|
76
|
+
title?: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
category?: string;
|
|
79
|
+
[key: string]: any;
|
|
80
|
+
}): InMemoryMCPServerConfig;
|
|
81
|
+
/**
|
|
82
|
+
* Helper to create a tool with type safety
|
|
83
|
+
*/
|
|
84
|
+
export declare function createTool<TParams = any>(config: SimpleTool): SimpleTool;
|
|
85
|
+
/**
|
|
86
|
+
* Helper to create a tool with typed parameters
|
|
87
|
+
*/
|
|
88
|
+
export declare function createTypedTool<TParams extends z.ZodSchema>(config: Omit<SimpleTool, "execute"> & {
|
|
89
|
+
parameters: TParams;
|
|
90
|
+
execute: (args: z.infer<TParams>, context?: ToolContext) => Promise<any> | any;
|
|
91
|
+
}): SimpleTool;
|
|
92
|
+
/**
|
|
93
|
+
* Validate tool configuration with detailed error messages
|
|
94
|
+
*/
|
|
95
|
+
export declare function validateTool(name: string, tool: SimpleTool): void;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuroLink SDK Tool Registration API
|
|
3
|
+
* Simple interface for developers to register custom tools
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { tool as createAISDKTool } from "ai";
|
|
7
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
8
|
+
import { logger } from "../utils/logger.js";
|
|
9
|
+
/**
|
|
10
|
+
* Configuration constants for tool validation
|
|
11
|
+
*/
|
|
12
|
+
const envValue = parseInt(process.env.NEUROLINK_TOOL_DESCRIPTION_MAX_LENGTH || "200", 10);
|
|
13
|
+
const DEFAULT_DESCRIPTION_MAX_LENGTH = Number.isInteger(envValue) && envValue > 0 ? envValue : 200;
|
|
14
|
+
/**
|
|
15
|
+
* Converts a SimpleTool to Vercel AI SDK format
|
|
16
|
+
*/
|
|
17
|
+
export function convertToAISDKTool(name, simpleTool) {
|
|
18
|
+
return createAISDKTool({
|
|
19
|
+
description: simpleTool.description,
|
|
20
|
+
parameters: simpleTool.parameters || z.object({}),
|
|
21
|
+
execute: async (args) => {
|
|
22
|
+
try {
|
|
23
|
+
// Create a minimal context for standalone execution
|
|
24
|
+
const context = {
|
|
25
|
+
sessionId: `tool-${name}-${Date.now()}`,
|
|
26
|
+
logger,
|
|
27
|
+
};
|
|
28
|
+
const result = await simpleTool.execute(args, context);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.error(`Tool ${name} execution failed:`, error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Converts a SimpleTool to MCP tool format
|
|
40
|
+
*/
|
|
41
|
+
export function convertToMCPTool(simpleTool) {
|
|
42
|
+
return {
|
|
43
|
+
description: simpleTool.description,
|
|
44
|
+
execute: async (params) => {
|
|
45
|
+
try {
|
|
46
|
+
const result = await simpleTool.execute(params);
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
data: result,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
54
|
+
logger.error("MCP tool execution failed:", error);
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: errorMessage,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
inputSchema: simpleTool.parameters,
|
|
62
|
+
isImplemented: true,
|
|
63
|
+
metadata: simpleTool.metadata,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates an in-memory MCP server configuration from a set of tools
|
|
68
|
+
*/
|
|
69
|
+
export function createMCPServerFromTools(serverId, tools, metadata) {
|
|
70
|
+
const mcpTools = {};
|
|
71
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
72
|
+
mcpTools[name] = convertToMCPTool(tool);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
server: {
|
|
76
|
+
title: metadata?.title || serverId,
|
|
77
|
+
description: metadata?.description,
|
|
78
|
+
tools: mcpTools,
|
|
79
|
+
},
|
|
80
|
+
category: metadata?.category,
|
|
81
|
+
metadata: metadata || {},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Helper to create a tool with type safety
|
|
86
|
+
*/
|
|
87
|
+
export function createTool(config) {
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Helper to create a tool with typed parameters
|
|
92
|
+
*/
|
|
93
|
+
export function createTypedTool(config) {
|
|
94
|
+
return config;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Validate tool description length
|
|
98
|
+
*/
|
|
99
|
+
function validateDescriptionLength(name, description) {
|
|
100
|
+
const maxDescriptionLength = Number.isInteger(DEFAULT_DESCRIPTION_MAX_LENGTH) &&
|
|
101
|
+
DEFAULT_DESCRIPTION_MAX_LENGTH > 0
|
|
102
|
+
? DEFAULT_DESCRIPTION_MAX_LENGTH
|
|
103
|
+
: 200;
|
|
104
|
+
if (description.length > maxDescriptionLength) {
|
|
105
|
+
throw new Error(`Tool '${name}' description should be concise (max ${maxDescriptionLength} characters). ` +
|
|
106
|
+
`Current length: ${description.length}. ` +
|
|
107
|
+
`Consider shortening: "${description.substring(0, 50)}..."`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate tool configuration with detailed error messages
|
|
112
|
+
*/
|
|
113
|
+
export function validateTool(name, tool) {
|
|
114
|
+
// Validate tool name
|
|
115
|
+
if (!name || typeof name !== "string" || name.trim() === "") {
|
|
116
|
+
throw new Error(`Invalid tool name: must be a non-empty string. Received: ${name}`);
|
|
117
|
+
}
|
|
118
|
+
// Validate tool name format (alphanumeric, hyphens, underscores only)
|
|
119
|
+
const validNamePattern = /^[a-zA-Z0-9_-]+$/;
|
|
120
|
+
if (!validNamePattern.test(name)) {
|
|
121
|
+
throw new Error(`Invalid tool name format: '${name}'. Tool names must contain only alphanumeric characters, hyphens, and underscores. ` +
|
|
122
|
+
`Examples: 'calculate-tax', 'get_weather', 'sendEmail123'`);
|
|
123
|
+
}
|
|
124
|
+
// Validate tool object
|
|
125
|
+
if (!tool || typeof tool !== "object") {
|
|
126
|
+
throw new Error(`Tool '${name}' must be an object with description and execute properties. Received: ${typeof tool}`);
|
|
127
|
+
}
|
|
128
|
+
// Validate description
|
|
129
|
+
if (!tool.description ||
|
|
130
|
+
typeof tool.description !== "string" ||
|
|
131
|
+
tool.description.trim() === "") {
|
|
132
|
+
throw new Error(`Tool '${name}' must have a non-empty description string. ` +
|
|
133
|
+
`Example: { description: "Calculates mathematical expressions", execute: async (params) => {...} }`);
|
|
134
|
+
}
|
|
135
|
+
// Validate execute function with signature guidance
|
|
136
|
+
if (typeof tool.execute !== "function") {
|
|
137
|
+
throw new Error(`Tool '${name}' must have an execute function. ` +
|
|
138
|
+
`Expected signature: async (params?: any) => Promise<any>. ` +
|
|
139
|
+
`Received: ${typeof tool.execute}. ` +
|
|
140
|
+
`Example: { execute: async (params) => { return { success: true, data: result }; } }`);
|
|
141
|
+
}
|
|
142
|
+
// Validate parameters schema if provided - support both Zod and custom schemas
|
|
143
|
+
if (tool.parameters) {
|
|
144
|
+
if (typeof tool.parameters !== "object") {
|
|
145
|
+
throw new Error(`Tool '${name}' parameters must be an object. ` +
|
|
146
|
+
`Received: ${typeof tool.parameters}`);
|
|
147
|
+
}
|
|
148
|
+
// Check for common schema validation methods (Zod uses 'parse', others might use 'validate')
|
|
149
|
+
const params = tool.parameters;
|
|
150
|
+
const hasValidationMethod = typeof params.parse === "function" ||
|
|
151
|
+
typeof params.validate === "function" ||
|
|
152
|
+
"_def" in params; // Zod schemas have _def property
|
|
153
|
+
if (!hasValidationMethod) {
|
|
154
|
+
const errorMessage = typeof params.parse === "function" || "_def" in params
|
|
155
|
+
? `Tool '${name}' has a Zod-like schema but validation failed. Ensure it's a valid Zod schema: z.object({ ... })`
|
|
156
|
+
: typeof params.validate === "function"
|
|
157
|
+
? `Tool '${name}' has a validate method but it may not be callable. Ensure: { parameters: { validate: (data) => { ... } } }`
|
|
158
|
+
: `Tool '${name}' parameters must be a schema object with validation. ` +
|
|
159
|
+
`Supported formats:\n` +
|
|
160
|
+
`• Zod schema: { parameters: z.object({ value: z.string() }) }\n` +
|
|
161
|
+
`• Custom schema: { parameters: { validate: (data) => { ... } } }\n` +
|
|
162
|
+
`• Custom schema: { parameters: { parse: (data) => { ... } } }`;
|
|
163
|
+
throw new Error(errorMessage);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Validate description length for better UX
|
|
167
|
+
validateDescriptionLength(name, tool.description);
|
|
168
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
3
4
|
export class StreamingManager extends EventEmitter {
|
|
4
5
|
activeSessions = new Map();
|
|
5
6
|
streamingPools = new Map();
|
|
@@ -40,7 +41,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
40
41
|
};
|
|
41
42
|
this.activeSessions.set(sessionId, session);
|
|
42
43
|
this.updateGlobalMetrics();
|
|
43
|
-
|
|
44
|
+
logger.debug(`[Streaming Manager] Created session ${sessionId} for provider ${config.provider}`);
|
|
44
45
|
this.emit("session-created", session);
|
|
45
46
|
return session;
|
|
46
47
|
}
|
|
@@ -52,7 +53,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
52
53
|
session.status = "terminated";
|
|
53
54
|
this.activeSessions.delete(sessionId);
|
|
54
55
|
this.updateGlobalMetrics();
|
|
55
|
-
|
|
56
|
+
logger.debug(`[Streaming Manager] Terminated session ${sessionId}`);
|
|
56
57
|
this.emit("session-terminated", session);
|
|
57
58
|
}
|
|
58
59
|
async pauseStreamingSession(sessionId) {
|
|
@@ -62,7 +63,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
62
63
|
}
|
|
63
64
|
if (session.status === "active") {
|
|
64
65
|
session.status = "paused";
|
|
65
|
-
|
|
66
|
+
logger.debug(`[Streaming Manager] Paused session ${sessionId}`);
|
|
66
67
|
this.emit("session-paused", session);
|
|
67
68
|
}
|
|
68
69
|
}
|
|
@@ -74,7 +75,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
74
75
|
if (session.status === "paused") {
|
|
75
76
|
session.status = "active";
|
|
76
77
|
session.lastActivity = Date.now();
|
|
77
|
-
|
|
78
|
+
logger.debug(`[Streaming Manager] Resumed session ${sessionId}`);
|
|
78
79
|
this.emit("session-resumed", session);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
@@ -97,7 +98,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
97
98
|
session.config.bufferSize = Math.max(session.config.bufferSize * 0.8, 1024);
|
|
98
99
|
session.config.streamingMode = "real-time";
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
+
logger.debug(`[Streaming Manager] Optimized session ${sessionId}: latency=${currentLatency}ms, mode=${session.config.streamingMode}`);
|
|
101
102
|
}
|
|
102
103
|
async enableStreamingCompression(sessionId) {
|
|
103
104
|
const session = this.activeSessions.get(sessionId);
|
|
@@ -105,7 +106,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
session.config.compressionEnabled = true;
|
|
108
|
-
|
|
109
|
+
logger.debug(`[Streaming Manager] Enabled compression for session ${sessionId}`);
|
|
109
110
|
}
|
|
110
111
|
async configureStreamingBuffering(sessionId, bufferConfig) {
|
|
111
112
|
const session = this.activeSessions.get(sessionId);
|
|
@@ -114,7 +115,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
114
115
|
}
|
|
115
116
|
session.config.bufferSize = bufferConfig.maxSize;
|
|
116
117
|
session.config.maxChunkSize = Math.min(session.config.maxChunkSize, bufferConfig.flushThreshold);
|
|
117
|
-
|
|
118
|
+
logger.debug(`[Streaming Manager] Updated buffer config for session ${sessionId}:`, bufferConfig);
|
|
118
119
|
}
|
|
119
120
|
// Multi-Stream Coordination
|
|
120
121
|
async createStreamingPool(poolId, config) {
|
|
@@ -126,7 +127,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
126
127
|
loadBalancer: config.loadBalancing,
|
|
127
128
|
};
|
|
128
129
|
this.streamingPools.set(poolId, pool);
|
|
129
|
-
|
|
130
|
+
logger.debug(`[Streaming Manager] Created pool ${poolId} with max ${config.maxConcurrentSessions} sessions`);
|
|
130
131
|
}
|
|
131
132
|
async balanceStreamingLoad(poolId) {
|
|
132
133
|
const pool = this.streamingPools.get(poolId);
|
|
@@ -160,7 +161,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
160
161
|
const newMaxSessions = Math.max(1, Math.floor(pool.maxSessions * scale));
|
|
161
162
|
pool.maxSessions = newMaxSessions;
|
|
162
163
|
pool.config.maxConcurrentSessions = newMaxSessions;
|
|
163
|
-
|
|
164
|
+
logger.debug(`[Streaming Manager] Scaled pool ${poolId} to ${newMaxSessions} max sessions (${scale}x)`);
|
|
164
165
|
}
|
|
165
166
|
// Performance Monitoring
|
|
166
167
|
getStreamingMetrics(sessionId) {
|
|
@@ -217,7 +218,7 @@ export class StreamingManager extends EventEmitter {
|
|
|
217
218
|
this.healthCheckInterval = setInterval(() => {
|
|
218
219
|
const health = this.getStreamingHealthStatus();
|
|
219
220
|
if (health.status !== "healthy") {
|
|
220
|
-
|
|
221
|
+
logger.debug("[Streaming Manager] Health check:", health);
|
|
221
222
|
this.emit("health-warning", health);
|
|
222
223
|
}
|
|
223
224
|
}, 30000); // Check every 30 seconds
|