converse-mcp-server 2.9.6 → 2.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/async/fileCache.js +3 -36
- package/src/providers/codex.js +2 -2
- package/src/tools/chat.js +29 -80
- package/src/tools/checkStatus.js +2 -17
- package/src/utils/idValidation.js +0 -32
package/package.json
CHANGED
package/src/async/fileCache.js
CHANGED
|
@@ -11,7 +11,6 @@ import { promises as fs } from 'fs';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import { debugLog, debugError } from '../utils/console.js';
|
|
13
13
|
import { ConverseMCPError, ERROR_CODES } from '../utils/errorHandler.js';
|
|
14
|
-
import { isSafeIdSegment } from '../utils/idValidation.js';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* File cache specific error class
|
|
@@ -150,14 +149,6 @@ export class FileCache extends FileCacheInterface {
|
|
|
150
149
|
* @private
|
|
151
150
|
*/
|
|
152
151
|
getJobDir(jobId) {
|
|
153
|
-
if (!isSafeIdSegment(jobId)) {
|
|
154
|
-
throw new FileCacheError(
|
|
155
|
-
'Job ID contains unsafe characters',
|
|
156
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
157
|
-
{ jobId },
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
152
|
const today = new Date().toISOString().split('T')[0]; // yyyy-mm-dd
|
|
162
153
|
return path.join(this.baseDir, today, jobId);
|
|
163
154
|
}
|
|
@@ -210,15 +201,7 @@ export class FileCache extends FileCacheInterface {
|
|
|
210
201
|
if (!jobId || typeof jobId !== 'string') {
|
|
211
202
|
throw new FileCacheError(
|
|
212
203
|
'Job ID must be a non-empty string',
|
|
213
|
-
ERROR_CODES.
|
|
214
|
-
{ jobId },
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (!isSafeIdSegment(jobId)) {
|
|
219
|
-
throw new FileCacheError(
|
|
220
|
-
'Job ID contains unsafe characters',
|
|
221
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
204
|
+
ERROR_CODES.CACHE_WRITE_FAILED,
|
|
222
205
|
{ jobId },
|
|
223
206
|
);
|
|
224
207
|
}
|
|
@@ -282,15 +265,7 @@ export class FileCache extends FileCacheInterface {
|
|
|
282
265
|
if (!jobId || typeof jobId !== 'string') {
|
|
283
266
|
throw new FileCacheError(
|
|
284
267
|
'Job ID must be a non-empty string',
|
|
285
|
-
ERROR_CODES.
|
|
286
|
-
{ jobId },
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (!isSafeIdSegment(jobId)) {
|
|
291
|
-
throw new FileCacheError(
|
|
292
|
-
'Job ID contains unsafe characters',
|
|
293
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
268
|
+
ERROR_CODES.CACHE_WRITE_FAILED,
|
|
294
269
|
{ jobId },
|
|
295
270
|
);
|
|
296
271
|
}
|
|
@@ -349,15 +324,7 @@ export class FileCache extends FileCacheInterface {
|
|
|
349
324
|
if (!jobId || typeof jobId !== 'string') {
|
|
350
325
|
throw new FileCacheError(
|
|
351
326
|
'Job ID must be a non-empty string',
|
|
352
|
-
ERROR_CODES.
|
|
353
|
-
{ jobId },
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (!isSafeIdSegment(jobId)) {
|
|
358
|
-
throw new FileCacheError(
|
|
359
|
-
'Job ID contains unsafe characters',
|
|
360
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
327
|
+
ERROR_CODES.CACHE_READ_FAILED,
|
|
361
328
|
{ jobId },
|
|
362
329
|
);
|
|
363
330
|
}
|
package/src/providers/codex.js
CHANGED
|
@@ -284,7 +284,7 @@ export const codexProvider = {
|
|
|
284
284
|
const rawWorkingDirectory = config.server?.client_cwd || process.cwd();
|
|
285
285
|
// Normalize Windows extended-length paths (\\?\C:\...) to regular paths
|
|
286
286
|
const workingDirectory = normalizeExtendedPath(rawWorkingDirectory);
|
|
287
|
-
const
|
|
287
|
+
const sandboxMode = config.providers?.codexsandboxmode || 'read-only';
|
|
288
288
|
const skipGitRepoCheck =
|
|
289
289
|
config.providers?.codexskipgitcheck !== undefined
|
|
290
290
|
? config.providers.codexskipgitcheck
|
|
@@ -294,7 +294,7 @@ export const codexProvider = {
|
|
|
294
294
|
// Create or resume thread
|
|
295
295
|
const threadOptions = {
|
|
296
296
|
workingDirectory,
|
|
297
|
-
|
|
297
|
+
sandboxMode,
|
|
298
298
|
skipGitRepoCheck,
|
|
299
299
|
approvalPolicy,
|
|
300
300
|
};
|
package/src/tools/chat.js
CHANGED
|
@@ -21,7 +21,6 @@ import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
|
|
|
21
21
|
import { validateAllPaths } from '../utils/fileValidator.js';
|
|
22
22
|
import { SummarizationService } from '../services/summarizationService.js';
|
|
23
23
|
import { exportConversation } from '../utils/conversationExporter.js';
|
|
24
|
-
import { isRecoverableError, retryWithBackoff } from '../utils/errorHandler.js';
|
|
25
24
|
|
|
26
25
|
const logger = createLogger('chat');
|
|
27
26
|
|
|
@@ -266,14 +265,12 @@ export async function chatTool(args, dependencies) {
|
|
|
266
265
|
|
|
267
266
|
messages.push(userMessage);
|
|
268
267
|
|
|
269
|
-
// Select provider
|
|
268
|
+
// Select provider
|
|
270
269
|
let selectedProvider;
|
|
271
270
|
let providerName;
|
|
272
271
|
|
|
273
|
-
const providerCandidates = [];
|
|
274
|
-
|
|
275
272
|
if (model === 'auto') {
|
|
276
|
-
// Auto-select
|
|
273
|
+
// Auto-select first available provider in priority order
|
|
277
274
|
// Prioritize subscription-based providers (codex, gemini-cli, claude) over API-key providers
|
|
278
275
|
const providerOrder = [
|
|
279
276
|
'codex',
|
|
@@ -291,17 +288,20 @@ export async function chatTool(args, dependencies) {
|
|
|
291
288
|
for (const name of providerOrder) {
|
|
292
289
|
const provider = providers[name];
|
|
293
290
|
if (provider && provider.isAvailable && provider.isAvailable(config)) {
|
|
294
|
-
|
|
291
|
+
providerName = name;
|
|
292
|
+
selectedProvider = provider;
|
|
293
|
+
break;
|
|
295
294
|
}
|
|
296
295
|
}
|
|
297
296
|
|
|
298
|
-
if (
|
|
297
|
+
if (!providerName) {
|
|
299
298
|
return createToolError(
|
|
300
299
|
'No providers available. Please configure at least one API key.',
|
|
301
300
|
);
|
|
302
301
|
}
|
|
303
302
|
} else {
|
|
304
303
|
// Use specified provider/model
|
|
304
|
+
// Try to map model to provider
|
|
305
305
|
providerName = mapModelToProvider(model, providers);
|
|
306
306
|
selectedProvider = providers[providerName];
|
|
307
307
|
|
|
@@ -314,60 +314,32 @@ export async function chatTool(args, dependencies) {
|
|
|
314
314
|
`Provider ${providerName} is not available. Check API key configuration.`,
|
|
315
315
|
);
|
|
316
316
|
}
|
|
317
|
-
|
|
318
|
-
providerCandidates.push({ name: providerName, provider: selectedProvider });
|
|
319
317
|
}
|
|
320
318
|
|
|
321
|
-
//
|
|
319
|
+
// Resolve model name and prepare provider options
|
|
320
|
+
const resolvedModel = resolveAutoModel(model, providerName);
|
|
321
|
+
const providerOptions = {
|
|
322
|
+
model: resolvedModel,
|
|
323
|
+
temperature,
|
|
324
|
+
reasoning_effort,
|
|
325
|
+
verbosity,
|
|
326
|
+
use_websearch,
|
|
327
|
+
config,
|
|
328
|
+
continuation_id, // Pass for thread resumption
|
|
329
|
+
continuationStore, // Pass store for state management
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// Call provider
|
|
322
333
|
let response;
|
|
323
334
|
const startTime = Date.now();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
resolvedModel = resolveAutoModel(model, providerName);
|
|
333
|
-
const providerOptions = {
|
|
334
|
-
model: resolvedModel,
|
|
335
|
-
temperature,
|
|
336
|
-
reasoning_effort,
|
|
337
|
-
verbosity,
|
|
338
|
-
use_websearch,
|
|
339
|
-
config,
|
|
340
|
-
continuation_id, // Pass for thread resumption
|
|
341
|
-
continuationStore, // Pass store for state management
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
response = await retryWithBackoff(
|
|
346
|
-
() => selectedProvider.invoke(messages, providerOptions),
|
|
347
|
-
getProviderRetryOptions(config, providerName),
|
|
348
|
-
);
|
|
349
|
-
break;
|
|
350
|
-
} catch (error) {
|
|
351
|
-
lastError = error;
|
|
352
|
-
logger.error('Provider error', {
|
|
353
|
-
error,
|
|
354
|
-
data: { provider: providerName },
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
model !== 'auto' ||
|
|
359
|
-
!shouldFailoverToNextProvider(error) ||
|
|
360
|
-
candidate === providerCandidates[providerCandidates.length - 1]
|
|
361
|
-
) {
|
|
362
|
-
return createToolError(`Provider error: ${error.message}`);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (!response) {
|
|
368
|
-
return createToolError(
|
|
369
|
-
`Provider error: ${(lastError && lastError.message) || 'Unknown error'}`,
|
|
370
|
-
);
|
|
335
|
+
try {
|
|
336
|
+
response = await selectedProvider.invoke(messages, providerOptions);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.error('Provider error', {
|
|
339
|
+
error,
|
|
340
|
+
data: { provider: providerName },
|
|
341
|
+
});
|
|
342
|
+
return createToolError(`Provider error: ${error.message}`);
|
|
371
343
|
}
|
|
372
344
|
const executionTime = (Date.now() - startTime) / 1000; // Convert to seconds
|
|
373
345
|
|
|
@@ -500,29 +472,6 @@ function resolveAutoModel(model, providerName) {
|
|
|
500
472
|
return defaults[providerName] || 'gpt-5';
|
|
501
473
|
}
|
|
502
474
|
|
|
503
|
-
function getProviderRetryOptions(config, providerName) {
|
|
504
|
-
const nodeEnv = config?.environment?.nodeEnv || process.env.NODE_ENV;
|
|
505
|
-
const isTest = nodeEnv === 'test';
|
|
506
|
-
|
|
507
|
-
return {
|
|
508
|
-
retries: isTest ? 1 : 3,
|
|
509
|
-
delay: isTest ? 0 : 500,
|
|
510
|
-
maxDelay: isTest ? 0 : 10000,
|
|
511
|
-
operation: `provider-invoke:${providerName}`,
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function shouldFailoverToNextProvider(error) {
|
|
516
|
-
if (isRecoverableError(error)) {
|
|
517
|
-
return true;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const message = (error && error.message) || '';
|
|
521
|
-
return /(api key|authentication|unauthorized|forbidden|invalid|not available)/i.test(
|
|
522
|
-
message,
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
475
|
export function mapModelToProvider(model, providers) {
|
|
527
476
|
const modelLower = model.toLowerCase();
|
|
528
477
|
|
package/src/tools/checkStatus.js
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
formatJobListHumanReadable,
|
|
18
18
|
formatConversationHistory,
|
|
19
19
|
} from '../utils/formatStatus.js';
|
|
20
|
-
import { isSafeIdSegment } from '../utils/idValidation.js';
|
|
21
20
|
|
|
22
21
|
const logger = createLogger('check-status');
|
|
23
22
|
|
|
@@ -37,22 +36,8 @@ export async function checkStatusTool(args, dependencies) {
|
|
|
37
36
|
const { continuation_id, full_history = false } = args;
|
|
38
37
|
|
|
39
38
|
// Validate arguments
|
|
40
|
-
if (continuation_id !==
|
|
41
|
-
|
|
42
|
-
return createToolError('continuation_id must be a string');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (continuation_id.length === 0) {
|
|
46
|
-
return createToolError(
|
|
47
|
-
'Invalid continuation_id: must be a non-empty string',
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!isSafeIdSegment(continuation_id)) {
|
|
52
|
-
return createToolError(
|
|
53
|
-
'Invalid continuation_id: contains unsafe characters',
|
|
54
|
-
);
|
|
55
|
-
}
|
|
39
|
+
if (continuation_id && typeof continuation_id !== 'string') {
|
|
40
|
+
return createToolError('continuation_id must be a string');
|
|
56
41
|
}
|
|
57
42
|
|
|
58
43
|
const asyncJobStore = getAsyncJobStore();
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ID validation helpers
|
|
3
|
-
*
|
|
4
|
-
* These are intentionally conservative and are primarily used to ensure IDs that
|
|
5
|
-
* are used as filesystem path segments cannot escape their intended directory.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Check whether an ID is safe to use as a single filesystem path segment.
|
|
10
|
-
*
|
|
11
|
-
* Allowed characters: A–Z a–z 0–9 _ -
|
|
12
|
-
* Disallowed: path separators, dots, whitespace, and other punctuation.
|
|
13
|
-
*
|
|
14
|
-
* @param {unknown} id
|
|
15
|
-
* @param {object} [options]
|
|
16
|
-
* @param {number} [options.maxLength]
|
|
17
|
-
* @returns {boolean}
|
|
18
|
-
*/
|
|
19
|
-
export function isSafeIdSegment(id, options = {}) {
|
|
20
|
-
const { maxLength = 128 } = options;
|
|
21
|
-
|
|
22
|
-
if (typeof id !== 'string') {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (id.length === 0 || id.length > maxLength) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
31
|
-
}
|
|
32
|
-
|