converse-mcp-server 3.7.0 → 4.0.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,29 +18,19 @@ import { fileURLToPath } from 'node:url';
18
18
  import { debugLog, debugError } from '../utils/console.js';
19
19
  import { ProviderError, ErrorCodes, StopReasons } from './interface.js';
20
20
  import { clampReasoningEffort } from '../utils/reasoningEffort.js';
21
+ import { findCatalogEntry, findCatalogId } from '../utils/modelCatalog.js';
22
+ import { isPackageResolvable } from '../utils/localProviderAuth.js';
21
23
 
22
- const SUPPORTED_MODELS = {
23
- copilot: {
24
- modelName: 'copilot',
25
- friendlyName: 'GitHub Copilot (via CLI SDK)',
26
- contextWindow: 128000,
27
- maxOutputTokens: 16384,
28
- supportsStreaming: true,
29
- supportsImages: false,
30
- supportsWebSearch: false,
31
- timeout: 1800000,
32
- description:
33
- 'GitHub Copilot via CLI SDK - uses default or env-configured model',
34
- aliases: ['copilot-sdk', 'github-copilot'],
35
- },
24
+ const DEFAULT_MODEL = 'gpt-6-sol';
36
25
 
26
+ // Keyed by the SDK model ID, which is also the canonical ID the router
27
+ // resolves to. Every name here is reached only through the `copilot:`
28
+ // namespace — Copilot never serves bare model names.
29
+ const SUPPORTED_MODELS = {
37
30
  // OpenAI models
38
- // Bare `gpt-6` / `gpt-5.6` route to that generation's Sol, matching
39
- // Copilot's own bare-alias behavior; `sol`/`luna` and the legacy `gpt-5`
40
- // shortcut follow the current generation. `codex` and `gpt` point at the
41
- // latest GPT tier (reachable only via the `copilot:` namespace — bare
42
- // `codex` routes to the Codex provider and bare `gpt*` keyword-routes to
43
- // OpenAI before Copilot's catalog is consulted).
31
+ // `gpt-6` / `gpt-5.6` point at that generation's Sol, matching Copilot's own
32
+ // bare-alias behavior; `sol`/`luna` and the legacy `gpt-5` shortcut follow
33
+ // the current generation. `codex` and `gpt` point at the latest GPT tier.
44
34
  'gpt-6-sol': {
45
35
  modelName: 'gpt-6-sol',
46
36
  friendlyName: 'GPT-6 Sol (via Copilot)',
@@ -205,22 +195,6 @@ class CopilotProviderError extends ProviderError {
205
195
  }
206
196
  }
207
197
 
208
- /**
209
- * Check if Copilot SDK is available (installed as dependency)
210
- */
211
- let _sdkAvailable = null;
212
- function isCopilotSDKAvailable() {
213
- if (_sdkAvailable !== null) return _sdkAvailable;
214
- try {
215
- // Use synchronous resolve to check if the package exists
216
- import.meta.resolve('@github/copilot-sdk');
217
- _sdkAvailable = true;
218
- } catch {
219
- _sdkAvailable = false;
220
- }
221
- return _sdkAvailable;
222
- }
223
-
224
198
  /**
225
199
  * Dynamically import Copilot SDK (lazy loading)
226
200
  */
@@ -443,106 +417,24 @@ function createPermissionHandler(accessLevel) {
443
417
  }
444
418
 
445
419
  /**
446
- * Resolve a friendly alias to its SDK model identifier (case-insensitive)
447
- * Returns the resolved model name, or null if no alias matches
448
- */
449
- function resolveModelAlias(name) {
450
- if (typeof name !== 'string') return null;
451
- const lower = name.toLowerCase().trim();
452
- if (!lower) return null;
453
-
454
- // Direct key match
455
- if (SUPPORTED_MODELS[lower] && lower !== 'copilot') {
456
- return SUPPORTED_MODELS[lower].modelName;
457
- }
458
-
459
- // Alias match
460
- for (const config of Object.values(SUPPORTED_MODELS)) {
461
- if (config.modelName === 'copilot') continue;
462
- if (
463
- config.aliases &&
464
- config.aliases.some((alias) => alias.toLowerCase() === lower)
465
- ) {
466
- return config.modelName;
467
- }
468
- }
469
-
470
- return null;
471
- }
472
-
473
- /**
474
- * Look up model config from SUPPORTED_MODELS by name or alias.
475
- * Strips copilot: prefix and falls back to the base copilot config.
476
- */
477
- function findModelConfig(modelName) {
478
- if (typeof modelName !== 'string') return null;
479
-
480
- let name = modelName;
481
- if (name.toLowerCase().startsWith('copilot:')) {
482
- name = name.slice('copilot:'.length).trim();
483
- }
484
- if (!name) return SUPPORTED_MODELS.copilot;
485
-
486
- const nameLower = name.toLowerCase();
487
-
488
- if (SUPPORTED_MODELS[nameLower]) {
489
- return SUPPORTED_MODELS[nameLower];
490
- }
491
-
492
- for (const config of Object.values(SUPPORTED_MODELS)) {
493
- if (
494
- config.aliases &&
495
- config.aliases.some((alias) => alias.toLowerCase() === nameLower)
496
- ) {
497
- return config;
498
- }
499
- }
500
-
501
- return null;
502
- }
503
-
504
- /**
505
- * Resolve model to pass to SDK session
506
- * Precedence: explicit model param > config COPILOT_MODEL > omit (SDK default)
507
- *
508
- * Handles copilot: prefix stripping, alias resolution, and env var fallback.
509
- * Note: "copilot" is a Converse routing alias, not a valid SDK model ID.
420
+ * Resolve a model name to the SDK model ID for the session. The router hands
421
+ * over canonical catalog IDs; aliases are accepted for direct callers.
422
+ * @param {string} [model] - Catalog ID or alias; defaults to DEFAULT_MODEL
423
+ * @returns {string}
424
+ * @throws {CopilotProviderError} When the name is not in the catalog
510
425
  */
511
- function resolveSessionModel(requestModel, config) {
512
- const converseAliases = ['copilot', 'copilot-sdk', 'github-copilot'];
513
-
514
- // Guard non-string inputs
515
- if (typeof requestModel !== 'string') {
516
- requestModel = '';
426
+ function resolveSessionModel(model) {
427
+ if (typeof model !== 'string' || !model.trim()) {
428
+ return DEFAULT_MODEL;
517
429
  }
518
-
519
- // Strip copilot: prefix (case-insensitive)
520
- let effectiveModel = requestModel;
521
- if (effectiveModel.toLowerCase().startsWith('copilot:')) {
522
- effectiveModel = effectiveModel.slice('copilot:'.length).trim();
523
- }
524
-
525
- // Empty suffix or converse alias → use env/default
526
- if (
527
- !effectiveModel ||
528
- converseAliases.includes(effectiveModel.toLowerCase())
529
- ) {
530
- const envModel = config?.providers?.copilotmodel;
531
- if (envModel) {
532
- let resolved = typeof envModel === 'string' ? envModel : '';
533
- if (resolved.toLowerCase().startsWith('copilot:')) {
534
- resolved = resolved.slice('copilot:'.length).trim();
535
- }
536
- if (!resolved || converseAliases.includes(resolved.toLowerCase())) {
537
- return undefined;
538
- }
539
- return resolveModelAlias(resolved) || resolved;
540
- }
541
- return undefined;
430
+ const id = findCatalogId(SUPPORTED_MODELS, model);
431
+ if (!id) {
432
+ throw new CopilotProviderError(
433
+ `Unknown Copilot model "${model}"`,
434
+ ErrorCodes.MODEL_NOT_FOUND,
435
+ );
542
436
  }
543
-
544
- // Resolve alias or passthrough unknown models to SDK
545
- return resolveModelAlias(effectiveModel) || effectiveModel;
437
+ return SUPPORTED_MODELS[id].modelName;
546
438
  }
547
439
 
548
440
  /**
@@ -590,23 +482,20 @@ async function checkReasoningSupport(client, modelId) {
590
482
  async function* createStreamingGenerator(client, prompt, options, signal, config) {
591
483
  const { model, timeout = 1800000, reasoning_effort } = options;
592
484
 
593
- const sessionModel = resolveSessionModel(model, config);
485
+ const sessionModel = resolveSessionModel(model);
594
486
  const accessLevel = getToolAccessLevel(config);
595
487
 
596
488
  const sessionConfig = {
597
489
  streaming: true,
598
490
  onPermissionRequest: createPermissionHandler(accessLevel),
491
+ model: sessionModel,
599
492
  };
600
493
 
601
- if (sessionModel) {
602
- sessionConfig.model = sessionModel;
603
- }
604
-
605
494
  if (reasoning_effort) {
606
495
  const mapped = mapReasoningEffort(reasoning_effort);
607
496
  if (mapped) {
608
- const effectiveModel = sessionModel || model;
609
- const modelDef = findModelConfig(effectiveModel);
497
+ const effectiveModel = sessionModel;
498
+ const modelDef = SUPPORTED_MODELS[sessionModel];
610
499
 
611
500
  let supported;
612
501
  if (modelDef && modelDef.supportsReasoningEffort !== undefined) {
@@ -650,7 +539,7 @@ async function* createStreamingGenerator(client, prompt, options, signal, config
650
539
  yield {
651
540
  type: 'start',
652
541
  provider: 'copilot',
653
- model: sessionModel || 'copilot',
542
+ model: sessionModel,
654
543
  };
655
544
 
656
545
  // Bridge push-based SDK events to pull-based generator using queue + promise
@@ -775,7 +664,7 @@ async function* createStreamingGenerator(client, prompt, options, signal, config
775
664
  }
776
665
  }
777
666
 
778
- export { resolveModelAlias, resolveSessionModel, resolveCopilotCliPath };
667
+ export { resolveSessionModel, resolveCopilotCliPath };
779
668
 
780
669
  /**
781
670
  * Copilot SDK Provider Implementation
@@ -783,7 +672,7 @@ export { resolveModelAlias, resolveSessionModel, resolveCopilotCliPath };
783
672
  export const copilotProvider = {
784
673
  async invoke(messages, options = {}) {
785
674
  const {
786
- model = 'copilot',
675
+ model = DEFAULT_MODEL,
787
676
  config,
788
677
  stream = false,
789
678
  signal,
@@ -797,15 +686,17 @@ export const copilotProvider = {
797
686
  );
798
687
  }
799
688
 
689
+ // Validated before the client spawns the Copilot CLI.
690
+ const sessionModel = resolveSessionModel(model);
691
+
800
692
  try {
801
693
  const cwd = config.server?.client_cwd || process.cwd();
802
694
  const client = await getCopilotClient(cwd, config);
803
695
  const prompt = convertMessagesToPrompt(messages);
804
696
 
805
- const sessionModel = resolveSessionModel(model, config);
806
- const modelConfig = findModelConfig(sessionModel || model) || SUPPORTED_MODELS.copilot;
697
+ const modelConfig = SUPPORTED_MODELS[sessionModel];
807
698
  const invokeOptions = {
808
- model,
699
+ model: sessionModel,
809
700
  timeout: modelConfig.timeout,
810
701
  reasoning_effort,
811
702
  };
@@ -843,7 +734,7 @@ export const copilotProvider = {
843
734
  rawResponse: { content, usage },
844
735
  metadata: {
845
736
  provider: 'copilot',
846
- model: sessionModel || 'copilot',
737
+ model: sessionModel,
847
738
  usage: usage
848
739
  ? {
849
740
  input_tokens: usage.input_tokens || 0,
@@ -901,12 +792,14 @@ export const copilotProvider = {
901
792
  }
902
793
  },
903
794
 
795
+ defaultModel: DEFAULT_MODEL,
796
+
904
797
  /**
905
798
  * Validate Copilot SDK configuration
906
799
  * Returns true optimistically — auth errors surface at runtime
907
800
  */
908
801
  validateConfig(_config) {
909
- return isCopilotSDKAvailable();
802
+ return isPackageResolvable('@github/copilot-sdk');
910
803
  },
911
804
 
912
805
  isAvailable(config) {
@@ -918,6 +811,6 @@ export const copilotProvider = {
918
811
  },
919
812
 
920
813
  getModelConfig(modelName) {
921
- return findModelConfig(modelName);
814
+ return findCatalogEntry(SUPPORTED_MODELS, modelName);
922
815
  },
923
816
  };
@@ -140,6 +140,7 @@ export const deepseekProvider = createOpenAICompatibleProvider({
140
140
  baseURL: 'https://api.deepseek.com',
141
141
  providerName: 'DeepSeek',
142
142
  supportedModels: SUPPORTED_MODELS,
143
+ defaultModel: 'deepseek-v4-pro',
143
144
  validateApiKey,
144
145
  transformRequest,
145
146
  transformResponse,
@@ -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',
@@ -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',