Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions internal/agent/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ func (c *coordinator) buildGoogleProvider(baseURL, apiKey string, headers map[st
return google.New(opts...)
}

func (c *coordinator) buildGoogleVertexProvider(headers map[string]string, options map[string]string) (fantasy.Provider, error) {
func (c *coordinator) buildGoogleVertexProvider(baseURL, apiKey string, headers map[string]string, options map[string]string) (fantasy.Provider, error) {
opts := []google.Option{}
if c.cfg.Config().Options.Debug {
httpClient := log.NewHTTPClient()
Expand All @@ -961,7 +961,16 @@ func (c *coordinator) buildGoogleVertexProvider(headers map[string]string, optio
project := options["project"]
location := options["location"]

opts = append(opts, google.WithVertex(project, location))
if project != "" && location != "" {
opts = append(opts, google.WithVertex(project, location))
} else {
// Without native GCP credentials, authenticate with the configured
// API key (e.g. against a Gemini-API-compatible proxy).
opts = append(opts, google.WithGeminiAPIKey(apiKey))
}
if baseURL != "" {
opts = append(opts, google.WithBaseURL(baseURL))
}
Comment on lines +964 to +973

return google.New(opts...)
}
Expand Down Expand Up @@ -1016,7 +1025,7 @@ func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model con
case google.Name:
return c.buildGoogleProvider(baseURL, apiKey, headers)
case "google-vertex":
return c.buildGoogleVertexProvider(headers, providerCfg.ExtraParams)
return c.buildGoogleVertexProvider(baseURL, apiKey, headers, providerCfg.ExtraParams)
case openaicompat.Name, hyper.Name:
switch providerCfg.ID {
case hyper.Name:
Expand Down
10 changes: 7 additions & 3 deletions internal/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,19 @@ func (c *Config) configureProviders(store *ConfigStore, env env.Env, resolver Va
project = env.Get("VERTEXAI_PROJECT")
location = env.Get("VERTEXAI_LOCATION")
)
if project == "" || location == "" {
if project != "" && location != "" {
prepared.ExtraParams["project"] = project
prepared.ExtraParams["location"] = location
} else if v, err := resolver.ResolveValue(p.APIKey); v == "" || err != nil {
// Without native GCP credentials an API key works like any
// other provider (e.g. against a Gemini-API-compatible
// proxy); only drop the provider when neither is available.
if configExists {
slog.Warn("Skipping Vertex AI provider due to missing credentials")
c.Providers.Del(string(p.ID))
Comment on lines +290 to 296
}
continue
}
prepared.ExtraParams["project"] = project
prepared.ExtraParams["location"] = location
case catwalk.InferenceProviderAzure:
endpoint, err := resolver.ResolveValue(p.APIEndpoint)
if err != nil || endpoint == "" {
Expand Down
41 changes: 41 additions & 0 deletions internal/config/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,47 @@ func TestConfig_configureProvidersVertexAIMissingProject(t *testing.T) {
require.Equal(t, cfg.Providers.Len(), 0)
}

// TestConfig_configureProvidersVertexAIWithAPIKey is a regression test for
// issue #3074: a Vertex AI provider configured with an api_key (and a custom
// base_url pointing at a proxy) used to be dropped during config load because
// the vertexai branch only looked at the VERTEXAI_PROJECT / VERTEXAI_LOCATION
// env vars and ignored the configured api_key entirely. Once dropped, Crush
// thought the provider was unconfigured and prompted the user for an API key,
// even though the same api_key style works for openai/anthropic.
//
// With the fix, an api_key keeps the provider alive when the GCP env vars are
// absent, matching the generic-provider behavior.
func TestConfig_configureProvidersVertexAIWithAPIKey(t *testing.T) {
knownProviders := []catwalk.Provider{
{
ID: catwalk.InferenceProviderVertexAI,
APIKey: "test-api-key", // user configured an api_key
APIEndpoint: "https://vertex-proxy.example.com", // custom proxy base_url
Models: []catwalk.Model{{
ID: "gemini-pro",
}},
},
}

cfg := &Config{}
cfg.setDefaults("/tmp", "")
// No VERTEXAI_PROJECT / VERTEXAI_LOCATION on purpose: the user is
// authenticating via api_key + proxy, not native GCP credentials.
env := env.NewFromMap(map[string]string{})
resolver := NewShellVariableResolver(env)
Comment on lines +577 to +582
err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
require.NoError(t, err)
require.Equal(t, 1, cfg.Providers.Len(),
"vertexai provider with an api_key must be kept (issue #3074)")

vertexProvider, ok := cfg.Providers.Get("vertexai")
require.True(t, ok, "VertexAI provider should be present")
require.Equal(t, "test-api-key", vertexProvider.APIKey)
require.Equal(t, "https://vertex-proxy.example.com", vertexProvider.BaseURL)
require.Empty(t, vertexProvider.ExtraParams["project"], "no GCP project expected when authenticating via api_key")
require.Empty(t, vertexProvider.ExtraParams["location"])
}

func TestConfig_configureProvidersSetProviderID(t *testing.T) {
knownProviders := []catwalk.Provider{
{
Expand Down
Loading