diff --git a/config/config.go b/config/config.go index 33640a3..904032a 100644 --- a/config/config.go +++ b/config/config.go @@ -337,6 +337,7 @@ const ( DefaultAnthropicBaseURL = "https://api.anthropic.com" DefaultGeminiBaseURL = "https://generativelanguage.googleapis.com/v1beta" DefaultShengSuanYunBaseURL = "https://router.shengsuanyun.com/api/v1" + DefaultNovitaBaseURL = "https://api.novita.ai/openai" DefaultOllamaBaseURL = "http://localhost:11434/v1" DefaultWebFetchMaxResponseBytes = int64(500_000) DefaultWebFetchTimeoutSec = 30 @@ -811,6 +812,8 @@ func (cfg *Config) ApplyLLMRouting() (provider string, configuredModel string) { cfg.LLM.BaseURL = DefaultOllamaBaseURL case "shengsuanyun": cfg.LLM.BaseURL = DefaultShengSuanYunBaseURL + case "novita": + cfg.LLM.BaseURL = DefaultNovitaBaseURL } } @@ -822,6 +825,8 @@ func (cfg *Config) ApplyLLMRouting() (provider string, configuredModel string) { cfg.LLM.APIKey = strings.TrimSpace(cfg.Env["OPENROUTER_API_KEY"]) case "shengsuanyun": cfg.LLM.APIKey = strings.TrimSpace(cfg.Env["SHENGSUANYUN_API_KEY"]) + case "novita": + cfg.LLM.APIKey = strings.TrimSpace(cfg.Env["NOVITA_API_KEY"]) case "anthropic": cfg.LLM.APIKey = strings.TrimSpace(cfg.Env["ANTHROPIC_API_KEY"]) case "gemini": @@ -849,6 +854,9 @@ func parseRoutedModel(s string) (provider string, model string) { if after, ok := strings.CutPrefix(s, "shengsuanyun/"); ok { return "shengsuanyun", after } + if after, ok := strings.CutPrefix(s, "novita/"); ok { + return "novita", after + } if after, ok := strings.CutPrefix(s, "anthropic/"); ok { return "anthropic", after } diff --git a/config/config_test.go b/config/config_test.go index a220ce6..35c5bf1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -225,6 +225,31 @@ func TestApplyLLMRouting_LocalAlias(t *testing.T) { } } +func TestApplyLLMRouting_Novita(t *testing.T) { + cfg := Default() + cfg.Env["NOVITA_API_KEY"] = "sk-novita-123" + cfg.Agents.Defaults.Model = "novita/moonshotai/kimi-k2.5" + cfg.LLM.BaseURL = "" + cfg.LLM.APIKey = "" + + provider, configured := cfg.ApplyLLMRouting() + if provider != "novita" { + t.Fatalf("provider=%q", provider) + } + if configured != "novita/moonshotai/kimi-k2.5" { + t.Fatalf("configured=%q", configured) + } + if cfg.LLM.BaseURL != DefaultNovitaBaseURL { + t.Fatalf("baseURL=%q", cfg.LLM.BaseURL) + } + if cfg.LLM.APIKey != "sk-novita-123" { + t.Fatalf("apiKey=%q", cfg.LLM.APIKey) + } + if cfg.LLM.Model != "moonshotai/kimi-k2.5" { + t.Fatalf("model=%q", cfg.LLM.Model) + } +} + func TestLoad_MediaDefaults(t *testing.T) { cfg := Default() cfg.Tools.Media = MediaToolsConfig{} diff --git a/llm/client.go b/llm/client.go index 3561ed9..0d9f3da 100644 --- a/llm/client.go +++ b/llm/client.go @@ -42,7 +42,7 @@ func (c *Client) Chat(ctx context.Context, messages []Message, tools []ToolDefin c.HTTP = &http.Client{Timeout: 120 * time.Second} } switch normalizeProvider(c.Provider) { - case "", "openai", "openrouter", "ollama","shengsuanyun": + case "", "openai", "openrouter", "ollama", "shengsuanyun", "novita": return c.chatOpenAICompatible(ctx, messages, tools) case "anthropic": return c.chatAnthropic(ctx, messages, tools)