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.
Files changed (42) hide show
  1. package/README.md +771 -738
  2. package/docs/API.md +10 -1
  3. package/docs/PROVIDERS.md +8 -4
  4. package/package.json +12 -12
  5. package/src/async/asyncJobStore.js +82 -52
  6. package/src/async/eventBus.js +25 -20
  7. package/src/async/fileCache.js +121 -40
  8. package/src/async/jobRunner.js +65 -39
  9. package/src/async/providerStreamNormalizer.js +203 -117
  10. package/src/config.js +374 -102
  11. package/src/continuationStore.js +32 -24
  12. package/src/index.js +45 -25
  13. package/src/prompts/helpPrompt.js +328 -305
  14. package/src/providers/anthropic.js +303 -119
  15. package/src/providers/codex.js +103 -45
  16. package/src/providers/deepseek.js +24 -8
  17. package/src/providers/google.js +337 -93
  18. package/src/providers/index.js +1 -1
  19. package/src/providers/interface.js +16 -11
  20. package/src/providers/mistral.js +179 -69
  21. package/src/providers/openai-compatible.js +231 -94
  22. package/src/providers/openai.js +1094 -914
  23. package/src/providers/openrouter-endpoints-client.js +220 -216
  24. package/src/providers/openrouter.js +426 -381
  25. package/src/providers/xai.js +153 -56
  26. package/src/resources/helpResource.js +70 -67
  27. package/src/router.js +95 -67
  28. package/src/services/summarizationService.js +51 -24
  29. package/src/systemPrompts.js +89 -89
  30. package/src/tools/cancelJob.js +31 -19
  31. package/src/tools/chat.js +997 -883
  32. package/src/tools/checkStatus.js +86 -65
  33. package/src/tools/consensus.js +400 -234
  34. package/src/tools/index.js +39 -16
  35. package/src/transport/httpTransport.js +82 -55
  36. package/src/utils/contextProcessor.js +54 -37
  37. package/src/utils/errorHandler.js +95 -45
  38. package/src/utils/fileValidator.js +107 -98
  39. package/src/utils/formatStatus.js +122 -64
  40. package/src/utils/logger.js +459 -449
  41. package/src/utils/pathUtils.js +2 -2
  42. 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 { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
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 { helpResourceHandler, helpResourceMetadata, listResources } from './resources/helpResource.js';
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(`Router initialized with ${Object.keys(tools).length} tools`);
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: { executionTime: `${executionTime}ms`, argCount: Object.keys(toolArgs).length }
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: handler.description || `${name} tool - no description provided`,
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(toolArgs, tool.inputSchema);
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') return value !== null && typeof value === 'object' && !Array.isArray(value);
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') return typeof value === 'number' && Number.isInteger(value);
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 (schema.type === 'object' && (typeof args !== 'object' || args === null)) {
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 (propSchema.type && !isValidJsonSchemaType(value, propSchema.type)) {
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
- { argument: key, minLength: propSchema.minLength, actual: value.length }
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
- { argument: key, maxLength: propSchema.maxLength, actual: value.length }
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
- 'Failed to get router statistics',
578
- 'STATS_ERROR',
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(`SummarizationService initialized - enabled: ${this.enabled}, model: ${this.configuredModel || 'auto-select'}`);
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(`Summarization: Provider ${providerName} not available for title generation`);
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: '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.'
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(`Summarization: Provider ${providerName} not available for streaming summary`);
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: '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."'
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.trim().replace(/\n+/g, ' ').replace(/\s+/g, ' ');
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(`Summarization: Provider ${providerName} not available for final summary`);
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: 'Generate a concise summary (1-2 sentences) that captures the key points and outcome of the content. Be direct and informative.'
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(this.configuredModel, this.providers);
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(`Summarization: Using configured model ${this.configuredModel} from ${providerName}`);
247
+ debugLog(
248
+ `Summarization: Using configured model ${this.configuredModel} from ${providerName}`,
249
+ );
231
250
  return this.configuredModel;
232
251
  }
233
- debugLog(`Summarization: Configured model ${this.configuredModel} not available, falling back to auto-selection`);
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(`Summarization: Selected fast model ${fastModel} from ${providerName}`);
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(`Summarization: No fast model available, using ${fallbackModel} as fallback`);
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 ? ` Currently: ${currentFocus.substring(0, 50)}` : '';
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');