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.
@@ -20,10 +20,15 @@ import {
20
20
  EFFORT_LADDER,
21
21
  clampReasoningEffort,
22
22
  } from '../utils/reasoningEffort.js';
23
+ import { findCatalogEntry, findCatalogId } from '../utils/modelCatalog.js';
24
+ import {
25
+ hasCodexCredentials,
26
+ isPackageResolvable,
27
+ } from '../utils/localProviderAuth.js';
23
28
 
24
29
  /**
25
- * Backend models Codex can run, keyed by the slug passed to the CLI as
26
- * --model. The reasoning tiers are the ones each model's API accepts, verified
30
+ * Models Codex can run, keyed by the slug passed to the CLI as --model. The
31
+ * catalog key is the canonical model ID the router resolves to. The reasoning tiers are the ones each model's API accepts, verified
27
32
  * against the API's own rejection messages (gpt-6-astra: "Supported values
28
33
  * are: 'low', 'medium', 'high', 'xhigh', and 'max'"; the Sol/Luna tiers of
29
34
  * both generations accept 'none' as well). The SDK's ModelReasoningEffort
@@ -38,68 +43,56 @@ import {
38
43
  * it reasons — so it is deliberately absent from every supportedEfforts list
39
44
  * and nothing at the tool level can select it.
40
45
  */
41
- const CODEX_BACKEND_MODELS = {
42
- 'gpt-6-sol': {
46
+ const ALL_EFFORTS = ['none', 'low', 'medium', 'high', 'xhigh', 'max'];
47
+
48
+ function codexModel(slug, friendlyName, { aliases, contextWindow = 272000, supportedEfforts = ALL_EFFORTS }) {
49
+ return {
50
+ modelName: slug,
51
+ friendlyName: `OpenAI Codex (${friendlyName})`,
52
+ contextWindow,
53
+ maxOutputTokens: 128000,
54
+ supportsStreaming: true,
55
+ supportsImages: true, // Codex SDK 0.118+ supports images via --image (local_image input)
56
+ supportsWebSearch: false, // Codex accesses files directly, not web
57
+ timeout: 1800000, // 30 minutes
58
+ description: `${friendlyName} via the Codex agentic coding assistant, with local file access and tool execution`,
59
+ aliases,
60
+ supportedEfforts,
61
+ };
62
+ }
63
+
64
+ const SUPPORTED_MODELS = {
65
+ 'gpt-6-sol': codexModel('gpt-6-sol', 'GPT-6 Sol', {
43
66
  aliases: ['sol', 'gpt-6', 'gpt6', 'gpt6-sol', 'gpt-6-codex'],
44
- contextWindow: 272000,
45
- supportedEfforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
46
- },
47
- 'gpt-6-luna': {
67
+ }),
68
+ 'gpt-6-luna': codexModel('gpt-6-luna', 'GPT-6 Luna', {
48
69
  aliases: ['luna', 'gpt6-luna'],
49
- contextWindow: 272000,
50
- supportedEfforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
51
- },
52
- 'gpt-6-astra': {
70
+ }),
71
+ 'gpt-6-astra': codexModel('gpt-6-astra', 'GPT-6 Astra', {
53
72
  aliases: ['astra', 'gpt6-astra'],
54
- contextWindow: 272000,
55
73
  supportedEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
56
- },
57
- 'gpt-5.6-sol': {
74
+ }),
75
+ 'gpt-5.6-sol': codexModel('gpt-5.6-sol', 'GPT-5.6 Sol', {
58
76
  aliases: ['gpt-5.6', 'gpt5.6', 'gpt5.6-sol', 'gpt-5.6-codex'],
59
- contextWindow: 272000,
60
- supportedEfforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
61
- },
62
- 'gpt-5.6-terra': {
77
+ }),
78
+ 'gpt-5.6-terra': codexModel('gpt-5.6-terra', 'GPT-5.6 Terra', {
63
79
  aliases: ['terra', 'gpt5.6-terra'],
64
- contextWindow: 272000,
65
- supportedEfforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
66
- },
67
- 'gpt-5.6-luna': {
80
+ }),
81
+ 'gpt-5.6-luna': codexModel('gpt-5.6-luna', 'GPT-5.6 Luna', {
68
82
  aliases: ['gpt5.6-luna'],
69
- contextWindow: 272000,
70
- supportedEfforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
71
- },
72
- 'gpt-5.5': {
83
+ }),
84
+ 'gpt-5.5': codexModel('gpt-5.5', 'GPT-5.5', {
73
85
  aliases: ['gpt5.5'],
74
- contextWindow: 272000,
75
86
  supportedEfforts: ['low', 'medium', 'high', 'xhigh'],
76
- },
77
- 'gpt-5.3-codex-spark': {
87
+ }),
88
+ 'gpt-5.3-codex-spark': codexModel('gpt-5.3-codex-spark', 'GPT-5.3 Codex Spark', {
78
89
  aliases: ['spark', 'codex-spark'],
79
90
  contextWindow: 128000,
80
91
  supportedEfforts: ['low', 'medium', 'high', 'xhigh'],
81
- },
92
+ }),
82
93
  };
83
94
 
84
- const DEFAULT_BACKEND_MODEL = 'gpt-6-sol';
85
-
86
- // The single user-facing model the router exposes. The backend model behind it
87
- // comes from CODEX_MODEL, or from a `codex:<model>` spec.
88
- const SUPPORTED_MODELS = {
89
- codex: {
90
- modelName: 'codex',
91
- friendlyName: 'OpenAI Codex (GPT-6 Sol)',
92
- contextWindow: CODEX_BACKEND_MODELS[DEFAULT_BACKEND_MODEL].contextWindow,
93
- maxOutputTokens: 128000,
94
- supportsStreaming: true,
95
- supportsImages: true, // Codex SDK 0.118+ supports images via --image (local_image input)
96
- supportsWebSearch: false, // Codex accesses files directly, not web
97
- timeout: 1800000, // 30 minutes
98
- description:
99
- 'OpenAI Codex agentic coding assistant with local file access and tool execution (GPT-6 Sol by default; pick another backend with codex:<model> or CODEX_MODEL)',
100
- aliases: [],
101
- },
102
- };
95
+ const DEFAULT_MODEL = 'gpt-6-sol';
103
96
 
104
97
  /**
105
98
  * Custom error class for Codex provider errors
@@ -111,26 +104,14 @@ class CodexProviderError extends ProviderError {
111
104
  }
112
105
  }
113
106
 
114
- /**
115
- * Check if Codex SDK is available (optional dependency)
116
- * Uses import.meta.resolve when available, falls back to filesystem check
117
- */
118
- function isCodexAvailable() {
119
- try {
120
- // Just try to dynamically check if we can import it
121
- // This is a simple presence check that works in ES modules
122
- return true; // If SDK not available, the actual import() will fail later with clear error
123
- } catch {
124
- return false;
125
- }
126
- }
107
+ const CODEX_SDK_PACKAGE = '@openai/codex-sdk';
127
108
 
128
109
  /**
129
110
  * Dynamically import Codex SDK (lazy loading)
130
111
  * This keeps the SDK as an optional dependency
131
112
  */
132
113
  async function getCodexSDK() {
133
- if (!isCodexAvailable()) {
114
+ if (!isPackageResolvable(CODEX_SDK_PACKAGE)) {
134
115
  throw new CodexProviderError(
135
116
  'Codex SDK not installed. Install with: npm install @openai/codex-sdk',
136
117
  'CODEX_NOT_INSTALLED',
@@ -248,54 +229,24 @@ function extractPromptText(input) {
248
229
  }
249
230
 
250
231
  /**
251
- * Resolve a backend slug or alias (case-insensitive) to its catalog slug.
252
- * @param {string} name
253
- * @returns {string|null}
232
+ * Resolve a model name to the slug passed to the CLI. The router hands over
233
+ * canonical catalog IDs; aliases are accepted for direct callers.
234
+ * @param {string} [model] - Catalog ID or alias; defaults to DEFAULT_MODEL
235
+ * @returns {string} Backend slug for the SDK's `model` option
236
+ * @throws {CodexProviderError} When the name is not in the catalog
254
237
  */
255
- function findBackendSlug(name) {
256
- const lower = String(name || '').trim().toLowerCase();
257
- if (!lower) {
258
- return null;
238
+ export function resolveBackendModel(model) {
239
+ if (model === undefined || model === null || model === '') {
240
+ return DEFAULT_MODEL;
259
241
  }
260
- if (CODEX_BACKEND_MODELS[lower]) {
261
- return lower;
242
+ const slug = findCatalogId(SUPPORTED_MODELS, model);
243
+ if (!slug) {
244
+ throw new CodexProviderError(
245
+ `Unknown Codex model "${model}"`,
246
+ ErrorCodes.MODEL_NOT_FOUND,
247
+ );
262
248
  }
263
- return (
264
- Object.keys(CODEX_BACKEND_MODELS).find((slug) =>
265
- CODEX_BACKEND_MODELS[slug].aliases.includes(lower),
266
- ) || null
267
- );
268
- }
269
-
270
- /**
271
- * Catalog entry for a backend model, or null when the slug is not catalogued.
272
- * @param {string} name - Backend slug or alias
273
- * @returns {{ slug: string, aliases: string[], contextWindow: number, supportedEfforts: string[] }|null}
274
- */
275
- export function getBackendModelConfig(name) {
276
- const slug = findBackendSlug(name);
277
- return slug ? { slug, ...CODEX_BACKEND_MODELS[slug] } : null;
278
- }
279
-
280
- /**
281
- * Resolve the requested model spec to the backend slug passed to the CLI.
282
- *
283
- * `codex` uses CODEX_MODEL (default gpt-6-sol); `codex:<model>` names a
284
- * backend directly, by slug or alias. Unknown names pass through verbatim so a
285
- * newly released model works before it is catalogued here — the CLI rejects
286
- * anything the backend does not know.
287
- *
288
- * @param {string} spec - Requested model, e.g. 'codex' or 'codex:sol'
289
- * @param {Object} [config] - Loaded configuration
290
- * @returns {string} Backend slug for the SDK's `model` option
291
- */
292
- export function resolveBackendModel(spec, config) {
293
- const raw = String(spec || '').trim();
294
- const requested = raw.toLowerCase().startsWith('codex:')
295
- ? raw.slice('codex:'.length).trim()
296
- : '';
297
- const name = requested || config?.providers?.codexmodel || DEFAULT_BACKEND_MODEL;
298
- return findBackendSlug(name) || name;
249
+ return slug;
299
250
  }
300
251
 
301
252
  /**
@@ -319,30 +270,6 @@ async function getThreadIdFromContinuation(
319
270
  }
320
271
  }
321
272
 
322
- /**
323
- * Resolve a user-facing model name to its entry in SUPPORTED_MODELS.
324
- * @param {string} modelName
325
- * @returns {Object|null}
326
- */
327
- function findModelConfig(modelName) {
328
- const modelNameLower = String(modelName || '').toLowerCase();
329
-
330
- if (SUPPORTED_MODELS[modelNameLower]) {
331
- return SUPPORTED_MODELS[modelNameLower];
332
- }
333
-
334
- for (const config of Object.values(SUPPORTED_MODELS)) {
335
- if (
336
- config.aliases &&
337
- config.aliases.some((alias) => alias.toLowerCase() === modelNameLower)
338
- ) {
339
- return config;
340
- }
341
- }
342
-
343
- return null;
344
- }
345
-
346
273
  /**
347
274
  * Create stream generator for Codex streaming responses.
348
275
  * `input` is the Codex SDK Input (string | UserInput[]) — strings for plain
@@ -382,7 +309,7 @@ export const codexProvider = {
382
309
  */
383
310
  async invoke(messages, options = {}) {
384
311
  const {
385
- model = 'codex',
312
+ model = DEFAULT_MODEL,
386
313
  config,
387
314
  stream = false,
388
315
  signal,
@@ -400,6 +327,10 @@ export const codexProvider = {
400
327
  );
401
328
  }
402
329
 
330
+ // Resolved outside the try so an unknown model keeps MODEL_NOT_FOUND
331
+ // instead of being rewrapped as a generic execution failure.
332
+ const backendModel = resolveBackendModel(model);
333
+
403
334
  try {
404
335
  // Get Codex SDK
405
336
  const Codex = await getCodexSDK();
@@ -456,7 +387,6 @@ export const codexProvider = {
456
387
  const approvalPolicy = config.providers?.codexapprovalpolicy || 'never';
457
388
 
458
389
  // Create or resume thread
459
- const backendModel = resolveBackendModel(model, config);
460
390
  const threadOptions = {
461
391
  model: backendModel,
462
392
  workingDirectory,
@@ -467,7 +397,7 @@ export const codexProvider = {
467
397
 
468
398
  if (reasoning_effort) {
469
399
  const supportedEfforts =
470
- getBackendModelConfig(backendModel)?.supportedEfforts || EFFORT_LADDER;
400
+ SUPPORTED_MODELS[backendModel]?.supportedEfforts || EFFORT_LADDER;
471
401
  const mappedEffort = clampReasoningEffort(reasoning_effort, supportedEfforts);
472
402
  threadOptions.modelReasoningEffort = mappedEffort;
473
403
  if (mappedEffort !== reasoning_effort) {
@@ -575,15 +505,14 @@ export const codexProvider = {
575
505
  }
576
506
  },
577
507
 
508
+ defaultModel: DEFAULT_MODEL,
509
+
578
510
  /**
579
- * Validate Codex configuration
580
- * Codex uses ChatGPT authentication or CODEX_API_KEY (NOT OPENAI_API_KEY)
511
+ * Validate Codex configuration: the SDK is installed and a credential is
512
+ * present (ChatGPT login file or CODEX_API_KEY — NOT OPENAI_API_KEY).
581
513
  */
582
- validateConfig(_config) {
583
- // Codex can work with either ChatGPT login or API key
584
- // Since we can't reliably check ChatGPT login status, we'll be permissive
585
- // and let the SDK handle authentication errors
586
- return isCodexAvailable();
514
+ validateConfig(config) {
515
+ return isPackageResolvable(CODEX_SDK_PACKAGE) && hasCodexCredentials(config);
587
516
  },
588
517
 
589
518
  /**
@@ -604,6 +533,6 @@ export const codexProvider = {
604
533
  * Get model configuration for specific model
605
534
  */
606
535
  getModelConfig(modelName) {
607
- return findModelConfig(modelName);
536
+ return findCatalogEntry(SUPPORTED_MODELS, modelName);
608
537
  },
609
538
  };
@@ -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,