converse-mcp-server 2.3.1 → 2.4.1
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/README.md +771 -738
- package/docs/API.md +10 -1
- package/docs/PROVIDERS.md +8 -4
- package/package.json +12 -12
- package/src/async/asyncJobStore.js +82 -52
- package/src/async/eventBus.js +25 -20
- package/src/async/fileCache.js +121 -40
- package/src/async/jobRunner.js +65 -39
- package/src/async/providerStreamNormalizer.js +203 -117
- package/src/config.js +374 -102
- package/src/continuationStore.js +32 -24
- package/src/index.js +45 -25
- package/src/prompts/helpPrompt.js +328 -305
- package/src/providers/anthropic.js +303 -119
- package/src/providers/codex.js +103 -45
- package/src/providers/deepseek.js +24 -8
- package/src/providers/google.js +337 -93
- package/src/providers/index.js +1 -1
- package/src/providers/interface.js +16 -11
- package/src/providers/mistral.js +179 -69
- package/src/providers/openai-compatible.js +231 -94
- package/src/providers/openai.js +1094 -914
- package/src/providers/openrouter-endpoints-client.js +220 -216
- package/src/providers/openrouter.js +426 -381
- package/src/providers/xai.js +153 -56
- package/src/resources/helpResource.js +70 -67
- package/src/router.js +95 -67
- package/src/services/summarizationService.js +51 -24
- package/src/systemPrompts.js +89 -89
- package/src/tools/cancelJob.js +31 -19
- package/src/tools/chat.js +997 -883
- package/src/tools/checkStatus.js +86 -65
- package/src/tools/consensus.js +400 -234
- package/src/tools/index.js +39 -16
- package/src/transport/httpTransport.js +82 -55
- package/src/utils/contextProcessor.js +54 -37
- package/src/utils/errorHandler.js +95 -45
- package/src/utils/fileValidator.js +107 -98
- package/src/utils/formatStatus.js +122 -64
- package/src/utils/logger.js +459 -449
- package/src/utils/pathUtils.js +2 -2
- package/src/utils/tokenLimiter.js +216 -216
package/src/router.js
CHANGED
|
@@ -6,12 +6,23 @@
|
|
|
6
6
|
* Follows functional architecture with comprehensive error handling.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema,
|
|
12
|
+
ListPromptsRequestSchema,
|
|
13
|
+
GetPromptRequestSchema,
|
|
14
|
+
ListResourcesRequestSchema,
|
|
15
|
+
ReadResourceRequestSchema,
|
|
16
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
10
17
|
import { getContinuationStore } from './continuationStore.js';
|
|
11
18
|
import { getTools } from './tools/index.js';
|
|
12
19
|
import { getProviders } from './providers/index.js';
|
|
13
20
|
import { helpPromptHandler, helpPromptMetadata } from './prompts/helpPrompt.js';
|
|
14
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
helpResourceHandler,
|
|
23
|
+
helpResourceMetadata,
|
|
24
|
+
listResources,
|
|
25
|
+
} from './resources/helpResource.js';
|
|
15
26
|
import { processUnifiedContext } from './utils/contextProcessor.js';
|
|
16
27
|
import { getAsyncJobStore } from './async/asyncJobStore.js';
|
|
17
28
|
import { getJobRunner } from './async/jobRunner.js';
|
|
@@ -25,7 +36,7 @@ import {
|
|
|
25
36
|
ValidationError,
|
|
26
37
|
createMCPErrorResponse,
|
|
27
38
|
withErrorHandler,
|
|
28
|
-
ERROR_CODES
|
|
39
|
+
ERROR_CODES,
|
|
29
40
|
} from './utils/errorHandler.js';
|
|
30
41
|
|
|
31
42
|
const logger = createLogger('router');
|
|
@@ -61,7 +72,7 @@ function validateTool(toolName, tools) {
|
|
|
61
72
|
if (!toolName || typeof toolName !== 'string') {
|
|
62
73
|
throw new RouterError(
|
|
63
74
|
'Tool name must be a non-empty string',
|
|
64
|
-
'INVALID_TOOL_NAME'
|
|
75
|
+
'INVALID_TOOL_NAME',
|
|
65
76
|
);
|
|
66
77
|
}
|
|
67
78
|
|
|
@@ -70,7 +81,7 @@ function validateTool(toolName, tools) {
|
|
|
70
81
|
throw new RouterError(
|
|
71
82
|
`Tool error: Unknown tool '${toolName}'. Available tools: ${availableTools.join(', ')}`,
|
|
72
83
|
'UNKNOWN_TOOL',
|
|
73
|
-
{ requestedTool: toolName, availableTools }
|
|
84
|
+
{ requestedTool: toolName, availableTools },
|
|
74
85
|
);
|
|
75
86
|
}
|
|
76
87
|
|
|
@@ -78,13 +89,13 @@ function validateTool(toolName, tools) {
|
|
|
78
89
|
throw new RouterError(
|
|
79
90
|
`Tool ${toolName} is not callable`,
|
|
80
91
|
'INVALID_TOOL_HANDLER',
|
|
81
|
-
{ toolName, toolType: typeof tools[toolName] }
|
|
92
|
+
{ toolName, toolType: typeof tools[toolName] },
|
|
82
93
|
);
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
return {
|
|
86
97
|
isValid: true,
|
|
87
|
-
tool: tools[toolName]
|
|
98
|
+
tool: tools[toolName],
|
|
88
99
|
};
|
|
89
100
|
}
|
|
90
101
|
|
|
@@ -105,42 +116,42 @@ async function createDependencies(config, context = {}) {
|
|
|
105
116
|
const fileCache = getFileCache(); // Initialize FileCache
|
|
106
117
|
const jobRunner = getJobRunner({
|
|
107
118
|
asyncJobStore,
|
|
108
|
-
fileCache // Pass FileCache to JobRunner
|
|
119
|
+
fileCache, // Pass FileCache to JobRunner
|
|
109
120
|
});
|
|
110
121
|
|
|
111
122
|
// Validate that we have the necessary dependencies
|
|
112
123
|
if (!continuationStore) {
|
|
113
124
|
throw new RouterError(
|
|
114
125
|
'Failed to initialize continuation store',
|
|
115
|
-
'DEPENDENCY_ERROR'
|
|
126
|
+
'DEPENDENCY_ERROR',
|
|
116
127
|
);
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
if (!tools || Object.keys(tools).length === 0) {
|
|
120
131
|
throw new RouterError(
|
|
121
132
|
'No tools available - tools registry is empty',
|
|
122
|
-
'NO_TOOLS_AVAILABLE'
|
|
133
|
+
'NO_TOOLS_AVAILABLE',
|
|
123
134
|
);
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
if (!providers || Object.keys(providers).length === 0) {
|
|
127
138
|
throw new RouterError(
|
|
128
139
|
'No providers available - providers registry is empty',
|
|
129
|
-
'NO_PROVIDERS_AVAILABLE'
|
|
140
|
+
'NO_PROVIDERS_AVAILABLE',
|
|
130
141
|
);
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
if (!asyncJobStore) {
|
|
134
145
|
throw new RouterError(
|
|
135
146
|
'Failed to initialize async job store',
|
|
136
|
-
'DEPENDENCY_ERROR'
|
|
147
|
+
'DEPENDENCY_ERROR',
|
|
137
148
|
);
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
if (!jobRunner) {
|
|
141
152
|
throw new RouterError(
|
|
142
153
|
'Failed to initialize job runner',
|
|
143
|
-
'DEPENDENCY_ERROR'
|
|
154
|
+
'DEPENDENCY_ERROR',
|
|
144
155
|
);
|
|
145
156
|
}
|
|
146
157
|
|
|
@@ -159,7 +170,6 @@ async function createDependencies(config, context = {}) {
|
|
|
159
170
|
validateToolArguments,
|
|
160
171
|
},
|
|
161
172
|
};
|
|
162
|
-
|
|
163
173
|
} catch (error) {
|
|
164
174
|
debugError('Failed to create dependencies:', error);
|
|
165
175
|
throw error;
|
|
@@ -183,7 +193,9 @@ export async function createRouter(server, config) {
|
|
|
183
193
|
const dependencies = await createDependencies(config);
|
|
184
194
|
const tools = getTools(config);
|
|
185
195
|
|
|
186
|
-
createRouterLogger.info(
|
|
196
|
+
createRouterLogger.info(
|
|
197
|
+
`Router initialized with ${Object.keys(tools).length} tools`,
|
|
198
|
+
);
|
|
187
199
|
|
|
188
200
|
// Register unified tool call handler with enhanced error handling
|
|
189
201
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -194,7 +206,7 @@ export async function createRouter(server, config) {
|
|
|
194
206
|
|
|
195
207
|
try {
|
|
196
208
|
requestLogger.info('Tool execution started', {
|
|
197
|
-
data: { toolName, argCount: Object.keys(toolArgs).length }
|
|
209
|
+
data: { toolName, argCount: Object.keys(toolArgs).length },
|
|
198
210
|
});
|
|
199
211
|
|
|
200
212
|
// Validate tool existence and callability
|
|
@@ -209,8 +221,8 @@ export async function createRouter(server, config) {
|
|
|
209
221
|
ERROR_CODES.INVALID_TOOL_ARGS,
|
|
210
222
|
{
|
|
211
223
|
providedArgs: Object.keys(toolArgs),
|
|
212
|
-
expectedSchema: tool.inputSchema
|
|
213
|
-
}
|
|
224
|
+
expectedSchema: tool.inputSchema,
|
|
225
|
+
},
|
|
214
226
|
);
|
|
215
227
|
}
|
|
216
228
|
}
|
|
@@ -220,7 +232,7 @@ export async function createRouter(server, config) {
|
|
|
220
232
|
|
|
221
233
|
const executionTime = toolTimer('completed');
|
|
222
234
|
requestLogger.info('Tool execution completed', {
|
|
223
|
-
data: { executionTime: `${executionTime}ms` }
|
|
235
|
+
data: { executionTime: `${executionTime}ms` },
|
|
224
236
|
});
|
|
225
237
|
|
|
226
238
|
// Ensure result has proper format
|
|
@@ -229,23 +241,25 @@ export async function createRouter(server, config) {
|
|
|
229
241
|
`Tool ${toolName} returned invalid result format`,
|
|
230
242
|
ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
231
243
|
{ result },
|
|
232
|
-
toolName
|
|
244
|
+
toolName,
|
|
233
245
|
);
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
return result;
|
|
237
|
-
|
|
238
249
|
} catch (error) {
|
|
239
250
|
const executionTime = toolTimer('failed');
|
|
240
251
|
requestLogger.error('Tool execution failed', {
|
|
241
252
|
error,
|
|
242
|
-
data: {
|
|
253
|
+
data: {
|
|
254
|
+
executionTime: `${executionTime}ms`,
|
|
255
|
+
argCount: Object.keys(toolArgs).length,
|
|
256
|
+
},
|
|
243
257
|
});
|
|
244
258
|
|
|
245
259
|
return createErrorResponse(error, toolName, {
|
|
246
260
|
executionTime,
|
|
247
261
|
arguments: Object.keys(toolArgs),
|
|
248
|
-
requestId: request.id || 'unknown'
|
|
262
|
+
requestId: request.id || 'unknown',
|
|
249
263
|
});
|
|
250
264
|
}
|
|
251
265
|
});
|
|
@@ -256,11 +270,12 @@ export async function createRouter(server, config) {
|
|
|
256
270
|
const toolList = Object.entries(tools).map(([name, handler]) => {
|
|
257
271
|
const toolInfo = {
|
|
258
272
|
name,
|
|
259
|
-
description:
|
|
273
|
+
description:
|
|
274
|
+
handler.description || `${name} tool - no description provided`,
|
|
260
275
|
inputSchema: handler.inputSchema || {
|
|
261
276
|
type: 'object',
|
|
262
277
|
properties: {},
|
|
263
|
-
description: 'No input schema defined'
|
|
278
|
+
description: 'No input schema defined',
|
|
264
279
|
},
|
|
265
280
|
};
|
|
266
281
|
|
|
@@ -280,16 +295,15 @@ export async function createRouter(server, config) {
|
|
|
280
295
|
metadata: {
|
|
281
296
|
totalTools: toolList.length,
|
|
282
297
|
timestamp: new Date().toISOString(),
|
|
283
|
-
routerVersion: '1.0.0'
|
|
284
|
-
}
|
|
298
|
+
routerVersion: '1.0.0',
|
|
299
|
+
},
|
|
285
300
|
};
|
|
286
|
-
|
|
287
301
|
} catch (error) {
|
|
288
302
|
debugError('Error listing tools:', error);
|
|
289
303
|
throw new RouterError(
|
|
290
304
|
'Failed to list available tools',
|
|
291
305
|
'TOOLS_LIST_ERROR',
|
|
292
|
-
{ error: error.message }
|
|
306
|
+
{ error: error.message },
|
|
293
307
|
);
|
|
294
308
|
}
|
|
295
309
|
});
|
|
@@ -297,7 +311,7 @@ export async function createRouter(server, config) {
|
|
|
297
311
|
// Register list_prompts handler
|
|
298
312
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
299
313
|
return {
|
|
300
|
-
prompts: [helpPromptMetadata]
|
|
314
|
+
prompts: [helpPromptMetadata],
|
|
301
315
|
};
|
|
302
316
|
});
|
|
303
317
|
|
|
@@ -311,14 +325,14 @@ export async function createRouter(server, config) {
|
|
|
311
325
|
|
|
312
326
|
return {
|
|
313
327
|
description: helpPromptMetadata.description,
|
|
314
|
-
...result
|
|
328
|
+
...result,
|
|
315
329
|
};
|
|
316
330
|
}
|
|
317
331
|
|
|
318
332
|
throw new RouterError(
|
|
319
333
|
`Prompt '${promptName}' not found`,
|
|
320
334
|
'PROMPT_NOT_FOUND',
|
|
321
|
-
{ requestedPrompt: promptName }
|
|
335
|
+
{ requestedPrompt: promptName },
|
|
322
336
|
);
|
|
323
337
|
});
|
|
324
338
|
|
|
@@ -326,7 +340,7 @@ export async function createRouter(server, config) {
|
|
|
326
340
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
327
341
|
const resources = listResources();
|
|
328
342
|
return {
|
|
329
|
-
resources
|
|
343
|
+
resources,
|
|
330
344
|
};
|
|
331
345
|
});
|
|
332
346
|
|
|
@@ -341,7 +355,7 @@ export async function createRouter(server, config) {
|
|
|
341
355
|
throw new RouterError(
|
|
342
356
|
`Resource '${resourceUri}' not found`,
|
|
343
357
|
'RESOURCE_NOT_FOUND',
|
|
344
|
-
{ requestedResource: resourceUri }
|
|
358
|
+
{ requestedResource: resourceUri },
|
|
345
359
|
);
|
|
346
360
|
});
|
|
347
361
|
|
|
@@ -353,8 +367,8 @@ export async function createRouter(server, config) {
|
|
|
353
367
|
tools: Object.keys(tools).length,
|
|
354
368
|
providers: Object.keys(dependencies.providers).length,
|
|
355
369
|
continuationStore: dependencies.continuationStore.constructor.name,
|
|
356
|
-
environment: config.environment.nodeEnv
|
|
357
|
-
}
|
|
370
|
+
environment: config.environment.nodeEnv,
|
|
371
|
+
},
|
|
358
372
|
});
|
|
359
373
|
|
|
360
374
|
// Return router interface for testing purposes
|
|
@@ -365,7 +379,7 @@ export async function createRouter(server, config) {
|
|
|
365
379
|
tools: Object.entries(tools).map(([name, tool]) => {
|
|
366
380
|
const toolSchema = {
|
|
367
381
|
name,
|
|
368
|
-
description: tool.description || 'No description available'
|
|
382
|
+
description: tool.description || 'No description available',
|
|
369
383
|
};
|
|
370
384
|
|
|
371
385
|
if (tool.inputSchema) {
|
|
@@ -373,7 +387,7 @@ export async function createRouter(server, config) {
|
|
|
373
387
|
}
|
|
374
388
|
|
|
375
389
|
return toolSchema;
|
|
376
|
-
})
|
|
390
|
+
}),
|
|
377
391
|
};
|
|
378
392
|
},
|
|
379
393
|
|
|
@@ -387,15 +401,18 @@ export async function createRouter(server, config) {
|
|
|
387
401
|
|
|
388
402
|
// Validate tool arguments if schema is provided
|
|
389
403
|
if (tool.inputSchema) {
|
|
390
|
-
const isValidArgs = validateToolArguments(
|
|
404
|
+
const isValidArgs = validateToolArguments(
|
|
405
|
+
toolArgs,
|
|
406
|
+
tool.inputSchema,
|
|
407
|
+
);
|
|
391
408
|
if (!isValidArgs) {
|
|
392
409
|
throw new ValidationError(
|
|
393
410
|
`Invalid arguments for tool ${toolName}`,
|
|
394
411
|
ERROR_CODES.INVALID_TOOL_ARGS,
|
|
395
412
|
{
|
|
396
413
|
providedArgs: Object.keys(toolArgs),
|
|
397
|
-
expectedSchema: tool.inputSchema
|
|
398
|
-
}
|
|
414
|
+
expectedSchema: tool.inputSchema,
|
|
415
|
+
},
|
|
399
416
|
);
|
|
400
417
|
}
|
|
401
418
|
}
|
|
@@ -406,19 +423,18 @@ export async function createRouter(server, config) {
|
|
|
406
423
|
return createErrorResponse(error, toolName, {
|
|
407
424
|
arguments: toolArgs,
|
|
408
425
|
executionTime: 0,
|
|
409
|
-
requestId: 'test'
|
|
426
|
+
requestId: 'test',
|
|
410
427
|
});
|
|
411
428
|
}
|
|
412
|
-
}
|
|
429
|
+
},
|
|
413
430
|
};
|
|
414
|
-
|
|
415
431
|
} catch (error) {
|
|
416
432
|
timer('failed');
|
|
417
433
|
createRouterLogger.error('Router initialization failed', { error });
|
|
418
434
|
throw new RouterError(
|
|
419
435
|
'Router initialization failed',
|
|
420
436
|
ERROR_CODES.ROUTER_ERROR,
|
|
421
|
-
{ originalError: error.message }
|
|
437
|
+
{ originalError: error.message },
|
|
422
438
|
);
|
|
423
439
|
}
|
|
424
440
|
}
|
|
@@ -446,11 +462,13 @@ function getJsonSchemaType(value) {
|
|
|
446
462
|
function isValidJsonSchemaType(value, schemaType) {
|
|
447
463
|
if (schemaType === 'null') return value === null;
|
|
448
464
|
if (schemaType === 'array') return Array.isArray(value);
|
|
449
|
-
if (schemaType === 'object')
|
|
465
|
+
if (schemaType === 'object')
|
|
466
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
450
467
|
if (schemaType === 'boolean') return typeof value === 'boolean';
|
|
451
468
|
if (schemaType === 'string') return typeof value === 'string';
|
|
452
469
|
if (schemaType === 'number') return typeof value === 'number';
|
|
453
|
-
if (schemaType === 'integer')
|
|
470
|
+
if (schemaType === 'integer')
|
|
471
|
+
return typeof value === 'number' && Number.isInteger(value);
|
|
454
472
|
|
|
455
473
|
// For unknown types, fall back to JavaScript typeof
|
|
456
474
|
return typeof value === schemaType;
|
|
@@ -471,22 +489,25 @@ export function validateToolArguments(args, schema) {
|
|
|
471
489
|
}
|
|
472
490
|
|
|
473
491
|
// Basic type checking
|
|
474
|
-
if (
|
|
492
|
+
if (
|
|
493
|
+
schema.type === 'object' &&
|
|
494
|
+
(typeof args !== 'object' || args === null)
|
|
495
|
+
) {
|
|
475
496
|
throw new RouterError(
|
|
476
497
|
'Arguments must be an object',
|
|
477
498
|
'INVALID_ARGUMENT_TYPE',
|
|
478
|
-
{ expected: 'object', received: typeof args }
|
|
499
|
+
{ expected: 'object', received: typeof args },
|
|
479
500
|
);
|
|
480
501
|
}
|
|
481
502
|
|
|
482
503
|
// Check required properties
|
|
483
504
|
if (schema.required && Array.isArray(schema.required)) {
|
|
484
|
-
const missing = schema.required.filter(key => !(key in args));
|
|
505
|
+
const missing = schema.required.filter((key) => !(key in args));
|
|
485
506
|
if (missing.length > 0) {
|
|
486
507
|
throw new RouterError(
|
|
487
508
|
`Validation error: Missing required arguments: ${missing.join(', ')}`,
|
|
488
509
|
'MISSING_REQUIRED_ARGS',
|
|
489
|
-
{ missing, provided: Object.keys(args) }
|
|
510
|
+
{ missing, provided: Object.keys(args) },
|
|
490
511
|
);
|
|
491
512
|
}
|
|
492
513
|
}
|
|
@@ -498,7 +519,10 @@ export function validateToolArguments(args, schema) {
|
|
|
498
519
|
const value = args[key];
|
|
499
520
|
|
|
500
521
|
// Basic type validation using JSON Schema type semantics
|
|
501
|
-
if (
|
|
522
|
+
if (
|
|
523
|
+
propSchema.type &&
|
|
524
|
+
!isValidJsonSchemaType(value, propSchema.type)
|
|
525
|
+
) {
|
|
502
526
|
const actualType = getJsonSchemaType(value);
|
|
503
527
|
throw new RouterError(
|
|
504
528
|
`Argument '${key}' must be of type ${propSchema.type}`,
|
|
@@ -506,8 +530,8 @@ export function validateToolArguments(args, schema) {
|
|
|
506
530
|
{
|
|
507
531
|
argument: key,
|
|
508
532
|
expected: propSchema.type,
|
|
509
|
-
received: actualType
|
|
510
|
-
}
|
|
533
|
+
received: actualType,
|
|
534
|
+
},
|
|
511
535
|
);
|
|
512
536
|
}
|
|
513
537
|
|
|
@@ -517,14 +541,22 @@ export function validateToolArguments(args, schema) {
|
|
|
517
541
|
throw new RouterError(
|
|
518
542
|
`Argument '${key}' must be at least ${propSchema.minLength} characters`,
|
|
519
543
|
'ARGUMENT_TOO_SHORT',
|
|
520
|
-
{
|
|
544
|
+
{
|
|
545
|
+
argument: key,
|
|
546
|
+
minLength: propSchema.minLength,
|
|
547
|
+
actual: value.length,
|
|
548
|
+
},
|
|
521
549
|
);
|
|
522
550
|
}
|
|
523
551
|
if (propSchema.maxLength && value.length > propSchema.maxLength) {
|
|
524
552
|
throw new RouterError(
|
|
525
553
|
`Argument '${key}' must be at most ${propSchema.maxLength} characters`,
|
|
526
554
|
'ARGUMENT_TOO_LONG',
|
|
527
|
-
{
|
|
555
|
+
{
|
|
556
|
+
argument: key,
|
|
557
|
+
maxLength: propSchema.maxLength,
|
|
558
|
+
actual: value.length,
|
|
559
|
+
},
|
|
528
560
|
);
|
|
529
561
|
}
|
|
530
562
|
}
|
|
@@ -533,7 +565,6 @@ export function validateToolArguments(args, schema) {
|
|
|
533
565
|
}
|
|
534
566
|
|
|
535
567
|
return true;
|
|
536
|
-
|
|
537
568
|
} catch (error) {
|
|
538
569
|
if (error instanceof RouterError) {
|
|
539
570
|
throw error;
|
|
@@ -541,7 +572,7 @@ export function validateToolArguments(args, schema) {
|
|
|
541
572
|
throw new RouterError(
|
|
542
573
|
`Argument validation failed: ${error.message}`,
|
|
543
574
|
'VALIDATION_ERROR',
|
|
544
|
-
{ originalError: error.message }
|
|
575
|
+
{ originalError: error.message },
|
|
545
576
|
);
|
|
546
577
|
}
|
|
547
578
|
}
|
|
@@ -561,22 +592,19 @@ export async function getRouterStats(dependencies) {
|
|
|
561
592
|
uptime: process.uptime(),
|
|
562
593
|
tools: {
|
|
563
594
|
count: Object.keys(tools).length,
|
|
564
|
-
available: Object.keys(tools)
|
|
595
|
+
available: Object.keys(tools),
|
|
565
596
|
},
|
|
566
597
|
providers: {
|
|
567
598
|
count: Object.keys(dependencies.providers).length,
|
|
568
|
-
available: Object.keys(dependencies.providers)
|
|
599
|
+
available: Object.keys(dependencies.providers),
|
|
569
600
|
},
|
|
570
601
|
continuationStore: storeStats,
|
|
571
602
|
memory: process.memoryUsage(),
|
|
572
|
-
environment: dependencies.config.environment.nodeEnv
|
|
603
|
+
environment: dependencies.config.environment.nodeEnv,
|
|
573
604
|
};
|
|
574
|
-
|
|
575
605
|
} catch (error) {
|
|
576
|
-
throw new RouterError(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
{ error: error.message }
|
|
580
|
-
);
|
|
606
|
+
throw new RouterError('Failed to get router statistics', 'STATS_ERROR', {
|
|
607
|
+
error: error.message,
|
|
608
|
+
});
|
|
581
609
|
}
|
|
582
610
|
}
|
|
@@ -22,7 +22,7 @@ const FAST_MODELS = {
|
|
|
22
22
|
anthropic: 'claude-3-5-haiku-latest',
|
|
23
23
|
mistral: 'mistral-small-latest',
|
|
24
24
|
deepseek: 'deepseek-chat',
|
|
25
|
-
openrouter: 'qwen/qwen-2.5-32b-instruct'
|
|
25
|
+
openrouter: 'qwen/qwen-2.5-32b-instruct',
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
// Temperature for consistent summarization
|
|
@@ -37,7 +37,9 @@ export class SummarizationService {
|
|
|
37
37
|
// Store configured model preference
|
|
38
38
|
this.configuredModel = config.summarization?.model || null;
|
|
39
39
|
|
|
40
|
-
debugLog(
|
|
40
|
+
debugLog(
|
|
41
|
+
`SummarizationService initialized - enabled: ${this.enabled}, model: ${this.configuredModel || 'auto-select'}`,
|
|
42
|
+
);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -58,7 +60,9 @@ export class SummarizationService {
|
|
|
58
60
|
const provider = this.providers[providerName];
|
|
59
61
|
|
|
60
62
|
if (!provider || !provider.isAvailable(this.config)) {
|
|
61
|
-
debugLog(
|
|
63
|
+
debugLog(
|
|
64
|
+
`Summarization: Provider ${providerName} not available for title generation`,
|
|
65
|
+
);
|
|
62
66
|
return this._fallbackTitle(prompt);
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -66,12 +70,13 @@ export class SummarizationService {
|
|
|
66
70
|
const messages = [
|
|
67
71
|
{
|
|
68
72
|
role: 'system',
|
|
69
|
-
content:
|
|
73
|
+
content:
|
|
74
|
+
'Generate a concise title (max 50 characters) that captures the essence of the user\'s request. Return ONLY the title text, no quotes or formatting.',
|
|
70
75
|
},
|
|
71
76
|
{
|
|
72
77
|
role: 'user',
|
|
73
|
-
content: prompt
|
|
74
|
-
}
|
|
78
|
+
content: prompt,
|
|
79
|
+
},
|
|
75
80
|
];
|
|
76
81
|
|
|
77
82
|
// Invoke provider with minimal reasoning for speed
|
|
@@ -81,7 +86,7 @@ export class SummarizationService {
|
|
|
81
86
|
maxTokens: 200, // Increased to prevent incomplete responses
|
|
82
87
|
reasoning_effort: 'minimal', // Use minimal reasoning for fast summaries
|
|
83
88
|
verbosity: 'low', // Keep outputs concise
|
|
84
|
-
config: this.config
|
|
89
|
+
config: this.config,
|
|
85
90
|
});
|
|
86
91
|
|
|
87
92
|
if (response && response.content) {
|
|
@@ -118,7 +123,9 @@ export class SummarizationService {
|
|
|
118
123
|
const provider = this.providers[providerName];
|
|
119
124
|
|
|
120
125
|
if (!provider || !provider.isAvailable(this.config)) {
|
|
121
|
-
debugLog(
|
|
126
|
+
debugLog(
|
|
127
|
+
`Summarization: Provider ${providerName} not available for streaming summary`,
|
|
128
|
+
);
|
|
122
129
|
return this._fallbackStreamingSummary(content, currentFocus);
|
|
123
130
|
}
|
|
124
131
|
|
|
@@ -126,12 +133,13 @@ export class SummarizationService {
|
|
|
126
133
|
const messages = [
|
|
127
134
|
{
|
|
128
135
|
role: 'system',
|
|
129
|
-
content:
|
|
136
|
+
content:
|
|
137
|
+
'Generate a single continuous status description of what the AI is doing from the perspective of the AI. Start with the main task/topic, then use transition phrases like "Currently exploring", "Currently investigating", "Now examining", "Now writing about", "Now discussing" to describe the current focus. Return ONLY the status text in one flowing sentence, no labels or formatting. Example: "Writing a technical review of database architecture with focus on scalability and performance. Currently exploring connection pooling strategies and their impact on resource utilization."',
|
|
130
138
|
},
|
|
131
139
|
{
|
|
132
140
|
role: 'user',
|
|
133
|
-
content: `Full content so far:\n${content}\n\n---\nLast section (current focus):\n${currentFocus}\n\nProvide a single status description from the perspective of the AI as if you were generating it, that flows naturally from the overall task to the current focus
|
|
134
|
-
}
|
|
141
|
+
content: `Full content so far:\n${content}\n\n---\nLast section (current focus):\n${currentFocus}\n\nProvide a single status description from the perspective of the AI as if you were generating it, that flows naturally from the overall task to the current focus.`,
|
|
142
|
+
},
|
|
135
143
|
];
|
|
136
144
|
|
|
137
145
|
// Invoke provider with minimal reasoning for speed
|
|
@@ -141,12 +149,15 @@ export class SummarizationService {
|
|
|
141
149
|
maxTokens: 300, // Increased to prevent incomplete responses
|
|
142
150
|
reasoning_effort: 'minimal', // Use minimal reasoning for fast summaries
|
|
143
151
|
verbosity: 'low', // Keep outputs concise
|
|
144
|
-
config: this.config
|
|
152
|
+
config: this.config,
|
|
145
153
|
});
|
|
146
154
|
|
|
147
155
|
if (response && response.content) {
|
|
148
156
|
// Remove any newlines and extra spaces from the summary
|
|
149
|
-
const summary = response.content
|
|
157
|
+
const summary = response.content
|
|
158
|
+
.trim()
|
|
159
|
+
.replace(/\n+/g, ' ')
|
|
160
|
+
.replace(/\s+/g, ' ');
|
|
150
161
|
debugLog('Summarization: Generated streaming summary');
|
|
151
162
|
return summary;
|
|
152
163
|
}
|
|
@@ -177,7 +188,9 @@ export class SummarizationService {
|
|
|
177
188
|
const provider = this.providers[providerName];
|
|
178
189
|
|
|
179
190
|
if (!provider || !provider.isAvailable(this.config)) {
|
|
180
|
-
debugLog(
|
|
191
|
+
debugLog(
|
|
192
|
+
`Summarization: Provider ${providerName} not available for final summary`,
|
|
193
|
+
);
|
|
181
194
|
return this._fallbackFinalSummary(content);
|
|
182
195
|
}
|
|
183
196
|
|
|
@@ -185,12 +198,13 @@ export class SummarizationService {
|
|
|
185
198
|
const messages = [
|
|
186
199
|
{
|
|
187
200
|
role: 'system',
|
|
188
|
-
content:
|
|
201
|
+
content:
|
|
202
|
+
'Generate a concise summary (1-2 sentences) that captures the key points and outcome of the content. Be direct and informative.',
|
|
189
203
|
},
|
|
190
204
|
{
|
|
191
205
|
role: 'user',
|
|
192
|
-
content
|
|
193
|
-
}
|
|
206
|
+
content,
|
|
207
|
+
},
|
|
194
208
|
];
|
|
195
209
|
|
|
196
210
|
// Invoke provider with minimal reasoning for speed
|
|
@@ -200,7 +214,7 @@ export class SummarizationService {
|
|
|
200
214
|
maxTokens: 250, // Increased to prevent incomplete responses
|
|
201
215
|
reasoning_effort: 'minimal', // Use minimal reasoning for fast summaries
|
|
202
216
|
verbosity: 'low', // Keep outputs concise
|
|
203
|
-
config: this.config
|
|
217
|
+
config: this.config,
|
|
204
218
|
});
|
|
205
219
|
|
|
206
220
|
if (response && response.content) {
|
|
@@ -224,27 +238,38 @@ export class SummarizationService {
|
|
|
224
238
|
_selectFastModel() {
|
|
225
239
|
// If a model is configured, try to use it first
|
|
226
240
|
if (this.configuredModel) {
|
|
227
|
-
const providerName = mapModelToProvider(
|
|
241
|
+
const providerName = mapModelToProvider(
|
|
242
|
+
this.configuredModel,
|
|
243
|
+
this.providers,
|
|
244
|
+
);
|
|
228
245
|
const provider = this.providers[providerName];
|
|
229
246
|
if (provider && provider.isAvailable(this.config)) {
|
|
230
|
-
debugLog(
|
|
247
|
+
debugLog(
|
|
248
|
+
`Summarization: Using configured model ${this.configuredModel} from ${providerName}`,
|
|
249
|
+
);
|
|
231
250
|
return this.configuredModel;
|
|
232
251
|
}
|
|
233
|
-
debugLog(
|
|
252
|
+
debugLog(
|
|
253
|
+
`Summarization: Configured model ${this.configuredModel} not available, falling back to auto-selection`,
|
|
254
|
+
);
|
|
234
255
|
}
|
|
235
256
|
|
|
236
257
|
// Check which providers are available and return the first fast model
|
|
237
258
|
for (const [providerName, fastModel] of Object.entries(FAST_MODELS)) {
|
|
238
259
|
const provider = this.providers[providerName];
|
|
239
260
|
if (provider && provider.isAvailable(this.config)) {
|
|
240
|
-
debugLog(
|
|
261
|
+
debugLog(
|
|
262
|
+
`Summarization: Selected fast model ${fastModel} from ${providerName}`,
|
|
263
|
+
);
|
|
241
264
|
return fastModel;
|
|
242
265
|
}
|
|
243
266
|
}
|
|
244
267
|
|
|
245
268
|
// Fallback to default or configured model (gpt-5-nano is fastest)
|
|
246
269
|
const fallbackModel = this.configuredModel || 'gpt-5-nano';
|
|
247
|
-
debugLog(
|
|
270
|
+
debugLog(
|
|
271
|
+
`Summarization: No fast model available, using ${fallbackModel} as fallback`,
|
|
272
|
+
);
|
|
248
273
|
return fallbackModel;
|
|
249
274
|
}
|
|
250
275
|
|
|
@@ -268,7 +293,9 @@ export class SummarizationService {
|
|
|
268
293
|
if (!content) return 'Processing...';
|
|
269
294
|
|
|
270
295
|
const contentSnippet = content.substring(0, 100).trim();
|
|
271
|
-
const focusSnippet = currentFocus
|
|
296
|
+
const focusSnippet = currentFocus
|
|
297
|
+
? ` Currently: ${currentFocus.substring(0, 50)}`
|
|
298
|
+
: '';
|
|
272
299
|
|
|
273
300
|
const summary = `${contentSnippet}...${focusSnippet}`;
|
|
274
301
|
debugLog('Summarization: Using fallback streaming summary');
|