From cd1d6bed671984064d16607cb98e344a960062fb Mon Sep 17 00:00:00 2001 From: Gary Blankenship Date: Mon, 6 Apr 2026 17:31:34 -0400 Subject: [PATCH 1/2] fix: preserve config base_url when API key comes from environment ProviderCredentials discarded the entire provider config (including base_url) when api_key was empty, falling back to EnvCredentials which returned no base URL for custom providers. Requests then hit the default OpenAI endpoint instead of the configured one. Also fix ensureProviderSetup to try env var fallback before erroring when a config file exists but api_key is empty. --- internal/bootstrap/input.go | 12 +++++++----- internal/config/settings.go | 10 ++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/internal/bootstrap/input.go b/internal/bootstrap/input.go index aec08c3..5a2a549 100644 --- a/internal/bootstrap/input.go +++ b/internal/bootstrap/input.go @@ -93,13 +93,11 @@ func resolveInput(opts Options) (*resolvedInput, error) { func ensureProviderSetup(cwd string, settings config.Resolved, nonTTY bool) (config.Resolved, string, error) { configExists := config.GlobalConfigExists() || config.ProjectConfigExists(cwd) - if configExists { - if hasConfiguredProviderCredentials(settings, settings.Provider) { - return settings, "", nil - } - return settings, "", fmt.Errorf("configuration error: settings.provider=%q is missing or not configured in settings.json", settings.Provider) + if configExists && hasConfiguredProviderCredentials(settings, settings.Provider) { + return settings, "", nil } + // Even with a config file, try env var fallback when api_key is empty. apiKey, _ := settings.ProviderCredentials(settings.Provider) if apiKey != "" { return settings, envHintFor(settings), nil @@ -112,6 +110,10 @@ func ensureProviderSetup(cwd string, settings config.Resolved, nonTTY bool) (con return settings, fmt.Sprintf("Using %s from environment", envKey), nil } + if configExists { + return settings, "", fmt.Errorf("configuration error: settings.provider=%q is missing or not configured in settings.json", settings.Provider) + } + if nonTTY { return settings, "", fmt.Errorf("api key not set; set %s or configure providers in %s", config.ProviderEnvKey(settings.Provider), filepath.Join(config.UserConfigDir(), "settings.json")) diff --git a/internal/config/settings.go b/internal/config/settings.go index e606e4d..958d056 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -120,11 +120,17 @@ var providerEnvVars = map[string]struct{ key, base string }{ // ProviderCredentials returns API key and base URL for the given provider. // It checks the providers map first, then falls back to standard environment variables. +// Config base URL is preserved even when the API key comes from the environment. func (r Resolved) ProviderCredentials(prov string) (apiKey, baseURL string) { - if pc, ok := r.Providers[prov]; ok && pc.APIKey != "" { + pc, hasConfig := r.Providers[prov] + if hasConfig && pc.APIKey != "" { return pc.APIKey, pc.BaseURL } - return EnvCredentials(prov) + apiKey, baseURL = EnvCredentials(prov) + if hasConfig && baseURL == "" { + baseURL = pc.BaseURL + } + return apiKey, baseURL } // ProviderEnvKey returns the standard environment variable name for a provider's API key. From 316de6320fb264df3cd6a1b9786d9c2a943d04d2 Mon Sep 17 00:00:00 2001 From: Gary Blankenship Date: Mon, 6 Apr 2026 17:33:11 -0400 Subject: [PATCH 2/2] feat: add first-class GLM (Zhipu AI) provider support Wire up the native litellm GLM provider instead of routing through the OpenAI provider, which incorrectly appends /v1/ to base URLs. - Add "zhipu" to KnownProviderTypes (maps to "glm" protocol) - Add ZHIPU_API_KEY / ZHIPU_BASE_URL environment variable support - Add "glm" case in provider factory using litellm's registered GLM provider with correct endpoint path handling Configuration example: "zhipu": { "base_url": "https://api.z.ai/api/coding/paas/v4", "models": ["glm-5.1", "glm-5", "glm-4.7"] } --- internal/config/settings.go | 2 ++ internal/provider/provider.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/internal/config/settings.go b/internal/config/settings.go index 958d056..5381757 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -18,6 +18,7 @@ var KnownProviderTypes = map[string]string{ "openai": "openai", "openrouter": "openrouter", "gemini": "gemini", + "zhipu": "glm", } // ProviderConfig holds credentials and model configuration for a single provider. @@ -116,6 +117,7 @@ var providerEnvVars = map[string]struct{ key, base string }{ "openai": {"OPENAI_API_KEY", "OPENAI_BASE_URL"}, "openrouter": {"OPENROUTER_API_KEY", "OPENROUTER_BASE_URL"}, "gemini": {"GEMINI_API_KEY", "GEMINI_BASE_URL"}, + "zhipu": {"ZHIPU_API_KEY", "ZHIPU_BASE_URL"}, } // ProviderCredentials returns API key and base URL for the given provider. diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6f1b20e..d57bd01 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,10 +1,12 @@ package provider import ( + "fmt" "strings" "github.com/voocel/agentcore" "github.com/voocel/agentcore/llm" + "github.com/voocel/litellm" ) // CreateModel creates a ChatModel for the given provider, model name, API key, and optional base URL. @@ -40,6 +42,8 @@ func newProviderModel(prov, name, apiKey, baseURL string) (agentcore.ChatModel, return llm.NewOpenAIModel(name, apiKey, baseURL) } return llm.NewOpenAIModel(name, apiKey) + case "glm": + return newGLMModel(name, apiKey, baseURL) default: if baseURL != "" { return llm.NewOpenAIModel(name, apiKey, baseURL) @@ -48,6 +52,18 @@ func newProviderModel(prov, name, apiKey, baseURL string) (agentcore.ChatModel, } } +func newGLMModel(model, apiKey, baseURL string) (agentcore.ChatModel, error) { + cfg := litellm.ProviderConfig{APIKey: apiKey} + if baseURL != "" { + cfg.BaseURL = baseURL + } + client, err := litellm.NewWithProvider("glm", cfg) + if err != nil { + return nil, fmt.Errorf("glm: %w", err) + } + return llm.NewLiteLLMAdapter(model, client), nil +} + func applyProviderDefaults(prov, modelName string, model agentcore.ChatModel) { cfgOwner, ok := model.(interface { GetConfig() *llm.GenerationConfig