From a4546ffb8f208f0b4b9f69262e20b3754c884279 Mon Sep 17 00:00:00 2001 From: Kyle D Date: Fri, 27 Feb 2026 03:02:07 +0000 Subject: [PATCH] feat: add Avian as a named LLM provider Add Avian (https://avian.io) as an OpenAI-compatible provider with API base https://api.avian.io/v1 and AVIAN_API_KEY env var support. Models: deepseek/deepseek-v3.2, moonshotai/kimi-k2.5, z-ai/glm-5, minimax/minimax-m2.5. Supports chat completions, streaming, and function calling. Changes: - Add Avian to ProvidersConfig struct, IsEmpty(), HasProvidersConfig() - Add avian protocol to factory provider and default API base - Add avian case to legacy provider selection (factory.go) - Add avian migration rule for old config format - Add default model entries to ModelList (deepseek-v3.2, kimi-k2.5) - Add avian to example config - Update AllProviders test count from 18 to 19 --- config/config.example.json | 4 ++++ pkg/config/config.go | 4 +++- pkg/config/defaults.go | 14 ++++++++++++++ pkg/config/migration.go | 17 +++++++++++++++++ pkg/config/migration_test.go | 1 + pkg/providers/factory.go | 16 ++++++++++++++++ pkg/providers/factory_provider.go | 4 +++- 7 files changed, 58 insertions(+), 2 deletions(-) diff --git a/config/config.example.json b/config/config.example.json index adae6f05c..1db97d0bb 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -222,6 +222,10 @@ "mistral": { "api_key": "", "api_base": "https://api.mistral.ai/v1" + }, + "avian": { + "api_key": "", + "api_base": "https://api.avian.io/v1" } }, "tools": { diff --git a/pkg/config/config.go b/pkg/config/config.go index cb2799bba..e50a5c3e8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -429,6 +429,7 @@ type ProvidersConfig struct { Antigravity ProviderConfig `json:"antigravity"` Qwen ProviderConfig `json:"qwen"` Mistral ProviderConfig `json:"mistral"` + Avian ProviderConfig `json:"avian"` } // IsEmpty checks if all provider configs are empty (no API keys or API bases set) @@ -452,7 +453,8 @@ func (p ProvidersConfig) IsEmpty() bool { p.GitHubCopilot.APIKey == "" && p.GitHubCopilot.APIBase == "" && p.Antigravity.APIKey == "" && p.Antigravity.APIBase == "" && p.Qwen.APIKey == "" && p.Qwen.APIBase == "" && - p.Mistral.APIKey == "" && p.Mistral.APIBase == "" + p.Mistral.APIKey == "" && p.Mistral.APIBase == "" && + p.Avian.APIKey == "" && p.Avian.APIBase == "" } // MarshalJSON implements custom JSON marshaling for ProvidersConfig diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 9fc09c5f1..5472dd94a 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -306,6 +306,20 @@ func DefaultConfig() *Config { APIKey: "", }, + // Avian - https://avian.io + { + ModelName: "deepseek-v3.2", + Model: "avian/deepseek/deepseek-v3.2", + APIBase: "https://api.avian.io/v1", + APIKey: "", + }, + { + ModelName: "kimi-k2.5", + Model: "avian/moonshotai/kimi-k2.5", + APIBase: "https://api.avian.io/v1", + APIKey: "", + }, + // VLLM (local) - http://localhost:8000 { ModelName: "local-model", diff --git a/pkg/config/migration.go b/pkg/config/migration.go index 772f714fd..4a17dd6c9 100644 --- a/pkg/config/migration.go +++ b/pkg/config/migration.go @@ -373,6 +373,23 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { }, true }, }, + { + providerNames: []string{"avian"}, + protocol: "avian", + buildConfig: func(p ProvidersConfig) (ModelConfig, bool) { + if p.Avian.APIKey == "" && p.Avian.APIBase == "" { + return ModelConfig{}, false + } + return ModelConfig{ + ModelName: "avian", + Model: "avian/deepseek/deepseek-v3.2", + APIKey: p.Avian.APIKey, + APIBase: p.Avian.APIBase, + Proxy: p.Avian.Proxy, + RequestTimeout: p.Avian.RequestTimeout, + }, true + }, + }, } // Process each provider migration diff --git a/pkg/config/migration_test.go b/pkg/config/migration_test.go index e24e9fa1d..dc86beb41 100644 --- a/pkg/config/migration_test.go +++ b/pkg/config/migration_test.go @@ -160,6 +160,7 @@ func TestConvertProvidersToModelList_AllProviders(t *testing.T) { Antigravity: ProviderConfig{AuthMethod: "oauth"}, Qwen: ProviderConfig{APIKey: "key17"}, Mistral: ProviderConfig{APIKey: "key18"}, + Avian: ProviderConfig{APIKey: "key19"}, }, } diff --git a/pkg/providers/factory.go b/pkg/providers/factory.go index 5b3e42b9e..a0d09a835 100644 --- a/pkg/providers/factory.go +++ b/pkg/providers/factory.go @@ -181,6 +181,15 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) { sel.model = "deepseek-chat" } } + case "avian": + if cfg.Providers.Avian.APIKey != "" { + sel.apiKey = cfg.Providers.Avian.APIKey + sel.apiBase = cfg.Providers.Avian.APIBase + sel.proxy = cfg.Providers.Avian.Proxy + if sel.apiBase == "" { + sel.apiBase = "https://api.avian.io/v1" + } + } case "mistral": if cfg.Providers.Mistral.APIKey != "" { sel.apiKey = cfg.Providers.Mistral.APIKey @@ -300,6 +309,13 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) { if sel.apiBase == "" { sel.apiBase = "https://api.mistral.ai/v1" } + case strings.HasPrefix(model, "avian/") && cfg.Providers.Avian.APIKey != "": + sel.apiKey = cfg.Providers.Avian.APIKey + sel.apiBase = cfg.Providers.Avian.APIBase + sel.proxy = cfg.Providers.Avian.Proxy + if sel.apiBase == "" { + sel.apiBase = "https://api.avian.io/v1" + } case cfg.Providers.VLLM.APIBase != "": sel.apiKey = cfg.Providers.VLLM.APIKey sel.apiBase = cfg.Providers.VLLM.APIBase diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index 155317a3b..c05fb0ad4 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -94,7 +94,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", - "volcengine", "vllm", "qwen", "mistral": + "volcengine", "vllm", "qwen", "mistral", "avian": // All other OpenAI-compatible HTTP providers if cfg.APIKey == "" && cfg.APIBase == "" { return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol) @@ -208,6 +208,8 @@ func getDefaultAPIBase(protocol string) string { return "http://localhost:8000/v1" case "mistral": return "https://api.mistral.ai/v1" + case "avian": + return "https://api.avian.io/v1" default: return "" }