mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-11-13 16:03:44 -04:00
fix(gemini-vertex): saved profile wins over ambient env in profile-only routing
getAnthropicClient's saved-profile-only Vertex branch resolved the project/model from process.env before falling back to the profile, contradicting its own comment. A saved gemini-vertex profile targeting one project/model could route to an ambient GOOGLE_CLOUD_PROJECT / stale GEMINI_VERTEX_MODEL when no CLAUDE_CODE_USE_GEMINI_VERTEX flag is set. profileProject/profileModel are only populated when routedFromProfileOnly, so leading the precedence with them makes the selected profile win in profile-only mode while env still wins for an explicit Vertex selection. Regression locks the precedence (profile project + model win over ambient env). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a65f9b220c
commit
eff1b9c0f2
@@ -1,10 +1,11 @@
|
||||
import { afterEach, beforeEach, expect, test } from 'bun:test'
|
||||
import { afterEach, beforeEach, expect, spyOn, test } from 'bun:test'
|
||||
import { acquireSharedMutationLock, releaseSharedMutationLock } from '../../test/sharedMutationLock.js'
|
||||
import {
|
||||
_clearRegistryForTesting,
|
||||
ensureIntegrationsLoaded,
|
||||
registerGateway,
|
||||
} from '../../integrations/index.js'
|
||||
import * as providerProfilesModule from '../../utils/providerProfiles.js'
|
||||
import { getAnthropicClient } from './client.js'
|
||||
|
||||
type FetchType = typeof globalThis.fetch
|
||||
@@ -428,6 +429,67 @@ test('routes Gemini Vertex provider requests through the native client', async (
|
||||
})
|
||||
})
|
||||
|
||||
test('saved-profile-only Vertex prefers the profile project/model over ambient env', async () => {
|
||||
let capturedUrl: string | undefined
|
||||
|
||||
delete process.env.CLAUDE_CODE_USE_OPENAI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI
|
||||
delete process.env.CLAUDE_CODE_USE_GEMINI_VERTEX
|
||||
// Ambient env that must NOT win over the selected profile in profile-only mode.
|
||||
process.env.GOOGLE_CLOUD_PROJECT = 'ambient-project'
|
||||
process.env.GEMINI_VERTEX_MODEL = 'ambient-model'
|
||||
process.env.GEMINI_VERTEX_LOCATION = 'us-central1'
|
||||
process.env.GEMINI_ACCESS_TOKEN = 'vertex-access-token'
|
||||
|
||||
const spy = spyOn(
|
||||
providerProfilesModule,
|
||||
'getActiveProviderProfile',
|
||||
).mockReturnValue({
|
||||
id: 'vertex_prof',
|
||||
provider: 'gemini-vertex',
|
||||
name: 'Vertex',
|
||||
baseUrl: 'profile-project',
|
||||
model: 'profile-model',
|
||||
} as ReturnType<typeof providerProfilesModule.getActiveProviderProfile>)
|
||||
|
||||
globalThis.fetch = (async (input, _init) => {
|
||||
capturedUrl =
|
||||
typeof input === 'string'
|
||||
? input
|
||||
: input instanceof URL
|
||||
? input.toString()
|
||||
: input.url
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
candidates: [{ content: { parts: [{ text: 'vertex ok' }] } }],
|
||||
}),
|
||||
{ headers: { 'Content-Type': 'application/json' } },
|
||||
)
|
||||
}) as FetchType
|
||||
|
||||
try {
|
||||
const client = (await getAnthropicClient({
|
||||
maxRetries: 0,
|
||||
})) as unknown as ShimClient
|
||||
|
||||
await client.beta.messages.create({
|
||||
model: 'profile-model',
|
||||
messages: [{ role: 'user', content: 'hello' }],
|
||||
max_tokens: 64,
|
||||
stream: false,
|
||||
})
|
||||
|
||||
// Profile project + model win; the ambient GOOGLE_CLOUD_PROJECT / stale
|
||||
// GEMINI_VERTEX_MODEL must not leak into the request.
|
||||
expect(capturedUrl).toContain('/projects/profile-project/')
|
||||
expect(capturedUrl).toContain('/models/profile-model:generateContent')
|
||||
expect(capturedUrl).not.toContain('ambient-project')
|
||||
expect(capturedUrl).not.toContain('ambient-model')
|
||||
} finally {
|
||||
spy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
test('Gemini Vertex provider uses GEMINI_VERTEX_MODEL instead of generic OPENAI_MODEL', async () => {
|
||||
let capturedUrl: string | undefined
|
||||
|
||||
|
||||
@@ -555,14 +555,17 @@ export async function getAnthropicClient({
|
||||
const profileModel = routedFromProfileOnly && activeProfile?.model
|
||||
? getPrimaryModel(activeProfile.model)
|
||||
: undefined
|
||||
const project = getGeminiVertexProjectId(process.env) ?? profileProject
|
||||
// In profile-only routing the selected profile must win over ambient env;
|
||||
// profileProject/profileModel are only set when routedFromProfileOnly, so
|
||||
// leading with them keeps env precedence for an explicit Vertex selection.
|
||||
const project = profileProject ?? getGeminiVertexProjectId(process.env)
|
||||
const location = getGeminiVertexLocation(process.env)
|
||||
// getGeminiVertexModel already falls back to the default model; the trailing
|
||||
// literal only satisfies its `string | undefined` signature (it never
|
||||
// actually returns undefined) so geminiVertexModel stays a plain string.
|
||||
const geminiVertexModel =
|
||||
process.env.GEMINI_VERTEX_MODEL?.trim() ||
|
||||
profileModel ||
|
||||
process.env.GEMINI_VERTEX_MODEL?.trim() ||
|
||||
model?.trim() ||
|
||||
getGeminiVertexModel(process.env) ||
|
||||
DEFAULT_GEMINI_VERTEX_MODEL
|
||||
|
||||
Reference in New Issue
Block a user