Files
picoclaw/pkg/providers/factory_provider.go
T
yinwm ef7078a356 refactor: reorganize commands and provider architecture
Refactor command handlers into separate files to improve code organization
and maintainability. Each command (agent, auth, cron, gateway, migrate,
onboard, skills, status) now has its own dedicated file.

Restructure provider creation to support new model_list configuration
system that enables zero-code addition of OpenAI-compatible providers.
Move legacy provider logic to separate file for backward compatibility.

Move configuration functions from config.go to separate files
(defaults.go, migration.go) for better organization.
2026-02-19 01:03:34 +08:00

137 lines
3.9 KiB
Go

// 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 ""
}
}