From 85186fbe46513c9b0ac25fb635124d5782bcf92d Mon Sep 17 00:00:00 2001 From: Badgerbees Date: Thu, 2 Apr 2026 07:12:15 +0700 Subject: [PATCH] Providers: add xAI compat support --- README.md | 1 + config/config.example.json | 5 +++++ docs/providers.md | 1 + docs/zh/providers.md | 2 ++ pkg/config/defaults.go | 7 +++++++ pkg/providers/factory_provider.go | 4 ++++ pkg/providers/factory_provider_test.go | 12 ++++++++++++ pkg/providers/model_ref.go | 2 ++ pkg/providers/model_ref_test.go | 3 +++ pkg/providers/openai_compat/provider.go | 1 + pkg/providers/openai_compat/provider_test.go | 8 ++++++++ 11 files changed, 46 insertions(+) diff --git a/README.md b/README.md index db38e644f..961f07726 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,7 @@ PicoClaw supports 30+ LLM providers through the `model_list` configuration. Use | [Groq](https://console.groq.com/keys) | `groq/` | Required | Fast inference (Llama, Mixtral) | | [Moonshot (Kimi)](https://platform.moonshot.cn/console/api-keys) | `moonshot/` | Required | Kimi models | | [Minimax](https://platform.minimaxi.com/user-center/basic-information/interface-key) | `minimax/` | Required | MiniMax models | +| [xAI](https://console.x.ai/) | `xai/` | Required | Grok models | | [Mistral](https://console.mistral.ai/api-keys) | `mistral/` | Required | Mistral Large, Codestral | | [NVIDIA NIM](https://build.nvidia.com/) | `nvidia/` | Required | NVIDIA hosted models | | [Cerebras](https://cloud.cerebras.ai/) | `cerebras/` | Required | Fast inference | diff --git a/config/config.example.json b/config/config.example.json index bedd543d7..6ac79d7bc 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -53,6 +53,11 @@ "model": "venice/venice-uncensored", "api_key": "your-venice-api-key" }, + { + "model_name": "grok-4", + "model": "xai/grok-4", + "api_key": "your-xai-api-key" + }, { "model_name": "lmstudio-local", "model": "lmstudio/openai/gpt-oss-20b" diff --git a/docs/providers.md b/docs/providers.md index b0dfa0bc8..dc9c94790 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -25,6 +25,7 @@ | `nvidia` | LLM (NVIDIA NIM) | [build.nvidia.com](https://build.nvidia.com) | | `moonshot` | LLM (Kimi/Moonshot direct) | [platform.moonshot.cn](https://platform.moonshot.cn) | | `minimax` | LLM (Minimax direct) | [platform.minimaxi.com](https://platform.minimaxi.com) | +| `xai` | LLM (xAI Grok direct) | [console.x.ai](https://console.x.ai) | | `avian` | LLM (Avian direct) | [avian.io](https://avian.io) | | `mistral` | LLM (Mistral direct) | [console.mistral.ai](https://console.mistral.ai) | | `longcat` | LLM (Longcat direct) | [longcat.ai](https://longcat.ai) | diff --git a/docs/zh/providers.md b/docs/zh/providers.md index 43c4f26db..23b3be2a6 100644 --- a/docs/zh/providers.md +++ b/docs/zh/providers.md @@ -16,6 +16,7 @@ | `anthropic` | LLM (Claude 直连) | [console.anthropic.com](https://console.anthropic.com) | | `openai` | LLM (GPT 直连) | [platform.openai.com](https://platform.openai.com) | | `venice` | LLM (Venice AI 直连) | [venice.ai](https://venice.ai) | +| `xai` | LLM (xAI Grok 直连) | [console.x.ai](https://console.x.ai) | | `deepseek` | LLM (DeepSeek 直连) | [platform.deepseek.com](https://platform.deepseek.com) | | `qwen` | LLM (通义千问) | [dashscope.console.aliyun.com](https://dashscope.console.aliyun.com) | | `groq` | LLM + **语音转录** (Whisper) | [console.groq.com](https://console.groq.com) | @@ -46,6 +47,7 @@ | ------------------- | ----------------- | --------------------------------------------------- | --------- | ----------------------------------------------------------------- | | **OpenAI** | `openai/` | `https://api.openai.com/v1` | OpenAI | [获取密钥](https://platform.openai.com) | | **Venice AI** | `venice/` | `https://api.venice.ai/api/v1` | OpenAI | [获取密钥](https://venice.ai) | +| **xAI** | `xai/` | `https://api.x.ai/v1` | OpenAI | [获取密钥](https://console.x.ai) | | **Anthropic** | `anthropic/` | `https://api.anthropic.com/v1` | Anthropic | [获取密钥](https://console.anthropic.com) | | **智谱 AI (GLM)** | `zhipu/` | `https://open.bigmodel.cn/api/paas/v4` | OpenAI | [获取密钥](https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys) | | **DeepSeek** | `deepseek/` | `https://api.deepseek.com/v1` | OpenAI | [获取密钥](https://platform.deepseek.com) | diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index a9a107975..c47244ab7 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -192,6 +192,13 @@ func DefaultConfig() *Config { APIBase: "https://api.venice.ai/api/v1", }, + // xAI - https://console.x.ai/ + { + ModelName: "grok-4", + Model: "xai/grok-4", + APIBase: "https://api.x.ai/v1", + }, + // Google Gemini - https://ai.google.dev/ { ModelName: "gemini-2.0-flash", diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index fb5191bf8..05f081266 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -40,6 +40,9 @@ var protocolMetaByName = map[string]protocolMeta{ "cerebras": {defaultAPIBase: "https://api.cerebras.ai/v1"}, "vivgrid": {defaultAPIBase: "https://api.vivgrid.com/v1"}, "volcengine": {defaultAPIBase: "https://ark.cn-beijing.volces.com/api/v3"}, + "xai": {defaultAPIBase: "https://api.x.ai/v1"}, + "x-ai": {defaultAPIBase: "https://api.x.ai/v1"}, + "x.ai": {defaultAPIBase: "https://api.x.ai/v1"}, "qwen": {defaultAPIBase: "https://dashscope.aliyuncs.com/compatible-mode/v1"}, "qwen-intl": {defaultAPIBase: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"}, "qwen-international": {defaultAPIBase: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"}, @@ -214,6 +217,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", "vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl", "qwen-us", "dashscope-us", "mistral", "avian", "longcat", "modelscope", "novita", + "xai", "x-ai", "x.ai", "coding-plan", "alibaba-coding", "qwen-coding", "mimo": // All other OpenAI-compatible HTTP providers if cfg.APIKey() == "" && cfg.APIBase == "" && !isEmptyAPIKeyAllowed(protocol) { diff --git a/pkg/providers/factory_provider_test.go b/pkg/providers/factory_provider_test.go index e2eafb934..95cf58bf8 100644 --- a/pkg/providers/factory_provider_test.go +++ b/pkg/providers/factory_provider_test.go @@ -118,6 +118,9 @@ func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) { {"openrouter", "openrouter"}, {"cerebras", "cerebras"}, {"vivgrid", "vivgrid"}, + {"xai", "xai"}, + {"x-ai", "x-ai"}, + {"x.ai", "x.ai"}, {"qwen", "qwen"}, {"vllm", "vllm"}, {"deepseek", "deepseek"}, @@ -751,6 +754,15 @@ func TestGetDefaultAPIBase_QwenUSAliases(t *testing.T) { } } +func TestGetDefaultAPIBase_XAI(t *testing.T) { + expectedURL := "https://api.x.ai/v1" + for _, protocol := range []string{"xai", "x-ai", "x.ai"} { + if got := getDefaultAPIBase(protocol); got != expectedURL { + t.Fatalf("getDefaultAPIBase(%q) = %q, want %q", protocol, got, expectedURL) + } + } +} + func TestCreateProviderFromConfig_MinimaxInjectsReasoningSplit(t *testing.T) { var requestBody map[string]any diff --git a/pkg/providers/model_ref.go b/pkg/providers/model_ref.go index be9f63bc6..a1147e94a 100644 --- a/pkg/providers/model_ref.go +++ b/pkg/providers/model_ref.go @@ -53,6 +53,8 @@ func NormalizeProvider(provider string) string { return "zhipu" case "google": return "gemini" + case "x-ai", "x.ai": + return "xai" case "alibaba-coding", "qwen-coding": return "coding-plan" case "alibaba-coding-anthropic": diff --git a/pkg/providers/model_ref_test.go b/pkg/providers/model_ref_test.go index 040c511ba..3fc1f2300 100644 --- a/pkg/providers/model_ref_test.go +++ b/pkg/providers/model_ref_test.go @@ -73,6 +73,9 @@ func TestNormalizeProvider(t *testing.T) { {"glm", "zhipu"}, {"google", "gemini"}, {"groq", "groq"}, + {"xai", "xai"}, + {"x-ai", "xai"}, + {"x.ai", "xai"}, // Alibaba Coding Plan aliases {"alibaba-coding", "coding-plan"}, {"qwen-coding", "coding-plan"}, diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index 4ff42506f..6c37d78bd 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -53,6 +53,7 @@ var stripModelPrefixProviders = map[string]struct{}{ "google": {}, "openrouter": {}, "zhipu": {}, + "xai": {}, "mistral": {}, "vivgrid": {}, "minimax": {}, diff --git a/pkg/providers/openai_compat/provider_test.go b/pkg/providers/openai_compat/provider_test.go index 30aa76eb3..8da23e8eb 100644 --- a/pkg/providers/openai_compat/provider_test.go +++ b/pkg/providers/openai_compat/provider_test.go @@ -484,6 +484,11 @@ func TestProviderChat_StripsKnownProviderPrefixes(t *testing.T) { input: "venice/venice-uncensored", wantModel: "venice-uncensored", }, + { + name: "strips xai prefix", + input: "xai/grok-4", + wantModel: "grok-4", + }, { name: "strips deepseek prefix", input: "deepseek/deepseek-chat", @@ -595,6 +600,9 @@ func TestNormalizeModel_UsesAPIBase(t *testing.T) { if got := normalizeModel("venice/venice-uncensored", "https://api.venice.ai/api/v1"); got != "venice-uncensored" { t.Fatalf("normalizeModel(venice) = %q, want %q", got, "venice-uncensored") } + if got := normalizeModel("xai/grok-4", "https://api.x.ai/v1"); got != "grok-4" { + t.Fatalf("normalizeModel(xai) = %q, want %q", got, "grok-4") + } if got := normalizeModel("openrouter/auto", "https://openrouter.ai/api/v1"); got != "openrouter/auto" { t.Fatalf("normalizeModel(openrouter) = %q, want %q", got, "openrouter/auto") }