From 76d2c290c2df0d50001e42bf3a0ecf4293b98c09 Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Mon, 20 Jul 2026 02:38:15 +0800 Subject: [PATCH] Add Atlas Cloud provider defaults --- cmd/gateway_providers.go | 16 ++++++++++++++++ cmd/gateway_providers_defaults_test.go | 11 +++++++++++ cmd/providers_cmd.go | 5 ++++- cmd/setup_provider.go | 9 +++++++-- docs/02-providers.md | 1 + internal/config/config_channels.go | 4 ++++ internal/config/config_load.go | 2 ++ internal/config/config_secrets.go | 3 +++ internal/http/provider_models.go | 2 ++ internal/http/providers.go | 5 +++++ internal/store/provider_store.go | 6 ++++++ 11 files changed, 61 insertions(+), 3 deletions(-) diff --git a/cmd/gateway_providers.go b/cmd/gateway_providers.go index cc587866..b266fb51 100644 --- a/cmd/gateway_providers.go +++ b/cmd/gateway_providers.go @@ -43,6 +43,17 @@ func registerProviders(registry *providers.Registry, cfg *config.Config, modelRe slog.Info("registered provider", "name", "openai") } + if cfg.Providers.AtlasCloud.APIKey != "" { + base := cfg.Providers.AtlasCloud.APIBase + if base == "" { + base = store.AtlasCloudDefaultAPIBase + } + prov := providers.NewOpenAIProvider("atlascloud", cfg.Providers.AtlasCloud.APIKey, base, store.AtlasCloudDefaultModel) + prov.WithProviderType(store.ProviderAtlasCloud) + registry.Register(prov) + slog.Info("registered provider", "name", "atlascloud") + } + if cfg.Providers.OpenRouter.APIKey != "" { orProv := providers.NewOpenAIProvider("openrouter", cfg.Providers.OpenRouter.APIKey, "https://openrouter.ai/api/v1", "anthropic/claude-sonnet-4-5-20250929") orProv.WithSiteInfo("https://goclaw.sh", "GoClaw") @@ -461,6 +472,11 @@ func openAIProviderDefaults(providerType, apiBase string) (string, string) { apiBase = store.MiniMaxDefaultAPIBase } return apiBase, store.MiniMaxDefaultModel + case store.ProviderAtlasCloud: + if apiBase == "" { + apiBase = store.AtlasCloudDefaultAPIBase + } + return apiBase, store.AtlasCloudDefaultModel default: return apiBase, "" } diff --git a/cmd/gateway_providers_defaults_test.go b/cmd/gateway_providers_defaults_test.go index ac25ddbe..56288550 100644 --- a/cmd/gateway_providers_defaults_test.go +++ b/cmd/gateway_providers_defaults_test.go @@ -17,6 +17,7 @@ import ( func TestRegisterProvidersUsesCurrentMiniMaxAndZaiDefaults(t *testing.T) { cfg := &config.Config{} + cfg.Providers.AtlasCloud.APIKey = "atlas-token" cfg.Providers.MiniMax.APIKey = "minimax-token" cfg.Providers.Zai.APIKey = "zai-token" cfg.Providers.ZaiCoding.APIKey = "zai-coding-token" @@ -24,6 +25,7 @@ func TestRegisterProvidersUsesCurrentMiniMaxAndZaiDefaults(t *testing.T) { registry := providers.NewRegistry(nil) registerProviders(registry, cfg, providers.NewInMemoryRegistry()) + assertProviderDefault(t, registry, providers.MasterTenantID, "atlascloud", "qwen/qwen3.5-flash", "https://api.atlascloud.ai/v1") assertProviderDefault(t, registry, providers.MasterTenantID, "minimax", "MiniMax-M3", "https://api.minimax.io/v1") assertProviderDefault(t, registry, providers.MasterTenantID, "zai", "glm-5.2", "https://api.z.ai/api/paas/v4") assertProviderDefault(t, registry, providers.MasterTenantID, "zai-coding", "glm-5.2", "https://api.z.ai/api/coding/paas/v4") @@ -78,6 +80,14 @@ func TestRegisterProvidersFromDBUsesCurrentMiniMaxAndZaiDefaults(t *testing.T) { APIKey: "aimlapi-token", Enabled: true, }, + { + BaseModel: store.BaseModel{ID: uuid.New()}, + TenantID: tenantID, + Name: "db-atlascloud", + ProviderType: store.ProviderAtlasCloud, + APIKey: "atlas-token", + Enabled: true, + }, { BaseModel: store.BaseModel{ID: uuid.New()}, TenantID: tenantID, @@ -109,6 +119,7 @@ func TestRegisterProvidersFromDBUsesCurrentMiniMaxAndZaiDefaults(t *testing.T) { registerProvidersFromDB(registry, providerStore, nil, "", "", nil, &config.Config{}, providers.NewInMemoryRegistry()) assertProviderDefault(t, registry, tenantID, "db-aimlapi", providers.AIMLAPIDefaultModel, providers.AIMLAPIDefaultAPIBase) + assertProviderDefault(t, registry, tenantID, "db-atlascloud", "qwen/qwen3.5-flash", "https://api.atlascloud.ai/v1") assertProviderDefault(t, registry, tenantID, "db-minimax", "MiniMax-M3", "https://api.minimax.io/v1") assertProviderDefault(t, registry, tenantID, "db-zai", "glm-5.2", "https://api.z.ai/api/paas/v4") assertProviderDefault(t, registry, tenantID, "db-zai-coding", "glm-5.2", "https://api.z.ai/api/coding/paas/v4") diff --git a/cmd/providers_cmd.go b/cmd/providers_cmd.go index f93ae7b7..33c0b76d 100644 --- a/cmd/providers_cmd.go +++ b/cmd/providers_cmd.go @@ -123,6 +123,7 @@ func runProvidersAdd() { typeOptions := []SelectOption[string]{ {"Anthropic", "anthropic"}, {"OpenAI", "openai"}, + {"Atlas Cloud", "atlascloud"}, {"OpenRouter", "openrouter"}, {"DashScope (Alibaba)", "dashscope"}, {"OpenAI-compatible", "openai_compat"}, @@ -150,7 +151,7 @@ func runProvidersAdd() { // Step 4: Base URL (pre-fill per type, editable) defaultURL := defaultBaseURL(providerType) baseURL := "" - if providerType == "openai_compat" { + if providerType == "openai_compat" || providerType == "atlascloud" { baseURL, err = promptString("Base URL", "e.g. https://api.example.com/v1", defaultURL) if err != nil { fmt.Println("Cancelled.") @@ -310,6 +311,8 @@ func defaultBaseURL(providerType string) string { return "https://api.anthropic.com" case "openai": return "https://api.openai.com/v1" + case "atlascloud": + return "https://api.atlascloud.ai/v1" case "openrouter": return "https://openrouter.ai/api/v1" case "dashscope": diff --git a/cmd/setup_provider.go b/cmd/setup_provider.go index b50df6d5..73ed6e80 100644 --- a/cmd/setup_provider.go +++ b/cmd/setup_provider.go @@ -47,6 +47,7 @@ func addProvider() { typeOptions := []SelectOption[string]{ {"Anthropic", "anthropic"}, {"OpenAI", "openai"}, + {"Atlas Cloud", "atlascloud"}, {"OpenRouter", "openrouter"}, {"DashScope (Alibaba)", "dashscope"}, {"OpenAI-compatible", "openai_compat"}, @@ -68,8 +69,12 @@ func addProvider() { } baseURL := "" - if providerType == "openai_compat" { - baseURL, err = promptString("Base URL", "e.g. https://api.example.com/v1", "") + if providerType == "openai_compat" || providerType == "atlascloud" { + defaultURL := "" + if providerType == "atlascloud" { + defaultURL = "https://api.atlascloud.ai/v1" + } + baseURL, err = promptString("Base URL", "e.g. https://api.example.com/v1", defaultURL) if err != nil { return } diff --git a/docs/02-providers.md b/docs/02-providers.md index ed998e7f..162f6a99 100644 --- a/docs/02-providers.md +++ b/docs/02-providers.md @@ -96,6 +96,7 @@ Supported price units: input, output, cache read, cache write, reasoning, reques | Provider | API Base | Default Model | Notes | |----------|----------|---------------|-------| | openai | `https://api.openai.com/v1` | `gpt-4o` | | +| atlascloud | `https://api.atlascloud.ai/v1` | `qwen/qwen3.5-flash` | Atlas Cloud OpenAI-compatible LLM endpoint | | openrouter | `https://openrouter.ai/api/v1` | `anthropic/claude-sonnet-4-5-20250929` | Model must contain `/` | | groq | `https://api.groq.com/openai/v1` | `llama-3.3-70b-versatile` | | | deepseek | `https://api.deepseek.com/v1` | `deepseek-chat` | | diff --git a/internal/config/config_channels.go b/internal/config/config_channels.go index c87ee3d4..ff042cfe 100644 --- a/internal/config/config_channels.go +++ b/internal/config/config_channels.go @@ -255,6 +255,7 @@ type FeishuConfig struct { type ProvidersConfig struct { Anthropic ProviderConfig `json:"anthropic"` OpenAI ProviderConfig `json:"openai"` + AtlasCloud ProviderConfig `json:"atlascloud"` // Atlas Cloud (OpenAI-compatible endpoint) OpenRouter ProviderConfig `json:"openrouter"` Groq ProviderConfig `json:"groq"` Gemini ProviderConfig `json:"gemini"` @@ -331,6 +332,8 @@ func (p *ProvidersConfig) APIBaseForType(providerType string) string { return p.Anthropic.APIBase case "openai", "openai_compat": return p.OpenAI.APIBase + case "atlascloud": + return p.AtlasCloud.APIBase case "openrouter": return p.OpenRouter.APIBase case "groq": @@ -378,6 +381,7 @@ func (c *Config) HasAnyProvider() bool { p := c.Providers return p.Anthropic.APIKey != "" || p.OpenAI.APIKey != "" || + p.AtlasCloud.APIKey != "" || p.OpenRouter.APIKey != "" || p.Groq.APIKey != "" || p.Gemini.APIKey != "" || diff --git a/internal/config/config_load.go b/internal/config/config_load.go index a6befd5c..7fc174c7 100644 --- a/internal/config/config_load.go +++ b/internal/config/config_load.go @@ -159,6 +159,8 @@ func (c *Config) applyEnvOverrides() { envStr("GOCLAW_ANTHROPIC_BASE_URL", &c.Providers.Anthropic.APIBase) envStr("GOCLAW_OPENAI_API_KEY", &c.Providers.OpenAI.APIKey) envStr("GOCLAW_OPENAI_BASE_URL", &c.Providers.OpenAI.APIBase) + envStr("GOCLAW_ATLASCLOUD_API_KEY", &c.Providers.AtlasCloud.APIKey) + envStr("GOCLAW_ATLASCLOUD_BASE_URL", &c.Providers.AtlasCloud.APIBase) envStr("GOCLAW_OPENROUTER_API_KEY", &c.Providers.OpenRouter.APIKey) envStr("GOCLAW_GROQ_API_KEY", &c.Providers.Groq.APIKey) envStr("GOCLAW_DEEPSEEK_API_KEY", &c.Providers.DeepSeek.APIKey) diff --git a/internal/config/config_secrets.go b/internal/config/config_secrets.go index 49d02eed..ab35376c 100644 --- a/internal/config/config_secrets.go +++ b/internal/config/config_secrets.go @@ -23,6 +23,7 @@ func (c *Config) MaskedCopy() *Config { // Mask provider API keys maskNonEmpty(&cp.Providers.Anthropic.APIKey) maskNonEmpty(&cp.Providers.OpenAI.APIKey) + maskNonEmpty(&cp.Providers.AtlasCloud.APIKey) maskNonEmpty(&cp.Providers.OpenRouter.APIKey) maskNonEmpty(&cp.Providers.Groq.APIKey) maskNonEmpty(&cp.Providers.DeepSeek.APIKey) @@ -72,6 +73,7 @@ func (c *Config) StripSecrets() { // Provider API keys c.Providers.Anthropic.APIKey = "" c.Providers.OpenAI.APIKey = "" + c.Providers.AtlasCloud.APIKey = "" c.Providers.OpenRouter.APIKey = "" c.Providers.Groq.APIKey = "" c.Providers.DeepSeek.APIKey = "" @@ -126,6 +128,7 @@ func (c *Config) StripMaskedSecrets() { // Provider API keys stripIfMasked(&c.Providers.Anthropic.APIKey) stripIfMasked(&c.Providers.OpenAI.APIKey) + stripIfMasked(&c.Providers.AtlasCloud.APIKey) stripIfMasked(&c.Providers.OpenRouter.APIKey) stripIfMasked(&c.Providers.Groq.APIKey) stripIfMasked(&c.Providers.DeepSeek.APIKey) diff --git a/internal/http/provider_models.go b/internal/http/provider_models.go index 5a1f1a3d..6175db7c 100644 --- a/internal/http/provider_models.go +++ b/internal/http/provider_models.go @@ -142,6 +142,8 @@ func openAIModelsAPIBase(providerType, apiBase string) string { return base } switch providerType { + case store.ProviderAtlasCloud: + return store.AtlasCloudDefaultAPIBase case store.ProviderKimiCoding: return store.KimiCodingDefaultAPIBase default: diff --git a/internal/http/providers.go b/internal/http/providers.go index 69358ebe..0a7a0668 100644 --- a/internal/http/providers.go +++ b/internal/http/providers.go @@ -400,6 +400,11 @@ func openAIProviderDefaults(providerType, apiBase string) (string, string) { apiBase = store.MiniMaxDefaultAPIBase } return apiBase, store.MiniMaxDefaultModel + case store.ProviderAtlasCloud: + if apiBase == "" { + apiBase = store.AtlasCloudDefaultAPIBase + } + return apiBase, store.AtlasCloudDefaultModel default: return apiBase, "" } diff --git a/internal/store/provider_store.go b/internal/store/provider_store.go index 2f1a0e99..73be871d 100644 --- a/internal/store/provider_store.go +++ b/internal/store/provider_store.go @@ -36,6 +36,7 @@ const ( ProviderBytePlusCoding = "byteplus_coding" // BytePlus ModelArk Coding Plan ProviderVertex = "vertex" // Google Cloud Vertex AI (OAuth2 service account + ADC) ProviderKimiCoding = "kimi_coding" // Moonshot Kimi Coding (OpenAI-compat, requires fixed User-Agent) + ProviderAtlasCloud = "atlascloud" // Atlas Cloud (OpenAI-compatible endpoint) // MiniMax defaults. MiniMaxDefaultAPIBase = "https://api.minimax.io/v1" @@ -61,6 +62,10 @@ const ( KimiCodingDefaultAPIBase = "https://api.kimi.com/coding/v1" KimiCodingDefaultModel = "kimi-k2-turbo-preview" KimiCodingRequiredUserAgent = "claude-code/0.1.0" + + // Atlas Cloud defaults. + AtlasCloudDefaultAPIBase = "https://api.atlascloud.ai/v1" + AtlasCloudDefaultModel = "qwen/qwen3.5-flash" ) // Vertex AI constants live in internal/providers/vertex.go to avoid a store→providers import cycle @@ -96,6 +101,7 @@ var ValidProviderTypes = map[string]bool{ ProviderBytePlusCoding: true, ProviderVertex: true, ProviderKimiCoding: true, + ProviderAtlasCloud: true, } // VertexProviderSettings holds Vertex-specific config stored in llm_providers.settings JSONB.