converse-mcp-server 2.27.2 → 2.28.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.
- package/README.md +2 -2
- package/docs/API.md +21 -19
- package/docs/ARCHITECTURE.md +0 -1
- package/docs/PROVIDERS.md +33 -32
- package/package.json +4 -6
- package/src/config.js +17 -4
- package/src/prompts/helpPrompt.js +2 -2
- package/src/providers/gemini-cli.js +665 -438
- package/src/tools/chat.js +26 -7
- package/src/tools/consensus.js +31 -3
- package/src/tools/conversation.js +20 -4
- package/src/utils/modelRouting.js +50 -0
package/src/tools/chat.js
CHANGED
|
@@ -24,6 +24,10 @@ import { validateAllPaths } from '../utils/fileValidator.js';
|
|
|
24
24
|
import { SummarizationService } from '../services/summarizationService.js';
|
|
25
25
|
import { exportConversation } from '../utils/conversationExporter.js';
|
|
26
26
|
import { isRecoverableError, retryWithBackoff } from '../utils/errorHandler.js';
|
|
27
|
+
import {
|
|
28
|
+
providerSupportsImages,
|
|
29
|
+
getProviderUnavailableMessage,
|
|
30
|
+
} from '../utils/modelRouting.js';
|
|
27
31
|
|
|
28
32
|
const logger = createLogger('chat');
|
|
29
33
|
|
|
@@ -313,9 +317,16 @@ export async function chatTool(args, dependencies) {
|
|
|
313
317
|
'openrouter',
|
|
314
318
|
];
|
|
315
319
|
|
|
320
|
+
const requestHasImages = Array.isArray(images) && images.length > 0;
|
|
321
|
+
|
|
316
322
|
for (const name of providerOrder) {
|
|
317
323
|
const provider = providers[name];
|
|
318
324
|
if (provider && provider.isAvailable && provider.isAvailable(config)) {
|
|
325
|
+
// When the request has images, skip text-only providers (e.g.
|
|
326
|
+
// gemini-cli, copilot) so auto routing lands on an image-capable one.
|
|
327
|
+
if (requestHasImages && !providerSupportsImages(provider, name)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
319
330
|
providerCandidates.push({ name, provider });
|
|
320
331
|
}
|
|
321
332
|
}
|
|
@@ -335,9 +346,7 @@ export async function chatTool(args, dependencies) {
|
|
|
335
346
|
}
|
|
336
347
|
|
|
337
348
|
if (!selectedProvider.isAvailable(config)) {
|
|
338
|
-
return createToolError(
|
|
339
|
-
`Provider ${providerName} is not available. Check API key configuration.`,
|
|
340
|
-
);
|
|
349
|
+
return createToolError(getProviderUnavailableMessage(providerName));
|
|
341
350
|
}
|
|
342
351
|
|
|
343
352
|
providerCandidates.push({
|
|
@@ -590,6 +599,12 @@ export function mapModelToProvider(model, providers) {
|
|
|
590
599
|
return 'gemini-cli';
|
|
591
600
|
}
|
|
592
601
|
|
|
602
|
+
// Check gemini: prefix (e.g., gemini:flash, gemini:pro) - routes to Antigravity
|
|
603
|
+
// CLI provider. Must be before the google flash/pro keyword rule so it wins.
|
|
604
|
+
if (modelLower.startsWith('gemini:')) {
|
|
605
|
+
return 'gemini-cli';
|
|
606
|
+
}
|
|
607
|
+
|
|
593
608
|
// Check Claude SDK (exact match only - routes to SDK provider instead of Anthropic API)
|
|
594
609
|
if (
|
|
595
610
|
modelLower === 'claude' ||
|
|
@@ -882,9 +897,15 @@ async function executeChatWithStreaming(args, dependencies, context) {
|
|
|
882
897
|
'openrouter',
|
|
883
898
|
];
|
|
884
899
|
|
|
900
|
+
const requestHasImages = Array.isArray(images) && images.length > 0;
|
|
901
|
+
|
|
885
902
|
for (const name of providerOrder) {
|
|
886
903
|
const provider = providers[name];
|
|
887
904
|
if (provider && provider.isAvailable && provider.isAvailable(config)) {
|
|
905
|
+
// Skip text-only providers when the request includes images.
|
|
906
|
+
if (requestHasImages && !providerSupportsImages(provider, name)) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
888
909
|
providerName = name;
|
|
889
910
|
selectedProvider = provider;
|
|
890
911
|
break;
|
|
@@ -906,9 +927,7 @@ async function executeChatWithStreaming(args, dependencies, context) {
|
|
|
906
927
|
}
|
|
907
928
|
|
|
908
929
|
if (!selectedProvider.isAvailable(config)) {
|
|
909
|
-
throw new Error(
|
|
910
|
-
`Provider ${providerName} is not available. Check API key configuration.`,
|
|
911
|
-
);
|
|
930
|
+
throw new Error(getProviderUnavailableMessage(providerName));
|
|
912
931
|
}
|
|
913
932
|
}
|
|
914
933
|
|
|
@@ -1156,7 +1175,7 @@ chatTool.inputSchema = {
|
|
|
1156
1175
|
model: {
|
|
1157
1176
|
type: 'string',
|
|
1158
1177
|
description:
|
|
1159
|
-
'AI model to use. Examples: "auto" (recommended), "codex", "gemini", "claude", "claude:fable", "claude:opus", "copilot", "copilot:codex". Defaults to auto-selection.',
|
|
1178
|
+
'AI model to use. Examples: "auto" (recommended), "codex", "gemini", "gemini:flash", "claude", "claude:fable", "claude:opus", "copilot", "copilot:codex". Defaults to auto-selection.',
|
|
1160
1179
|
},
|
|
1161
1180
|
files: {
|
|
1162
1181
|
type: 'array',
|
package/src/tools/consensus.js
CHANGED
|
@@ -27,6 +27,10 @@ import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
|
|
|
27
27
|
import { validateAllPaths } from '../utils/fileValidator.js';
|
|
28
28
|
import { SummarizationService } from '../services/summarizationService.js';
|
|
29
29
|
import { exportConversation } from '../utils/conversationExporter.js';
|
|
30
|
+
import {
|
|
31
|
+
providerSupportsImages,
|
|
32
|
+
getProviderUnavailableMessage,
|
|
33
|
+
} from '../utils/modelRouting.js';
|
|
30
34
|
|
|
31
35
|
const logger = createLogger('consensus');
|
|
32
36
|
|
|
@@ -315,10 +319,19 @@ export async function consensusTool(args, dependencies) {
|
|
|
315
319
|
'openrouter',
|
|
316
320
|
];
|
|
317
321
|
|
|
322
|
+
const requestHasImages = Array.isArray(images) && images.length > 0;
|
|
323
|
+
|
|
318
324
|
for (const providerName of providerOrder) {
|
|
319
325
|
if (availableProviders.length >= 3) break;
|
|
320
326
|
const provider = providers[providerName];
|
|
321
327
|
if (provider && provider.isAvailable(config)) {
|
|
328
|
+
// Skip text-only providers when the request includes images.
|
|
329
|
+
if (
|
|
330
|
+
requestHasImages &&
|
|
331
|
+
!providerSupportsImages(provider, providerName)
|
|
332
|
+
) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
322
335
|
availableProviders.push(providerName);
|
|
323
336
|
}
|
|
324
337
|
}
|
|
@@ -369,7 +382,7 @@ export async function consensusTool(args, dependencies) {
|
|
|
369
382
|
failedModels.push({
|
|
370
383
|
model: modelName,
|
|
371
384
|
provider: providerName,
|
|
372
|
-
error:
|
|
385
|
+
error: getProviderUnavailableMessage(providerName),
|
|
373
386
|
status: 'failed',
|
|
374
387
|
});
|
|
375
388
|
continue;
|
|
@@ -794,6 +807,12 @@ function mapModelToProvider(model, providers) {
|
|
|
794
807
|
return 'gemini-cli';
|
|
795
808
|
}
|
|
796
809
|
|
|
810
|
+
// Check gemini: prefix (e.g., gemini:flash, gemini:pro) - routes to Antigravity
|
|
811
|
+
// CLI provider. Must be before the google flash/pro keyword rule so it wins.
|
|
812
|
+
if (modelLower.startsWith('gemini:')) {
|
|
813
|
+
return 'gemini-cli';
|
|
814
|
+
}
|
|
815
|
+
|
|
797
816
|
// Check Claude SDK (exact match only - routes to SDK provider instead of Anthropic API)
|
|
798
817
|
if (
|
|
799
818
|
modelLower === 'claude' ||
|
|
@@ -1070,10 +1089,19 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
|
|
|
1070
1089
|
'openrouter',
|
|
1071
1090
|
];
|
|
1072
1091
|
|
|
1092
|
+
const requestHasImages = Array.isArray(images) && images.length > 0;
|
|
1093
|
+
|
|
1073
1094
|
for (const providerName of providerOrder) {
|
|
1074
1095
|
if (availableProviders.length >= 3) break;
|
|
1075
1096
|
const provider = providers[providerName];
|
|
1076
1097
|
if (provider && provider.isAvailable(config)) {
|
|
1098
|
+
// Skip text-only providers when the request includes images.
|
|
1099
|
+
if (
|
|
1100
|
+
requestHasImages &&
|
|
1101
|
+
!providerSupportsImages(provider, providerName)
|
|
1102
|
+
) {
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1077
1105
|
availableProviders.push(providerName);
|
|
1078
1106
|
}
|
|
1079
1107
|
}
|
|
@@ -1125,7 +1153,7 @@ async function executeConsensusWithStreaming(args, dependencies, context) {
|
|
|
1125
1153
|
failedModels.push({
|
|
1126
1154
|
model: modelName,
|
|
1127
1155
|
provider: providerName,
|
|
1128
|
-
error:
|
|
1156
|
+
error: getProviderUnavailableMessage(providerName),
|
|
1129
1157
|
status: 'failed',
|
|
1130
1158
|
});
|
|
1131
1159
|
continue;
|
|
@@ -1712,7 +1740,7 @@ consensusTool.inputSchema = {
|
|
|
1712
1740
|
items: { type: 'string' },
|
|
1713
1741
|
minItems: 1,
|
|
1714
1742
|
description:
|
|
1715
|
-
'List of models to consult. Examples: ["codex", "gemini", "claude", "claude:opus", "copilot", "copilot:codex"]',
|
|
1743
|
+
'List of models to consult. Examples: ["codex", "gemini", "gemini:flash", "claude", "claude:opus", "copilot", "copilot:codex"]',
|
|
1716
1744
|
},
|
|
1717
1745
|
files: {
|
|
1718
1746
|
type: 'array',
|
|
@@ -42,6 +42,8 @@ import {
|
|
|
42
42
|
mapModelToProvider,
|
|
43
43
|
resolveAutoModel,
|
|
44
44
|
getDefaultModelForProvider,
|
|
45
|
+
providerSupportsImages,
|
|
46
|
+
getProviderUnavailableMessage,
|
|
45
47
|
} from '../utils/modelRouting.js';
|
|
46
48
|
|
|
47
49
|
const logger = createLogger('conversation');
|
|
@@ -202,7 +204,7 @@ function formatLapTranscript(lapTurns) {
|
|
|
202
204
|
* @param {object} config - Configuration
|
|
203
205
|
* @returns {Array<object>} Ordered turn plan entries
|
|
204
206
|
*/
|
|
205
|
-
function resolveTurnPlan(models, providers, config) {
|
|
207
|
+
function resolveTurnPlan(models, providers, config, hasImages = false) {
|
|
206
208
|
// Single "auto" expands to the first available provider's default model only
|
|
207
209
|
// (a single-model round-table is valid). Multiple explicit models resolve per-entry.
|
|
208
210
|
let modelsToProcess = models;
|
|
@@ -225,6 +227,10 @@ function resolveTurnPlan(models, providers, config) {
|
|
|
225
227
|
for (const providerName of providerOrder) {
|
|
226
228
|
const provider = providers[providerName];
|
|
227
229
|
if (provider && provider.isAvailable(config)) {
|
|
230
|
+
// Skip text-only providers when the request includes images.
|
|
231
|
+
if (hasImages && !providerSupportsImages(provider, providerName)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
228
234
|
firstAvailable = providerName;
|
|
229
235
|
break;
|
|
230
236
|
}
|
|
@@ -268,7 +274,7 @@ function resolveTurnPlan(models, providers, config) {
|
|
|
268
274
|
provider: providerName,
|
|
269
275
|
providerInstance: null,
|
|
270
276
|
resolvedModel,
|
|
271
|
-
preFailReason:
|
|
277
|
+
preFailReason: getProviderUnavailableMessage(providerName),
|
|
272
278
|
};
|
|
273
279
|
}
|
|
274
280
|
|
|
@@ -573,7 +579,12 @@ export async function conversationTool(args, dependencies) {
|
|
|
573
579
|
);
|
|
574
580
|
|
|
575
581
|
// Resolve ordered turn plan (unavailable models kept as pre-failed turns)
|
|
576
|
-
const turnPlan = resolveTurnPlan(
|
|
582
|
+
const turnPlan = resolveTurnPlan(
|
|
583
|
+
models,
|
|
584
|
+
providers,
|
|
585
|
+
config,
|
|
586
|
+
Array.isArray(images) && images.length > 0,
|
|
587
|
+
);
|
|
577
588
|
|
|
578
589
|
const startedAt = Date.now();
|
|
579
590
|
const lapTurns = [];
|
|
@@ -939,7 +950,12 @@ async function executeConversationWithStreaming(args, dependencies, context) {
|
|
|
939
950
|
);
|
|
940
951
|
|
|
941
952
|
const priorTranscriptText = renderStoredTranscriptToText(conversationHistory);
|
|
942
|
-
const turnPlan = resolveTurnPlan(
|
|
953
|
+
const turnPlan = resolveTurnPlan(
|
|
954
|
+
models,
|
|
955
|
+
providers,
|
|
956
|
+
config,
|
|
957
|
+
Array.isArray(images) && images.length > 0,
|
|
958
|
+
);
|
|
943
959
|
const modelsList = models.join(', ');
|
|
944
960
|
|
|
945
961
|
// Use passed title or generate if not provided
|
|
@@ -44,6 +44,49 @@ export function resolveAutoModel(model, providerName) {
|
|
|
44
44
|
return getDefaultModelForProvider(providerName);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Provider-specific setup hints appended to "Provider X is not available."
|
|
49
|
+
* errors so users know how to enable a provider. Keyed by registry name.
|
|
50
|
+
*/
|
|
51
|
+
const PROVIDER_SETUP_HINTS = {
|
|
52
|
+
'gemini-cli':
|
|
53
|
+
'Install the Antigravity CLI and run `agy` once to log in (https://antigravity.google)',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the "provider not available" error message with an optional setup hint.
|
|
58
|
+
* @param {string} providerName - Provider registry name
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export function getProviderUnavailableMessage(providerName) {
|
|
62
|
+
const base = `Provider ${providerName} is not available. Check API key configuration.`;
|
|
63
|
+
const hint = PROVIDER_SETUP_HINTS[providerName];
|
|
64
|
+
return hint ? `${base} ${hint}` : base;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether a provider's default model supports image inputs. Used by the "auto"
|
|
69
|
+
* selection paths to skip text-only providers (gemini-cli, copilot) when the
|
|
70
|
+
* request includes images. Providers without a resolvable config are treated as
|
|
71
|
+
* image-capable (fail open — they surface their own errors downstream).
|
|
72
|
+
* @param {object} providerInstance - Provider implementation
|
|
73
|
+
* @param {string} providerName - Provider registry name
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
export function providerSupportsImages(providerInstance, providerName) {
|
|
77
|
+
if (!providerInstance || typeof providerInstance.getModelConfig !== 'function') {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const defaultModel = getDefaultModelForProvider(providerName);
|
|
82
|
+
const modelConfig = providerInstance.getModelConfig(defaultModel);
|
|
83
|
+
if (!modelConfig) return true;
|
|
84
|
+
return modelConfig.supportsImages !== false;
|
|
85
|
+
} catch {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
47
90
|
/**
|
|
48
91
|
* Map model name to provider name
|
|
49
92
|
* @param {string} model - Model name
|
|
@@ -80,6 +123,13 @@ export function mapModelToProvider(model, providers) {
|
|
|
80
123
|
return 'gemini-cli';
|
|
81
124
|
}
|
|
82
125
|
|
|
126
|
+
// Check gemini: prefix (e.g., gemini:flash, gemini:pro) - routes to Antigravity
|
|
127
|
+
// CLI provider. Must be before the google flash/pro keyword rule below so it
|
|
128
|
+
// wins over Google API routing. Bare gemini-pro/gemini-flash still hit google.
|
|
129
|
+
if (modelLower.startsWith('gemini:')) {
|
|
130
|
+
return 'gemini-cli';
|
|
131
|
+
}
|
|
132
|
+
|
|
83
133
|
// Check Claude SDK (exact match only - routes to SDK provider instead of Anthropic API)
|
|
84
134
|
if (
|
|
85
135
|
modelLower === 'claude' ||
|