diff --git a/cmd/shelley/main.go b/cmd/shelley/main.go index ddc66d77..7ed3a093 100644 --- a/cmd/shelley/main.go +++ b/cmd/shelley/main.go @@ -412,6 +412,7 @@ func buildLLMModelSources(ctx context.Context, global GlobalConfig, logger *slog openAIKey := os.Getenv("OPENAI_API_KEY") geminiKey := os.Getenv("GEMINI_API_KEY") fireworksKey := os.Getenv("FIREWORKS_API_KEY") + zaiKey := os.Getenv("ZAI_API_KEY") var sources []modelsources.Source @@ -464,6 +465,9 @@ func buildLLMModelSources(ctx context.Context, global GlobalConfig, logger *slog if geminiKey != "" { sources = append(sources, modelsources.Env("", "", geminiKey, "")) } + if zaiKey != "" { + sources = append(sources, modelsources.ZAIEnv(zaiKey)) + } } else if gateway != "" { logger.Info("Using LLM gateway", "gateway", gateway) sources = append(sources, modelsources.Gateway(gateway, anthropicKey, openAIKey, fireworksKey)) @@ -472,8 +476,16 @@ func buildLLMModelSources(ctx context.Context, global GlobalConfig, logger *slog if geminiKey != "" { sources = append(sources, modelsources.Env("", "", geminiKey, "")) } - } else if anthropicKey != "" || openAIKey != "" || geminiKey != "" || fireworksKey != "" { + if zaiKey != "" { + sources = append(sources, modelsources.ZAIEnv(zaiKey)) + } + } else if anthropicKey != "" || openAIKey != "" || geminiKey != "" || fireworksKey != "" || zaiKey != "" { // 3. Env vars. + // 3a. z.ai gets its own source so z.ai models use ZAI_API_KEY + // instead of OPENAI_API_KEY. + if zaiKey != "" { + sources = append(sources, modelsources.ZAIEnv(zaiKey)) + } sources = append(sources, modelsources.Env(anthropicKey, openAIKey, geminiKey, fireworksKey)) } diff --git a/llm/oai/oai.go b/llm/oai/oai.go index 1eddb6c7..b4070712 100644 --- a/llm/oai/oai.go +++ b/llm/oai/oai.go @@ -26,6 +26,7 @@ const ( GeminiURL = "https://generativelanguage.googleapis.com/v1beta/openai/" MistralURL = "https://api.mistral.ai/v1" MoonshotURL = "https://api.moonshot.ai/v1" + ZAIURL = "https://api.z.ai/api/coding/paas/v4" // Environment variable names for API keys OpenAIAPIKeyEnv = "OPENAI_API_KEY" @@ -35,6 +36,7 @@ const ( GeminiAPIKeyEnv = "GEMINI_API_KEY" MistralAPIKeyEnv = "MISTRAL_API_KEY" MoonshotAPIKeyEnv = "MOONSHOT_API_KEY" + ZAIAPIKeyEnv = "ZAI_API_KEY" ) //exe:completeinit @@ -328,6 +330,36 @@ var ( SupportsImages: false, } + GLM52ZAI = Model{ + UserName: "glm-5.2-zai", + ModelName: "glm-5.2", + URL: ZAIURL, + APIKeyEnv: ZAIAPIKeyEnv, + IsReasoningModel: false, + UseSimplifiedPatch: false, + SupportsImages: false, + } + + GLM51ZAI = Model{ + UserName: "glm-5.1-zai", + ModelName: "glm-5.1", + URL: ZAIURL, + APIKeyEnv: ZAIAPIKeyEnv, + IsReasoningModel: false, + UseSimplifiedPatch: false, + SupportsImages: false, + } + + GLM46ZAI = Model{ + UserName: "glm-4.6-zai", + ModelName: "glm-4.6", + URL: ZAIURL, + APIKeyEnv: ZAIAPIKeyEnv, + IsReasoningModel: false, + UseSimplifiedPatch: false, + SupportsImages: false, + } + KimiK26Fireworks = Model{ UserName: "kimi-k2.6-fireworks", ModelName: "accounts/fireworks/models/kimi-k2p6", @@ -552,6 +584,9 @@ var ModelsRegistry = []Model{ DevstralSmall, GLM52Fireworks, GLM51Fireworks, + GLM52ZAI, + GLM51ZAI, + GLM46ZAI, KimiK26Fireworks, Qwen36PlusFireworks, GPTOSS120B, @@ -1095,6 +1130,8 @@ func (s *Service) TokenContextWindow() int { return 200000 // 200k for O3 models case "glm": return 128000 + case "glm-5.2", "glm-5.1", "glm-4.6": + return 128000 case "qwen": return 256000 case "gpt-oss-20b", "gpt-oss-120b": diff --git a/models/models.go b/models/models.go index d713aaa1..9abd2203 100644 --- a/models/models.go +++ b/models/models.go @@ -241,6 +241,24 @@ func All() []Model { APIType: APITypeOpenAIChat, DefaultBaseURL: DefaultFireworksBaseURL, Build: oaiChatSvc(oai.GLM51Fireworks, "fireworks"), }, + { + ID: "glm-5.2-zai", Provider: ProviderOpenAI, + Description: "GLM-5.2 (z.ai Coding Plan)", APIModelName: oai.GLM52ZAI.ModelName, + APIType: APITypeOpenAIChat, DefaultBaseURL: "https://api.z.ai/api/coding/paas/v4", + Build: oaiChatSvc(oai.GLM52ZAI, "zai"), + }, + { + ID: "glm-5.1-zai", Provider: ProviderOpenAI, + Description: "GLM-5.1 (z.ai Coding Plan)", APIModelName: oai.GLM51ZAI.ModelName, + APIType: APITypeOpenAIChat, DefaultBaseURL: "https://api.z.ai/api/coding/paas/v4", + Build: oaiChatSvc(oai.GLM51ZAI, "zai"), + }, + { + ID: "glm-4.6-zai", Provider: ProviderOpenAI, + Description: "GLM-4.6 (z.ai Coding Plan)", APIModelName: oai.GLM46ZAI.ModelName, + APIType: APITypeOpenAIChat, DefaultBaseURL: "https://api.z.ai/api/coding/paas/v4", + Build: oaiChatSvc(oai.GLM46ZAI, "zai"), + }, { ID: "gemini-3-pro", Provider: ProviderGemini, Description: "Gemini 3 Pro", APIModelName: "gemini-3-pro-preview", diff --git a/modelsources/modelsources.go b/modelsources/modelsources.go index d8f51384..abdbe709 100644 --- a/modelsources/modelsources.go +++ b/modelsources/modelsources.go @@ -113,6 +113,27 @@ func Env(anthropicKey, openAIKey, geminiKey, fireworksKey string) Source { return Source{label: "env", providers: prov, providerLabels: labels} } +// ZAIEnv returns a Source for z.ai Coding Plan models using ZAI_API_KEY. +// z.ai models are catalogued with ProviderOpenAI but use a distinct base +// URL and API key, so they need their own source to avoid being claimed +// by the generic OpenAI env source with the wrong key. +func ZAIEnv(zaiKey string) Source { + if zaiKey == "" { + return Source{} + } + return Source{ + label: "$ZAI_API_KEY", + providers: map[models.Provider]*providerConn{ + models.ProviderOpenAI: {apiKey: zaiKey}, + }, + allowedAPIModels: map[string]bool{ + "glm-5.2": true, + "glm-5.1": true, + "glm-4.6": true, + }, + } +} + // LLMIntegration returns a Source backed by one exe.dev "llm" // integration. idSuffix, when non-empty, is appended to each // materialized model ID to disambiguate multiple integrations.