From 79d7980703451807d1dbaaca9770d63eb36cde54 Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 18:34:10 +0530 Subject: [PATCH 1/7] feat: add support for dynamic discovery of GitHub models --- src/integrations/descriptors.ts | 2 +- src/integrations/discoveryService.ts | 74 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/integrations/descriptors.ts b/src/integrations/descriptors.ts index eacd5092f..0011c7b20 100644 --- a/src/integrations/descriptors.ts +++ b/src/integrations/descriptors.ts @@ -96,7 +96,7 @@ export interface ModelCatalogConfig { models?: ModelCatalogEntry[] } -export type ModelDiscoveryKind = 'openai-compatible' | 'ollama' | 'custom' +export type ModelDiscoveryKind = 'openai-compatible' | 'ollama' | 'custom' | 'github-models' export interface ModelDiscoveryConfig { kind: ModelDiscoveryKind diff --git a/src/integrations/discoveryService.ts b/src/integrations/discoveryService.ts index 7fa6cfd66..7fb063eb1 100644 --- a/src/integrations/discoveryService.ts +++ b/src/integrations/discoveryService.ts @@ -244,6 +244,80 @@ async function runDiscovery( return models?.map(model => toDiscoveredModelEntry(model)) ?? null } + case 'github-models': { + const apiKey = getRouteDiscoveryApiKey(routeId, options) + const headers: Record = { + Authorization: `Bearer ${apiKey}`, + 'Editor-Version': 'vscode/1.96.0', + 'Editor-Plugin-Version': 'copilot/1.250.0', + 'User-Agent': 'GitHubCopilot/1.250.0', + Accept: 'application/json', + } + + function formatModelLabel(rawId: string): string { + const base = rawId.replace(/-\d{4}-\d{2}-\d{2}$/, '').replace(/-\d{4}$/, '') + if (base.startsWith('claude-')) { + const parts = base.replace('claude-', '').split('-') + if (parts.length >= 2) return `Claude ${parts[0].charAt(0).toUpperCase() + parts[0].slice(1)} ${parts.slice(1).join('.')}` + } + if (base.startsWith('gpt-')) { + const rest = base.slice(4) + const named = rest + .replace(/^4o-mini/, '4o Mini') + .replace(/^4o/, '4o') + .replace(/^4-o/, '4o') + .replace(/^4\.1/, '4.1') + .replace(/^4/, '4') + .replace(/^3\.5-turbo/, '3.5 Turbo') + .replace(/^5\.5-mini/, '5.5 Mini') + .replace(/^5\.5/, '5.5') + .replace(/^5\.4-mini/, '5.4 Mini') + .replace(/^5\.4/, '5.4') + .replace(/^5\.3-codex/, '5.3 Codex') + .replace(/^5\.2-codex/, '5.2 Codex') + .replace(/^5\.2/, '5.2') + .replace(/^5\.1-codex/, '5.1 Codex') + .replace(/^5-mini/, '5 Mini') + .replace(/^5/, '5') + return `GPT-${named}` + } + if (base.startsWith('gemini-')) return base.replace('gemini-', 'Gemini ') + if (base.startsWith('grok-')) { + const v = base.replace('grok-', '') + if (v === 'code-fast-1') return 'Grok Code Fast 1' + return `Grok ${v}` + } + return rawId + } + + try { + const response = await fetch('https://api.githubcopilot.com/models', { + headers, + signal: AbortSignal.timeout(5000), + }) + if (!response.ok) return null + + const body = (await response.json()) as { data?: Array<{ id?: string }> } + if (!body.data || !Array.isArray(body.data)) return null + + const seen = new Set() + const models = body.data + .map(item => { + const id = item.id?.trim() + if (!id) return null + if (id.startsWith('accounts/') || id.startsWith('oswe-') || id.includes('text-embedding')) return null + if (seen.has(id)) return null + seen.add(id) + return { id, apiName: id, label: formatModelLabel(id) } as ModelCatalogEntry + }) + .filter((m): m is ModelCatalogEntry => m !== null) + + return models.length > 0 ? models : null + } catch { + return null + } + } + case 'custom': return null } From 9f807e2d1a3a1a236909d7cb804e819996aec7cf Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 18:35:29 +0530 Subject: [PATCH 2/7] feat: implement dynamic discovery for GitHub models and remove hardcoded Copilot model options --- src/integrations/gateways/github.ts | 10 +- src/utils/model/copilotModels.ts | 383 ---------------------------- src/utils/model/modelOptions.ts | 12 +- 3 files changed, 6 insertions(+), 399 deletions(-) delete mode 100644 src/utils/model/copilotModels.ts diff --git a/src/integrations/gateways/github.ts b/src/integrations/gateways/github.ts index e40a06388..f358e070e 100644 --- a/src/integrations/gateways/github.ts +++ b/src/integrations/gateways/github.ts @@ -43,11 +43,11 @@ export default defineGateway({ 'GitHub Copilot token is invalid or corrupted.\nRun /onboard-github to sign in again with your GitHub account.', }, catalog: { - source: 'static', - models: [ - { id: 'github-claude-sonnet', apiName: 'claude-sonnet-4-6', label: 'Claude Sonnet (GitHub)', modelDescriptorId: 'claude-sonnet-4-6' }, - { id: 'github-gpt-4o', apiName: 'gpt-4o', label: 'GPT-4o (GitHub)', modelDescriptorId: 'gpt-4o' }, - ], + source: 'dynamic', + discovery: { kind: 'github-models', requiresAuth: true }, + discoveryCacheTtl: '1h', + discoveryRefreshMode: 'on-open', + allowManualRefresh: true, }, usage: { supported: false }, }) diff --git a/src/utils/model/copilotModels.ts b/src/utils/model/copilotModels.ts deleted file mode 100644 index 51f039c54..000000000 --- a/src/utils/model/copilotModels.ts +++ /dev/null @@ -1,383 +0,0 @@ -/** - * Hardcoded Copilot model registry from models.dev/api.json - * These are the 19 models available through GitHub Copilot. - */ - -export type CopilotModel = { - id: string - name: string - family: string - attachment: boolean - reasoning: boolean - tool_call: boolean - temperature: boolean - knowledge: string - release_date: string - last_updated: string - modalities: { - input: string[] - output: string[] - } - open_weights: boolean - cost: { - input: number - output: number - cache_read?: number - } - limit: { - context: number - input?: number - output: number - } -} - -export const COPILOT_MODELS: Record = { - 'gpt-5.5': { - id: 'gpt-5.5', - name: 'GPT-5.5', - family: 'gpt', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.5-mini': { - id: 'gpt-5.5-mini', - name: 'GPT-5.5 mini', - family: 'gpt-mini', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.4': { - id: 'gpt-5.4', - name: 'GPT-5.4', - family: 'gpt', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.4-mini': { - id: 'gpt-5.4-mini', - name: 'GPT-5.4 mini', - family: 'gpt-mini', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.3-codex': { - id: 'gpt-5.3-codex', - name: 'GPT-5.3-Codex', - family: 'gpt-codex', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.2-codex': { - id: 'gpt-5.2-codex', - name: 'GPT-5.2-Codex', - family: 'gpt-codex', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.2': { - id: 'gpt-5.2', - name: 'GPT-5.2', - family: 'gpt', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 264000, output: 32768 }, - }, - 'gpt-5.1-codex': { - id: 'gpt-5.1-codex', - name: 'GPT-5.1-Codex', - family: 'gpt-codex', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.1-codex-max': { - id: 'gpt-5.1-codex-max', - name: 'GPT-5.1-Codex-max', - family: 'gpt-codex', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-5.1-codex-mini': { - id: 'gpt-5.1-codex-mini', - name: 'GPT-5.1-Codex-mini', - family: 'gpt-codex', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 400000, output: 32768 }, - }, - 'gpt-4o': { - id: 'gpt-4o', - name: 'GPT-4o', - family: 'gpt', - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: '2023-10', - release_date: '2024-05-01', - last_updated: '2024-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 16384 }, - }, - 'gpt-4.1': { - id: 'gpt-4.1', - name: 'GPT-4.1', - family: 'gpt', - attachment: false, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: '2024-06', - release_date: '2024-06-01', - last_updated: '2024-06-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - 'claude-opus-4.6': { - id: 'claude-opus-4.6', - name: 'Claude Opus 4.6', - family: 'claude-opus', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 144000, output: 32768 }, - }, - 'claude-opus-4.5': { - id: 'claude-opus-4.5', - name: 'Claude Opus 4.5', - family: 'claude-opus', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 160000, output: 32768 }, - }, - 'claude-sonnet-4.6': { - id: 'claude-sonnet-4.6', - name: 'Claude Sonnet 4.6', - family: 'claude-sonnet', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 200000, output: 32768 }, - }, - 'claude-sonnet-4.5': { - id: 'claude-sonnet-4.5', - name: 'Claude Sonnet 4.5', - family: 'claude-sonnet', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 144000, output: 32768 }, - }, - 'claude-haiku-4.5': { - id: 'claude-haiku-4.5', - name: 'Claude Haiku 4.5', - family: 'claude-haiku', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 144000, output: 32768 }, - }, - 'gemini-3.1-pro-preview': { - id: 'gemini-3.1-pro-preview', - name: 'Gemini 3.1 Pro Preview', - family: 'gemini-pro', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image', 'audio'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - 'gemini-3-flash-preview': { - id: 'gemini-3-flash-preview', - name: 'Gemini 3 Flash', - family: 'gemini-flash', - attachment: true, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - 'gemini-2.5-pro': { - id: 'gemini-2.5-pro', - name: 'Gemini 2.5 Pro', - family: 'gemini-pro', - attachment: true, - reasoning: false, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text', 'image'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, - 'grok-code-fast-1': { - id: 'grok-code-fast-1', - name: 'Grok Code Fast 1', - family: 'grok', - attachment: false, - reasoning: true, - tool_call: true, - temperature: true, - knowledge: '2025-05', - release_date: '2025-05-01', - last_updated: '2025-05-01', - modalities: { input: ['text'], output: ['text'] }, - open_weights: false, - cost: { input: 0, output: 0 }, - limit: { context: 128000, output: 32768 }, - }, -} - -export function getCopilotModelIds(): string[] { - return Object.keys(COPILOT_MODELS) -} - -export function getCopilotModel(id: string): CopilotModel | undefined { - return COPILOT_MODELS[id] -} - -export function getAllCopilotModels(): CopilotModel[] { - return Object.values(COPILOT_MODELS) -} diff --git a/src/utils/model/modelOptions.ts b/src/utils/model/modelOptions.ts index a4270494b..9b1a9c3f9 100644 --- a/src/utils/model/modelOptions.ts +++ b/src/utils/model/modelOptions.ts @@ -388,19 +388,9 @@ function getCodexModelOptions(): ModelOption[] { // @[MODEL LAUNCH]: Update the model picker lists below to include/reorder options for the new model. // Each user tier (ant, Max/Team Premium, Pro/Team Standard/Enterprise, PAYG 1P, PAYG 3P) has its own list. -import { getAllCopilotModels } from './copilotModels.js' - -function getCopilotModelOptions(): ModelOption[] { - return getAllCopilotModels().map(m => ({ - value: m.id, - label: m.name, - description: `${m.family}${m.reasoning ? ' · Reasoning' : ''}${m.tool_call ? ' · Tool call' : ''} · ${Math.round(m.limit.context / 1000)}K context`, - })) -} - function getModelOptionsBase(fastMode = false): ModelOption[] { if (getAPIProvider() === 'github') { - return [getDefaultOptionForUser(fastMode), ...getCopilotModelOptions()] + return [getDefaultOptionForUser(fastMode)] } // When using Ollama, show models from the Ollama server instead of Claude models From 0dbbf1a56badfb8f722311e61c28caf3f45a2497 Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 18:37:47 +0530 Subject: [PATCH 3/7] feat: update model detection to use initial settings and patch the deprecated import to new import --- src/components/StartupScreen.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/StartupScreen.ts b/src/components/StartupScreen.ts index ccf8ee437..13f0daf78 100644 --- a/src/components/StartupScreen.ts +++ b/src/components/StartupScreen.ts @@ -11,8 +11,8 @@ import { resolveRouteIdFromBaseUrl, } from '../integrations/routeMetadata.js' import { getLocalOpenAICompatibleProviderLabel } from '../utils/providerDiscovery.js' -import { getSettings_DEPRECATED } from '../utils/settings/settings.js' -import { parseUserSpecifiedModel } from '../utils/model/model.js' +import { getInitialSettings } from '../utils/settings/settings.js' +import { getPublicModelDisplayName, parseUserSpecifiedModel } from '../utils/model/model.js' import { DEFAULT_GEMINI_MODEL } from '../utils/providerProfile.js' import { getGlobalConfig } from '../utils/config.js' import { ANSI_DIM, ANSI_RESET, ansiRgb } from '../utils/terminalAnsi.js' @@ -93,10 +93,12 @@ export function detectProvider(modelOverride?: string): { name: string; model: s } if (useGithub) { - const model = modelOverride || process.env.OPENAI_MODEL || 'github:copilot' + const settings = getInitialSettings() || {} + const model = modelOverride || settings.model || process.env.OPENAI_MODEL || 'gpt-4o' + const displayName = model === 'github:copilot' ? 'GPT-4o' : (getPublicModelDisplayName(model) || model) const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.githubcopilot.com' - return { name: 'GitHub Copilot', model, baseUrl, isLocal: false } + return { name: 'GitHub Copilot', model: displayName, baseUrl, isLocal: false } } if (useOpenAI) { @@ -156,7 +158,7 @@ export function detectProvider(modelOverride?: string): { name: string; model: s } // Default: Anthropic - check settings.model first, then env vars - const settings = getSettings_DEPRECATED() || {} + const settings = getInitialSettings() || {} const modelSetting = modelOverride || process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || settings.model || 'claude-sonnet-4-6' const resolvedModel = parseUserSpecifiedModel(modelSetting) const baseUrl = process.env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com' From bebeb7f0d986ec3fc9dde9c36cf7b08571bc95ad Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 18:38:44 +0530 Subject: [PATCH 4/7] feat: update default model from 'github:copilot' to 'gpt-4o' across multiple components for startup screen --- src/commands/model/model.tsx | 2 +- src/commands/onboard-github/onboard-github.tsx | 4 ++-- src/commands/provider/provider.tsx | 2 +- src/components/ProviderManager.tsx | 2 +- src/services/api/providerConfig.ts | 2 +- src/utils/model/model.ts | 15 ++++++++++----- src/utils/providerProfile.ts | 4 ++-- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/commands/model/model.tsx b/src/commands/model/model.tsx index 898910a46..e8d55b8de 100644 --- a/src/commands/model/model.tsx +++ b/src/commands/model/model.tsx @@ -353,7 +353,7 @@ function ModelPickerWrapper({ discoveryContext: ModelDiscoveryContext | null onDone: (result?: string, options?: { display?: CommandResultDisplay }) => void }) { - const mainLoopModel = useAppState((s: AppState) => s.mainLoopModel) + const mainLoopModel = useAppState((s: AppState) => s.mainLoopModel) ?? getDefaultMainLoopModelSetting() const mainLoopModelForSession = useAppState( (s: AppState) => s.mainLoopModelForSession, ) diff --git a/src/commands/onboard-github/onboard-github.tsx b/src/commands/onboard-github/onboard-github.tsx index 1f7937a46..efcc51082 100644 --- a/src/commands/onboard-github/onboard-github.tsx +++ b/src/commands/onboard-github/onboard-github.tsx @@ -17,7 +17,7 @@ import { } from '../../utils/githubModelsCredentials.js' import { getSettingsForSource, updateSettingsForSource } from '../../utils/settings/settings.js' -const DEFAULT_MODEL = 'github:copilot' +const DEFAULT_MODEL = 'gpt-4o' const FORCE_RELOGIN_ARGS = new Set([ 'force', '--force', @@ -342,7 +342,7 @@ export const call: LocalJSXCommandCall = async (onDone, context, args) => { if (!activated.ok) { onDone( `GitHub token detected, but settings activation failed: ${activated.detail ?? 'unknown error'}. ` + - 'Set CLAUDE_CODE_USE_GITHUB=1 and OPENAI_MODEL=github:copilot in user settings manually.', + 'Set CLAUDE_CODE_USE_GITHUB=1 and OPENAI_MODEL=gpt-4o in user settings manually.', { display: 'system' }, ) return null diff --git a/src/commands/provider/provider.tsx b/src/commands/provider/provider.tsx index 3aa9ac646..f601d2b5f 100644 --- a/src/commands/provider/provider.tsx +++ b/src/commands/provider/provider.tsx @@ -326,7 +326,7 @@ export function buildCurrentProviderSummary(options?: { return { providerLabel: 'GitHub Models', modelLabel: getSafeDisplayValue( - processEnv.OPENAI_MODEL ?? 'github:copilot', + processEnv.OPENAI_MODEL ?? 'gpt-4o', secretSource, ), endpointLabel: getSafeDisplayValue( diff --git a/src/components/ProviderManager.tsx b/src/components/ProviderManager.tsx index 984ef7513..b6367cce4 100644 --- a/src/components/ProviderManager.tsx +++ b/src/components/ProviderManager.tsx @@ -195,7 +195,7 @@ const FORM_STEPS: Array<{ const GITHUB_PROVIDER_ID = '__github_models__' const GITHUB_PROVIDER_LABEL = 'GitHub Models' -const GITHUB_PROVIDER_DEFAULT_MODEL = 'github:copilot' +const GITHUB_PROVIDER_DEFAULT_MODEL = 'gpt-4o' const GITHUB_PROVIDER_DEFAULT_BASE_URL = 'https://models.github.ai/inference' const CODEX_OAUTH_PROVIDER_NAME = 'Codex OAuth' const CODEX_OAUTH_PROVIDER_MODEL = 'codexplan' diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index 1f4dfffa1..d5d396462 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -605,7 +605,7 @@ export function resolveProviderRequest(options?: { : process.env.OPENAI_MODEL?.trim()) || options?.fallbackModel?.trim() || (isGeminiMode ? DEFAULT_GEMINI_MODEL : undefined) || - (isGithubMode ? 'github:copilot' : 'codexplan') + (isGithubMode ? 'gpt-4o' : 'codexplan') const descriptor = parseModelDescriptor(requestedModel) const explicitBaseUrl = asEnvUrl(options?.baseUrl) diff --git a/src/utils/model/model.ts b/src/utils/model/model.ts index 630401b58..01059c022 100644 --- a/src/utils/model/model.ts +++ b/src/utils/model/model.ts @@ -60,7 +60,7 @@ export function getSmallFastModel(): ModelName { } // For GitHub Copilot provider if (getAPIProvider() === 'github') { - return process.env.OPENAI_MODEL || 'github:copilot' + return process.env.OPENAI_MODEL || 'gpt-4o' } // NVIDIA NIM — OPENAI_MODEL carries the user's active NIM model; use a // small Meta Llama variant as the conservative fallback. @@ -136,6 +136,11 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined { undefined } + // Normalize legacy GitHub Copilot alias to concrete model ID + if (specifiedModel === 'github:copilot') { + specifiedModel = 'gpt-4o' + } + // Ignore the user-specified model if it's not in the availableModels allowlist. if (specifiedModel && !isModelAllowed(specifiedModel)) { return undefined @@ -191,7 +196,7 @@ export function getDefaultOpusModel(): ModelName { } // GitHub Copilot provider if (getAPIProvider() === 'github') { - return process.env.OPENAI_MODEL || 'github:copilot' + return process.env.OPENAI_MODEL || 'gpt-4o' } // NVIDIA NIM if (getAPIProvider() === 'nvidia-nim') { @@ -237,7 +242,7 @@ export function getDefaultSonnetModel(): ModelName { } // GitHub Copilot provider if (getAPIProvider() === 'github') { - return process.env.OPENAI_MODEL || 'github:copilot' + return process.env.OPENAI_MODEL || 'gpt-4o' } // NVIDIA NIM if (getAPIProvider() === 'nvidia-nim') { @@ -277,7 +282,7 @@ export function getDefaultHaikuModel(): ModelName { } // GitHub Copilot provider if (getAPIProvider() === 'github') { - return process.env.OPENAI_MODEL || 'github:copilot' + return process.env.OPENAI_MODEL || 'gpt-4o' } // Gemini provider if (getAPIProvider() === 'gemini') { @@ -345,7 +350,7 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias { return ( normalizeModelSetting(settings.model) || normalizeModelSetting(process.env.OPENAI_MODEL) || - 'github:copilot' + 'gpt-4o' ) } // Gemini provider: always use the configured Gemini model diff --git a/src/utils/providerProfile.ts b/src/utils/providerProfile.ts index fc6fc785c..862efa326 100644 --- a/src/utils/providerProfile.ts +++ b/src/utils/providerProfile.ts @@ -315,7 +315,7 @@ export function buildGithubProfileEnv(options: { OPENAI_MODEL: normalizeProfileModel( sanitizeProviderConfigValue(options.model), - ) || 'github:copilot', + ) || 'gpt-4o', } const baseUrl = sanitizeProviderConfigValue(options.baseUrl) @@ -1036,7 +1036,7 @@ export async function buildLaunchEnv(options: { processEnv, compatibilityMode: 'github', profileEnv: buildGithubProfileEnv({ - model: shellOpenAIModel || persistedOpenAIModel || 'github:copilot', + model: shellOpenAIModel || persistedOpenAIModel || 'gpt-4o', baseUrl: shellOpenAIBaseUrl || persistedOpenAIBaseUrl, }), }) From 57d18f88acf081c550b21aaef9834e896f7e57e2 Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 18:51:32 +0530 Subject: [PATCH 5/7] feat: update discoveryRefreshMode to 'background-if-stale' for GitHub models --- src/integrations/gateways/github.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrations/gateways/github.ts b/src/integrations/gateways/github.ts index f358e070e..c28ff65ae 100644 --- a/src/integrations/gateways/github.ts +++ b/src/integrations/gateways/github.ts @@ -46,7 +46,7 @@ export default defineGateway({ source: 'dynamic', discovery: { kind: 'github-models', requiresAuth: true }, discoveryCacheTtl: '1h', - discoveryRefreshMode: 'on-open', + discoveryRefreshMode: 'background-if-stale', allowManualRefresh: true, }, usage: { supported: false }, From 568ff75cef75fff55b7836a363ba52a3ae74b849 Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 19:06:35 +0530 Subject: [PATCH 6/7] feat: add support for reasoning effort based on model compatibility in OpenAI shim --- src/services/api/openaiShim.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index d131aa409..a854f1e5f 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -84,6 +84,7 @@ import { getStreamStats, } from '../../utils/streamingOptimizer.js' import { stableStringify } from '../../utils/stableStringify.js' +import { modelSupportsEffort } from '../../utils/effort.js' type SecretValueSource = Partial<{ OPENAI_API_KEY: string @@ -1573,7 +1574,9 @@ class OpenAIShimMessages { // request carries a reasoning effort (set via /effort, model alias default, // or `?reasoning=` query on the model string). OpenAI, Codex, and // most OpenAI-compatible endpoints read it from this top-level field. - if (request.reasoning) { + // Only emit when the resolved model actually supports reasoning effort to + // avoid 400 errors from providers that reject the field for non-reasoning models. + if (request.reasoning && modelSupportsEffort(request.resolvedModel)) { body.reasoning_effort = request.reasoning.effort } // Convert max_tokens to max_completion_tokens for OpenAI API compatibility. From 3a7a0183db310a423499c6dbaed174942250531a Mon Sep 17 00:00:00 2001 From: Meet Patel Date: Wed, 13 May 2026 19:34:24 +0530 Subject: [PATCH 7/7] patch(github): normalize GitHub model alias and improve discovery timeout handling for test --- src/components/StartupScreen.test.ts | 2 +- src/integrations/discoveryService.ts | 6 +++++- src/services/api/openaiShim.ts | 5 +---- src/utils/model/model.openai-shim-providers.test.ts | 4 ++-- src/utils/model/modelOptions.github.test.ts | 6 ++---- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/StartupScreen.test.ts b/src/components/StartupScreen.test.ts index 3c4aef3b9..c37dbd43b 100644 --- a/src/components/StartupScreen.test.ts +++ b/src/components/StartupScreen.test.ts @@ -309,7 +309,7 @@ describe('detectProvider — modelOverride from --model flag', () => { test('modelOverride works for GitHub provider', () => { process.env.CLAUDE_CODE_USE_GITHUB = '1' const result = detectProvider('gpt-4o') - expect(result.model).toContain('gpt-4o') + expect(result.model).toContain('GPT-4o') }) test('undefined modelOverride preserves default behavior', () => { diff --git a/src/integrations/discoveryService.ts b/src/integrations/discoveryService.ts index 7fb063eb1..0fd58b7c1 100644 --- a/src/integrations/discoveryService.ts +++ b/src/integrations/discoveryService.ts @@ -290,10 +290,12 @@ async function runDiscovery( return rawId } + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 5000) try { const response = await fetch('https://api.githubcopilot.com/models', { headers, - signal: AbortSignal.timeout(5000), + signal: controller.signal, }) if (!response.ok) return null @@ -315,6 +317,8 @@ async function runDiscovery( return models.length > 0 ? models : null } catch { return null + } finally { + clearTimeout(timeoutId) } } diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index a854f1e5f..d131aa409 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -84,7 +84,6 @@ import { getStreamStats, } from '../../utils/streamingOptimizer.js' import { stableStringify } from '../../utils/stableStringify.js' -import { modelSupportsEffort } from '../../utils/effort.js' type SecretValueSource = Partial<{ OPENAI_API_KEY: string @@ -1574,9 +1573,7 @@ class OpenAIShimMessages { // request carries a reasoning effort (set via /effort, model alias default, // or `?reasoning=` query on the model string). OpenAI, Codex, and // most OpenAI-compatible endpoints read it from this top-level field. - // Only emit when the resolved model actually supports reasoning effort to - // avoid 400 errors from providers that reject the field for non-reasoning models. - if (request.reasoning && modelSupportsEffort(request.resolvedModel)) { + if (request.reasoning) { body.reasoning_effort = request.reasoning.effort } // Convert max_tokens to max_completion_tokens for OpenAI API compatibility. diff --git a/src/utils/model/model.openai-shim-providers.test.ts b/src/utils/model/model.openai-shim-providers.test.ts index d85242b85..4fa7f52ef 100644 --- a/src/utils/model/model.openai-shim-providers.test.ts +++ b/src/utils/model/model.openai-shim-providers.test.ts @@ -146,14 +146,14 @@ test('openai provider still reads OPENAI_MODEL (regression guard)', async () => expect(model).toBe('gpt-4o') }) -test('github provider still reads OPENAI_MODEL (regression guard)', async () => { +test('github provider normalizes legacy github:copilot alias to gpt-4o', async () => { saveGlobalConfig(current => ({ ...current, model: 'stale-default' })) process.env.CLAUDE_CODE_USE_GITHUB = '1' process.env.OPENAI_MODEL = 'github:copilot' const { getUserSpecifiedModelSetting } = await importFreshModelModule() const model = getUserSpecifiedModelSetting() - expect(model).toBe('github:copilot') + expect(model).toBe('gpt-4o') }) // --------------------------------------------------------------------------- diff --git a/src/utils/model/modelOptions.github.test.ts b/src/utils/model/modelOptions.github.test.ts index 2a5dd9bbf..f538dfa1e 100644 --- a/src/utils/model/modelOptions.github.test.ts +++ b/src/utils/model/modelOptions.github.test.ts @@ -83,7 +83,7 @@ afterEach(() => { resetModelStringsForTestingOnly() }) -test('GitHub provider exposes default + all Copilot models in /model options', async () => { +test('GitHub provider exposes default option in /model options (models loaded via discovery)', async () => { process.env.CLAUDE_CODE_USE_GITHUB = '1' delete process.env.CLAUDE_CODE_USE_OPENAI delete process.env.CLAUDE_CODE_USE_GEMINI @@ -100,7 +100,5 @@ test('GitHub provider exposes default + all Copilot models in /model options', a (option: { value: unknown }) => option.value !== null, ) - expect(nonDefault.length).toBeGreaterThan(1) - expect(nonDefault.some((o: { value: unknown }) => o.value === 'gpt-4o')).toBe(true) - expect(nonDefault.some((o: { value: unknown }) => o.value === 'gpt-5.3-codex')).toBe(true) + expect(nonDefault.length).toBe(0) })