feat: add Z.ai provider support (general API + coding plan) (#102)

* feat: add Z.ai provider support (general API + coding plan)

Add Z.ai (GLM) as a new LLM provider with two variants:
- `zai`: general API (api.z.ai/api/paas/v4)
- `zai_coding`: coding plan (api.z.ai/api/coding/paas/v4)

Reuses OpenAIProvider — Z.ai API is OpenAI-compatible with Bearer
token auth, SSE streaming, and reasoning_content support.

Includes: store constants, config struct fields, env var loading
(GOCLAW_ZAI_API_KEY, GOCLAW_ZAI_CODING_API_KEY), secret masking,
config + DB registration, onboard wizard, and UI provider types.

Default model: glm-5

Closes #100

* docs: add Z.ai provider entries to providers documentation
This commit is contained in:
Duc Nguyen
2026-03-09 22:28:18 +07:00
committed by GitHub
parent 344e2ac7d1
commit e63ff014cb
8 changed files with 52 additions and 0 deletions
+30
View File
@@ -98,6 +98,24 @@ func registerProviders(registry *providers.Registry, cfg *config.Config) {
slog.Info("registered provider", "name", "bailian")
}
if cfg.Providers.Zai.APIKey != "" {
base := cfg.Providers.Zai.APIBase
if base == "" {
base = "https://api.z.ai/api/paas/v4"
}
registry.Register(providers.NewOpenAIProvider("zai", cfg.Providers.Zai.APIKey, base, "glm-5"))
slog.Info("registered provider", "name", "zai")
}
if cfg.Providers.ZaiCoding.APIKey != "" {
base := cfg.Providers.ZaiCoding.APIBase
if base == "" {
base = "https://api.z.ai/api/coding/paas/v4"
}
registry.Register(providers.NewOpenAIProvider("zai-coding", cfg.Providers.ZaiCoding.APIKey, base, "glm-5"))
slog.Info("registered provider", "name", "zai-coding")
}
// Claude CLI provider (subscription-based, no API key needed)
if cfg.Providers.ClaudeCLI.CLIPath != "" {
cliPath := cfg.Providers.ClaudeCLI.CLIPath
@@ -243,6 +261,18 @@ func registerProvidersFromDB(registry *providers.Registry, provStore store.Provi
base = "https://coding-intl.dashscope.aliyuncs.com/v1"
}
registry.Register(providers.NewOpenAIProvider(p.Name, p.APIKey, base, "qwen3.5-plus"))
case store.ProviderZai:
base := p.APIBase
if base == "" {
base = "https://api.z.ai/api/paas/v4"
}
registry.Register(providers.NewOpenAIProvider(p.Name, p.APIKey, base, "glm-5"))
case store.ProviderZaiCoding:
base := p.APIBase
if base == "" {
base = "https://api.z.ai/api/coding/paas/v4"
}
registry.Register(providers.NewOpenAIProvider(p.Name, p.APIKey, base, "glm-5"))
case store.ProviderSuno:
// Suno is a media-only provider (music gen). Register as OpenAI-compat
// so credentialProvider interface works for API key/base extraction.
+2
View File
@@ -31,6 +31,8 @@ var defaultPlaceholderProviders = []store.LLMProviderData{
{Name: "synthetic", DisplayName: "Synthetic", ProviderType: store.ProviderOpenAICompat, APIBase: "https://api.synthetic.new/openai/v1", Enabled: false},
{Name: "alicloud-api", DisplayName: "AliCloud API", ProviderType: store.ProviderDashScope, APIBase: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", Enabled: false},
{Name: "alicloud-sub", DisplayName: "AliCloud Sub", ProviderType: store.ProviderBailian, APIBase: "https://coding-intl.dashscope.aliyuncs.com/v1", Enabled: false},
{Name: "zai", DisplayName: "Z.ai API", ProviderType: store.ProviderZai, APIBase: "https://api.z.ai/api/paas/v4", Enabled: false},
{Name: "zai-coding", DisplayName: "Z.ai Coding Plan", ProviderType: store.ProviderZaiCoding, APIBase: "https://api.z.ai/api/coding/paas/v4", Enabled: false},
}
// seedOnboardPlaceholders opens a PG store and seeds disabled placeholder providers
+2
View File
@@ -45,6 +45,8 @@ The Anthropic provider uses `x-api-key` header authentication and the `anthropic
| perplexity | OpenAI-compatible | `https://api.perplexity.ai` | `sonar-pro` |
| dashscope | OpenAI-compatible | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | `qwen3-max` |
| bailian | OpenAI-compatible | `https://coding-intl.dashscope.aliyuncs.com/v1` | `qwen3.5-plus` |
| zai | OpenAI-compatible | `https://api.z.ai/api/paas/v4` | `glm-5` |
| zai_coding | OpenAI-compatible | `https://api.z.ai/api/coding/paas/v4` | `glm-5` |
---
+4
View File
@@ -182,6 +182,8 @@ type ProvidersConfig struct {
Perplexity ProviderConfig `json:"perplexity"`
DashScope ProviderConfig `json:"dashscope"`
Bailian ProviderConfig `json:"bailian"`
Zai ProviderConfig `json:"zai"`
ZaiCoding ProviderConfig `json:"zai_coding"`
ClaudeCLI ClaudeCLIConfig `json:"claude_cli"`
}
@@ -214,6 +216,8 @@ func (c *Config) HasAnyProvider() bool {
p.Perplexity.APIKey != "" ||
p.DashScope.APIKey != "" ||
p.Bailian.APIKey != "" ||
p.Zai.APIKey != "" ||
p.ZaiCoding.APIKey != "" ||
p.ClaudeCLI.CLIPath != ""
}
+2
View File
@@ -110,6 +110,8 @@ func (c *Config) applyEnvOverrides() {
envStr("GOCLAW_PERPLEXITY_API_KEY", &c.Providers.Perplexity.APIKey)
envStr("GOCLAW_DASHSCOPE_API_KEY", &c.Providers.DashScope.APIKey)
envStr("GOCLAW_BAILIAN_API_KEY", &c.Providers.Bailian.APIKey)
envStr("GOCLAW_ZAI_API_KEY", &c.Providers.Zai.APIKey)
envStr("GOCLAW_ZAI_CODING_API_KEY", &c.Providers.ZaiCoding.APIKey)
envStr("GOCLAW_GATEWAY_TOKEN", &c.Gateway.Token)
envStr("GOCLAW_TELEGRAM_TOKEN", &c.Channels.Telegram.Token)
envStr("GOCLAW_DISCORD_TOKEN", &c.Channels.Discord.Token)
+6
View File
@@ -34,6 +34,8 @@ func (c *Config) MaskedCopy() *Config {
maskNonEmpty(&cp.Providers.Perplexity.APIKey)
maskNonEmpty(&cp.Providers.DashScope.APIKey)
maskNonEmpty(&cp.Providers.Bailian.APIKey)
maskNonEmpty(&cp.Providers.Zai.APIKey)
maskNonEmpty(&cp.Providers.ZaiCoding.APIKey)
// Mask gateway token
maskNonEmpty(&cp.Gateway.Token)
@@ -81,6 +83,8 @@ func (c *Config) StripSecrets() {
c.Providers.Perplexity.APIKey = ""
c.Providers.DashScope.APIKey = ""
c.Providers.Bailian.APIKey = ""
c.Providers.Zai.APIKey = ""
c.Providers.ZaiCoding.APIKey = ""
// Gateway token
c.Gateway.Token = ""
@@ -133,6 +137,8 @@ func (c *Config) StripMaskedSecrets() {
stripIfMasked(&c.Providers.Perplexity.APIKey)
stripIfMasked(&c.Providers.DashScope.APIKey)
stripIfMasked(&c.Providers.Bailian.APIKey)
stripIfMasked(&c.Providers.Zai.APIKey)
stripIfMasked(&c.Providers.ZaiCoding.APIKey)
// Gateway token
stripIfMasked(&c.Gateway.Token)
+4
View File
@@ -26,6 +26,8 @@ const (
ProviderClaudeCLI = "claude_cli"
ProviderSuno = "suno"
ProviderYesScale = "yescale"
ProviderZai = "zai"
ProviderZaiCoding = "zai_coding"
)
// ValidProviderTypes lists all accepted provider_type values.
@@ -47,6 +49,8 @@ var ValidProviderTypes = map[string]bool{
ProviderClaudeCLI: true,
ProviderSuno: true,
ProviderYesScale: true,
ProviderZai: true,
ProviderZaiCoding: true,
}
// LLMProviderData represents an LLM provider configuration.
+2
View File
@@ -21,5 +21,7 @@ export const PROVIDER_TYPES: ProviderTypeInfo[] = [
{ value: "dashscope", label: "DashScope (Qwen)", apiBase: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", placeholder: "" },
{ value: "bailian", label: "Bailian Coding", apiBase: "https://coding-intl.dashscope.aliyuncs.com/v1", placeholder: "" },
{ value: "yescale", label: "YesScale", apiBase: "https://api.yescale.one/v1", placeholder: "" },
{ value: "zai", label: "Z.ai API", apiBase: "https://api.z.ai/api/paas/v4", placeholder: "" },
{ value: "zai_coding", label: "Z.ai Coding Plan", apiBase: "https://api.z.ai/api/coding/paas/v4", placeholder: "" },
{ value: "claude_cli", label: "Claude CLI (Local)", apiBase: "", placeholder: "" },
];