refactor(provider-flags): derive flag-agnostic checks from the registry

Address jatmn review 4558518184 on PR #1614:

- Export hasAnyTruthyProviderSelectionFlag / hasConflictingProviderFlag from
  providerSelectionFlags.ts so the flag-agnostic call sites derive from the
  single source of truth instead of hand-enumerating CLAUDE_CODE_USE_* flags.
  Adopted by hasExplicitProviderSelection, hasNoExplicitNonOpenAICompatibleProvider,
  isUsing3PServices, and hasConflictingProviderFlagsForProfile (via an exhaustive
  activeProviderFlagForCompatibilityMode switch that preserves the
  github-enterprise -> CLAUDE_CODE_USE_GITHUB mapping and is compile-checked).
  Per-provider checks (hasConcreteProviderSelection, hasCompleteProviderSelection)
  stay as-is since they are not flag-agnostic.
- Give the two source-scanning parity tests an explicit 30s timeout so they no
  longer flake under parallel test load.
- Add unit tests for both helpers covering every registered flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gitdevine
2026-06-26 11:37:40 +02:00
co-authored by Claude Opus 4.8
parent ce14db1865
commit 2566cd7906
6 changed files with 137 additions and 46 deletions
+2 -10
View File
@@ -14,6 +14,7 @@ import {
} from './index.js'
import { hasUsableOpenAICredential } from '../services/api/credentialPool.js'
import { isEnvTruthy } from '../utils/envUtils.js'
import { hasAnyTruthyProviderSelectionFlag } from '../utils/providerSelectionFlags.js'
import { PROVIDER_SELECTION_FLAGS } from '../utils/providerSelectionFlags.js'
export type RouteDescriptor = GatewayDescriptor | VendorDescriptor
@@ -468,16 +469,7 @@ function hasConflictingOpenAIBaseUrlForRoute(
function hasNoExplicitNonOpenAICompatibleProvider(
processEnv: NodeJS.ProcessEnv,
): boolean {
return (
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_OPENAI) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_GITHUB) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI_VERTEX) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_MISTRAL) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_BEDROCK) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_VERTEX) &&
!isEnvTruthy(processEnv.CLAUDE_CODE_USE_FOUNDRY)
)
return !hasAnyTruthyProviderSelectionFlag(processEnv)
}
export function hasXaiEnvOnlyProviderIntent(
+3 -11
View File
@@ -54,6 +54,7 @@ import {
isEnvTruthy,
isRunningOnHomespace,
} from './envUtils.js'
import { hasAnyTruthyProviderSelectionFlag } from './providerSelectionFlags.js'
import { errorMessage } from './errors.js'
import { execSyncWithDefaults_DEPRECATED } from './execFileNoThrow.js'
import * as lockfile from './lockfile.js'
@@ -1751,18 +1752,9 @@ export function getSubscriptionName(): string {
}
}
/** Check if using third-party services (Bedrock or Vertex or Foundry or OpenAI-compatible or Gemini or GitHub Models) */
/** Check if any third-party provider-selection flag is active (derives from PROVIDER_SELECTION_FLAGS). */
export function isUsing3PServices(): boolean {
return !!(
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI_VERTEX) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_MISTRAL) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB)
)
return hasAnyTruthyProviderSelectionFlag()
}
/**
+2 -10
View File
@@ -45,6 +45,7 @@ export {
sanitizeProviderConfigValue,
} from './providerSecrets.js'
import { getClaudeConfigHomeDir, isEnvTruthy } from './envUtils.js'
import { hasAnyTruthyProviderSelectionFlag } from './providerSelectionFlags.js'
export const PROFILE_FILE_NAME = '.openclaude-profile.json'
export const DEFAULT_GEMINI_BASE_URL =
@@ -1279,16 +1280,7 @@ export function hasExplicitProviderSelection(
return true
}
return (
isEnvTruthy(processEnv.CLAUDE_CODE_USE_OPENAI) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_GITHUB) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI_VERTEX) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_MISTRAL) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_BEDROCK) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_VERTEX) ||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_FOUNDRY)
)
return hasAnyTruthyProviderSelectionFlag(processEnv)
}
function hasConcreteProviderSelection(
+35 -11
View File
@@ -13,7 +13,11 @@ import { isEnvTruthy } from './envUtils.js'
import { resolveGeminiVertexAuthMode } from './geminiAuth.js'
import type { ModelOption } from './model/modelOptions.js'
import { getPrimaryModel, parseModelList } from './providerModels.js'
import { PROVIDER_SELECTION_FLAGS } from './providerSelectionFlags.js'
import {
PROVIDER_SELECTION_FLAGS,
hasConflictingProviderFlag,
type ProviderSelectionFlag,
} from './providerSelectionFlags.js'
import {
buildCompatibilityProcessEnv,
createProfileFile,
@@ -547,19 +551,39 @@ function hasConflictingProviderFlagsForProfile(
return hasProviderSelectionFlags(processEnv)
}
return (
(compatibilityMode !== 'openai' && processEnv.CLAUDE_CODE_USE_OPENAI !== undefined) ||
(compatibilityMode !== 'gemini' && processEnv.CLAUDE_CODE_USE_GEMINI !== undefined) ||
(compatibilityMode !== 'gemini-vertex' && processEnv.CLAUDE_CODE_USE_GEMINI_VERTEX !== undefined) ||
(compatibilityMode !== 'mistral' && processEnv.CLAUDE_CODE_USE_MISTRAL !== undefined) ||
(!isGithubCompatibilityMode(compatibilityMode) &&
processEnv.CLAUDE_CODE_USE_GITHUB !== undefined) ||
(compatibilityMode !== 'bedrock' && processEnv.CLAUDE_CODE_USE_BEDROCK !== undefined) ||
(compatibilityMode !== 'vertex' && processEnv.CLAUDE_CODE_USE_VERTEX !== undefined) ||
processEnv.CLAUDE_CODE_USE_FOUNDRY !== undefined
// The active profile's own flag is the only non-conflicting selection. The
// mapping is exhaustive (TS errors if a new compatibility mode is added), and
// ProfileCompatibilityMode never resolves to 'foundry', so the Foundry flag is
// always treated as a conflict — matching the prior hand-enumerated check.
return hasConflictingProviderFlag(
processEnv,
activeProviderFlagForCompatibilityMode(compatibilityMode),
)
}
/** Maps a non-anthropic compatibility mode to the selection flag it owns. */
function activeProviderFlagForCompatibilityMode(
compatibilityMode: Exclude<ProfileCompatibilityMode, 'anthropic'>,
): ProviderSelectionFlag {
switch (compatibilityMode) {
case 'openai':
return 'CLAUDE_CODE_USE_OPENAI'
case 'gemini':
return 'CLAUDE_CODE_USE_GEMINI'
case 'gemini-vertex':
return 'CLAUDE_CODE_USE_GEMINI_VERTEX'
case 'mistral':
return 'CLAUDE_CODE_USE_MISTRAL'
case 'github':
case 'github-enterprise':
return 'CLAUDE_CODE_USE_GITHUB'
case 'bedrock':
return 'CLAUDE_CODE_USE_BEDROCK'
case 'vertex':
return 'CLAUDE_CODE_USE_VERTEX'
}
}
/**
* Whether the API client should route to the native Gemini Vertex client
* based on the saved active profile alone (i.e. CLAUDE_CODE_USE_GEMINI_VERTEX
+62 -2
View File
@@ -1,7 +1,11 @@
import { describe, expect, test } from 'bun:test'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { PROVIDER_SELECTION_FLAGS } from './providerSelectionFlags.js'
import {
PROVIDER_SELECTION_FLAGS,
hasAnyTruthyProviderSelectionFlag,
hasConflictingProviderFlag,
} from './providerSelectionFlags.js'
/**
* Feature toggles that share the CLAUDE_CODE_USE_ prefix but do not select
@@ -15,6 +19,9 @@ const NON_PROVIDER_USE_FLAGS = new Set([
])
describe('PROVIDER_SELECTION_FLAGS parity', () => {
// These two tests synchronously walk the whole src/ tree, which is I/O-bound
// and can exceed Bun's 5000 ms default under parallel test load (~8.5 s cold,
// ~0.2 s isolated). A generous explicit timeout keeps them from flaking in CI.
test('every CLAUDE_CODE_USE_* provider flag in src is registered', () => {
const srcRoot = join(import.meta.dir, '..')
const glob = new Bun.Glob('**/*.{ts,tsx}')
@@ -42,7 +49,7 @@ describe('PROVIDER_SELECTION_FLAGS parity', () => {
// startup-selection, env-propagation and cleanup paths all recognise it
// (this is how gemini-vertex drifted out of the reference lists).
expect(unregistered).toEqual([])
})
}, 30000)
test('registered flags are actually consumed outside the registry definition', () => {
const srcRoot = join(import.meta.dir, '..')
@@ -63,5 +70,58 @@ describe('PROVIDER_SELECTION_FLAGS parity', () => {
for (const flag of PROVIDER_SELECTION_FLAGS) {
expect(allContent).toContain(flag)
}
}, 30000)
})
describe('hasAnyTruthyProviderSelectionFlag', () => {
test('returns false for an empty env', () => {
expect(hasAnyTruthyProviderSelectionFlag({})).toBe(false)
})
// Every registered flag must be recognised by the shared helper — this is the
// guard that prevents a flag-agnostic call site (e.g. isUsing3PServices) from
// silently omitting a provider, as Gemini Vertex was before the registry.
test.each([...PROVIDER_SELECTION_FLAGS])(
'recognises %s when truthy',
flag => {
expect(hasAnyTruthyProviderSelectionFlag({ [flag]: '1' })).toBe(true)
},
)
test('ignores falsy values', () => {
expect(
hasAnyTruthyProviderSelectionFlag({ CLAUDE_CODE_USE_OPENAI: '0' }),
).toBe(false)
})
})
describe('hasConflictingProviderFlag', () => {
test('returns false for an empty env', () => {
expect(hasConflictingProviderFlag({})).toBe(false)
})
test('treats any present flag (even bare) as a conflict', () => {
expect(hasConflictingProviderFlag({ CLAUDE_CODE_USE_GEMINI: '' })).toBe(true)
})
test('does not flag the excepted active flag', () => {
expect(
hasConflictingProviderFlag(
{ CLAUDE_CODE_USE_GEMINI_VERTEX: '1' },
'CLAUDE_CODE_USE_GEMINI_VERTEX',
),
).toBe(false)
})
test('flags a different provider even when the active flag is set', () => {
expect(
hasConflictingProviderFlag(
{
CLAUDE_CODE_USE_GEMINI_VERTEX: '1',
CLAUDE_CODE_USE_OPENAI: '1',
},
'CLAUDE_CODE_USE_GEMINI_VERTEX',
),
).toBe(true)
})
})
+33 -2
View File
@@ -9,9 +9,12 @@
* registered here, which is how the Gemini Vertex provider drifted out of
* several reference lists in the first place.
*
* Dependency-free on purpose: both low-level (model/providers.ts) and
* high-level (providerProfiles.ts) modules import it without cycles.
* Leaf module on purpose: it only depends on envUtils (itself a leaf), so
* both low-level (model/providers.ts) and high-level (providerProfiles.ts)
* modules import it without cycles.
*/
import { isEnvTruthy } from './envUtils.js'
export const PROVIDER_SELECTION_FLAGS = [
'CLAUDE_CODE_USE_OPENAI',
'CLAUDE_CODE_USE_GEMINI',
@@ -24,3 +27,31 @@ export const PROVIDER_SELECTION_FLAGS = [
] as const
export type ProviderSelectionFlag = (typeof PROVIDER_SELECTION_FLAGS)[number]
/**
* True when any registered provider-selection flag is set to a truthy value.
* Derives from PROVIDER_SELECTION_FLAGS so flag-agnostic call sites (explicit
* selection, 3P-service detection, non-OpenAI-compatible gating) never drift
* out of sync — this is exactly the check that silently omitted Gemini Vertex.
*/
export function hasAnyTruthyProviderSelectionFlag(
processEnv: NodeJS.ProcessEnv = process.env,
): boolean {
return PROVIDER_SELECTION_FLAGS.some(flag => isEnvTruthy(processEnv[flag]))
}
/**
* True when any registered provider-selection flag other than `exceptFlag` is
* present in env. Uses presence (`!== undefined`), not truthiness, because even
* a bare `CLAUDE_CODE_USE_<provider>=` signals intent for the conflict check. Pass the
* flag of the active profile to detect a conflicting selection for any other
* provider.
*/
export function hasConflictingProviderFlag(
processEnv: NodeJS.ProcessEnv = process.env,
exceptFlag?: ProviderSelectionFlag,
): boolean {
return PROVIDER_SELECTION_FLAGS.some(
flag => flag !== exceptFlag && processEnv[flag] !== undefined,
)
}