feat: GitHub provider lifecycle and onboarding hardening (#351)
* feat: improve GitHub provider onboarding and lifecycle * fix: address copilot review in provider manager * fix: address follow-up copilot review comments * test: resolve rebase conflict in provider profiles suite * fix: clear stale github hydrated marker * fix: harden github onboarding auth precedence * fix: remove merge markers from provider tests * fix: resolve latest copilot onboarding comments --------- Co-authored-by: KRATOS <84986124+gnanam1990@users.noreply.github.com>
This commit is contained in:
@@ -80,7 +80,9 @@ export function getUserSpecifiedModelSetting(): ModelSetting | undefined {
|
||||
const provider = getAPIProvider()
|
||||
specifiedModel =
|
||||
(provider === 'gemini' ? process.env.GEMINI_MODEL : undefined) ||
|
||||
(provider === 'openai' || provider === 'gemini' ? process.env.OPENAI_MODEL : undefined) ||
|
||||
(provider === 'openai' || provider === 'gemini' || provider === 'github'
|
||||
? process.env.OPENAI_MODEL
|
||||
: undefined) ||
|
||||
(provider === 'firstParty' ? process.env.ANTHROPIC_MODEL : undefined) ||
|
||||
settings.model ||
|
||||
undefined
|
||||
@@ -237,6 +239,10 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
|
||||
if (getAPIProvider() === 'openai') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-4o'
|
||||
}
|
||||
// GitHub provider: always use the configured GitHub model
|
||||
if (getAPIProvider() === 'github') {
|
||||
return process.env.OPENAI_MODEL || 'github:copilot'
|
||||
}
|
||||
// Codex provider: always use the configured Codex model (default gpt-5.4)
|
||||
if (getAPIProvider() === 'codex') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-5.4'
|
||||
|
||||
46
src/utils/model/modelOptions.github.test.ts
Normal file
46
src/utils/model/modelOptions.github.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { afterEach, expect, test } from 'bun:test'
|
||||
|
||||
import { getModelOptions } from './modelOptions.js'
|
||||
|
||||
const originalEnv = {
|
||||
CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB,
|
||||
CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,
|
||||
CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,
|
||||
CLAUDE_CODE_USE_BEDROCK: process.env.CLAUDE_CODE_USE_BEDROCK,
|
||||
CLAUDE_CODE_USE_VERTEX: process.env.CLAUDE_CODE_USE_VERTEX,
|
||||
CLAUDE_CODE_USE_FOUNDRY: process.env.CLAUDE_CODE_USE_FOUNDRY,
|
||||
OPENAI_MODEL: process.env.OPENAI_MODEL,
|
||||
OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,
|
||||
ANTHROPIC_CUSTOM_MODEL_OPTION: process.env.ANTHROPIC_CUSTOM_MODEL_OPTION,
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
process.env.CLAUDE_CODE_USE_GITHUB = originalEnv.CLAUDE_CODE_USE_GITHUB
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = originalEnv.CLAUDE_CODE_USE_OPENAI
|
||||
process.env.CLAUDE_CODE_USE_GEMINI = originalEnv.CLAUDE_CODE_USE_GEMINI
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = originalEnv.CLAUDE_CODE_USE_BEDROCK
|
||||
process.env.CLAUDE_CODE_USE_VERTEX = originalEnv.CLAUDE_CODE_USE_VERTEX
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY = originalEnv.CLAUDE_CODE_USE_FOUNDRY
|
||||
process.env.OPENAI_MODEL = originalEnv.OPENAI_MODEL
|
||||
process.env.OPENAI_BASE_URL = originalEnv.OPENAI_BASE_URL
|
||||
process.env.ANTHROPIC_CUSTOM_MODEL_OPTION =
|
||||
originalEnv.ANTHROPIC_CUSTOM_MODEL_OPTION
|
||||
})
|
||||
|
||||
test('GitHub provider exposes only default + GitHub model in /model options', () => {
|
||||
process.env.CLAUDE_CODE_USE_GITHUB = '1'
|
||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
||||
delete process.env.CLAUDE_CODE_USE_VERTEX
|
||||
delete process.env.CLAUDE_CODE_USE_FOUNDRY
|
||||
|
||||
process.env.OPENAI_MODEL = 'github:copilot'
|
||||
delete process.env.ANTHROPIC_CUSTOM_MODEL_OPTION
|
||||
|
||||
const options = getModelOptions(false)
|
||||
const nonDefault = options.filter(option => option.value !== null)
|
||||
|
||||
expect(nonDefault.length).toBe(1)
|
||||
expect(nonDefault[0]?.value).toBe('github:copilot')
|
||||
})
|
||||
@@ -352,6 +352,18 @@ 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.
|
||||
function getModelOptionsBase(fastMode = false): ModelOption[] {
|
||||
if (getAPIProvider() === 'github') {
|
||||
const githubModel = process.env.OPENAI_MODEL?.trim() || 'github:copilot'
|
||||
return [
|
||||
getDefaultOptionForUser(fastMode),
|
||||
{
|
||||
value: githubModel,
|
||||
label: githubModel,
|
||||
description: 'GitHub Models default',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// When using Ollama, show models from the Ollama server instead of Claude models
|
||||
if (getAPIProvider() === 'openai' && isOllamaProvider()) {
|
||||
const defaultOption = getDefaultOptionForUser(fastMode)
|
||||
@@ -579,6 +591,10 @@ function getKnownModelOption(model: string): ModelOption | null {
|
||||
}
|
||||
|
||||
export function getModelOptions(fastMode = false): ModelOption[] {
|
||||
if (getAPIProvider() === 'github') {
|
||||
return filterModelOptionsByAllowlist(getModelOptionsBase(fastMode))
|
||||
}
|
||||
|
||||
const options = getModelOptionsBase(fastMode)
|
||||
|
||||
// Add the custom model from the ANTHROPIC_CUSTOM_MODEL_OPTION env var
|
||||
|
||||
54
src/utils/model/modelStrings.github.test.ts
Normal file
54
src/utils/model/modelStrings.github.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { afterEach, expect, test } from 'bun:test'
|
||||
|
||||
import { resetModelStringsForTestingOnly } from '../../bootstrap/state.js'
|
||||
import { parseUserSpecifiedModel } from './model.js'
|
||||
import { getModelStrings } from './modelStrings.js'
|
||||
|
||||
const originalEnv = {
|
||||
CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB,
|
||||
CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,
|
||||
CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,
|
||||
CLAUDE_CODE_USE_BEDROCK: process.env.CLAUDE_CODE_USE_BEDROCK,
|
||||
CLAUDE_CODE_USE_VERTEX: process.env.CLAUDE_CODE_USE_VERTEX,
|
||||
CLAUDE_CODE_USE_FOUNDRY: process.env.CLAUDE_CODE_USE_FOUNDRY,
|
||||
}
|
||||
|
||||
function clearProviderFlags(): void {
|
||||
delete process.env.CLAUDE_CODE_USE_GITHUB
|
||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||
delete process.env.CLAUDE_CODE_USE_BEDROCK
|
||||
delete process.env.CLAUDE_CODE_USE_VERTEX
|
||||
delete process.env.CLAUDE_CODE_USE_FOUNDRY
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
process.env.CLAUDE_CODE_USE_GITHUB = originalEnv.CLAUDE_CODE_USE_GITHUB
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = originalEnv.CLAUDE_CODE_USE_OPENAI
|
||||
process.env.CLAUDE_CODE_USE_GEMINI = originalEnv.CLAUDE_CODE_USE_GEMINI
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = originalEnv.CLAUDE_CODE_USE_BEDROCK
|
||||
process.env.CLAUDE_CODE_USE_VERTEX = originalEnv.CLAUDE_CODE_USE_VERTEX
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY = originalEnv.CLAUDE_CODE_USE_FOUNDRY
|
||||
resetModelStringsForTestingOnly()
|
||||
})
|
||||
|
||||
test('GitHub provider model strings are concrete IDs', () => {
|
||||
clearProviderFlags()
|
||||
process.env.CLAUDE_CODE_USE_GITHUB = '1'
|
||||
|
||||
const modelStrings = getModelStrings()
|
||||
|
||||
for (const value of Object.values(modelStrings)) {
|
||||
expect(typeof value).toBe('string')
|
||||
expect(value.trim().length).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
test('GitHub provider model strings are safe to parse', () => {
|
||||
clearProviderFlags()
|
||||
process.env.CLAUDE_CODE_USE_GITHUB = '1'
|
||||
|
||||
const modelStrings = getModelStrings()
|
||||
|
||||
expect(() => parseUserSpecifiedModel(modelStrings.sonnet46 as any)).not.toThrow()
|
||||
})
|
||||
@@ -25,7 +25,7 @@ const MODEL_KEYS = Object.keys(ALL_MODEL_CONFIGS) as ModelKey[]
|
||||
function getBuiltinModelStrings(provider: APIProvider): ModelStrings {
|
||||
// Codex piggybacks on the OpenAI provider transport for Anthropic tier aliases.
|
||||
// Reuse OpenAI mappings so model string lookups never return undefined.
|
||||
const providerKey = provider === 'codex' ? 'openai' : provider
|
||||
const providerKey = provider === 'codex' || provider === 'github' ? 'openai' : provider
|
||||
const out = {} as ModelStrings
|
||||
for (const key of MODEL_KEYS) {
|
||||
out[key] = ALL_MODEL_CONFIGS[key][providerKey]
|
||||
|
||||
Reference in New Issue
Block a user