// PicoClaw - Ultra-lightweight personal AI agent // License: MIT // // Copyright (c) 2026 PicoClaw contributors package providers import ( "fmt" "strings" "github.com/sipeed/picoclaw/pkg/config" ) // ExtractProtocol extracts the protocol prefix and model identifier from a model string. // If no prefix is specified, it defaults to "openai". // Examples: // - "openai/gpt-4o" -> ("openai", "gpt-4o") // - "anthropic/claude-3" -> ("anthropic", "claude-3") // - "gpt-4o" -> ("openai", "gpt-4o") // default protocol func ExtractProtocol(model string) (protocol, modelID string) { model = strings.TrimSpace(model) for i := 0; i < len(model); i++ { if model[i] == '/' { return model[:i], model[i+1:] } } // No prefix found, default to openai return "openai", model } // CreateProviderFromConfig creates a provider based on the ModelConfig. // It uses the protocol prefix in the Model field to determine which provider to create. // Supported protocols: openai, anthropic, antigravity, claude-cli, codex-cli, github-copilot // Returns the provider, the model ID (without protocol prefix), and any error. func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, error) { if cfg == nil { return nil, "", fmt.Errorf("config is nil") } if cfg.Model == "" { return nil, "", fmt.Errorf("model is required") } protocol, modelID := ExtractProtocol(cfg.Model) switch protocol { case "openai", "openrouter", "groq", "zhipu", "gemini", "nvidia", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", "volcengine", "vllm", "qwen": // All 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) } apiBase := cfg.APIBase if apiBase == "" { apiBase = getDefaultAPIBase(protocol) } return NewHTTPProvider(cfg.APIKey, apiBase, cfg.Proxy), modelID, nil case "anthropic": if cfg.AuthMethod == "oauth" || cfg.AuthMethod == "token" { // Use Claude SDK with token return NewClaudeProvider(cfg.APIKey), modelID, nil } // Use HTTP API apiBase := cfg.APIBase if apiBase == "" { apiBase = "https://api.anthropic.com/v1" } return NewHTTPProvider(cfg.APIKey, apiBase, cfg.Proxy), modelID, nil case "antigravity": return NewAntigravityProvider(), modelID, nil case "claude-cli", "claudecli": workspace := "." return NewClaudeCliProvider(workspace), modelID, nil case "codex-cli", "codexcli": workspace := "." return NewCodexCliProvider(workspace), modelID, nil case "github-copilot", "copilot": apiBase := cfg.APIBase if apiBase == "" { apiBase = "localhost:4321" } connectMode := cfg.ConnectMode if connectMode == "" { connectMode = "grpc" } provider, err := NewGitHubCopilotProvider(apiBase, connectMode, modelID) if err != nil { return nil, "", err } return provider, modelID, nil default: return nil, "", fmt.Errorf("unknown protocol %q in model %q", protocol, cfg.Model) } } // getDefaultAPIBase returns the default API base URL for a given protocol. func getDefaultAPIBase(protocol string) string { switch protocol { case "openai": return "https://api.openai.com/v1" case "openrouter": return "https://openrouter.ai/api/v1" case "groq": return "https://api.groq.com/openai/v1" case "zhipu": return "https://open.bigmodel.cn/api/paas/v4" case "gemini": return "https://generativelanguage.googleapis.com/v1beta" case "nvidia": return "https://integrate.api.nvidia.com/v1" case "ollama": return "http://localhost:11434/v1" case "moonshot": return "https://api.moonshot.cn/v1" case "shengsuanyun": return "https://router.shengsuanyun.com/api/v1" case "deepseek": return "https://api.deepseek.com/v1" case "cerebras": return "https://api.cerebras.ai/v1" case "volcengine": return "https://ark.cn-beijing.volces.com/api/v3" case "qwen": return "https://dashscope.aliyuncs.com/compatible-mode/v1" default: return "" } }