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
@@ -5,9 +5,19 @@
5
5
  * Calls all available providers simultaneously and aggregates responses.
6
6
  */
7
7
 
8
- import { createToolResponse, createToolError, formatFailureDetails } from './index.js';
9
- import { processUnifiedContext, createFileContext } from '../utils/contextProcessor.js';
10
- import { generateContinuationId, addMessageToHistory } from '../continuationStore.js';
8
+ import {
9
+ createToolResponse,
10
+ createToolError,
11
+ formatFailureDetails,
12
+ } from './index.js';
13
+ import {
14
+ processUnifiedContext,
15
+ createFileContext,
16
+ } from '../utils/contextProcessor.js';
17
+ import {
18
+ generateContinuationId,
19
+ addMessageToHistory,
20
+ } from '../continuationStore.js';
11
21
  import { debugLog, debugError } from '../utils/console.js';
12
22
  import { createLogger } from '../utils/logger.js';
13
23
  import { CONSENSUS_PROMPT } from '../systemPrompts.js';
@@ -25,15 +35,28 @@ const logger = createLogger('consensus');
25
35
  */
26
36
  export async function consensusTool(args, dependencies) {
27
37
  try {
28
- const { config, providers, continuationStore, contextProcessor, jobRunner, providerStreamNormalizer } = dependencies;
38
+ const {
39
+ config,
40
+ providers,
41
+ continuationStore,
42
+ contextProcessor,
43
+ jobRunner,
44
+ providerStreamNormalizer,
45
+ } = dependencies;
29
46
 
30
47
  // Validate required arguments
31
48
  if (!args.prompt || typeof args.prompt !== 'string') {
32
49
  return createToolError('Prompt is required and must be a string');
33
50
  }
34
51
 
35
- if (!args.models || !Array.isArray(args.models) || args.models.length === 0) {
36
- return createToolError('Models array is required and must contain at least one model');
52
+ if (
53
+ !args.models ||
54
+ !Array.isArray(args.models) ||
55
+ args.models.length === 0
56
+ ) {
57
+ return createToolError(
58
+ 'Models array is required and must contain at least one model',
59
+ );
37
60
  }
38
61
 
39
62
  // Extract and validate arguments
@@ -48,14 +71,16 @@ export async function consensusTool(args, dependencies) {
48
71
  temperature = 0.2,
49
72
  reasoning_effort = 'medium',
50
73
  use_websearch = false,
51
- async = false
74
+ async = false,
52
75
  } = args;
53
76
 
54
77
  // Handle async execution mode
55
78
  if (async) {
56
79
  // Validate async dependencies are available
57
80
  if (!jobRunner || !providerStreamNormalizer) {
58
- return createToolError('Async execution not available - missing async dependencies');
81
+ return createToolError(
82
+ 'Async execution not available - missing async dependencies',
83
+ );
59
84
  }
60
85
 
61
86
  // Generate continuation ID for background execution result
@@ -69,9 +94,14 @@ export async function consensusTool(args, dependencies) {
69
94
  let title = null;
70
95
  try {
71
96
  title = await summarizationService.generateTitle(prompt);
72
- debugLog(`Consensus: Generated title for initial response - "${title}"`);
97
+ debugLog(
98
+ `Consensus: Generated title for initial response - "${title}"`,
99
+ );
73
100
  } catch (error) {
74
- debugError('Consensus: Failed to generate title for initial response', error);
101
+ debugError(
102
+ 'Consensus: Failed to generate title for initial response',
103
+ error,
104
+ );
75
105
  title = prompt.substring(0, 50);
76
106
  }
77
107
 
@@ -85,8 +115,8 @@ export async function consensusTool(args, dependencies) {
85
115
  ...args,
86
116
  jobId: bgContinuationId, // Use continuation ID as job ID
87
117
  models_list: modelsList, // Add models list for status display
88
- title // Pass the generated title
89
- }
118
+ title, // Pass the generated title
119
+ },
90
120
  },
91
121
  async (context) => {
92
122
  // Execute consensus in background using stream normalizer
@@ -95,23 +125,25 @@ export async function consensusTool(args, dependencies) {
95
125
  {
96
126
  ...dependencies,
97
127
  continuationId: bgContinuationId,
98
- title // Pass title to execution context
128
+ title, // Pass title to execution context
99
129
  },
100
- context
130
+ context,
101
131
  );
102
- }
132
+ },
103
133
  );
104
134
 
105
135
  // Format initial response like check_status output
106
- const startTime = new Date().toLocaleString('en-GB', {
107
- day: '2-digit',
108
- month: '2-digit',
109
- year: 'numeric',
110
- hour: '2-digit',
111
- minute: '2-digit',
112
- second: '2-digit',
113
- hour12: false
114
- }).replace(',', '');
136
+ const startTime = new Date()
137
+ .toLocaleString('en-GB', {
138
+ day: '2-digit',
139
+ month: '2-digit',
140
+ year: 'numeric',
141
+ hour: '2-digit',
142
+ minute: '2-digit',
143
+ second: '2-digit',
144
+ hour12: false,
145
+ })
146
+ .replace(',', '');
115
147
 
116
148
  const statusLine = `⏳ SUBMITTED | CONSENSUS | ${bgContinuationId} | 1/1 | Started: ${startTime} | "${title || 'Processing...'}" | ${modelsList}`;
117
149
 
@@ -119,12 +151,11 @@ export async function consensusTool(args, dependencies) {
119
151
  return createToolResponse({
120
152
  content: `${statusLine}\ncontinuation_id: ${bgContinuationId}`,
121
153
  continuation: {
122
- id: bgContinuationId, // Use continuation_id as the primary ID
123
- status: 'processing'
154
+ id: bgContinuationId, // Use continuation_id as the primary ID
155
+ status: 'processing',
124
156
  },
125
- async_execution: true
157
+ async_execution: true,
126
158
  });
127
-
128
159
  } catch (error) {
129
160
  logger.error('Failed to submit async consensus job', { error });
130
161
  return createToolError(`Async execution failed: ${error.message}`);
@@ -137,7 +168,8 @@ export async function consensusTool(args, dependencies) {
137
168
  // Load existing conversation if continuation_id provided
138
169
  if (continuationId) {
139
170
  try {
140
- const existingState = await dependencies.continuationStore.get(continuationId);
171
+ const existingState =
172
+ await dependencies.continuationStore.get(continuationId);
141
173
  if (existingState) {
142
174
  conversationHistory = existingState.messages || [];
143
175
  } else {
@@ -156,10 +188,13 @@ export async function consensusTool(args, dependencies) {
156
188
 
157
189
  // Validate file paths before processing
158
190
  if (files.length > 0 || images.length > 0) {
159
- const validation = await validateAllPaths({
160
- files,
161
- images
162
- }, { clientCwd: config.server?.client_cwd });
191
+ const validation = await validateAllPaths(
192
+ {
193
+ files,
194
+ images,
195
+ },
196
+ { clientCwd: config.server?.client_cwd },
197
+ );
163
198
  if (!validation.valid) {
164
199
  logger.error('File validation failed', { errors: validation.errors });
165
200
  return validation.errorResponse;
@@ -172,24 +207,29 @@ export async function consensusTool(args, dependencies) {
172
207
  try {
173
208
  const contextRequest = {
174
209
  files: Array.isArray(files) ? files : [],
175
- images: Array.isArray(images) ? images : []
210
+ images: Array.isArray(images) ? images : [],
176
211
  };
177
212
 
178
- const contextResult = await contextProcessor.processUnifiedContext(contextRequest, {
179
- enforceSecurityCheck: false, // Allow files from any location
180
- skipSecurityCheck: true, // Legacy flag for backward compatibility
181
- clientCwd: config.server?.client_cwd // Use auto-detected client working directory
182
- });
213
+ const contextResult = await contextProcessor.processUnifiedContext(
214
+ contextRequest,
215
+ {
216
+ enforceSecurityCheck: false, // Allow files from any location
217
+ skipSecurityCheck: true, // Legacy flag for backward compatibility
218
+ clientCwd: config.server?.client_cwd, // Use auto-detected client working directory
219
+ },
220
+ );
183
221
 
184
222
  // Create context message from files and images
185
- const allProcessedFiles = [...contextResult.files, ...contextResult.images];
223
+ const allProcessedFiles = [
224
+ ...contextResult.files,
225
+ ...contextResult.images,
226
+ ];
186
227
  if (allProcessedFiles.length > 0) {
187
228
  contextMessage = createFileContext(allProcessedFiles, {
188
229
  includeMetadata: true,
189
- includeErrors: true
230
+ includeErrors: true,
190
231
  });
191
232
  }
192
-
193
233
  } catch (error) {
194
234
  logger.error('Error processing context', { error });
195
235
  // Continue without context if processing fails
@@ -202,7 +242,7 @@ export async function consensusTool(args, dependencies) {
202
242
  // Add system prompt
203
243
  messages.push({
204
244
  role: 'system',
205
- content: CONSENSUS_PROMPT
245
+ content: CONSENSUS_PROMPT,
206
246
  });
207
247
 
208
248
  // Add conversation history
@@ -211,7 +251,7 @@ export async function consensusTool(args, dependencies) {
211
251
  // Add user prompt with context
212
252
  const userMessage = {
213
253
  role: 'user',
214
- content: prompt // default to simple string content
254
+ content: prompt, // default to simple string content
215
255
  };
216
256
 
217
257
  // If we have context (files/images), create complex content array
@@ -219,7 +259,7 @@ export async function consensusTool(args, dependencies) {
219
259
  // Create complex content array
220
260
  userMessage.content = [
221
261
  ...contextMessage.content, // Include all file/image parts
222
- { type: 'text', text: prompt } // Add the user prompt as text
262
+ { type: 'text', text: prompt }, // Add the user prompt as text
223
263
  ];
224
264
  }
225
265
 
@@ -234,7 +274,15 @@ export async function consensusTool(args, dependencies) {
234
274
  if (models.length === 1 && models[0].toLowerCase() === 'auto') {
235
275
  // Find first 3 available providers
236
276
  const availableProviders = [];
237
- const providerOrder = ['openai', 'google', 'xai', 'anthropic', 'mistral', 'deepseek', 'openrouter'];
277
+ const providerOrder = [
278
+ 'openai',
279
+ 'google',
280
+ 'xai',
281
+ 'anthropic',
282
+ 'mistral',
283
+ 'deepseek',
284
+ 'openrouter',
285
+ ];
238
286
 
239
287
  for (const providerName of providerOrder) {
240
288
  if (availableProviders.length >= 3) break;
@@ -245,19 +293,21 @@ export async function consensusTool(args, dependencies) {
245
293
  }
246
294
 
247
295
  if (availableProviders.length === 0) {
248
- return createToolError('No providers available. Please configure at least one API key.');
296
+ return createToolError(
297
+ 'No providers available. Please configure at least one API key.',
298
+ );
249
299
  }
250
300
 
251
301
  // Create model names for each available provider with their default model
252
- modelsToProcess = availableProviders.map(providerName =>
253
- getDefaultModelForProvider(providerName)
302
+ modelsToProcess = availableProviders.map((providerName) =>
303
+ getDefaultModelForProvider(providerName),
254
304
  );
255
305
 
256
306
  logger.debug('Auto-expanded to providers', {
257
307
  data: {
258
308
  providers: availableProviders,
259
- models: modelsToProcess
260
- }
309
+ models: modelsToProcess,
310
+ },
261
311
  });
262
312
  }
263
313
 
@@ -266,7 +316,7 @@ export async function consensusTool(args, dependencies) {
266
316
  failedModels.push({
267
317
  model: modelName || 'unknown',
268
318
  error: 'Invalid model specification',
269
- status: 'failed'
319
+ status: 'failed',
270
320
  });
271
321
  continue;
272
322
  }
@@ -279,7 +329,7 @@ export async function consensusTool(args, dependencies) {
279
329
  model: modelName,
280
330
  provider: providerName,
281
331
  error: `Provider not found: ${providerName}`,
282
- status: 'failed'
332
+ status: 'failed',
283
333
  });
284
334
  continue;
285
335
  }
@@ -289,7 +339,7 @@ export async function consensusTool(args, dependencies) {
289
339
  model: modelName,
290
340
  provider: providerName,
291
341
  error: `Provider ${providerName} not available (check API key)`,
292
- status: 'failed'
342
+ status: 'failed',
293
343
  });
294
344
  continue;
295
345
  }
@@ -303,30 +353,35 @@ export async function consensusTool(args, dependencies) {
303
353
  reasoning_effort,
304
354
  use_websearch,
305
355
  config,
306
- model: resolvedModelName // Use resolved model name for API call
307
- }
356
+ model: resolvedModelName, // Use resolved model name for API call
357
+ },
308
358
  });
309
359
  }
310
360
 
311
361
  if (providerCalls.length === 0) {
312
362
  return createToolError(
313
- `No valid providers available for the specified models. Failed models: ${failedModels.map(f => f.model).join(', ')}`
363
+ `No valid providers available for the specified models. Failed models: ${failedModels.map((f) => f.model).join(', ')}`,
314
364
  );
315
365
  }
316
366
 
317
367
  // Phase 1: Initial parallel provider calls
318
- logger.debug('Calling providers in parallel', { data: { providerCount: providerCalls.length } });
368
+ logger.debug('Calling providers in parallel', {
369
+ data: { providerCount: providerCalls.length },
370
+ });
319
371
  const consensusStartTime = Date.now();
320
372
  const initialResults = await Promise.allSettled(
321
373
  providerCalls.map(async (call) => {
322
374
  try {
323
- const response = await call.providerInstance.invoke(messages, call.options);
375
+ const response = await call.providerInstance.invoke(
376
+ messages,
377
+ call.options,
378
+ );
324
379
  return {
325
380
  model: call.model,
326
381
  provider: call.provider,
327
382
  status: 'success',
328
383
  response: response.content,
329
- metadata: response.metadata || {}
384
+ metadata: response.metadata || {},
330
385
  };
331
386
  } catch (error) {
332
387
  return {
@@ -334,16 +389,16 @@ export async function consensusTool(args, dependencies) {
334
389
  provider: call.provider,
335
390
  status: 'failed',
336
391
  error: error.message,
337
- metadata: {}
392
+ metadata: {},
338
393
  };
339
394
  }
340
- })
395
+ }),
341
396
  );
342
397
 
343
398
  // Process initial results
344
399
  const initialPhase = {
345
400
  successful: [],
346
- failed: []
401
+ failed: [],
347
402
  };
348
403
 
349
404
  initialResults.forEach((result, index) => {
@@ -359,7 +414,7 @@ export async function consensusTool(args, dependencies) {
359
414
  provider: providerCalls[index].provider,
360
415
  status: 'failed',
361
416
  error: result.reason.message || 'Unknown error',
362
- metadata: {}
417
+ metadata: {},
363
418
  });
364
419
  }
365
420
  });
@@ -371,10 +426,13 @@ export async function consensusTool(args, dependencies) {
371
426
 
372
427
  // Phase 2: Cross-feedback (if enabled and we have multiple successful responses)
373
428
  if (enable_cross_feedback && initialPhase.successful.length > 1) {
374
- logger.debug('Running cross-feedback phase', { data: { responseCount: initialPhase.successful.length } });
429
+ logger.debug('Running cross-feedback phase', {
430
+ data: { responseCount: initialPhase.successful.length },
431
+ });
375
432
 
376
433
  // Create cross-feedback prompt
377
- const feedbackPrompt = cross_feedback_prompt ||
434
+ const feedbackPrompt =
435
+ cross_feedback_prompt ||
378
436
  `Based on the other AI responses below, please refine your answer to the original question. Consider different perspectives and provide your final response:
379
437
 
380
438
  Original Question: ${prompt}
@@ -391,29 +449,34 @@ Please provide your refined response:`;
391
449
  const refinementResults = await Promise.allSettled(
392
450
  initialPhase.successful.map(async (initialResult) => {
393
451
  try {
394
- const call = providerCalls.find(c => c.model === initialResult.model);
452
+ const call = providerCalls.find(
453
+ (c) => c.model === initialResult.model,
454
+ );
395
455
 
396
456
  // Build model-specific feedback messages with the assistant's initial response
397
457
  const modelFeedbackMessages = [...messages];
398
458
  // Add the assistant's initial response
399
459
  modelFeedbackMessages.push({
400
460
  role: 'assistant',
401
- content: initialResult.response
461
+ content: initialResult.response,
402
462
  });
403
463
  // Now add the feedback prompt
404
464
  modelFeedbackMessages.push({
405
465
  role: 'user',
406
- content: feedbackPrompt
466
+ content: feedbackPrompt,
407
467
  });
408
468
 
409
- const response = await call.providerInstance.invoke(modelFeedbackMessages, call.options);
469
+ const response = await call.providerInstance.invoke(
470
+ modelFeedbackMessages,
471
+ call.options,
472
+ );
410
473
 
411
474
  return {
412
475
  ...initialResult,
413
476
  refined_response: response.content,
414
477
  refined_metadata: response.metadata || {},
415
478
  initial_response: initialResult.response,
416
- status: 'success'
479
+ status: 'success',
417
480
  };
418
481
  } catch (error) {
419
482
  return {
@@ -421,10 +484,10 @@ Please provide your refined response:`;
421
484
  refined_response: null,
422
485
  refined_error: error.message,
423
486
  initial_response: initialResult.response,
424
- status: 'partial' // Had initial success but refinement failed
487
+ status: 'partial', // Had initial success but refinement failed
425
488
  };
426
489
  }
427
- })
490
+ }),
428
491
  );
429
492
 
430
493
  // Process refinement results
@@ -439,7 +502,7 @@ Please provide your refined response:`;
439
502
  ...originalResult,
440
503
  refined_response: null,
441
504
  refined_error: 'Refinement phase failed unexpectedly',
442
- status: 'partial'
505
+ status: 'partial',
443
506
  });
444
507
  }
445
508
  });
@@ -449,8 +512,11 @@ Please provide your refined response:`;
449
512
  try {
450
513
  const consensusMessage = {
451
514
  role: 'assistant',
452
- content: `Consensus completed with ${initialPhase.successful.length} successful responses` +
453
- (refinedPhase ? ` and ${refinedPhase.filter(r => r.status === 'success').length} refined responses` : '')
515
+ content:
516
+ `Consensus completed with ${initialPhase.successful.length} successful responses` +
517
+ (refinedPhase
518
+ ? ` and ${refinedPhase.filter((r) => r.status === 'success').length} refined responses`
519
+ : ''),
454
520
  };
455
521
 
456
522
  const conversationState = {
@@ -461,11 +527,14 @@ Please provide your refined response:`;
461
527
  modelsRequested: models.length,
462
528
  providersSuccessful: initialPhase.successful.length,
463
529
  providersFailed: initialPhase.failed.length,
464
- crossFeedbackEnabled: enable_cross_feedback
465
- }
530
+ crossFeedbackEnabled: enable_cross_feedback,
531
+ },
466
532
  };
467
533
 
468
- await dependencies.continuationStore.set(continuationId, conversationState);
534
+ await dependencies.continuationStore.set(
535
+ continuationId,
536
+ conversationState,
537
+ );
469
538
  } catch (error) {
470
539
  logger.error('Error saving consensus conversation', { error });
471
540
  // Continue even if save fails
@@ -479,17 +548,19 @@ Please provide your refined response:`;
479
548
 
480
549
  if (enable_cross_feedback && refinedPhase) {
481
550
  // When cross-feedback is enabled, count only models that succeeded in both phases
482
- finalSuccessCount = refinedPhase.filter(r => r.status === 'success').length;
551
+ finalSuccessCount = refinedPhase.filter(
552
+ (r) => r.status === 'success',
553
+ ).length;
483
554
 
484
555
  // Collect detailed failure information
485
- refinedPhase.forEach(result => {
556
+ refinedPhase.forEach((result) => {
486
557
  if (result.status === 'partial') {
487
558
  failureDetails.push(`${result.model} (refinement failed)`);
488
559
  }
489
560
  });
490
561
 
491
562
  // Add models that failed in initial phase
492
- initialPhase.failed.forEach(failure => {
563
+ initialPhase.failed.forEach((failure) => {
493
564
  failureDetails.push(`${failure.model} (initial failed)`);
494
565
  });
495
566
  } else {
@@ -497,21 +568,23 @@ Please provide your refined response:`;
497
568
  finalSuccessCount = initialPhase.successful.length;
498
569
 
499
570
  // Collect initial failure information
500
- initialPhase.failed.forEach(failure => {
571
+ initialPhase.failed.forEach((failure) => {
501
572
  failureDetails.push(`${failure.model} (${failure.error})`);
502
573
  });
503
574
  }
504
575
 
505
576
  // Create models list string for display
506
- const modelsList = providerCalls.map(call => call.model).join(', ');
507
-
577
+ const modelsList = providerCalls.map((call) => call.model).join(', ');
508
578
 
509
579
  // Create unified status line (similar to async status display)
510
- const finalCount = refinedPhase ? refinedPhase.filter(r => r.status === 'success').length : initialPhase.successful.length;
580
+ const finalCount = refinedPhase
581
+ ? refinedPhase.filter((r) => r.status === 'success').length
582
+ : initialPhase.successful.length;
511
583
  const totalCount = providerCalls.length;
512
- const statusLine = config.environment?.nodeEnv !== 'test'
513
- ? `✅ COMPLETED | CONSENSUS | ${continuationId} | ${consensusExecutionTime.toFixed(1)}s elapsed | ${finalCount}/${totalCount} succeeded | ${modelsList}\n`
514
- : '';
584
+ const statusLine =
585
+ config.environment?.nodeEnv !== 'test'
586
+ ? `✅ COMPLETED | CONSENSUS | ${continuationId} | ${consensusExecutionTime.toFixed(1)}s elapsed | ${finalCount}/${totalCount} succeeded | ${modelsList}\n`
587
+ : '';
515
588
 
516
589
  // Always include continuation_id line for clarity
517
590
  const continuationIdLine = `continuation_id: ${continuationId}\n\n`;
@@ -522,21 +595,23 @@ Please provide your refined response:`;
522
595
  models_consulted: models.length,
523
596
  successful_initial_responses: initialPhase.successful.length,
524
597
  failed_responses: initialPhase.failed.length,
525
- refined_responses: refinedPhase ? refinedPhase.filter(r => r.status === 'success').length : 0,
598
+ refined_responses: refinedPhase
599
+ ? refinedPhase.filter((r) => r.status === 'success').length
600
+ : 0,
526
601
  phases: {
527
602
  initial: initialPhase.successful,
528
603
  ...(refinedPhase !== null && { refined: refinedPhase }),
529
- failed: initialPhase.failed
604
+ failed: initialPhase.failed,
530
605
  },
531
606
  continuation: {
532
607
  id: continuationId,
533
- messageCount: messages.length + 1
608
+ messageCount: messages.length + 1,
534
609
  },
535
610
  settings: {
536
611
  enable_cross_feedback,
537
612
  temperature,
538
- models_requested: models
539
- }
613
+ models_requested: models,
614
+ },
540
615
  };
541
616
 
542
617
  // Apply token limiting to the final response
@@ -559,10 +634,9 @@ Please provide your refined response:`;
559
634
  content: finalContent,
560
635
  continuation: {
561
636
  id: continuationId,
562
- messageCount: messages.length + 1
563
- }
637
+ messageCount: messages.length + 1,
638
+ },
564
639
  });
565
-
566
640
  } catch (error) {
567
641
  logger.error('Consensus tool error', { error });
568
642
  return createToolError('Consensus tool failed', error);
@@ -579,13 +653,13 @@ Please provide your refined response:`;
579
653
  */
580
654
  function getDefaultModelForProvider(providerName) {
581
655
  const defaults = {
582
- 'openai': 'gpt-5',
583
- 'xai': 'grok-4-0709',
584
- 'google': 'gemini-2.5-pro',
585
- 'anthropic': 'claude-sonnet-4-20250514',
586
- 'mistral': 'magistral-medium-2506',
587
- 'deepseek': 'deepseek-reasoner',
588
- 'openrouter': 'qwen/qwen3-coder'
656
+ openai: 'gpt-5',
657
+ xai: 'grok-4-0709',
658
+ google: 'gemini-2.5-pro',
659
+ anthropic: 'claude-sonnet-4-20250514',
660
+ mistral: 'magistral-medium-2506',
661
+ deepseek: 'deepseek-reasoner',
662
+ openrouter: 'qwen/qwen3-coder',
589
663
  };
590
664
 
591
665
  return defaults[providerName] || 'gpt-5';
@@ -611,8 +685,12 @@ function mapModelToProvider(model, providers) {
611
685
  }
612
686
 
613
687
  // Check OpenRouter-specific patterns first
614
- if (modelLower === 'openrouter auto' || modelLower === 'auto router' ||
615
- modelLower === 'auto-router' || modelLower === 'openrouter-auto') {
688
+ if (
689
+ modelLower === 'openrouter auto' ||
690
+ modelLower === 'auto router' ||
691
+ modelLower === 'auto-router' ||
692
+ modelLower === 'openrouter-auto'
693
+ ) {
616
694
  return 'openrouter';
617
695
  }
618
696
 
@@ -622,7 +700,11 @@ function mapModelToProvider(model, providers) {
622
700
  for (const [providerName, provider] of Object.entries(providers)) {
623
701
  if (provider && provider.getModelConfig) {
624
702
  const modelConfig = provider.getModelConfig(model);
625
- if (modelConfig && !modelConfig.isDynamic && !modelConfig.needsApiUpdate) {
703
+ if (
704
+ modelConfig &&
705
+ !modelConfig.isDynamic &&
706
+ !modelConfig.needsApiUpdate
707
+ ) {
626
708
  // Model exists in this provider's static list
627
709
  return providerName;
628
710
  }
@@ -635,8 +717,12 @@ function mapModelToProvider(model, providers) {
635
717
  // For non-slash models, use keyword matching as before
636
718
 
637
719
  // OpenAI models
638
- if (modelLower.includes('gpt') || modelLower.includes('o1') ||
639
- modelLower.includes('o3') || modelLower.includes('o4')) {
720
+ if (
721
+ modelLower.includes('gpt') ||
722
+ modelLower.includes('o1') ||
723
+ modelLower.includes('o3') ||
724
+ modelLower.includes('o4')
725
+ ) {
640
726
  return 'openai';
641
727
  }
642
728
 
@@ -646,14 +732,22 @@ function mapModelToProvider(model, providers) {
646
732
  }
647
733
 
648
734
  // Google models
649
- if (modelLower.includes('gemini') || modelLower.includes('flash') ||
650
- modelLower.includes('pro') || modelLower === 'google') {
735
+ if (
736
+ modelLower.includes('gemini') ||
737
+ modelLower.includes('flash') ||
738
+ modelLower.includes('pro') ||
739
+ modelLower === 'google'
740
+ ) {
651
741
  return 'google';
652
742
  }
653
743
 
654
744
  // Anthropic models
655
- if (modelLower.includes('claude') || modelLower.includes('opus') ||
656
- modelLower.includes('sonnet') || modelLower.includes('haiku')) {
745
+ if (
746
+ modelLower.includes('claude') ||
747
+ modelLower.includes('opus') ||
748
+ modelLower.includes('sonnet') ||
749
+ modelLower.includes('haiku')
750
+ ) {
657
751
  return 'anthropic';
658
752
  }
659
753
 
@@ -663,14 +757,22 @@ function mapModelToProvider(model, providers) {
663
757
  }
664
758
 
665
759
  // DeepSeek models
666
- if (modelLower.includes('deepseek') || modelLower === 'reasoner' ||
667
- modelLower === 'r1' || modelLower === 'chat') {
760
+ if (
761
+ modelLower.includes('deepseek') ||
762
+ modelLower === 'reasoner' ||
763
+ modelLower === 'r1' ||
764
+ modelLower === 'chat'
765
+ ) {
668
766
  return 'deepseek';
669
767
  }
670
768
 
671
769
  // OpenRouter models (specific model patterns)
672
- if (modelLower.includes('qwen') || modelLower.includes('kimi') ||
673
- modelLower.includes('moonshot') || modelLower === 'k2') {
770
+ if (
771
+ modelLower.includes('qwen') ||
772
+ modelLower.includes('kimi') ||
773
+ modelLower.includes('moonshot') ||
774
+ modelLower === 'k2'
775
+ ) {
674
776
  return 'openrouter';
675
777
  }
676
778
 
@@ -693,7 +795,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
693
795
  contextProcessor,
694
796
  providerStreamNormalizer,
695
797
  continuationId,
696
- title: passedTitle // Title passed from initial submission
798
+ title: passedTitle, // Title passed from initial submission
697
799
  } = dependencies;
698
800
 
699
801
  const {
@@ -705,7 +807,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
705
807
  cross_feedback_prompt,
706
808
  temperature = 0.2,
707
809
  reasoning_effort = 'medium',
708
- use_websearch = false
810
+ use_websearch = false,
709
811
  } = args;
710
812
 
711
813
  let conversationHistory = [];
@@ -725,13 +827,18 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
725
827
 
726
828
  // Validate file paths before processing
727
829
  if (files.length > 0 || images.length > 0) {
728
- const validation = await validateAllPaths({
729
- files,
730
- images
731
- }, { clientCwd: config.server?.client_cwd });
830
+ const validation = await validateAllPaths(
831
+ {
832
+ files,
833
+ images,
834
+ },
835
+ { clientCwd: config.server?.client_cwd },
836
+ );
732
837
  if (!validation.valid) {
733
838
  logger.error('File validation failed', { errors: validation.errors });
734
- throw new Error(`File validation failed: ${validation.errors.join(', ')}`);
839
+ throw new Error(
840
+ `File validation failed: ${validation.errors.join(', ')}`,
841
+ );
735
842
  }
736
843
  }
737
844
 
@@ -741,24 +848,29 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
741
848
  try {
742
849
  const contextRequest = {
743
850
  files: Array.isArray(files) ? files : [],
744
- images: Array.isArray(images) ? images : []
851
+ images: Array.isArray(images) ? images : [],
745
852
  };
746
853
 
747
- const contextResult = await contextProcessor.processUnifiedContext(contextRequest, {
748
- enforceSecurityCheck: false,
749
- skipSecurityCheck: true,
750
- clientCwd: config.server?.client_cwd
751
- });
854
+ const contextResult = await contextProcessor.processUnifiedContext(
855
+ contextRequest,
856
+ {
857
+ enforceSecurityCheck: false,
858
+ skipSecurityCheck: true,
859
+ clientCwd: config.server?.client_cwd,
860
+ },
861
+ );
752
862
 
753
863
  // Create context message from files and images
754
- const allProcessedFiles = [...contextResult.files, ...contextResult.images];
864
+ const allProcessedFiles = [
865
+ ...contextResult.files,
866
+ ...contextResult.images,
867
+ ];
755
868
  if (allProcessedFiles.length > 0) {
756
869
  contextMessage = createFileContext(allProcessedFiles, {
757
870
  includeMetadata: true,
758
- includeErrors: true
871
+ includeErrors: true,
759
872
  });
760
873
  }
761
-
762
874
  } catch (error) {
763
875
  logger.error('Error processing context', { error });
764
876
  // Continue without context if processing fails
@@ -771,7 +883,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
771
883
  // Add system prompt
772
884
  messages.push({
773
885
  role: 'system',
774
- content: CONSENSUS_PROMPT
886
+ content: CONSENSUS_PROMPT,
775
887
  });
776
888
 
777
889
  // Add conversation history
@@ -780,14 +892,14 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
780
892
  // Add user prompt with context
781
893
  const userMessage = {
782
894
  role: 'user',
783
- content: prompt
895
+ content: prompt,
784
896
  };
785
897
 
786
898
  // If we have context (files/images), create complex content array
787
899
  if (contextMessage && contextMessage.content) {
788
900
  userMessage.content = [
789
901
  ...contextMessage.content,
790
- { type: 'text', text: prompt }
902
+ { type: 'text', text: prompt },
791
903
  ];
792
904
  }
793
905
 
@@ -801,7 +913,15 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
801
913
  let modelsToProcess = models;
802
914
  if (models.length === 1 && models[0].toLowerCase() === 'auto') {
803
915
  const availableProviders = [];
804
- const providerOrder = ['openai', 'google', 'xai', 'anthropic', 'mistral', 'deepseek', 'openrouter'];
916
+ const providerOrder = [
917
+ 'openai',
918
+ 'google',
919
+ 'xai',
920
+ 'anthropic',
921
+ 'mistral',
922
+ 'deepseek',
923
+ 'openrouter',
924
+ ];
805
925
 
806
926
  for (const providerName of providerOrder) {
807
927
  if (availableProviders.length >= 3) break;
@@ -812,18 +932,20 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
812
932
  }
813
933
 
814
934
  if (availableProviders.length === 0) {
815
- throw new Error('No providers available. Please configure at least one API key.');
935
+ throw new Error(
936
+ 'No providers available. Please configure at least one API key.',
937
+ );
816
938
  }
817
939
 
818
- modelsToProcess = availableProviders.map(providerName =>
819
- getDefaultModelForProvider(providerName)
940
+ modelsToProcess = availableProviders.map((providerName) =>
941
+ getDefaultModelForProvider(providerName),
820
942
  );
821
943
 
822
944
  logger.debug('Auto-expanded to providers', {
823
945
  data: {
824
946
  providers: availableProviders,
825
- models: modelsToProcess
826
- }
947
+ models: modelsToProcess,
948
+ },
827
949
  });
828
950
  }
829
951
 
@@ -833,7 +955,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
833
955
  failedModels.push({
834
956
  model: modelName || 'unknown',
835
957
  error: 'Invalid model specification',
836
- status: 'failed'
958
+ status: 'failed',
837
959
  });
838
960
  continue;
839
961
  }
@@ -847,7 +969,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
847
969
  model: modelName,
848
970
  provider: providerName,
849
971
  error: `Provider not found: ${providerName}`,
850
- status: 'failed'
972
+ status: 'failed',
851
973
  });
852
974
  continue;
853
975
  }
@@ -857,7 +979,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
857
979
  model: modelName,
858
980
  provider: providerName,
859
981
  error: `Provider ${providerName} not available (check API key)`,
860
- status: 'failed'
982
+ status: 'failed',
861
983
  });
862
984
  continue;
863
985
  }
@@ -872,19 +994,19 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
872
994
  use_websearch,
873
995
  signal: context?.signal, // Pass AbortSignal for cancellation support
874
996
  config,
875
- model: resolvedModelName
876
- }
997
+ model: resolvedModelName,
998
+ },
877
999
  });
878
1000
  }
879
1001
 
880
1002
  if (providerCalls.length === 0) {
881
1003
  throw new Error(
882
- `No valid providers available for the specified models. Failed models: ${failedModels.map(f => f.model).join(', ')}`
1004
+ `No valid providers available for the specified models. Failed models: ${failedModels.map((f) => f.model).join(', ')}`,
883
1005
  );
884
1006
  }
885
1007
 
886
1008
  // Create models list string for display
887
- const modelsList = providerCalls.map(call => call.model).join(', ');
1009
+ const modelsList = providerCalls.map((call) => call.model).join(', ');
888
1010
 
889
1011
  // Initialize SummarizationService
890
1012
  const summarizationService = new SummarizationService(providers, config);
@@ -914,27 +1036,29 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
914
1036
  total_providers: providerCalls.length,
915
1037
  completed_providers: 0,
916
1038
  failed_providers: failedModels.length,
917
- provider_status: {}
918
- }
1039
+ provider_status: {},
1040
+ },
919
1041
  });
920
1042
 
921
1043
  const consensusStartTime = Date.now();
922
1044
 
923
1045
  // Phase 1: Initial parallel provider calls with streaming
924
- logger.debug('Calling providers in parallel with streaming', { data: { providerCount: providerCalls.length } });
1046
+ logger.debug('Calling providers in parallel with streaming', {
1047
+ data: { providerCount: providerCalls.length },
1048
+ });
925
1049
 
926
1050
  const initialResults = await executeConsensusPhaseWithStreaming(
927
1051
  providerCalls,
928
1052
  messages,
929
1053
  'initial',
930
1054
  context,
931
- providerStreamNormalizer
1055
+ providerStreamNormalizer,
932
1056
  );
933
1057
 
934
1058
  // Process initial results
935
1059
  const initialPhase = {
936
1060
  successful: [],
937
- failed: []
1061
+ failed: [],
938
1062
  };
939
1063
 
940
1064
  initialResults.forEach((result) => {
@@ -952,7 +1076,9 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
952
1076
 
953
1077
  // Phase 2: Cross-feedback (if enabled and we have multiple successful responses)
954
1078
  if (enable_cross_feedback && initialPhase.successful.length > 1) {
955
- logger.debug('Running cross-feedback phase with streaming', { data: { responseCount: initialPhase.successful.length } });
1079
+ logger.debug('Running cross-feedback phase with streaming', {
1080
+ data: { responseCount: initialPhase.successful.length },
1081
+ });
956
1082
 
957
1083
  // Update job status for phase 2
958
1084
  await context.updateJob({
@@ -960,12 +1086,13 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
960
1086
  phase: 'cross_feedback',
961
1087
  total_providers: initialPhase.successful.length,
962
1088
  completed_providers: 0,
963
- provider_status: {}
964
- }
1089
+ provider_status: {},
1090
+ },
965
1091
  });
966
1092
 
967
1093
  // Create cross-feedback prompt
968
- const feedbackPrompt = cross_feedback_prompt ||
1094
+ const feedbackPrompt =
1095
+ cross_feedback_prompt ||
969
1096
  `Based on the other AI responses below, please refine your answer to the original question. Consider different perspectives and provide your final response:
970
1097
 
971
1098
  Original Question: ${prompt}
@@ -977,23 +1104,23 @@ Please provide your refined response:`;
977
1104
 
978
1105
  // Build model-specific feedback calls
979
1106
  const feedbackCalls = initialPhase.successful.map((initialResult) => {
980
- const call = providerCalls.find(c => c.model === initialResult.model);
1107
+ const call = providerCalls.find((c) => c.model === initialResult.model);
981
1108
 
982
1109
  // Build model-specific feedback messages with the assistant's initial response
983
1110
  const modelFeedbackMessages = [...messages];
984
1111
  modelFeedbackMessages.push({
985
1112
  role: 'assistant',
986
- content: initialResult.response
1113
+ content: initialResult.response,
987
1114
  });
988
1115
  modelFeedbackMessages.push({
989
1116
  role: 'user',
990
- content: feedbackPrompt
1117
+ content: feedbackPrompt,
991
1118
  });
992
1119
 
993
1120
  return {
994
1121
  ...call,
995
1122
  messages: modelFeedbackMessages,
996
- initialResult
1123
+ initialResult,
997
1124
  };
998
1125
  });
999
1126
 
@@ -1003,7 +1130,7 @@ Please provide your refined response:`;
1003
1130
  null, // messages already embedded in feedbackCalls
1004
1131
  'refinement',
1005
1132
  context,
1006
- providerStreamNormalizer
1133
+ providerStreamNormalizer,
1007
1134
  );
1008
1135
 
1009
1136
  // Process refinement results
@@ -1015,7 +1142,7 @@ Please provide your refined response:`;
1015
1142
  refined_metadata: result.status === 'success' ? result.metadata : {},
1016
1143
  refined_error: result.status === 'failed' ? result.error : null,
1017
1144
  initial_response: initialResult.response,
1018
- status: result.status === 'success' ? 'success' : 'partial'
1145
+ status: result.status === 'success' ? 'success' : 'partial',
1019
1146
  };
1020
1147
  });
1021
1148
  }
@@ -1024,8 +1151,11 @@ Please provide your refined response:`;
1024
1151
  try {
1025
1152
  const consensusMessage = {
1026
1153
  role: 'assistant',
1027
- content: `Consensus completed with ${initialPhase.successful.length} successful responses` +
1028
- (refinedPhase ? ` and ${refinedPhase.filter(r => r.status === 'success').length} refined responses` : '')
1154
+ content:
1155
+ `Consensus completed with ${initialPhase.successful.length} successful responses` +
1156
+ (refinedPhase
1157
+ ? ` and ${refinedPhase.filter((r) => r.status === 'success').length} refined responses`
1158
+ : ''),
1029
1159
  };
1030
1160
 
1031
1161
  const conversationState = {
@@ -1036,8 +1166,8 @@ Please provide your refined response:`;
1036
1166
  modelsRequested: models.length,
1037
1167
  providersSuccessful: initialPhase.successful.length,
1038
1168
  providersFailed: initialPhase.failed.length,
1039
- crossFeedbackEnabled: enable_cross_feedback
1040
- }
1169
+ crossFeedbackEnabled: enable_cross_feedback,
1170
+ },
1041
1171
  };
1042
1172
 
1043
1173
  await continuationStore.set(continuationId, conversationState);
@@ -1054,7 +1184,7 @@ Please provide your refined response:`;
1054
1184
  // Collect all successful responses for summary generation
1055
1185
  if (refinedPhase) {
1056
1186
  // Use refined responses when available
1057
- refinedPhase.forEach(result => {
1187
+ refinedPhase.forEach((result) => {
1058
1188
  if (result.status === 'success' && result.refined_response) {
1059
1189
  combinedResponses.push(`${result.model}:\n${result.refined_response}`);
1060
1190
  } else if (result.initial_response) {
@@ -1064,7 +1194,7 @@ Please provide your refined response:`;
1064
1194
  });
1065
1195
  } else {
1066
1196
  // Use initial responses
1067
- initialPhase.successful.forEach(result => {
1197
+ initialPhase.successful.forEach((result) => {
1068
1198
  if (result.response) {
1069
1199
  combinedResponses.push(`${result.model}:\n${result.response}`);
1070
1200
  }
@@ -1076,12 +1206,13 @@ Please provide your refined response:`;
1076
1206
  const combinedContent = combinedResponses.join('\n\n---\n\n');
1077
1207
  if (combinedContent.length > 100) {
1078
1208
  try {
1079
- finalSummary = await summarizationService.generateFinalSummary(combinedContent);
1209
+ finalSummary =
1210
+ await summarizationService.generateFinalSummary(combinedContent);
1080
1211
  debugLog(`Consensus: Generated final summary - "${finalSummary}"`);
1081
1212
 
1082
1213
  // Update job with final summary
1083
1214
  await context.updateJob({
1084
- final_summary: finalSummary
1215
+ final_summary: finalSummary,
1085
1216
  });
1086
1217
  } catch (error) {
1087
1218
  debugError('Consensus: Error generating final summary', error);
@@ -1095,20 +1226,22 @@ Please provide your refined response:`;
1095
1226
  const failureDetails = [];
1096
1227
 
1097
1228
  if (enable_cross_feedback && refinedPhase) {
1098
- finalSuccessCount = refinedPhase.filter(r => r.status === 'success').length;
1229
+ finalSuccessCount = refinedPhase.filter(
1230
+ (r) => r.status === 'success',
1231
+ ).length;
1099
1232
 
1100
- refinedPhase.forEach(result => {
1233
+ refinedPhase.forEach((result) => {
1101
1234
  if (result.status === 'partial') {
1102
1235
  failureDetails.push(`${result.model} (refinement failed)`);
1103
1236
  }
1104
1237
  });
1105
1238
 
1106
- initialPhase.failed.forEach(failure => {
1239
+ initialPhase.failed.forEach((failure) => {
1107
1240
  failureDetails.push(`${failure.model} (initial failed)`);
1108
1241
  });
1109
1242
  } else {
1110
1243
  finalSuccessCount = initialPhase.successful.length;
1111
- initialPhase.failed.forEach(failure => {
1244
+ initialPhase.failed.forEach((failure) => {
1112
1245
  failureDetails.push(`${failure.model} (${failure.error})`);
1113
1246
  });
1114
1247
  }
@@ -1119,20 +1252,22 @@ Please provide your refined response:`;
1119
1252
  models_consulted: models.length,
1120
1253
  successful_initial_responses: initialPhase.successful.length,
1121
1254
  failed_responses: initialPhase.failed.length,
1122
- refined_responses: refinedPhase ? refinedPhase.filter(r => r.status === 'success').length : 0,
1255
+ refined_responses: refinedPhase
1256
+ ? refinedPhase.filter((r) => r.status === 'success').length
1257
+ : 0,
1123
1258
  phases: {
1124
1259
  initial: initialPhase.successful,
1125
1260
  ...(refinedPhase !== null && { refined: refinedPhase }),
1126
- failed: initialPhase.failed
1261
+ failed: initialPhase.failed,
1127
1262
  },
1128
1263
  continuation: {
1129
1264
  id: continuationId,
1130
- messageCount: messages.length + 1
1265
+ messageCount: messages.length + 1,
1131
1266
  },
1132
1267
  settings: {
1133
1268
  enable_cross_feedback,
1134
1269
  temperature,
1135
- models_requested: models
1270
+ models_requested: models,
1136
1271
  },
1137
1272
  metadata: {
1138
1273
  execution_time: consensusExecutionTime,
@@ -1141,8 +1276,8 @@ Please provide your refined response:`;
1141
1276
  total_models: models.length,
1142
1277
  failure_details: failureDetails,
1143
1278
  title,
1144
- final_summary: finalSummary
1145
- }
1279
+ final_summary: finalSummary,
1280
+ },
1146
1281
  };
1147
1282
  }
1148
1283
 
@@ -1155,7 +1290,13 @@ Please provide your refined response:`;
1155
1290
  * @param {object} streamNormalizer - Stream normalizer instance
1156
1291
  * @returns {Promise<Array>} Results from all providers
1157
1292
  */
1158
- async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase, context, streamNormalizer) {
1293
+ async function executeConsensusPhaseWithStreaming(
1294
+ providerCalls,
1295
+ messages,
1296
+ phase,
1297
+ context,
1298
+ streamNormalizer,
1299
+ ) {
1159
1300
  let completedCount = 0;
1160
1301
  const totalCount = providerCalls.length;
1161
1302
  const providerContents = {}; // Store accumulated content per provider
@@ -1172,21 +1313,31 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1172
1313
  await context.updateJob({
1173
1314
  progress: {
1174
1315
  [`provider_${index}_status`]: 'prompting',
1175
- [`provider_${index}_model`]: call.model
1176
- }
1316
+ [`provider_${index}_model`]: call.model,
1317
+ },
1177
1318
  });
1178
1319
 
1179
1320
  const messagesToSend = call.messages || messages;
1180
1321
  let response;
1181
1322
 
1182
1323
  // Check if provider supports streaming
1183
- if (call.providerInstance.stream && typeof call.providerInstance.stream === 'function') {
1324
+ if (
1325
+ call.providerInstance.stream &&
1326
+ typeof call.providerInstance.stream === 'function'
1327
+ ) {
1184
1328
  // Use streaming with normalization
1185
- const stream = call.providerInstance.stream(messagesToSend, call.options);
1186
- const normalizedStream = streamNormalizer.normalize(call.provider, stream, {
1187
- model: call.options.model,
1188
- requestId: `${context.jobId}-${phase}-${index}`
1189
- });
1329
+ const stream = call.providerInstance.stream(
1330
+ messagesToSend,
1331
+ call.options,
1332
+ );
1333
+ const normalizedStream = streamNormalizer.normalize(
1334
+ call.provider,
1335
+ stream,
1336
+ {
1337
+ model: call.options.model,
1338
+ requestId: `${context.jobId}-${phase}-${index}`,
1339
+ },
1340
+ );
1190
1341
 
1191
1342
  // Process normalized stream
1192
1343
  let accumulatedContent = '';
@@ -1194,7 +1345,7 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1194
1345
  let finalMetadata = {};
1195
1346
 
1196
1347
  await context.updateJob({
1197
- progress: { [`provider_${index}_status`]: 'streaming' }
1348
+ progress: { [`provider_${index}_status`]: 'streaming' },
1198
1349
  });
1199
1350
 
1200
1351
  for await (const event of normalizedStream) {
@@ -1210,15 +1361,16 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1210
1361
 
1211
1362
  // Combine all provider contents for unified accumulated_content
1212
1363
  const combinedContent = Object.values(providerContents)
1213
- .filter(content => content && content.length > 0)
1364
+ .filter((content) => content && content.length > 0)
1214
1365
  .join('\n\n---\n\n');
1215
1366
 
1216
1367
  // Update with both provider preview and combined accumulated content
1217
1368
  await context.updateJob({
1218
- [`provider_${index}_preview`]: accumulatedContent.length > 150
1219
- ? accumulatedContent.substring(0, 150) + '...'
1220
- : accumulatedContent,
1221
- accumulated_content: combinedContent // Full combined content from all providers
1369
+ [`provider_${index}_preview`]:
1370
+ accumulatedContent.length > 150
1371
+ ? accumulatedContent.substring(0, 150) + '...'
1372
+ : accumulatedContent,
1373
+ accumulated_content: combinedContent, // Full combined content from all providers
1222
1374
  });
1223
1375
  break;
1224
1376
  case 'usage':
@@ -1239,16 +1391,18 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1239
1391
  metadata: {
1240
1392
  ...finalMetadata,
1241
1393
  usage: finalUsage,
1242
- streaming: true
1243
- }
1394
+ streaming: true,
1395
+ },
1244
1396
  };
1245
1397
 
1246
1398
  // Store final provider content
1247
1399
  providerContents[index] = accumulatedContent;
1248
-
1249
1400
  } else {
1250
1401
  // Fall back to regular invoke
1251
- response = await call.providerInstance.invoke(messagesToSend, call.options);
1402
+ response = await call.providerInstance.invoke(
1403
+ messagesToSend,
1404
+ call.options,
1405
+ );
1252
1406
 
1253
1407
  // Store provider content for non-streaming response
1254
1408
  if (response && response.content) {
@@ -1256,29 +1410,30 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1256
1410
 
1257
1411
  // Update accumulated content for non-streaming provider
1258
1412
  const combinedContent = Object.values(providerContents)
1259
- .filter(content => content && content.length > 0)
1413
+ .filter((content) => content && content.length > 0)
1260
1414
  .join('\n\n---\n\n');
1261
1415
 
1262
1416
  await context.updateJob({
1263
- accumulated_content: combinedContent
1417
+ accumulated_content: combinedContent,
1264
1418
  });
1265
1419
  }
1266
1420
  }
1267
1421
 
1268
1422
  // Update provider status to 'finished'
1269
1423
  completedCount++;
1270
- const progressText = phase === 'initial'
1271
- ? `${completedCount}/${totalCount} initial`
1272
- : phase === 'refinement'
1273
- ? `${completedCount}/${totalCount} refined`
1274
- : `${completedCount}/${totalCount} responded`;
1424
+ const progressText =
1425
+ phase === 'initial'
1426
+ ? `${completedCount}/${totalCount} initial`
1427
+ : phase === 'refinement'
1428
+ ? `${completedCount}/${totalCount} refined`
1429
+ : `${completedCount}/${totalCount} responded`;
1275
1430
 
1276
1431
  await context.updateJob({
1277
1432
  consensus_progress: progressText,
1278
1433
  progress: {
1279
1434
  [`provider_${index}_status`]: 'finished',
1280
- completed_providers: completedCount
1281
- }
1435
+ completed_providers: completedCount,
1436
+ },
1282
1437
  });
1283
1438
 
1284
1439
  return {
@@ -1286,16 +1441,15 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1286
1441
  provider: call.provider,
1287
1442
  status: 'success',
1288
1443
  response: response.content,
1289
- metadata: response.metadata || {}
1444
+ metadata: response.metadata || {},
1290
1445
  };
1291
-
1292
1446
  } catch (error) {
1293
1447
  // Update provider status to 'failed'
1294
1448
  await context.updateJob({
1295
1449
  progress: {
1296
1450
  [`provider_${index}_status`]: 'failed',
1297
- [`provider_${index}_error`]: error.message
1298
- }
1451
+ [`provider_${index}_error`]: error.message,
1452
+ },
1299
1453
  });
1300
1454
 
1301
1455
  return {
@@ -1303,20 +1457,20 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1303
1457
  provider: call.provider,
1304
1458
  status: 'failed',
1305
1459
  error: error.message,
1306
- metadata: {}
1460
+ metadata: {},
1307
1461
  };
1308
1462
  }
1309
- })
1463
+ }),
1310
1464
  );
1311
1465
 
1312
1466
  // After all providers complete, update with final combined content
1313
1467
  const finalCombinedContent = Object.values(providerContents)
1314
- .filter(content => content && content.length > 0)
1468
+ .filter((content) => content && content.length > 0)
1315
1469
  .join('\n\n---\n\n');
1316
1470
 
1317
1471
  if (finalCombinedContent) {
1318
1472
  await context.updateJob({
1319
- accumulated_content: finalCombinedContent
1473
+ accumulated_content: finalCombinedContent,
1320
1474
  });
1321
1475
  }
1322
1476
 
@@ -1329,14 +1483,15 @@ async function executeConsensusPhaseWithStreaming(providerCalls, messages, phase
1329
1483
  provider: providerCalls[index].provider,
1330
1484
  status: 'failed',
1331
1485
  error: result.reason.message || 'Unknown error',
1332
- metadata: {}
1486
+ metadata: {},
1333
1487
  };
1334
1488
  }
1335
1489
  });
1336
1490
  }
1337
1491
 
1338
1492
  // Tool metadata
1339
- consensusTool.description = 'PARALLEL CONSENSUS WITH CROSS-MODEL FEEDBACK - Query multiple models simultaneously, then optionally refine responses based on cross-feedback. For complex decisions, architectural choices, technical evaluations. Use models: ["auto"] for automatic selection.';
1493
+ consensusTool.description =
1494
+ 'PARALLEL CONSENSUS WITH CROSS-MODEL FEEDBACK - Query multiple models simultaneously, then optionally refine responses based on cross-feedback. For complex decisions, architectural choices, technical evaluations. Use models: ["auto"] for automatic selection.';
1340
1495
  consensusTool.inputSchema = {
1341
1496
  type: 'object',
1342
1497
  properties: {
@@ -1344,34 +1499,41 @@ consensusTool.inputSchema = {
1344
1499
  type: 'array',
1345
1500
  items: { type: 'string' },
1346
1501
  minItems: 1,
1347
- description: 'List of models to consult. Example: ["gpt-5", "gemini-2.5-pro", "grok-4-0709"]',
1502
+ description:
1503
+ 'List of models to consult. Example: ["gpt-5", "gemini-2.5-pro", "grok-4-0709"]',
1348
1504
  },
1349
1505
  files: {
1350
1506
  type: 'array',
1351
1507
  items: { type: 'string' },
1352
- description: 'File paths for additional context (absolute or relative paths). Example: ["C:\\Users\\username\\project\\architecture.md", "./requirements.txt"]',
1508
+ description:
1509
+ 'File paths for additional context (absolute or relative paths). Example: ["C:\\Users\\username\\project\\architecture.md", "./requirements.txt"]',
1353
1510
  },
1354
1511
  images: {
1355
1512
  type: 'array',
1356
1513
  items: { type: 'string' },
1357
- description: 'Image paths for visual context (absolute or relative paths, or base64). Example: ["C:\\Users\\username\\current_architecture.png", "./user_flow.jpg"]',
1514
+ description:
1515
+ 'Image paths for visual context (absolute or relative paths, or base64). Example: ["C:\\Users\\username\\current_architecture.png", "./user_flow.jpg"]',
1358
1516
  },
1359
1517
  continuation_id: {
1360
1518
  type: 'string',
1361
- description: 'Thread continuation ID for multi-turn conversations. Example: "consensus_1703123456789_xyz789"',
1519
+ description:
1520
+ 'Thread continuation ID for multi-turn conversations. Example: "consensus_1703123456789_xyz789"',
1362
1521
  },
1363
1522
  enable_cross_feedback: {
1364
1523
  type: 'boolean',
1365
- description: 'Enable refinement phase where models see others\' responses and can improve their answers. Example: true (recommended), false (faster single-phase only). Default: true',
1524
+ description:
1525
+ 'Enable refinement phase where models see others\' responses and can improve their answers. Example: true (recommended), false (faster single-phase only). Default: true',
1366
1526
  default: true,
1367
1527
  },
1368
1528
  cross_feedback_prompt: {
1369
1529
  type: 'string',
1370
- description: 'Custom prompt for refinement phase. Example: "Focus on scalability trade-offs in your refinement" or leave empty for default cross-feedback prompt',
1530
+ description:
1531
+ 'Custom prompt for refinement phase. Example: "Focus on scalability trade-offs in your refinement" or leave empty for default cross-feedback prompt',
1371
1532
  },
1372
1533
  temperature: {
1373
1534
  type: 'number',
1374
- description: 'Response randomness (0.0-1.0). Examples: 0.1 (very focused), 0.2 (analytical - default), 0.5 (balanced). Default: 0.2',
1535
+ description:
1536
+ 'Response randomness (0.0-1.0). Examples: 0.1 (very focused), 0.2 (analytical - default), 0.5 (balanced). Default: 0.2',
1375
1537
  minimum: 0.0,
1376
1538
  maximum: 1.0,
1377
1539
  default: 0.2,
@@ -1379,22 +1541,26 @@ consensusTool.inputSchema = {
1379
1541
  reasoning_effort: {
1380
1542
  type: 'string',
1381
1543
  enum: ['none', 'minimal', 'low', 'medium', 'high', 'max'],
1382
- description: 'Reasoning depth for thinking models. Examples: "none" (no reasoning, fastest - GPT-5.1+ only), "low" (light analysis), "medium" (balanced), "high" (complex analysis). Default: "medium"',
1383
- default: 'medium'
1544
+ description:
1545
+ 'Reasoning depth for thinking models. Examples: "none" (no reasoning, fastest - GPT-5.1+ only), "low" (light analysis), "medium" (balanced), "high" (complex analysis). Default: "medium"',
1546
+ default: 'medium',
1384
1547
  },
1385
1548
  use_websearch: {
1386
1549
  type: 'boolean',
1387
- description: 'Enable web search for current information. Only works with models that support web search (OpenAI, XAI, Google). Example: true for recent developments or up to date documentation. Default: false',
1388
- default: false
1550
+ description:
1551
+ 'Enable web search for current information. Only works with models that support web search (OpenAI, XAI, Google). Example: true for recent developments or up to date documentation. Default: false',
1552
+ default: false,
1389
1553
  },
1390
1554
  async: {
1391
1555
  type: 'boolean',
1392
- description: 'Execute consensus in background with detailed progress tracking. When true, returns continuation_id immediately and processes request asynchronously with per-provider status updates. Default: false',
1393
- default: false
1556
+ description:
1557
+ 'Execute consensus in background with detailed progress tracking. When true, returns continuation_id immediately and processes request asynchronously with per-provider status updates. Default: false',
1558
+ default: false,
1394
1559
  },
1395
1560
  prompt: {
1396
1561
  type: 'string',
1397
- description: 'The problem or proposal to gather consensus on. Include context and specific questions. Example: "Should we use microservices or monolith architecture for our e-commerce platform with 100k users?"',
1562
+ description:
1563
+ 'The problem or proposal to gather consensus on. Include context and specific questions. Example: "Should we use microservices or monolith architecture for our e-commerce platform with 100k users?"',
1398
1564
  },
1399
1565
  },
1400
1566
  required: ['prompt', 'models'],