Skip to content
2 changes: 1 addition & 1 deletion src/commands/model/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
4 changes: 2 additions & 2 deletions src/commands/onboard-github/onboard-github.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/commands/provider/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProviderManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion src/components/StartupScreen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@
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')

Check failure on line 312 in src/components/StartupScreen.test.ts

View workflow job for this annotation

GitHub Actions / smoke-and-tests

error: expect(received).toContain(expected)

Expected to contain: "GPT-4o" Received: "gpt-4o" at <anonymous> (/home/runner/work/openclaude/openclaude/src/components/StartupScreen.test.ts:312:26)
})

test('undefined modelOverride preserves default behavior', () => {
Expand Down
12 changes: 7 additions & 5 deletions src/components/StartupScreen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Comment on lines +14 to +15
import { DEFAULT_GEMINI_MODEL } from '../utils/providerProfile.js'
import { getGlobalConfig } from '../utils/config.js'
import { ANSI_DIM, ANSI_RESET, ansiRgb } from '../utils/terminalAnsi.js'
Expand Down Expand Up @@ -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 }
Comment on lines +96 to +101
Comment on lines +96 to +101
}

if (useOpenAI) {
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion src/integrations/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
78 changes: 78 additions & 0 deletions src/integrations/discoveryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,84 @@ async function runDiscovery(
return models?.map(model => toDiscoveredModelEntry(model)) ?? null
}

case 'github-models': {
const apiKey = getRouteDiscoveryApiKey(routeId, options)
const headers: Record<string, string> = {
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',
}
Comment on lines +248 to +255
Comment on lines +248 to +255

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
}
Comment on lines +263 to +291

const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
try {
const response = await fetch('https://api.githubcopilot.com/models', {
headers,
signal: controller.signal,
})
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<string>()
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
} finally {
clearTimeout(timeoutId)
}
Comment on lines +249 to +322
Comment on lines +295 to +322
}

case 'custom':
return null
}
Expand Down
10 changes: 5 additions & 5 deletions src/integrations/gateways/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: 'background-if-stale',
allowManualRefresh: true,
Comment on lines +46 to +50
},
usage: { supported: false },
})
2 changes: 1 addition & 1 deletion src/services/api/providerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading
Loading