converse-mcp-server 3.7.1 → 4.1.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.
@@ -18,11 +18,10 @@
18
18
  * interactively (`agy`) via Google OAuth. The first interactive login also
19
19
  * establishes workspace trust for the user's home directory.
20
20
  *
21
- * The provider registry key remains 'gemini-cli' and the user-facing alias
22
- * remains 'gemini' for routing/normalization stability. Only three user-facing
23
- * model names are exposed: gemini (= gemini:flash), gemini:flash, gemini:pro.
24
- * Flash is the default: Gemini 3.8 Flash is the current-generation model agy
25
- * lists first, while 3.1 Pro remains the only Pro tier Antigravity offers.
21
+ * The provider registry key remains 'gemini-cli'; its namespaces are `gemini:`
22
+ * and `agy:`. Flash is the default: Gemini 3.8 Flash is the current-generation
23
+ * model agy lists first, while 3.1 Pro remains the only Pro tier Antigravity
24
+ * offers.
26
25
  */
27
26
 
28
27
  import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
@@ -32,6 +31,7 @@ import { randomUUID } from 'node:crypto';
32
31
  import { debugLog, debugError } from '../utils/console.js';
33
32
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
34
33
  import { clampReasoningEffort } from '../utils/reasoningEffort.js';
34
+ import { findCatalogEntry } from '../utils/modelCatalog.js';
35
35
 
36
36
  // Prompts at or below this length pass directly as the -p argv value (fast
37
37
  // path). Larger prompts are written to a file and -p carries a bootstrap
@@ -53,13 +53,15 @@ const POST_KILL_GRACE_MS = 5000;
53
53
  const PTY_COLS = 1000;
54
54
 
55
55
  /**
56
- * Supported Gemini models. Only three user-facing names are exposed; each maps
57
- * to an agy display-name base that gets a reasoning-effort suffix appended at
58
- * spawn time. All are text-only (print mode has no image input channel).
56
+ * Supported Gemini models, keyed by the same model IDs the Google API provider
57
+ * uses so a bare name resolves to one model whichever provider serves it. Each
58
+ * maps to an agy display-name base that gets a reasoning-effort suffix
59
+ * appended at spawn time. All are text-only (print mode has no image input
60
+ * channel).
59
61
  */
60
62
  const SUPPORTED_MODELS = {
61
- gemini: {
62
- modelName: 'gemini',
63
+ 'gemini-3.8-flash': {
64
+ modelName: 'gemini-3.8-flash',
63
65
  friendlyName: 'Gemini 3.8 Flash (via Antigravity CLI)',
64
66
  contextWindow: 1048576,
65
67
  maxOutputTokens: 65536,
@@ -69,28 +71,23 @@ const SUPPORTED_MODELS = {
69
71
  supportsThinking: true,
70
72
  timeout: DEFAULT_TIMEOUT_MS,
71
73
  description:
72
- 'Gemini 3.8 Flash via Antigravity CLI (agy) - requires Antigravity Google OAuth login',
73
- aliases: ['gemini-cli'],
74
+ 'Gemini 3.8 Flash via Antigravity CLI (agy, default) - requires Antigravity Google OAuth login',
75
+ aliases: [
76
+ 'flash',
77
+ 'gemini-3.8',
78
+ 'gemini3.8',
79
+ 'gemini-3.8-flash-latest',
80
+ 'flash-3.8',
81
+ 'flash3.8',
82
+ 'gemini-flash-3.8',
83
+ 'gemini flash 3.8',
84
+ '3.8-flash',
85
+ ],
74
86
  // agy display-name base; reasoning_effort selects the parenthesized variant
75
87
  agyModelBase: 'Gemini 3.8 Flash',
76
88
  },
77
- 'gemini:flash': {
78
- modelName: 'gemini:flash',
79
- friendlyName: 'Gemini 3.8 Flash (via Antigravity CLI)',
80
- contextWindow: 1048576,
81
- maxOutputTokens: 65536,
82
- supportsStreaming: true,
83
- supportsImages: false,
84
- supportsWebSearch: false,
85
- supportsThinking: true,
86
- timeout: DEFAULT_TIMEOUT_MS,
87
- description:
88
- 'Gemini 3.8 Flash via Antigravity CLI (agy) - explicit alias of `gemini`',
89
- aliases: ['flash'],
90
- agyModelBase: 'Gemini 3.8 Flash',
91
- },
92
- 'gemini:pro': {
93
- modelName: 'gemini:pro',
89
+ 'gemini-3.1-pro-preview': {
90
+ modelName: 'gemini-3.1-pro-preview',
94
91
  friendlyName: 'Gemini 3.1 Pro (via Antigravity CLI)',
95
92
  contextWindow: 1048576,
96
93
  maxOutputTokens: 65536,
@@ -101,11 +98,26 @@ const SUPPORTED_MODELS = {
101
98
  timeout: DEFAULT_TIMEOUT_MS,
102
99
  description:
103
100
  'Gemini 3.1 Pro via Antigravity CLI (agy) - requires Antigravity Google OAuth login',
104
- aliases: ['pro'],
101
+ aliases: [
102
+ 'pro',
103
+ 'gemini-pro',
104
+ 'gemini pro',
105
+ 'gemini-3.1-pro',
106
+ 'gemini-3.1',
107
+ 'gemini3.1',
108
+ '3.1-pro',
109
+ 'gemini-3',
110
+ 'gemini3',
111
+ 'gemini-3-pro',
112
+ 'gemini-3-pro-preview',
113
+ '3-pro',
114
+ ],
105
115
  agyModelBase: 'Gemini 3.1 Pro',
106
116
  },
107
117
  };
108
118
 
119
+ const DEFAULT_MODEL = 'gemini-3.8-flash';
120
+
109
121
  /**
110
122
  * Custom error class for Gemini CLI (agy) provider errors
111
123
  */
@@ -197,45 +209,24 @@ function effortSuffix(base, reasoningEffort) {
197
209
  }
198
210
 
199
211
  /**
200
- * Resolve a user-facing model name + reasoning_effort to the agy display name
201
- * passed via --model. Strips the gemini: prefix (case-insensitive), maps the
202
- * alias, and appends the effort suffix. Full agy display names pass through
203
- * verbatim so power users aren't blocked.
204
- * @param {string} model - e.g. 'gemini', 'gemini:flash', or a full agy name
212
+ * Resolve a model name + reasoning_effort to the agy display name passed via
213
+ * --model. The router hands over canonical catalog IDs; aliases are accepted
214
+ * for direct callers.
215
+ * @param {string} [model] - Catalog ID or alias; defaults to DEFAULT_MODEL
205
216
  * @param {string} [reasoningEffort]
206
217
  * @returns {string} agy --model value, e.g. 'Gemini 3.8 Flash (High)'
218
+ * @throws {GeminiCliProviderError} When the name is not in the catalog
207
219
  */
208
220
  export function resolveAgyModel(model, reasoningEffort) {
209
- const raw = typeof model === 'string' ? model.trim() : '';
210
-
211
- // Full agy display-name passthrough (already contains a parenthesized variant)
212
- if (/\(.*\)\s*$/.test(raw) && /gemini/i.test(raw)) {
213
- return raw;
214
- }
215
-
216
- let name = raw;
217
- if (name.toLowerCase().startsWith('gemini:')) {
218
- name = name.slice('gemini:'.length).trim();
219
- }
220
-
221
- const nameLower = name.toLowerCase();
222
-
223
- // Determine the agy base
224
- let base;
225
- if (
226
- !nameLower ||
227
- nameLower === 'gemini' ||
228
- nameLower === 'gemini-cli' ||
229
- nameLower === 'flash'
230
- ) {
231
- base = SUPPORTED_MODELS.gemini.agyModelBase;
232
- } else if (nameLower === 'pro') {
233
- base = SUPPORTED_MODELS['gemini:pro'].agyModelBase;
234
- } else {
235
- // Unknown suffix: pass through verbatim (power-user agy display name)
236
- return raw;
221
+ const name = typeof model === 'string' && model.trim() ? model : DEFAULT_MODEL;
222
+ const entry = findCatalogEntry(SUPPORTED_MODELS, name);
223
+ if (!entry) {
224
+ throw new GeminiCliProviderError(
225
+ `Unknown Antigravity model "${model}"`,
226
+ ErrorCodes.MODEL_NOT_FOUND,
227
+ );
237
228
  }
238
-
229
+ const base = entry.agyModelBase;
239
230
  return `${base} ${effortSuffix(base, reasoningEffort)}`;
240
231
  }
241
232
 
@@ -654,7 +645,7 @@ async function* createStreamingGenerator(fullText, userFacingModel) {
654
645
  * provider errors.
655
646
  */
656
647
  async function executeAgy(messages, options) {
657
- const { model = 'gemini', reasoning_effort, signal, timeout } = options;
648
+ const { model = DEFAULT_MODEL, reasoning_effort, signal, timeout } = options;
658
649
 
659
650
  const prompt = buildPrompt(messages);
660
651
  const agyModel = resolveAgyModel(model, reasoning_effort);
@@ -699,7 +690,7 @@ export const geminiCliProvider = {
699
690
  * @returns {Promise<Object>|AsyncGenerator} Response or stream generator
700
691
  */
701
692
  async invoke(messages, options = {}) {
702
- const { model = 'gemini', stream = false, signal } = options;
693
+ const { model = DEFAULT_MODEL, stream = false, signal } = options;
703
694
 
704
695
  if (signal?.aborted) {
705
696
  throw new GeminiCliProviderError('Request cancelled', 'CANCELLED');
@@ -729,9 +720,13 @@ export const geminiCliProvider = {
729
720
  };
730
721
  },
731
722
 
723
+ defaultModel: DEFAULT_MODEL,
724
+
732
725
  /**
733
726
  * Validate configuration. agy uses OAuth (no env keys); always true.
734
- * Availability is determined by isAvailable (binary presence).
727
+ * Availability is determined by isAvailable (binary presence). agy keeps
728
+ * its login outside any file converse can check, so a logged-out agy
729
+ * surfaces at invoke time and bare-name/auto routing fails over.
735
730
  */
736
731
  validateConfig(_config) {
737
732
  return true;
@@ -752,37 +747,9 @@ export const geminiCliProvider = {
752
747
  },
753
748
 
754
749
  /**
755
- * Get model configuration for a specific model (alias-aware, prefix-aware).
750
+ * Get model configuration for a specific model (alias-aware).
756
751
  */
757
752
  getModelConfig(modelName) {
758
- if (typeof modelName !== 'string') return null;
759
-
760
- const name = modelName.toLowerCase().trim();
761
-
762
- // Full agy display-name passthrough → matching tier config. Any 3.x Flash
763
- // (agy also lists 3.6/3.7) shares the Flash config; any 3.x Pro the Pro one.
764
- if (/gemini 3\.\d+ flash/i.test(modelName)) {
765
- return SUPPORTED_MODELS['gemini:flash'];
766
- }
767
- if (/gemini 3\.\d+ pro/i.test(modelName)) {
768
- return SUPPORTED_MODELS['gemini:pro'];
769
- }
770
-
771
- // Exact key match (gemini, gemini:flash, gemini:pro)
772
- if (SUPPORTED_MODELS[name]) {
773
- return SUPPORTED_MODELS[name];
774
- }
775
-
776
- // Alias match
777
- for (const config of Object.values(SUPPORTED_MODELS)) {
778
- if (
779
- config.aliases &&
780
- config.aliases.some((alias) => alias.toLowerCase() === name)
781
- ) {
782
- return config;
783
- }
784
- }
785
-
786
- return null;
753
+ return findCatalogEntry(SUPPORTED_MODELS, modelName);
787
754
  },
788
755
  };
@@ -434,7 +434,11 @@ async function retryWithBackoff(fn, maxRetries = 4) {
434
434
  /**
435
435
  * Main Google provider implementation
436
436
  */
437
+ const DEFAULT_MODEL = 'gemini-3.1-pro-preview';
438
+
437
439
  export const googleProvider = {
440
+ defaultModel: DEFAULT_MODEL,
441
+
438
442
  /**
439
443
  * Unified provider interface: invoke messages with options
440
444
  * @param {Array} messages - Array of message objects with role and content
@@ -443,7 +447,7 @@ export const googleProvider = {
443
447
  */
444
448
  async invoke(messages, options = {}) {
445
449
  const {
446
- model = 'gemini-2.5-flash',
450
+ model = DEFAULT_MODEL,
447
451
  maxTokens = null,
448
452
  stream = false,
449
453
  reasoning_effort = 'medium',
@@ -319,7 +319,11 @@ function extractRateLimitInfo(headers) {
319
319
  /**
320
320
  * Main Mistral provider implementation
321
321
  */
322
+ const DEFAULT_MODEL = 'mistral-medium-3-5';
323
+
322
324
  export const mistralProvider = {
325
+ defaultModel: DEFAULT_MODEL,
326
+
323
327
  /**
324
328
  * Unified provider interface: invoke messages with options
325
329
  * @param {Array} messages - Array of message objects with role and content
@@ -328,7 +332,7 @@ export const mistralProvider = {
328
332
  */
329
333
  async invoke(messages, options = {}) {
330
334
  const {
331
- model = 'mistral-medium-3-5',
335
+ model = DEFAULT_MODEL,
332
336
  maxTokens = null,
333
337
  stream = false,
334
338
  reasoning_effort = 'medium',
@@ -273,6 +273,7 @@ export function createOpenAICompatibleProvider(providerConfig) {
273
273
  transformStreamChunk,
274
274
  resolveModelConfig,
275
275
  defaultParams = {},
276
+ defaultModel = Object.keys(supportedModels)[0],
276
277
  } = providerConfig;
277
278
 
278
279
  // Create custom error class for this provider
@@ -284,12 +285,14 @@ export function createOpenAICompatibleProvider(providerConfig) {
284
285
  }
285
286
 
286
287
  return {
288
+ defaultModel,
289
+
287
290
  /**
288
291
  * Unified provider interface: invoke messages with options
289
292
  */
290
293
  async invoke(messages, options = {}) {
291
294
  const {
292
- model = Object.keys(supportedModels)[0], // Default to first model
295
+ model = defaultModel,
293
296
  maxTokens = null,
294
297
  stream = false,
295
298
  reasoning_effort = 'medium',
@@ -547,7 +547,11 @@ function convertMessages(messages, useResponsesAPI = false) {
547
547
  /**
548
548
  * Main OpenAI provider implementation
549
549
  */
550
+ const DEFAULT_MODEL = 'gpt-6-sol';
551
+
550
552
  export const openaiProvider = {
553
+ defaultModel: DEFAULT_MODEL,
554
+
551
555
  /**
552
556
  * Unified provider interface: invoke messages with options
553
557
  * @param {Array} messages - Array of message objects with role and content
@@ -556,7 +560,7 @@ export const openaiProvider = {
556
560
  */
557
561
  async invoke(messages, options = {}) {
558
562
  const {
559
- model = 'gpt-6',
563
+ model = DEFAULT_MODEL,
560
564
  maxTokens = null,
561
565
  stream = false,
562
566
  reasoning_effort = 'medium',
@@ -203,7 +203,7 @@ function validateApiKey(apiKey) {
203
203
  * `X-Title`), while still accepting the legacy `openrouterreferer`/
204
204
  * `openroutertitle` config-key spellings as input.
205
205
  */
206
- function getCustomHeaders(config) {
206
+ export function getCustomHeaders(config) {
207
207
  const headers = {};
208
208
 
209
209
  const referer =
@@ -550,6 +550,7 @@ export const openrouterProvider = createOpenAICompatibleProvider({
550
550
  baseURL: 'https://openrouter.ai/api/v1',
551
551
  providerName: 'OpenRouter',
552
552
  supportedModels: SUPPORTED_MODELS,
553
+ defaultModel: 'z-ai/glm-5.2',
553
554
  validateApiKey,
554
555
  transformRequest,
555
556
  transformResponse,
@@ -212,7 +212,11 @@ function extractCitations(response) {
212
212
  /**
213
213
  * Main XAI provider implementation
214
214
  */
215
+ const DEFAULT_MODEL = 'grok-4.5';
216
+
215
217
  export const xaiProvider = {
218
+ defaultModel: DEFAULT_MODEL,
219
+
216
220
  /**
217
221
  * Unified provider interface: invoke messages with options
218
222
  * @param {Array} messages - Array of message objects with role and content
@@ -221,7 +225,7 @@ export const xaiProvider = {
221
225
  */
222
226
  async invoke(messages, options = {}) {
223
227
  const {
224
- model = 'grok-4.5',
228
+ model = DEFAULT_MODEL,
225
229
  maxTokens = null,
226
230
  stream = false,
227
231
  reasoning_effort = 'medium',
@@ -9,20 +9,21 @@
9
9
  import { createLogger } from '../utils/logger.js';
10
10
  import { debugLog, debugError } from '../utils/console.js';
11
11
 
12
- import { mapModelToProvider } from '../utils/modelRouting.js';
12
+ import { resolveModelSpec } from '../utils/modelRouting.js';
13
13
 
14
14
  const logger = createLogger('summarization');
15
15
 
16
- // Default fast models for summarization tasks (prioritize GPT-5-nano for speed)
17
- const FAST_MODELS = {
18
- openai: 'gpt-5-nano', // Fastest GPT-5 model with minimal reasoning
19
- google: 'flash',
20
- xai: 'grok-4.5',
21
- anthropic: 'claude-3-5-haiku-latest',
22
- mistral: 'mistral-small-latest',
23
- deepseek: 'deepseek-v4-flash',
24
- openrouter: 'z-ai/glm-5.2',
25
- };
16
+ // Fast models for summarization tasks, tried in order (GPT-5-nano first for
17
+ // speed). Namespaced so each names exactly one provider.
18
+ export const FAST_MODELS = [
19
+ 'openai:gpt-5-nano',
20
+ 'google:gemini-2.5-flash',
21
+ 'xai:grok-4.5',
22
+ 'anthropic:claude-haiku-4-5-20251001',
23
+ 'mistral:mistral-small-2603',
24
+ 'deepseek:deepseek-v4-flash',
25
+ 'openrouter:z-ai/glm-5.2',
26
+ ];
26
27
 
27
28
  export class SummarizationService {
28
29
  constructor(providers, config) {
@@ -51,16 +52,12 @@ export class SummarizationService {
51
52
 
52
53
  try {
53
54
  // Select fast model if not specified
54
- const selectedModel = model || this._selectFastModel();
55
- const providerName = mapModelToProvider(selectedModel, this.providers);
56
- const provider = this.providers[providerName];
57
-
58
- if (!provider || !provider.isAvailable(this.config)) {
59
- debugLog(
60
- `Summarization: Provider ${providerName} not available for title generation`,
61
- );
55
+ const selected = this._resolve(model || this._selectFastModel());
56
+ if (!selected) {
57
+ debugLog('Summarization: No available model for title generation');
62
58
  return this._fallbackTitle(prompt);
63
59
  }
60
+ const { provider, resolvedModel: selectedModel } = selected;
64
61
 
65
62
  // Create messages for title generation
66
63
  const messages = [
@@ -112,16 +109,12 @@ export class SummarizationService {
112
109
 
113
110
  try {
114
111
  // Select fast model if not specified
115
- const selectedModel = model || this._selectFastModel();
116
- const providerName = mapModelToProvider(selectedModel, this.providers);
117
- const provider = this.providers[providerName];
118
-
119
- if (!provider || !provider.isAvailable(this.config)) {
120
- debugLog(
121
- `Summarization: Provider ${providerName} not available for streaming summary`,
122
- );
112
+ const selected = this._resolve(model || this._selectFastModel());
113
+ if (!selected) {
114
+ debugLog('Summarization: No available model for streaming summary');
123
115
  return this._fallbackStreamingSummary(content, currentFocus);
124
116
  }
117
+ const { provider, resolvedModel: selectedModel } = selected;
125
118
 
126
119
  // Create messages for streaming summary
127
120
  const messages = [
@@ -175,16 +168,12 @@ export class SummarizationService {
175
168
 
176
169
  try {
177
170
  // Select fast model if not specified
178
- const selectedModel = model || this._selectFastModel();
179
- const providerName = mapModelToProvider(selectedModel, this.providers);
180
- const provider = this.providers[providerName];
181
-
182
- if (!provider || !provider.isAvailable(this.config)) {
183
- debugLog(
184
- `Summarization: Provider ${providerName} not available for final summary`,
185
- );
171
+ const selected = this._resolve(model || this._selectFastModel());
172
+ if (!selected) {
173
+ debugLog('Summarization: No available model for final summary');
186
174
  return this._fallbackFinalSummary(content);
187
175
  }
176
+ const { provider, resolvedModel: selectedModel } = selected;
188
177
 
189
178
  // Create messages for final summary
190
179
  const messages = [
@@ -221,6 +210,20 @@ export class SummarizationService {
221
210
  }
222
211
  }
223
212
 
213
+ /**
214
+ * Route a model spec to its first available provider.
215
+ * @private
216
+ * @returns {{ provider: object, providerName: string, resolvedModel: string }|null}
217
+ */
218
+ _resolve(spec) {
219
+ const resolution = resolveModelSpec(spec, this.providers, this.config);
220
+ if (resolution.status !== 'ok') {
221
+ debugLog(`Summarization: ${resolution.error}`);
222
+ return null;
223
+ }
224
+ return resolution;
225
+ }
226
+
224
227
  /**
225
228
  * Select the best available fast model
226
229
  * @private
@@ -228,14 +231,10 @@ export class SummarizationService {
228
231
  _selectFastModel() {
229
232
  // If a model is configured, try to use it first
230
233
  if (this.configuredModel) {
231
- const providerName = mapModelToProvider(
232
- this.configuredModel,
233
- this.providers,
234
- );
235
- const provider = this.providers[providerName];
236
- if (provider && provider.isAvailable(this.config)) {
234
+ const resolution = resolveModelSpec(this.configuredModel, this.providers, this.config);
235
+ if (resolution.status === 'ok') {
237
236
  debugLog(
238
- `Summarization: Using configured model ${this.configuredModel} from ${providerName}`,
237
+ `Summarization: Using configured model ${this.configuredModel} from ${resolution.providerName}`,
239
238
  );
240
239
  return this.configuredModel;
241
240
  }
@@ -244,13 +243,10 @@ export class SummarizationService {
244
243
  );
245
244
  }
246
245
 
247
- // Check which providers are available and return the first fast model
248
- for (const [providerName, fastModel] of Object.entries(FAST_MODELS)) {
249
- const provider = this.providers[providerName];
250
- if (provider && provider.isAvailable(this.config)) {
251
- debugLog(
252
- `Summarization: Selected fast model ${fastModel} from ${providerName}`,
253
- );
246
+ // Return the first fast model whose provider is available
247
+ for (const fastModel of FAST_MODELS) {
248
+ if (resolveModelSpec(fastModel, this.providers, this.config).status === 'ok') {
249
+ debugLog(`Summarization: Selected fast model ${fastModel}`);
254
250
  return fastModel;
255
251
  }
256
252
  }