mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-11-06 20:58:14 -04:00
fix(gemini-vertex): thread saved-profile-only routing through provider identity
The saved-profile-only Vertex route (active gemini-vertex profile, no CLAUDE_CODE_USE_GEMINI_VERTEX flag) was only honoured by the API client, so other env-flag-only "is this Vertex?" checks disagreed with it: - routeMetadata.ts: resolveActiveRouteIdFromEnv() now returns 'gemini-vertex' when activeProfileProvider is gemini-vertex and no provider env flag is set, so /model loads the Vertex catalog/discovery for the same session. - providerConfig.ts: getAdditionalModelOptionsCacheScope() scopes a saved gemini-vertex profile as third-party (null) instead of 'firstParty', so bootstrap/model-option traffic is not fetched through the Anthropic path. - provider.tsx: buildCurrentProviderSummary() reports the effective provider so a saved Vertex profile no longer shows "Anthropic"/api.anthropic.com. Tests cover each: saved-profile route resolution (and precedence when a flag is set), the cache-scope fallback, and the provider summary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
92701ee54f
commit
817d388209
@@ -621,6 +621,25 @@ test('buildCurrentProviderSummary redacts poisoned model and endpoint values', (
|
||||
expect(summary.endpointLabel).toBe('sk-...678')
|
||||
})
|
||||
|
||||
test('buildCurrentProviderSummary reports Gemini Vertex for a saved profile with no env flag', () => {
|
||||
// Saved-profile-only routing: no CLAUDE_CODE_USE_GEMINI_VERTEX, but the active
|
||||
// profile is gemini-vertex, so the summary must match the client's routing
|
||||
// instead of falling through to the Anthropic branch.
|
||||
const summary = buildCurrentProviderSummary({
|
||||
processEnv: { GEMINI_VERTEX_PROJECT: 'my-proj' } as NodeJS.ProcessEnv,
|
||||
persisted: {
|
||||
profile: 'gemini-vertex',
|
||||
env: { CLAUDE_CODE_USE_GEMINI_VERTEX: '1' },
|
||||
} as unknown as NonNullable<
|
||||
Parameters<typeof buildCurrentProviderSummary>[0]
|
||||
>['persisted'],
|
||||
})
|
||||
|
||||
expect(summary.providerLabel).toContain('Vertex')
|
||||
expect(summary.providerLabel).not.toBe('Anthropic')
|
||||
expect(summary.endpointLabel).toContain('/projects/my-proj/')
|
||||
})
|
||||
|
||||
test('buildCurrentProviderSummary labels generic local openai-compatible providers', () => {
|
||||
const summary = buildCurrentProviderSummary({
|
||||
processEnv: {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { probeRouteReadiness } from '../../integrations/discoveryService.js'
|
||||
import {
|
||||
getProviderPresetUiMetadata,
|
||||
getRouteLabel,
|
||||
resolveActiveRouteIdFromEnv,
|
||||
resolveRouteIdFromBaseUrl,
|
||||
} from '../../integrations/index.js'
|
||||
import {
|
||||
@@ -294,7 +295,17 @@ export function buildCurrentProviderSummary(options?: {
|
||||
const persisted = options?.persisted ?? loadProfileFile()
|
||||
const savedProfileLabel = persisted?.profile ?? 'none'
|
||||
|
||||
if (isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI_VERTEX)) {
|
||||
// Honour saved-profile-only Gemini Vertex routing (no env flag) so the summary
|
||||
// matches getAnthropicClient instead of falling through to the Anthropic
|
||||
// branch for a profile that will actually drive Vertex requests.
|
||||
const effectiveRouteId = resolveActiveRouteIdFromEnv(processEnv, {
|
||||
activeProfileProvider: persisted?.profile,
|
||||
})
|
||||
|
||||
if (
|
||||
isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI_VERTEX) ||
|
||||
effectiveRouteId === 'gemini-vertex'
|
||||
) {
|
||||
const vertexMetadata = getProviderPresetUiMetadata('gemini-vertex', processEnv)
|
||||
const location = getGeminiVertexLocation(processEnv)
|
||||
const project = getGeminiVertexProjectId(processEnv)
|
||||
|
||||
@@ -185,6 +185,23 @@ test('resolveActiveRouteIdFromEnv gives gemini-vertex precedence over gemini and
|
||||
).toBe('gemini-vertex')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv resolves a saved-profile-only gemini-vertex route with no env flag', () => {
|
||||
// Matches getAnthropicClient's saved-profile routing so /model loads the
|
||||
// Vertex catalog for the same session.
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({}, { activeProfileProvider: 'gemini-vertex' }),
|
||||
).toBe('gemini-vertex')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv ignores a saved gemini-vertex profile when another provider flag is set', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv(
|
||||
{ CLAUDE_CODE_USE_GEMINI: '1' },
|
||||
{ activeProfileProvider: 'gemini-vertex' },
|
||||
),
|
||||
).toBe('gemini')
|
||||
})
|
||||
|
||||
test('resolveActiveRouteIdFromEnv treats Xiaomi MiMo credential-only env as Xiaomi MiMo', () => {
|
||||
expect(
|
||||
resolveActiveRouteIdFromEnv({
|
||||
|
||||
@@ -855,6 +855,14 @@ export function resolveActiveRouteIdFromEnv(
|
||||
return 'custom'
|
||||
}
|
||||
|
||||
// Saved-profile-only Gemini Vertex: with no provider env flag set, the API
|
||||
// client still routes to the native Vertex client from the active profile
|
||||
// (shouldRouteToGeminiVertexFromProfile). Keep route resolution in sync, or
|
||||
// /model would skip the Vertex catalog/discovery for the same session.
|
||||
if (options?.activeProfileProvider === 'gemini-vertex') {
|
||||
return 'gemini-vertex'
|
||||
}
|
||||
|
||||
return 'anthropic'
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { isEnvTruthy } from '../../utils/envUtils.js'
|
||||
import { PROVIDER_SELECTION_FLAGS } from '../../utils/providerSelectionFlags.js'
|
||||
import { getGlobalConfig } from '../../utils/config.js'
|
||||
import {
|
||||
asTrimmedString,
|
||||
parseChatgptAccountId,
|
||||
@@ -972,6 +973,18 @@ export function resolveProviderRequest(options?: {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider of the saved active profile, if any. Used to detect saved-profile-only
|
||||
* routing (no provider env flag) that the API client honours via
|
||||
* shouldRouteToGeminiVertexFromProfile but env-flag checks would miss.
|
||||
*/
|
||||
function getActiveProfileProviderId(): string | undefined {
|
||||
const config = getGlobalConfig()
|
||||
return config.providerProfiles?.find(
|
||||
profile => profile.id === config.activeProviderProfileId,
|
||||
)?.provider
|
||||
}
|
||||
|
||||
export function getAdditionalModelOptionsCacheScope(): string | null {
|
||||
if (!isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI)) {
|
||||
const anotherProviderSelected = PROVIDER_SELECTION_FLAGS.some(
|
||||
@@ -979,6 +992,13 @@ export function getAdditionalModelOptionsCacheScope(): string | null {
|
||||
flag !== 'CLAUDE_CODE_USE_OPENAI' && isEnvTruthy(process.env[flag]),
|
||||
)
|
||||
if (!anotherProviderSelected) {
|
||||
// No provider env flag is set, but a saved Gemini Vertex profile still
|
||||
// routes the client to the native Vertex API. Scope it as third-party
|
||||
// (null) so bootstrap/model-option traffic is not treated as first-party
|
||||
// Anthropic — matching getAnthropicClient's saved-profile routing.
|
||||
if (getActiveProfileProviderId() === 'gemini-vertex') {
|
||||
return null
|
||||
}
|
||||
return 'firstParty'
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -3232,3 +3232,36 @@ describe('isGeminiVertexEffectiveProvider', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAdditionalModelOptionsCacheScope — saved-profile Gemini Vertex', () => {
|
||||
test('is not scoped as first-party when a saved gemini-vertex profile is active without an env flag', async () => {
|
||||
const { getAdditionalModelOptionsCacheScope } = await import(
|
||||
'../services/api/providerConfig.js'
|
||||
)
|
||||
// beforeEach clears every CLAUDE_CODE_USE_* flag; the saved profile is what
|
||||
// routes the client to Vertex, so the cache scope must be third-party (null)
|
||||
// rather than first-party Anthropic bootstrap traffic.
|
||||
saveMockGlobalConfig(current => ({
|
||||
...current,
|
||||
providerProfiles: [
|
||||
buildGeminiVertexProfile({ id: 'vertex_prof', baseUrl: 'my-proj' }),
|
||||
],
|
||||
activeProviderProfileId: 'vertex_prof',
|
||||
}))
|
||||
|
||||
expect(getAdditionalModelOptionsCacheScope()).toBeNull()
|
||||
})
|
||||
|
||||
test('remains first-party with no provider flag and no saved provider profile', async () => {
|
||||
const { getAdditionalModelOptionsCacheScope } = await import(
|
||||
'../services/api/providerConfig.js'
|
||||
)
|
||||
saveMockGlobalConfig(current => ({
|
||||
...current,
|
||||
providerProfiles: [],
|
||||
activeProviderProfileId: undefined,
|
||||
}))
|
||||
|
||||
expect(getAdditionalModelOptionsCacheScope()).toBe('firstParty')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user