From 40a7b96e1f19c43ce91cfa963e19b87ddf7f0a2f Mon Sep 17 00:00:00 2001 From: blackbird-merula Date: Mon, 10 Nov 2025 15:34:24 +0100 Subject: [PATCH] feat: add synthetic provider --- config.example.yaml | 6 ++++ config/config.go | 13 ++++++++- internal/ai_client.go | 11 +++++++- internal/config_helpers.go | 56 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index 70427e1..69835da 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -52,6 +52,12 @@ models: api_version: "2025-04-01-preview" deployment_name: "gpt-4o" + synthetic-glm-4-6: + provider: "synthetic" + model: "hf:zai-org/GLM-4.6" + api_key: "your-synthetic-api-key" + base_url: "https://api.synthetic.new/openai/v1" + # Confirm before AI executes a command exec_confirm: true diff --git a/config/config.go b/config/config.go index e871204..423371a 100644 --- a/config/config.go +++ b/config/config.go @@ -23,7 +23,8 @@ type Config struct { BlacklistPatterns []string `mapstructure:"blacklist_patterns"` OpenRouter OpenRouterConfig `mapstructure:"openrouter"` OpenAI OpenAIConfig `mapstructure:"openai"` - AzureOpenAI AzureOpenAIConfig `mapstructure:"azure_openai"` + AzureOpenAI AzureOpenAIConfig `mapstructure:"azure_open_ai"` + Synthetic SyntheticConfig `mapstructure:"synthetic"` DefaultModel string `mapstructure:"default_model"` Models map[string]ModelConfig `mapstructure:"models"` Prompts PromptsConfig `mapstructure:"prompts"` @@ -52,6 +53,13 @@ type AzureOpenAIConfig struct { DeploymentName string `mapstructure:"deployment_name"` } +// SyntheticConfig holds Synthetic API configuration +type SyntheticConfig struct { + APIKey string `mapstructure:"api_key"` + Model string `mapstructure:"model"` + BaseURL string `mapstructure:"base_url"` +} + // ModelConfig holds a single model configuration type ModelConfig struct { @@ -100,6 +108,9 @@ func DefaultConfig() *Config { BaseURL: "https://api.openai.com/v1", }, AzureOpenAI: AzureOpenAIConfig{}, + Synthetic: SyntheticConfig{ + BaseURL: "https://api.synthetic.new/openai/v1", + }, DefaultModel: "", Models: make(map[string]ModelConfig), Prompts: PromptsConfig{ diff --git a/internal/ai_client.go b/internal/ai_client.go index 7cb5fb6..91c2817 100644 --- a/internal/ai_client.go +++ b/internal/ai_client.go @@ -131,7 +131,7 @@ func (c *AiClient) determineAPIType(model string) string { return "responses" case "azure": return "azure" - case "openrouter": + case "openrouter", "synthetic": return "openrouter" default: return "openrouter" @@ -150,6 +150,11 @@ func (c *AiClient) determineAPIType(model string) string { return "azure" } + // If Synthetic is configured, use OpenRouter-compatible Chat Completions + if c.config.Synthetic.APIKey != "" { + return "openrouter" + } + // Default to OpenRouter Chat Completions return "openrouter" } @@ -239,6 +244,10 @@ func (c *AiClient) ChatCompletion(ctx context.Context, messages []Message, model apiBase = c.config.AzureOpenAI.APIBase apiVersion = c.config.AzureOpenAI.APIVersion deploymentName = c.config.AzureOpenAI.DeploymentName + } else if c.config.Synthetic.APIKey != "" { + provider = "synthetic" + apiKey = c.config.Synthetic.APIKey + baseURL = c.config.Synthetic.BaseURL } else if c.config.OpenRouter.APIKey != "" { provider = "openrouter" apiKey = c.config.OpenRouter.APIKey diff --git a/internal/config_helpers.go b/internal/config_helpers.go index 5dcf385..0ef139d 100644 --- a/internal/config_helpers.go +++ b/internal/config_helpers.go @@ -24,6 +24,9 @@ var AllowedConfigKeys = []string{ "azure_openai.deployment_name", "azure_openai.api_base", "azure_openai.api_version", + "synthetic.api_key", + "synthetic.model", + "synthetic.base_url", "default_model", } @@ -143,6 +146,36 @@ func (m *Manager) GetAzureOpenAIDeploymentName() string { return m.Config.AzureOpenAI.DeploymentName } +// GetSyntheticModel returns the Synthetic model value with session override if present +func (m *Manager) GetSyntheticModel() string { + if override, exists := m.SessionOverrides["synthetic.model"]; exists { + if val, ok := override.(string); ok { + return val + } + } + return m.Config.Synthetic.Model +} + +// GetSyntheticAPIKey returns the Synthetic API key value with session override if present +func (m *Manager) GetSyntheticAPIKey() string { + if override, exists := m.SessionOverrides["synthetic.api_key"]; exists { + if val, ok := override.(string); ok { + return val + } + } + return m.Config.Synthetic.APIKey +} + +// GetSyntheticBaseURL returns the Synthetic base URL value with session override if present +func (m *Manager) GetSyntheticBaseURL() string { + if override, exists := m.SessionOverrides["synthetic.base_url"]; exists { + if val, ok := override.(string); ok { + return val + } + } + return m.Config.Synthetic.BaseURL +} + // GetModelsDefault returns the default model configuration name with session override if present func (m *Manager) GetModelsDefault() string { // Check for session override first @@ -231,12 +264,12 @@ func (m *Manager) hasValidAIConfiguration() bool { } // Fall back to legacy configuration - return m.Config.OpenRouter.APIKey != "" || m.Config.OpenAI.APIKey != "" || m.Config.AzureOpenAI.APIKey != "" + return m.Config.OpenRouter.APIKey != "" || m.Config.OpenAI.APIKey != "" || m.Config.AzureOpenAI.APIKey != "" || m.Config.Synthetic.APIKey != "" } // getLegacyModelConfig converts the legacy provider configuration to a ModelConfig func (m *Manager) getLegacyModelConfig() config.ModelConfig { - // Priority: OpenAI > Azure > OpenRouter + // Priority: OpenAI > Azure > Synthetic > OpenRouter if m.GetOpenAIAPIKey() != "" { return config.ModelConfig{ Provider: "openai", @@ -257,6 +290,15 @@ func (m *Manager) getLegacyModelConfig() config.ModelConfig { } } + if m.GetSyntheticAPIKey() != "" { + return config.ModelConfig{ + Provider: "synthetic", + Model: m.GetSyntheticModel(), + APIKey: m.GetSyntheticAPIKey(), + BaseURL: m.GetSyntheticBaseURL(), + } + } + return config.ModelConfig{ Provider: "openrouter", Model: m.GetOpenRouterModel(), @@ -292,6 +334,16 @@ func (m *Manager) GetModel() string { return "gpt-4o" } + // If Synthetic is configured, use Synthetic model + if m.GetSyntheticAPIKey() != "" { + model := m.GetSyntheticModel() + if model != "" { + return model + } + // Default model for Synthetic if not specified + return "hf:zai-org/GLM-4.6" + } + // Default to OpenRouter return m.GetOpenRouterModel() }