From 06efabf2de33db3c73afe7d928c08c755600ef2d Mon Sep 17 00:00:00 2001 From: Bob Yang Date: Fri, 6 Mar 2026 11:40:56 -0800 Subject: [PATCH] feat: Introduce core configuration management with an example file and a provider factory. --- config/config.example.json | 10 +++++++++- pkg/config/config.go | 10 ++++++++-- pkg/providers/factory_provider.go | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/config/config.example.json b/config/config.example.json index 2f643d41b..fddcd5f94 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -46,6 +46,14 @@ "model": "openai/gpt-5.2", "api_key": "sk-key2", "api_base": "https://api2.example.com/v1" + }, + { + "_comment": "Cloudflare AI example: use 'provider' field when model name contains special characters like @cf/", + "model_name": "cf_qwen", + "model": "@cf/qwen/qwen3-30b-a3b-fp8", + "provider": "own", + "api_key": "YOUR_CLOUDFLARE_API_TOKEN", + "api_base": "https://api.cloudflare.com/client/v4/accounts/[YOUR_ACCOUNT_ID]/ai/v1" } ], "channels": { @@ -423,4 +431,4 @@ "host": "127.0.0.1", "port": 18790 } -} +} \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go index 72af3e2fb..5c7b14aa2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -501,10 +501,16 @@ type OpenAIProviderConfig struct { // The model field uses protocol prefix format: [protocol/]model-identifier // Supported protocols: openai, anthropic, antigravity, claude-cli, codex-cli, github-copilot // Default protocol is "openai" if no prefix is specified. +// +// The optional Provider field explicitly sets the protocol/provider, overriding the +// prefix parsed from the Model field. This is useful when the model identifier +// itself contains slashes that would be misinterpreted as a protocol prefix +// (e.g. Cloudflare AI models like "@cf/qwen/qwen3-30b-a3b-fp8"). type ModelConfig struct { // Required fields - ModelName string `json:"model_name"` // User-facing alias for the model - Model string `json:"model"` // Protocol/model-identifier (e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4.6") + ModelName string `json:"model_name"` // User-facing alias for the model + Model string `json:"model"` // Model identifier, optionally prefixed with protocol (e.g., "openai/gpt-4o") + Provider string `json:"provider,omitempty"` // Explicit protocol/provider override (e.g., "openai", "anthropic", "cloudflare", "own") // HTTP-based providers APIBase string `json:"api_base,omitempty"` // API endpoint URL diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index c05fb0ad4..7d79c3e38 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -42,6 +42,10 @@ func createCodexAuthProvider() (LLMProvider, error) { // - "openai/gpt-4o" -> ("openai", "gpt-4o") // - "anthropic/claude-sonnet-4.6" -> ("anthropic", "claude-sonnet-4.6") // - "gpt-4o" -> ("openai", "gpt-4o") // default protocol +// +// Note: use CreateProviderFromConfig when a ModelConfig is available, as it +// respects the explicit Provider override field (needed for models whose +// identifiers contain slashes, e.g. Cloudflare "@cf/qwen/qwen3-30b-a3b-fp8"). func ExtractProtocol(model string) (protocol, modelID string) { model = strings.TrimSpace(model) protocol, modelID, found := strings.Cut(model, "/") @@ -64,7 +68,17 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err return nil, "", fmt.Errorf("model is required") } - protocol, modelID := ExtractProtocol(cfg.Model) + // If Provider is explicitly set, use it as the protocol and treat the entire + // Model string as the model identifier (no prefix stripping). This handles + // models whose names contain slashes that would otherwise be misinterpreted + // as a protocol prefix (e.g. Cloudflare AI: "@cf/qwen/qwen3-30b-a3b-fp8"). + var protocol, modelID string + if cfg.Provider != "" { + protocol = strings.ToLower(strings.TrimSpace(cfg.Provider)) + modelID = cfg.Model + } else { + protocol, modelID = ExtractProtocol(cfg.Model) + } switch protocol { case "openai":