converse-mcp-server 4.6.0 → 4.6.2
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/docs/API.md +1 -1
- package/package.json +10 -10
- package/src/decisionProviders/formats.js +10 -9
- package/src/decisionProviders/http.js +5 -2
- package/src/tools/decide.js +7 -5
package/docs/API.md
CHANGED
|
@@ -474,7 +474,7 @@ A one-line summary per answer (options at 0.00 omitted), followed by the answers
|
|
|
474
474
|
Decision · typesafe/jev-1.13-20260917 via OpenRouter · 394 input tokens · $0.000017
|
|
475
475
|
- urgent (noul): 0.97
|
|
476
476
|
- team (choice): billing · confidence 1.00 · billing 1.00
|
|
477
|
-
- frustration (score): 1.24 on 0–2 · confidence 0.64 · 1 Frustrated 0.76, 2 Very angry 0.24
|
|
477
|
+
- frustration (score): 1.24 on 0–2 · confidence 0.64 · 1 "Frustrated" 0.76, 2 "Very angry" 0.24
|
|
478
478
|
|
|
479
479
|
```json
|
|
480
480
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "converse-mcp-server",
|
|
3
|
-
"version": "4.6.
|
|
3
|
+
"version": "4.6.2",
|
|
4
4
|
"description": "Converse MCP Server - Converse with other LLMs with chat and consensus tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -93,22 +93,22 @@
|
|
|
93
93
|
".env.example"
|
|
94
94
|
],
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
97
|
-
"@anthropic-ai/sdk": "^0.
|
|
98
|
-
"@github/copilot-sdk": "^1.0.
|
|
99
|
-
"@google/genai": "^2.
|
|
96
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.296",
|
|
97
|
+
"@anthropic-ai/sdk": "^0.133.0",
|
|
98
|
+
"@github/copilot-sdk": "^1.0.19",
|
|
99
|
+
"@google/genai": "^2.28.0",
|
|
100
100
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
101
101
|
"@mistralai/mistralai": "^2.7.0",
|
|
102
102
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
103
|
-
"@openai/codex-sdk": "^0.
|
|
103
|
+
"@openai/codex-sdk": "^0.162.0",
|
|
104
104
|
"cors": "^2.8.6",
|
|
105
|
-
"dotenv": "^18.0.
|
|
106
|
-
"express": "^5.
|
|
105
|
+
"dotenv": "^18.0.7",
|
|
106
|
+
"express": "^5.3.0",
|
|
107
107
|
"lru-cache": "^11.5.3",
|
|
108
108
|
"nanoid": "^6.0.2",
|
|
109
|
-
"openai": "^7.
|
|
109
|
+
"openai": "^7.31.0",
|
|
110
110
|
"p-limit": "^7.3.3",
|
|
111
|
-
"vite": "^8.3.
|
|
111
|
+
"vite": "^8.3.4"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@vitest/coverage-v8": "^5.0.3",
|
|
@@ -73,22 +73,26 @@ function decodedBytes(image) {
|
|
|
73
73
|
return Math.floor((image.base64.length * 3) / 4);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/** Clef's limits hold on every host, so a breach is checked before sending. */
|
|
77
|
+
function clefLimitError(message) {
|
|
78
|
+
return new DecisionError(message, { terminal: true, notSent: true });
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
function checkClefImages(images, state, questions) {
|
|
77
82
|
const sizes = images.map(decodedBytes);
|
|
78
83
|
const tooLarge = sizes.findIndex((bytes) => bytes > CLEF_MAX_IMAGE_BYTES);
|
|
79
84
|
if (tooLarge !== -1) {
|
|
80
|
-
throw
|
|
85
|
+
throw clefLimitError(`Clef accepts images up to 4 MiB each; image ${tooLarge + 1} is ${(sizes[tooLarge] / 1048576).toFixed(1)} MiB.`);
|
|
81
86
|
}
|
|
82
87
|
if (sizes.reduce((a, b) => a + b, 0) > CLEF_MAX_TOTAL_IMAGE_BYTES) {
|
|
83
|
-
throw
|
|
88
|
+
throw clefLimitError('Clef accepts at most 8 MiB of images per call.');
|
|
84
89
|
}
|
|
85
90
|
const chars = images.reduce((n, image) => n + dataUrl(image).length, 0) + asText(state ?? '').length + JSON.stringify(questions).length;
|
|
86
91
|
const estimate = Math.ceil(chars / CLEF_CHARS_PER_ESTIMATED_TOKEN);
|
|
87
92
|
if (estimate > CLEF_CONTEXT_TOKENS) {
|
|
88
|
-
throw
|
|
93
|
+
throw clefLimitError(
|
|
89
94
|
`Cloudflare estimates this request at ~${estimate} tokens (4 base64 characters per token), over Clef's ${CLEF_CONTEXT_TOKENS}-token window. ` +
|
|
90
95
|
'Downscale or recompress the images (about 180 KB in total fits), or use gpt-6-luna.',
|
|
91
|
-
{ terminal: true },
|
|
92
96
|
);
|
|
93
97
|
}
|
|
94
98
|
}
|
|
@@ -104,14 +108,11 @@ export const cloudflareFormat = {
|
|
|
104
108
|
request(model, state, questions, images = []) {
|
|
105
109
|
const ids = Object.keys(questions);
|
|
106
110
|
if (ids.length > CLEF_MAX_QUESTIONS) {
|
|
107
|
-
throw
|
|
111
|
+
throw clefLimitError(`Clef accepts at most ${CLEF_MAX_QUESTIONS} questions per call (got ${ids.length}).`);
|
|
108
112
|
}
|
|
109
113
|
const bad = ids.filter((id) => !CLEF_QUESTION_ID.test(id));
|
|
110
114
|
if (bad.length) {
|
|
111
|
-
throw
|
|
112
|
-
`Clef question names may use only letters, digits, "_", ".", and "-" (max 100 characters): ${bad.join(', ')}.`,
|
|
113
|
-
{ terminal: true },
|
|
114
|
-
);
|
|
115
|
+
throw clefLimitError(`Clef question names may use only letters, digits, "_", ".", and "-" (max 100 characters): ${bad.join(', ')}.`);
|
|
115
116
|
}
|
|
116
117
|
const body = { model, state: state ?? '', questions: toSystemOneQuestions(questions) };
|
|
117
118
|
if (images.length === 0) return body;
|
|
@@ -21,15 +21,18 @@ const MAX_RETRY_AFTER_MS = 30_000;
|
|
|
21
21
|
/**
|
|
22
22
|
* Error from a decision call. `retryable` marks failures worth repeating or
|
|
23
23
|
* failing over (network, timeout, 408/429/5xx, auth); `terminal` marks request
|
|
24
|
-
* faults that every host of the same model would reject the same way
|
|
24
|
+
* faults that every host of the same model would reject the same way;
|
|
25
|
+
* `notSent` marks a request refused by a local limit check before any network
|
|
26
|
+
* call, so it is not reported as a failed request.
|
|
25
27
|
*/
|
|
26
28
|
export class DecisionError extends Error {
|
|
27
|
-
constructor(message, { status = null, retryable = false, terminal = false, requestId = null, retryAfterMs = null } = {}) {
|
|
29
|
+
constructor(message, { status = null, retryable = false, terminal = false, notSent = false, requestId = null, retryAfterMs = null } = {}) {
|
|
28
30
|
super(message);
|
|
29
31
|
this.name = 'DecisionError';
|
|
30
32
|
this.status = status;
|
|
31
33
|
this.retryable = retryable;
|
|
32
34
|
this.terminal = terminal;
|
|
35
|
+
this.notSent = notSent;
|
|
33
36
|
this.requestId = requestId;
|
|
34
37
|
this.retryAfterMs = retryAfterMs;
|
|
35
38
|
}
|
package/src/tools/decide.js
CHANGED
|
@@ -250,7 +250,8 @@ function summarizeAnswer(key, answer) {
|
|
|
250
250
|
if (type === 'score') {
|
|
251
251
|
const levels = Object.keys(answer.legend || answer.probabilities || {});
|
|
252
252
|
const range = levels.length ? ` on 0–${levels.length - 1}` : '';
|
|
253
|
-
|
|
253
|
+
// Quoted so a numeric label ("4") stays distinct from its level index (3).
|
|
254
|
+
const label = (k) => (answer.legend?.[k] ? `${k} "${answer.legend[k]}"` : k);
|
|
254
255
|
return `- ${key} (score): ${fixed(answer.score)}${range} · confidence ${fixed(answer.confidence)} · ${byProbability(answer.probabilities, label)}`;
|
|
255
256
|
}
|
|
256
257
|
if (type === 'refusal') {
|
|
@@ -269,7 +270,7 @@ function formatResult(response, candidate, failures) {
|
|
|
269
270
|
|
|
270
271
|
const lines = [header];
|
|
271
272
|
for (const failure of failures) {
|
|
272
|
-
lines.push(`(${failure.provider} failed, fell back: ${failure.message})`);
|
|
273
|
+
lines.push(`(${failure.provider} ${failure.notSent ? 'skipped' : 'failed'}, fell back: ${failure.message})`);
|
|
273
274
|
}
|
|
274
275
|
lines.push(...Object.entries(response.answers).map(([key, answer]) => summarizeAnswer(key, answer)));
|
|
275
276
|
|
|
@@ -352,13 +353,14 @@ export async function decideTool(args, dependencies) {
|
|
|
352
353
|
} catch (error) {
|
|
353
354
|
if (signal?.aborted) return createToolError('Decision request cancelled.');
|
|
354
355
|
logger.error('Decision request failed', { provider: candidate.providerName, model: candidate.model, error: error.message });
|
|
355
|
-
failures.push({ provider: candidate.providerName, message: error.message });
|
|
356
|
+
failures.push({ provider: candidate.providerName, message: error.message, notSent: Boolean(error.notSent) });
|
|
356
357
|
if (error.terminal) rejectedFamilies.add(candidate.family);
|
|
357
358
|
}
|
|
358
359
|
}
|
|
359
360
|
|
|
360
|
-
const
|
|
361
|
-
|
|
361
|
+
const noneSent = failures.every((f) => f.notSent);
|
|
362
|
+
const detail = failures.map((f) => `${f.provider}${f.notSent && !noneSent ? ', not sent' : ''}: ${f.message}`).join('; ');
|
|
363
|
+
return createToolError(`Decision request ${noneSent ? 'not sent' : 'failed'} (${detail})`);
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
decideTool.description =
|