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:
Agent_J
2026-04-06 16:48:58 +05:30
committed by GitHub
parent 8ece290087
commit ff7d49990d
20 changed files with 1150 additions and 166 deletions

View File

@@ -37,6 +37,7 @@ export type ProviderPresetDefaults = Omit<ProviderProfileInput, 'provider'> & {
const DEFAULT_OLLAMA_BASE_URL = 'http://localhost:11434/v1'
const DEFAULT_OLLAMA_MODEL = 'llama3.1:8b'
const PROFILE_ENV_APPLIED_FLAG = 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED'
const PROFILE_ENV_APPLIED_ID = 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID'
function trimValue(value: string | undefined): string {
return value?.trim() ?? ''
@@ -264,6 +265,23 @@ function hasProviderSelectionFlags(
)
}
function hasConflictingProviderFlagsForProfile(
processEnv: NodeJS.ProcessEnv,
profile: ProviderProfile,
): boolean {
if (profile.provider === 'anthropic') {
return hasProviderSelectionFlags(processEnv)
}
return (
processEnv.CLAUDE_CODE_USE_GEMINI !== undefined ||
processEnv.CLAUDE_CODE_USE_GITHUB !== undefined ||
processEnv.CLAUDE_CODE_USE_BEDROCK !== undefined ||
processEnv.CLAUDE_CODE_USE_VERTEX !== undefined ||
processEnv.CLAUDE_CODE_USE_FOUNDRY !== undefined
)
}
function sameOptionalEnvValue(
left: string | undefined,
right: string | undefined,
@@ -284,6 +302,10 @@ function isProcessEnvAlignedWithProfile(
return false
}
if (trimOrUndefined(processEnv[PROFILE_ENV_APPLIED_ID]) !== profile.id) {
return false
}
if (profile.provider === 'anthropic') {
return (
!hasProviderSelectionFlags(processEnv) &&
@@ -339,11 +361,13 @@ export function clearProviderProfileEnvFromProcessEnv(
delete processEnv.ANTHROPIC_MODEL
delete processEnv.ANTHROPIC_API_KEY
delete processEnv[PROFILE_ENV_APPLIED_FLAG]
delete processEnv[PROFILE_ENV_APPLIED_ID]
}
export function applyProviderProfileToProcessEnv(profile: ProviderProfile): void {
clearProviderProfileEnvFromProcessEnv()
process.env[PROFILE_ENV_APPLIED_FLAG] = '1'
process.env[PROFILE_ENV_APPLIED_ID] = profile.id
process.env.ANTHROPIC_MODEL = profile.model
if (profile.provider === 'anthropic') {
@@ -386,12 +410,24 @@ export function applyActiveProviderProfileFromConfig(
return undefined
}
const isCurrentEnvProfileManaged =
processEnv[PROFILE_ENV_APPLIED_FLAG] === '1' &&
trimOrUndefined(processEnv[PROFILE_ENV_APPLIED_ID]) === activeProfile.id
if (!options?.force && hasProviderSelectionFlags(processEnv)) {
// Respect explicit startup provider intent. Re-apply only when the
// current process env is already profile-managed and aligned.
if (!isProcessEnvAlignedWithProfile(processEnv, activeProfile)) {
// Respect explicit startup provider intent. Auto-heal only when this
// exact active profile previously applied the current env.
if (!isCurrentEnvProfileManaged) {
return undefined
}
if (hasConflictingProviderFlagsForProfile(processEnv, activeProfile)) {
return undefined
}
if (isProcessEnvAlignedWithProfile(processEnv, activeProfile)) {
return activeProfile
}
}
applyProviderProfileToProcessEnv(activeProfile)
@@ -496,6 +532,61 @@ export function updateProviderProfile(
return updatedProfile
}
export function persistActiveProviderProfileModel(
model: string,
): ProviderProfile | null {
const nextModel = trimOrUndefined(model)
if (!nextModel) {
return null
}
const activeProfile = getActiveProviderProfile()
if (!activeProfile) {
return null
}
saveGlobalConfig(current => {
const currentProfiles = getProviderProfiles(current)
const profileIndex = currentProfiles.findIndex(
profile => profile.id === activeProfile.id,
)
if (profileIndex < 0) {
return current
}
const currentProfile = currentProfiles[profileIndex]
if (currentProfile.model === nextModel) {
return current
}
const nextProfiles = [...currentProfiles]
nextProfiles[profileIndex] = {
...currentProfile,
model: nextModel,
}
return {
...current,
providerProfiles: nextProfiles,
}
})
const resolvedProfile = getActiveProviderProfile()
if (!resolvedProfile || resolvedProfile.id !== activeProfile.id) {
return null
}
if (
process.env[PROFILE_ENV_APPLIED_FLAG] === '1' &&
trimOrUndefined(process.env[PROFILE_ENV_APPLIED_ID]) === resolvedProfile.id
) {
applyProviderProfileToProcessEnv(resolvedProfile)
}
return resolvedProfile
}
export function setActiveProviderProfile(
profileId: string,
): ProviderProfile | null {