converse-mcp-server 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +323 -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
@@ -50,12 +50,12 @@ export function getTools(config = null) {
50
50
  // Create a modified inputSchema without the async parameter
51
51
  const modifiedSchema = {
52
52
  ...tool.inputSchema,
53
- properties: { ...tool.inputSchema.properties }
53
+ properties: { ...tool.inputSchema.properties },
54
54
  };
55
55
  delete modifiedSchema.properties.async;
56
56
 
57
57
  // Create a wrapper function with modified metadata
58
- const wrappedTool = async function(...args) {
58
+ const wrappedTool = async function (...args) {
59
59
  return await tool(...args);
60
60
  };
61
61
  wrappedTool.description = tool.description;
@@ -117,7 +117,12 @@ export function getAvailableTools() {
117
117
  * @param {boolean} enableDisplay - Whether to enable metadata display
118
118
  * @returns {string} Formatted metadata string
119
119
  */
120
- export function formatMetadataDisplay(metadata = {}, toolName = '', executionTime = null, enableDisplay = true) {
120
+ export function formatMetadataDisplay(
121
+ metadata = {},
122
+ toolName = '',
123
+ executionTime = null,
124
+ enableDisplay = true,
125
+ ) {
121
126
  // Return empty string if display is disabled (useful for testing)
122
127
  if (!enableDisplay) {
123
128
  return '';
@@ -126,7 +131,10 @@ export function formatMetadataDisplay(metadata = {}, toolName = '', executionTim
126
131
  const parts = [];
127
132
 
128
133
  // Use elapsed_seconds from job if available, otherwise use executionTime
129
- const timeToShow = metadata.elapsed_seconds !== undefined ? metadata.elapsed_seconds : executionTime;
134
+ const timeToShow =
135
+ metadata.elapsed_seconds !== undefined
136
+ ? metadata.elapsed_seconds
137
+ : executionTime;
130
138
 
131
139
  if (timeToShow !== null) {
132
140
  // Format time appropriately based on duration
@@ -147,7 +155,9 @@ export function formatMetadataDisplay(metadata = {}, toolName = '', executionTim
147
155
  }
148
156
 
149
157
  if (metadata.successful_models !== undefined) {
150
- parts.push(`✅ ${metadata.successful_models}/${metadata.total_models} models`);
158
+ parts.push(
159
+ `✅ ${metadata.successful_models}/${metadata.total_models} models`,
160
+ );
151
161
  }
152
162
 
153
163
  if (metadata.continuation_id) {
@@ -178,7 +188,7 @@ export function formatFailureDetails(failureDetails = []) {
178
188
  return '';
179
189
  }
180
190
 
181
- const failureList = failureDetails.map(detail => `• ${detail}`).join('\n');
191
+ const failureList = failureDetails.map((detail) => `• ${detail}`).join('\n');
182
192
  return `\nModel failures:\n${failureList}`;
183
193
  }
184
194
 
@@ -189,20 +199,33 @@ export function formatFailureDetails(failureDetails = []) {
189
199
  * @param {object} additionalFields - Additional fields to include in response
190
200
  * @returns {object} MCP tool response
191
201
  */
192
- export function createToolResponse(content, isError = false, additionalFields = {}) {
202
+ export function createToolResponse(
203
+ content,
204
+ isError = false,
205
+ additionalFields = {},
206
+ ) {
193
207
  // If content is already a structured response object, use it directly
194
- if (typeof content === 'object' && content !== null && !Array.isArray(content)) {
208
+ if (
209
+ typeof content === 'object' &&
210
+ content !== null &&
211
+ !Array.isArray(content)
212
+ ) {
195
213
  // If it's a complete response object with content array, return it directly
196
214
  if (content.content && Array.isArray(content.content)) {
197
215
  return {
198
216
  ...content,
199
217
  isError: isError || content.isError || false,
200
- ...additionalFields
218
+ ...additionalFields,
201
219
  };
202
220
  }
203
221
 
204
222
  // If it's a tool result object (has continuation, metadata, etc.) convert to MCP format
205
- if (content.continuation || content.metadata || content.content || content.metadata_display) {
223
+ if (
224
+ content.continuation ||
225
+ content.metadata ||
226
+ content.content ||
227
+ content.metadata_display
228
+ ) {
206
229
  // Prepare the text content, potentially prefixing with metadata display
207
230
  let textContent = content.content || JSON.stringify(content, null, 2);
208
231
  if (content.metadata_display) {
@@ -213,11 +236,11 @@ export function createToolResponse(content, isError = false, additionalFields =
213
236
  content: [
214
237
  {
215
238
  type: 'text',
216
- text: textContent
217
- }
239
+ text: textContent,
240
+ },
218
241
  ],
219
242
  isError: isError || content.isError || false,
220
- ...additionalFields
243
+ ...additionalFields,
221
244
  };
222
245
 
223
246
  // Preserve continuation and metadata at top level
@@ -240,7 +263,7 @@ export function createToolResponse(content, isError = false, additionalFields =
240
263
  },
241
264
  ],
242
265
  isError,
243
- ...additionalFields
266
+ ...additionalFields,
244
267
  };
245
268
  }
246
269
 
@@ -253,7 +276,7 @@ export function createToolResponse(content, isError = false, additionalFields =
253
276
  },
254
277
  ],
255
278
  isError,
256
- ...additionalFields
279
+ ...additionalFields,
257
280
  };
258
281
  }
259
282
 
@@ -271,7 +294,7 @@ export function createToolError(message, error = null) {
271
294
  response.error = {
272
295
  message: errorText,
273
296
  type: 'ToolError',
274
- timestamp: new Date().toISOString()
297
+ timestamp: new Date().toISOString(),
275
298
  };
276
299
 
277
300
  return response;
@@ -11,7 +11,10 @@ import { randomUUID } from 'node:crypto';
11
11
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
12
12
  import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
13
13
  import { createLogger } from '../utils/logger.js';
14
- import { registerTransportSession, clearTransportSession } from '../utils/sessionManager.js';
14
+ import {
15
+ registerTransportSession,
16
+ clearTransportSession,
17
+ } from '../utils/sessionManager.js';
15
18
 
16
19
  const logger = createLogger('http-transport');
17
20
 
@@ -44,13 +47,14 @@ export class HTTPTransportServer {
44
47
  },
45
48
 
46
49
  // Security settings
47
- enableDnsRebindingProtection: config.enableDnsRebindingProtection || false,
50
+ enableDnsRebindingProtection:
51
+ config.enableDnsRebindingProtection || false,
48
52
  allowedHosts: config.allowedHosts || ['127.0.0.1', 'localhost'],
49
53
  rateLimitEnabled: config.rateLimitEnabled || false,
50
54
  rateLimitWindow: config.rateLimitWindow || 900000,
51
55
  rateLimitMaxRequests: config.rateLimitMaxRequests || 1000,
52
56
 
53
- ...config
57
+ ...config,
54
58
  };
55
59
 
56
60
  this.app = express();
@@ -78,10 +82,12 @@ export class HTTPTransportServer {
78
82
  */
79
83
  setupMiddleware() {
80
84
  // JSON parsing with size limit
81
- this.app.use(express.json({
82
- limit: this.config.maxRequestSize,
83
- strict: true
84
- }));
85
+ this.app.use(
86
+ express.json({
87
+ limit: this.config.maxRequestSize,
88
+ strict: true,
89
+ }),
90
+ );
85
91
 
86
92
  // Request timeout middleware
87
93
  this.app.use((req, res, next) => {
@@ -90,8 +96,8 @@ export class HTTPTransportServer {
90
96
  data: {
91
97
  method: req.method,
92
98
  path: req.path,
93
- timeout: this.config.requestTimeout
94
- }
99
+ timeout: this.config.requestTimeout,
100
+ },
95
101
  });
96
102
  if (!res.headersSent) {
97
103
  res.status(408).json({
@@ -118,15 +124,17 @@ export class HTTPTransportServer {
118
124
 
119
125
  // Clean old entries
120
126
  const clientRequests = rateLimitMap.get(clientId) || [];
121
- const validRequests = clientRequests.filter(time => time > windowStart);
127
+ const validRequests = clientRequests.filter(
128
+ (time) => time > windowStart,
129
+ );
122
130
 
123
131
  if (validRequests.length >= this.config.rateLimitMaxRequests) {
124
132
  logger.warn('Rate limit exceeded', {
125
133
  data: {
126
134
  clientId,
127
135
  requests: validRequests.length,
128
- limit: this.config.rateLimitMaxRequests
129
- }
136
+ limit: this.config.rateLimitMaxRequests,
137
+ },
130
138
  });
131
139
  res.status(429).json({
132
140
  jsonrpc: '2.0',
@@ -147,8 +155,8 @@ export class HTTPTransportServer {
147
155
  logger.debug('Rate limiting enabled', {
148
156
  data: {
149
157
  window: this.config.rateLimitWindow,
150
- maxRequests: this.config.rateLimitMaxRequests
151
- }
158
+ maxRequests: this.config.rateLimitMaxRequests,
159
+ },
152
160
  });
153
161
  }
154
162
 
@@ -156,7 +164,7 @@ export class HTTPTransportServer {
156
164
  if (this.config.enableCors) {
157
165
  this.app.use(cors(this.config.corsOptions));
158
166
  logger.debug('CORS enabled for HTTP transport', {
159
- data: { corsOptions: this.config.corsOptions }
167
+ data: { corsOptions: this.config.corsOptions },
160
168
  });
161
169
  }
162
170
 
@@ -166,8 +174,8 @@ export class HTTPTransportServer {
166
174
  data: {
167
175
  method: req.method,
168
176
  path: req.path,
169
- sessionId: req.headers['mcp-session-id']
170
- }
177
+ sessionId: req.headers['mcp-session-id'],
178
+ },
171
179
  });
172
180
  next();
173
181
  });
@@ -199,7 +207,7 @@ export class HTTPTransportServer {
199
207
  transport: 'http',
200
208
  server: this.mcpServer ? 'connected' : 'disconnected',
201
209
  sessions: this.transports.size,
202
- timestamp: new Date().toISOString()
210
+ timestamp: new Date().toISOString(),
203
211
  });
204
212
  });
205
213
 
@@ -212,9 +220,9 @@ export class HTTPTransportServer {
212
220
  endpoints: {
213
221
  mcp: '/mcp',
214
222
  health: '/health',
215
- info: '/info'
223
+ info: '/info',
216
224
  },
217
- sessions: this.transports.size
225
+ sessions: this.transports.size,
218
226
  });
219
227
  });
220
228
  }
@@ -238,14 +246,15 @@ export class HTTPTransportServer {
238
246
  logger.warn('Maximum concurrent sessions reached', {
239
247
  data: {
240
248
  currentSessions: this.transports.size,
241
- maxSessions: this.config.maxConcurrentSessions
242
- }
249
+ maxSessions: this.config.maxConcurrentSessions,
250
+ },
243
251
  });
244
252
  res.status(503).json({
245
253
  jsonrpc: '2.0',
246
254
  error: {
247
255
  code: -32000,
248
- message: 'Maximum concurrent sessions reached. Please try again later.',
256
+ message:
257
+ 'Maximum concurrent sessions reached. Please try again later.',
249
258
  },
250
259
  id: null,
251
260
  });
@@ -257,9 +266,12 @@ export class HTTPTransportServer {
257
266
  logger.info('Created new MCP transport', {});
258
267
  } else {
259
268
  // Invalid request
260
- logger.warn('Invalid MCP request - no session ID or not initialize request', {
261
- data: { sessionId, hasInitialize: isInitializeRequest(req.body) }
262
- });
269
+ logger.warn(
270
+ 'Invalid MCP request - no session ID or not initialize request',
271
+ {
272
+ data: { sessionId, hasInitialize: isInitializeRequest(req.body) },
273
+ },
274
+ );
263
275
  res.status(400).json({
264
276
  jsonrpc: '2.0',
265
277
  error: {
@@ -273,7 +285,6 @@ export class HTTPTransportServer {
273
285
 
274
286
  // Handle the request through the transport
275
287
  await transport.handleRequest(req, res, req.body);
276
-
277
288
  } catch (error) {
278
289
  logger.error('Error handling MCP request', { error });
279
290
  if (!res.headersSent) {
@@ -296,7 +307,9 @@ export class HTTPTransportServer {
296
307
  const sessionId = req.headers['mcp-session-id'];
297
308
 
298
309
  if (!sessionId || !this.transports.has(sessionId)) {
299
- logger.warn('SSE request with invalid session ID', { data: { sessionId } });
310
+ logger.warn('SSE request with invalid session ID', {
311
+ data: { sessionId },
312
+ });
300
313
  res.status(400).send('Invalid or missing session ID');
301
314
  return;
302
315
  }
@@ -307,7 +320,10 @@ export class HTTPTransportServer {
307
320
  await transport.handleRequest(req, res);
308
321
  logger.debug('SSE connection established', { data: { sessionId } });
309
322
  } catch (error) {
310
- logger.error('Error handling SSE request', { error, data: { sessionId } });
323
+ logger.error('Error handling SSE request', {
324
+ error,
325
+ data: { sessionId },
326
+ });
311
327
  if (!res.headersSent) {
312
328
  res.status(500).send('Internal server error');
313
329
  }
@@ -321,7 +337,9 @@ export class HTTPTransportServer {
321
337
  const sessionId = req.headers['mcp-session-id'];
322
338
 
323
339
  if (!sessionId || !this.transports.has(sessionId)) {
324
- logger.warn('Session termination with invalid session ID', { data: { sessionId } });
340
+ logger.warn('Session termination with invalid session ID', {
341
+ data: { sessionId },
342
+ });
325
343
  res.status(400).send('Invalid or missing session ID');
326
344
  return;
327
345
  }
@@ -364,7 +382,7 @@ export class HTTPTransportServer {
364
382
  this.cleanupSession(transport.sessionId);
365
383
  clearTransportSession(transport);
366
384
  logger.debug('Transport session closed', {
367
- data: { sessionId: transport.sessionId }
385
+ data: { sessionId: transport.sessionId },
368
386
  });
369
387
  }
370
388
  };
@@ -431,7 +449,7 @@ export class HTTPTransportServer {
431
449
 
432
450
  this.cleanupInterval = setInterval(() => {
433
451
  logger.debug('Running session cleanup', {
434
- data: { activeSessions: this.transports.size }
452
+ data: { activeSessions: this.transports.size },
435
453
  });
436
454
 
437
455
  // The timeout mechanism handles cleanup automatically,
@@ -439,7 +457,10 @@ export class HTTPTransportServer {
439
457
  }, this.config.sessionCleanupInterval);
440
458
 
441
459
  // Unref the interval so it doesn't keep the process alive
442
- if (this.cleanupInterval && typeof this.cleanupInterval.unref === 'function') {
460
+ if (
461
+ this.cleanupInterval &&
462
+ typeof this.cleanupInterval.unref === 'function'
463
+ ) {
443
464
  this.cleanupInterval.unref();
444
465
  }
445
466
  }
@@ -453,28 +474,34 @@ export class HTTPTransportServer {
453
474
  }
454
475
 
455
476
  return new Promise((resolve, reject) => {
456
- this.server = this.app.listen(this.config.port, this.config.host, (err) => {
457
- if (err) {
458
- logger.error('Failed to start HTTP transport server', { error: err });
459
- reject(err);
460
- return;
461
- }
477
+ this.server = this.app.listen(
478
+ this.config.port,
479
+ this.config.host,
480
+ (err) => {
481
+ if (err) {
482
+ logger.error('Failed to start HTTP transport server', {
483
+ error: err,
484
+ });
485
+ reject(err);
486
+ return;
487
+ }
462
488
 
463
- this.isStarted = true;
464
- this.startSessionCleanup();
489
+ this.isStarted = true;
490
+ this.startSessionCleanup();
465
491
 
466
- const address = this.server.address();
467
- logger.info('HTTP transport server started', {
468
- data: {
469
- host: address.address,
470
- port: address.port,
471
- endpoint: `http://${this.config.host}:${address.port}/mcp`,
472
- sessionTimeout: this.config.sessionTimeout,
473
- maxSessions: this.config.maxConcurrentSessions
474
- }
475
- });
476
- resolve(address);
477
- });
492
+ const address = this.server.address();
493
+ logger.info('HTTP transport server started', {
494
+ data: {
495
+ host: address.address,
496
+ port: address.port,
497
+ endpoint: `http://${this.config.host}:${address.port}/mcp`,
498
+ sessionTimeout: this.config.sessionTimeout,
499
+ maxSessions: this.config.maxConcurrentSessions,
500
+ },
501
+ });
502
+ resolve(address);
503
+ },
504
+ );
478
505
  });
479
506
  }
480
507
 
@@ -538,8 +565,8 @@ export class HTTPTransportServer {
538
565
  maxRequestSize: this.config.maxRequestSize,
539
566
  corsEnabled: this.config.enableCors,
540
567
  rateLimitEnabled: this.config.rateLimitEnabled,
541
- dnsRebindingProtection: this.config.enableDnsRebindingProtection
542
- }
568
+ dnsRebindingProtection: this.config.enableDnsRebindingProtection,
569
+ },
543
570
  };
544
571
  }
545
572
  }
@@ -33,7 +33,12 @@ export class ContextProcessorError extends Error {
33
33
  * Supported image extensions (everything else is treated as text)
34
34
  */
35
35
  const SUPPORTED_IMAGE_EXTENSIONS = [
36
- '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'
36
+ '.jpg',
37
+ '.jpeg',
38
+ '.png',
39
+ '.gif',
40
+ '.webp',
41
+ '.bmp',
37
42
  ];
38
43
 
39
44
  /**
@@ -48,19 +53,24 @@ async function validateFilePath(filePath, options = {}) {
48
53
  if (!filePath || typeof filePath !== 'string') {
49
54
  throw new ContextProcessorError(
50
55
  'File path must be a non-empty string',
51
- 'INVALID_PATH'
56
+ 'INVALID_PATH',
52
57
  );
53
58
  }
54
59
 
55
60
  // Convert to absolute path
56
61
  // For relative paths, resolve from the client's working directory if provided,
57
62
  // otherwise use process.cwd()
58
- const absolutePath = isAbsolute(filePath) ? filePath : resolve(options.clientCwd || process.cwd(), filePath);
63
+ const absolutePath = isAbsolute(filePath)
64
+ ? filePath
65
+ : resolve(options.clientCwd || process.cwd(), filePath);
59
66
 
60
67
  // Security check is now optional and disabled by default
61
68
  if (options.enforceSecurityCheck) {
62
- const allowedDirs = options.allowedDirectories || [process.cwd(), PROJECT_ROOT];
63
- const isAllowed = allowedDirs.some(dir => {
69
+ const allowedDirs = options.allowedDirectories || [
70
+ process.cwd(),
71
+ PROJECT_ROOT,
72
+ ];
73
+ const isAllowed = allowedDirs.some((dir) => {
64
74
  const resolvedDir = resolve(dir);
65
75
  return absolutePath.startsWith(resolvedDir);
66
76
  });
@@ -69,7 +79,7 @@ async function validateFilePath(filePath, options = {}) {
69
79
  throw new ContextProcessorError(
70
80
  'File access denied: path outside allowed directories',
71
81
  'SECURITY_VIOLATION',
72
- { path: absolutePath, allowedDirs }
82
+ { path: absolutePath, allowedDirs },
73
83
  );
74
84
  }
75
85
  }
@@ -81,7 +91,7 @@ async function validateFilePath(filePath, options = {}) {
81
91
  throw new ContextProcessorError(
82
92
  `File not accessible: ${error.message}`,
83
93
  'FILE_ACCESS_ERROR',
84
- { path: absolutePath }
94
+ { path: absolutePath },
85
95
  );
86
96
  }
87
97
 
@@ -106,7 +116,7 @@ export async function processFileContent(filePath, options = {}) {
106
116
  if (!dataUrlMatch) {
107
117
  throw new ContextProcessorError(
108
118
  'Invalid data URL format',
109
- 'INVALID_DATA_URL'
119
+ 'INVALID_DATA_URL',
110
120
  );
111
121
  }
112
122
 
@@ -123,7 +133,7 @@ export async function processFileContent(filePath, options = {}) {
123
133
  error: null,
124
134
  lastModified: new Date(),
125
135
  encoding: 'base64',
126
- mimeType
136
+ mimeType,
127
137
  };
128
138
  }
129
139
 
@@ -135,11 +145,9 @@ export async function processFileContent(filePath, options = {}) {
135
145
 
136
146
  // Check if it's actually a file (not a directory)
137
147
  if (!fileStats.isFile()) {
138
- throw new ContextProcessorError(
139
- 'Path is not a file',
140
- 'NOT_A_FILE',
141
- { path: validatedPath }
142
- );
148
+ throw new ContextProcessorError('Path is not a file', 'NOT_A_FILE', {
149
+ path: validatedPath,
150
+ });
143
151
  }
144
152
 
145
153
  const result = {
@@ -171,7 +179,6 @@ export async function processFileContent(filePath, options = {}) {
171
179
  result.content = buffer.toString('base64');
172
180
  result.mimeType = getMimeType(extension);
173
181
  result.encoding = 'base64';
174
-
175
182
  } else {
176
183
  // Read everything else as text
177
184
  result.type = 'text';
@@ -189,13 +196,15 @@ export async function processFileContent(filePath, options = {}) {
189
196
  }
190
197
 
191
198
  return result;
192
-
193
199
  } catch (error) {
194
200
  return {
195
201
  path: filePath,
196
202
  originalPath: filePath,
197
203
  type: 'error',
198
- error: error instanceof ContextProcessorError ? error.message : `Unexpected error: ${error.message}`,
204
+ error:
205
+ error instanceof ContextProcessorError
206
+ ? error.message
207
+ : `Unexpected error: ${error.message}`,
199
208
  errorCode: error.code || 'UNKNOWN_ERROR',
200
209
  content: null,
201
210
  lastModified: null,
@@ -213,13 +222,13 @@ export async function processMultipleFiles(filePaths, options = {}) {
213
222
  if (!Array.isArray(filePaths)) {
214
223
  throw new ContextProcessorError(
215
224
  'filePaths must be an array',
216
- 'INVALID_INPUT'
225
+ 'INVALID_INPUT',
217
226
  );
218
227
  }
219
228
 
220
229
  // Process files in parallel but isolate errors
221
230
  const results = await Promise.allSettled(
222
- filePaths.map(path => processFileContent(path, options))
231
+ filePaths.map((path) => processFileContent(path, options)),
223
232
  );
224
233
 
225
234
  // Convert Promise.allSettled results to consistent format
@@ -260,7 +269,7 @@ export async function processWebSearchContext(query, options = {}) {
260
269
  // - DuckDuckGo API
261
270
  // - Custom search engines
262
271
  placeholder: true,
263
- message: 'Web search integration placeholder - not yet implemented'
272
+ message: 'Web search integration placeholder - not yet implemented',
264
273
  };
265
274
  }
266
275
 
@@ -292,20 +301,25 @@ export async function processUnifiedContext(contextRequest, options = {}) {
292
301
  if (contextRequest.images && Array.isArray(contextRequest.images)) {
293
302
  result.images = await processMultipleFiles(contextRequest.images, {
294
303
  ...options,
295
- imageProcessingMode: true // Placeholder for future image-specific processing
304
+ imageProcessingMode: true, // Placeholder for future image-specific processing
296
305
  });
297
306
  }
298
307
 
299
308
  // Process web search if provided
300
- if (contextRequest.webSearch && typeof contextRequest.webSearch === 'string') {
301
- result.webSearch = await processWebSearchContext(contextRequest.webSearch, options);
309
+ if (
310
+ contextRequest.webSearch &&
311
+ typeof contextRequest.webSearch === 'string'
312
+ ) {
313
+ result.webSearch = await processWebSearchContext(
314
+ contextRequest.webSearch,
315
+ options,
316
+ );
302
317
  }
303
-
304
318
  } catch (error) {
305
319
  result.errors.push({
306
320
  type: 'unified_processing_error',
307
321
  message: error.message,
308
- code: error.code || 'UNKNOWN_ERROR'
322
+ code: error.code || 'UNKNOWN_ERROR',
309
323
  });
310
324
  }
311
325
 
@@ -323,9 +337,11 @@ export function createFileContext(processedFiles, options = {}) {
323
337
  return null;
324
338
  }
325
339
 
326
- const textFiles = processedFiles.filter(f => f.type === 'text' && !f.error);
327
- const imageFiles = processedFiles.filter(f => f.type === 'image' && !f.error);
328
- const errors = processedFiles.filter(f => f.error);
340
+ const textFiles = processedFiles.filter((f) => f.type === 'text' && !f.error);
341
+ const imageFiles = processedFiles.filter(
342
+ (f) => f.type === 'image' && !f.error,
343
+ );
344
+ const errors = processedFiles.filter((f) => f.error);
329
345
 
330
346
  const includeErrors = options.includeErrors !== false; // Default to true
331
347
 
@@ -377,11 +393,13 @@ export function createFileContext(processedFiles, options = {}) {
377
393
  data: image.content,
378
394
  },
379
395
  // Add metadata for debugging
380
- metadata: options.includeMetadata ? {
381
- path: image.originalPath || image.path,
382
- size: image.size,
383
- lastModified: image.lastModified
384
- } : undefined
396
+ metadata: options.includeMetadata
397
+ ? {
398
+ path: image.originalPath || image.path,
399
+ size: image.size,
400
+ lastModified: image.lastModified,
401
+ }
402
+ : undefined,
385
403
  });
386
404
  }
387
405
 
@@ -428,7 +446,7 @@ export async function validateFilePaths(filePaths, options = {}) {
428
446
  if (!Array.isArray(filePaths)) {
429
447
  throw new ContextProcessorError(
430
448
  'filePaths must be an array',
431
- 'INVALID_INPUT'
449
+ 'INVALID_INPUT',
432
450
  );
433
451
  }
434
452
 
@@ -444,13 +462,13 @@ export async function validateFilePaths(filePaths, options = {}) {
444
462
  results.valid.push({
445
463
  originalPath: path,
446
464
  validatedPath,
447
- isValid: true
465
+ isValid: true,
448
466
  });
449
467
  } catch (error) {
450
468
  const errorInfo = {
451
469
  path,
452
470
  error: error.message,
453
- code: error.code
471
+ code: error.code,
454
472
  };
455
473
 
456
474
  if (error.code === 'SECURITY_VIOLATION') {
@@ -463,4 +481,3 @@ export async function validateFilePaths(filePaths, options = {}) {
463
481
 
464
482
  return results;
465
483
  }
466
-