From 7e2050345f13e8368ef04d0da8a40c793977d7a0 Mon Sep 17 00:00:00 2001 From: mxrain Date: Thu, 12 Feb 2026 18:27:49 +0800 Subject: [PATCH] feat: add Moonshot/Kimi and NVIDIA provider support with proxy --- config.example.json | 9 +++++ pkg/config/config.go | 11 ++++-- pkg/providers/http_provider.go | 68 +++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/config.example.json b/config.example.json index db2bc180d..14d563a47 100644 --- a/config.example.json +++ b/config.example.json @@ -80,6 +80,15 @@ "vllm": { "api_key": "", "api_base": "" + }, + "nvidia": { + "api_key": "nvapi-xxx", + "api_base": "", + "proxy": "http://127.0.0.1:7890" + }, + "moonshot": { + "api_key": "sk-xxx", + "api_base": "" } }, "tools": { diff --git a/pkg/config/config.go b/pkg/config/config.go index ebca58949..fc537b344 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -125,9 +125,9 @@ type DingTalkConfig struct { } type SlackConfig struct { - Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_SLACK_ENABLED"` - BotToken string `json:"bot_token" env:"PICOCLAW_CHANNELS_SLACK_BOT_TOKEN"` - AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_SLACK_APP_TOKEN"` + Enabled bool `json:"enabled" env:"PICOCLAW_CHANNELS_SLACK_ENABLED"` + BotToken string `json:"bot_token" env:"PICOCLAW_CHANNELS_SLACK_BOT_TOKEN"` + AppToken string `json:"app_token" env:"PICOCLAW_CHANNELS_SLACK_APP_TOKEN"` AllowFrom []string `json:"allow_from" env:"PICOCLAW_CHANNELS_SLACK_ALLOW_FROM"` } @@ -139,11 +139,14 @@ type ProvidersConfig struct { Zhipu ProviderConfig `json:"zhipu"` VLLM ProviderConfig `json:"vllm"` Gemini ProviderConfig `json:"gemini"` + Nvidia ProviderConfig `json:"nvidia"` + Moonshot ProviderConfig `json:"moonshot"` } type ProviderConfig struct { APIKey string `json:"api_key" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_KEY"` APIBase string `json:"api_base" env:"PICOCLAW_PROVIDERS_{{.Name}}_API_BASE"` + Proxy string `json:"proxy,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_PROXY"` AuthMethod string `json:"auth_method,omitempty" env:"PICOCLAW_PROVIDERS_{{.Name}}_AUTH_METHOD"` } @@ -233,6 +236,8 @@ func DefaultConfig() *Config { Zhipu: ProviderConfig{}, VLLM: ProviderConfig{}, Gemini: ProviderConfig{}, + Nvidia: ProviderConfig{}, + Moonshot: ProviderConfig{}, }, Gateway: GatewayConfig{ Host: "0.0.0.0", diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index f63c68c0b..6f188e2f0 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -13,6 +13,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strings" "github.com/sipeed/picoclaw/pkg/auth" @@ -25,13 +26,24 @@ type HTTPProvider struct { httpClient *http.Client } -func NewHTTPProvider(apiKey, apiBase string) *HTTPProvider { +func NewHTTPProvider(apiKey, apiBase, proxy string) *HTTPProvider { + client := &http.Client{ + Timeout: 0, + } + + if proxy != "" { + proxyURL, err := url.Parse(proxy) + if err == nil { + client.Transport = &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + } + } + } + return &HTTPProvider{ - apiKey: apiKey, - apiBase: apiBase, - httpClient: &http.Client{ - Timeout: 0, - }, + apiKey: apiKey, + apiBase: apiBase, + httpClient: client, } } @@ -40,6 +52,14 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too return nil, fmt.Errorf("API base not configured") } + // Strip provider prefix from model name (e.g., moonshot/kimi-k2.5 -> kimi-k2.5) + if idx := strings.Index(model, "/"); idx != -1 { + prefix := model[:idx] + if prefix == "moonshot" || prefix == "nvidia" { + model = model[idx+1:] + } + } + requestBody := map[string]interface{}{ "model": model, "messages": messages, @@ -60,7 +80,13 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too } if temperature, ok := options["temperature"].(float64); ok { - requestBody["temperature"] = temperature + lowerModel := strings.ToLower(model) + // Kimi k2 models only support temperature=1 + if strings.Contains(lowerModel, "kimi") && strings.Contains(lowerModel, "k2") { + requestBody["temperature"] = 1.0 + } else { + requestBody["temperature"] = temperature + } } jsonData, err := json.Marshal(requestBody) @@ -195,13 +221,22 @@ func createCodexAuthProvider() (LLMProvider, error) { func CreateProvider(cfg *config.Config) (LLMProvider, error) { model := cfg.Agents.Defaults.Model - var apiKey, apiBase string + var apiKey, apiBase, proxy string lowerModel := strings.ToLower(model) switch { + case (strings.Contains(lowerModel, "kimi") || strings.Contains(lowerModel, "moonshot") || strings.HasPrefix(model, "moonshot/")) && cfg.Providers.Moonshot.APIKey != "": + apiKey = cfg.Providers.Moonshot.APIKey + apiBase = cfg.Providers.Moonshot.APIBase + proxy = cfg.Providers.Moonshot.Proxy + if apiBase == "" { + apiBase = "https://api.moonshot.cn/v1" + } + case strings.HasPrefix(model, "openrouter/") || strings.HasPrefix(model, "anthropic/") || strings.HasPrefix(model, "openai/") || strings.HasPrefix(model, "meta-llama/") || strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "google/"): apiKey = cfg.Providers.OpenRouter.APIKey + proxy = cfg.Providers.OpenRouter.Proxy if cfg.Providers.OpenRouter.APIBase != "" { apiBase = cfg.Providers.OpenRouter.APIBase } else { @@ -214,6 +249,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } apiKey = cfg.Providers.Anthropic.APIKey apiBase = cfg.Providers.Anthropic.APIBase + proxy = cfg.Providers.Anthropic.Proxy if apiBase == "" { apiBase = "https://api.anthropic.com/v1" } @@ -224,6 +260,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } apiKey = cfg.Providers.OpenAI.APIKey apiBase = cfg.Providers.OpenAI.APIBase + proxy = cfg.Providers.OpenAI.Proxy if apiBase == "" { apiBase = "https://api.openai.com/v1" } @@ -231,6 +268,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers.Gemini.APIKey != "": apiKey = cfg.Providers.Gemini.APIKey apiBase = cfg.Providers.Gemini.APIBase + proxy = cfg.Providers.Gemini.Proxy if apiBase == "" { apiBase = "https://generativelanguage.googleapis.com/v1beta" } @@ -238,6 +276,7 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { case (strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai")) && cfg.Providers.Zhipu.APIKey != "": apiKey = cfg.Providers.Zhipu.APIKey apiBase = cfg.Providers.Zhipu.APIBase + proxy = cfg.Providers.Zhipu.Proxy if apiBase == "" { apiBase = "https://open.bigmodel.cn/api/paas/v4" } @@ -245,17 +284,28 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { case (strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/")) && cfg.Providers.Groq.APIKey != "": apiKey = cfg.Providers.Groq.APIKey apiBase = cfg.Providers.Groq.APIBase + proxy = cfg.Providers.Groq.Proxy if apiBase == "" { apiBase = "https://api.groq.com/openai/v1" } + case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers.Nvidia.APIKey != "": + apiKey = cfg.Providers.Nvidia.APIKey + apiBase = cfg.Providers.Nvidia.APIBase + proxy = cfg.Providers.Nvidia.Proxy + if apiBase == "" { + apiBase = "https://integrate.api.nvidia.com/v1" + } + case cfg.Providers.VLLM.APIBase != "": apiKey = cfg.Providers.VLLM.APIKey apiBase = cfg.Providers.VLLM.APIBase + proxy = cfg.Providers.VLLM.Proxy default: if cfg.Providers.OpenRouter.APIKey != "" { apiKey = cfg.Providers.OpenRouter.APIKey + proxy = cfg.Providers.OpenRouter.Proxy if cfg.Providers.OpenRouter.APIBase != "" { apiBase = cfg.Providers.OpenRouter.APIBase } else { @@ -274,5 +324,5 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { return nil, fmt.Errorf("no API base configured for provider (model: %s)", model) } - return NewHTTPProvider(apiKey, apiBase), nil + return NewHTTPProvider(apiKey, apiBase, proxy), nil } \ No newline at end of file