refactor: enforce CLIProxyAPI as mandatory upstream and remove individual providers

This commit is contained in:
DBT
2026-02-12 04:21:20 +00:00
parent 5b257c085a
commit 8bbefbe9ae
5 changed files with 40 additions and 248 deletions

View File

@@ -11,7 +11,7 @@
- **💰 极低成本**:完美适配 LicheeRV Nano 或 Orange Pi Zero 等 $10 级别的单板机。 - **💰 极低成本**:完美适配 LicheeRV Nano 或 Orange Pi Zero 等 $10 级别的单板机。
- **🔌 即插即用**:单二进制文件,无复杂依赖。 - **🔌 即插即用**:单二进制文件,无复杂依赖。
- **🧩 技能系统**:通过 `clawhub``coding-agent` 等技能扩展能力。 - **🧩 技能系统**:通过 `clawhub``coding-agent` 等技能扩展能力。
- **🔐 便捷认证**:交互式 `login` 命令,支持 OpenAI、Anthropic、Gemini 等主流服务商 - **🔐 统一接口**:强制要求使用 [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) 作为上游
## 🏁 快速开始 ## 🏁 快速开始
@@ -20,14 +20,10 @@
clawgo onboard clawgo onboard
``` ```
**2. 配置服务商** **2. 配置 CLIProxyAPI**
交互式设置您的 API Key (OpenAI, Anthropic, Gemini, Zhipu 等) 确保您的 [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) 正在运行。
```bash ```bash
clawgo login clawgo login
# 或者直接指定服务商:
# clawgo login openai
# clawgo login anthropic
# clawgo login gemini
``` ```
**3. 开始聊天!** **3. 开始聊天!**

View File

@@ -7,7 +7,7 @@
- **💰 Ultra-Low Cost**: Perfect for $10 SBCs like LicheeRV Nano or Orange Pi Zero. - **💰 Ultra-Low Cost**: Perfect for $10 SBCs like LicheeRV Nano or Orange Pi Zero.
- **🔌 Plug & Play**: Single binary. No complex dependencies. - **🔌 Plug & Play**: Single binary. No complex dependencies.
- **🧩 Skill System**: Extend capabilities with `clawhub`, `coding-agent`, and more. - **🧩 Skill System**: Extend capabilities with `clawhub`, `coding-agent`, and more.
- **🔐 Easy Auth**: Interactive `login` command for OpenAI, Anthropic, Gemini, etc. - **🔐 Unified Interface**: Mandatory use of [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) as the upstream proxy.
## 🏁 Quick Start ## 🏁 Quick Start
@@ -16,14 +16,10 @@
clawgo onboard clawgo onboard
``` ```
**2. Configure Provider** **2. Configure CLIProxyAPI**
Interactively set up your API key (OpenAI, Anthropic, Gemini, Zhipu, etc.): Ensure [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) is running.
```bash ```bash
clawgo login clawgo login
# Or specify provider directly:
# clawgo login openai
# clawgo login anthropic
# clawgo login gemini
``` ```
**3. Chat!** **3. Chat!**

View File

@@ -171,7 +171,7 @@ func printHelp() {
fmt.Println(" gateway Start clawgo gateway") fmt.Println(" gateway Start clawgo gateway")
fmt.Println(" status Show clawgo status") fmt.Println(" status Show clawgo status")
fmt.Println(" cron Manage scheduled tasks") fmt.Println(" cron Manage scheduled tasks")
fmt.Println(" login Configure model provider credentials") fmt.Println(" login Configure CLIProxyAPI upstream")
fmt.Println(" skills Manage skills (install, list, remove)") fmt.Println(" skills Manage skills (install, list, remove)")
fmt.Println(" version Show version information") fmt.Println(" version Show version information")
} }
@@ -205,8 +205,8 @@ func onboard() {
fmt.Printf("%s clawgo is ready!\n", logo) fmt.Printf("%s clawgo is ready!\n", logo)
fmt.Println("\nNext steps:") fmt.Println("\nNext steps:")
fmt.Println(" 1. Add your API key to", configPath) fmt.Println(" 1. Configure CLIProxyAPI at", configPath)
fmt.Println(" Get one at: https://openrouter.ai/keys") fmt.Println(" Ensure CLIProxyAPI is running: https://github.com/router-for-me/CLIProxyAPI")
fmt.Println(" 2. Chat: clawgo agent -m \"Hello!\"") fmt.Println(" 2. Chat: clawgo agent -m \"Hello!\"")
} }
@@ -681,32 +681,13 @@ func statusCmd() {
if _, err := os.Stat(configPath); err == nil { if _, err := os.Stat(configPath); err == nil {
fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model) fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model)
fmt.Printf("CLIProxyAPI Base: %s\n", cfg.Providers.Proxy.APIBase)
hasOpenRouter := cfg.Providers.OpenRouter.APIKey != "" hasKey := cfg.Providers.Proxy.APIKey != ""
hasAnthropic := cfg.Providers.Anthropic.APIKey != "" status := "not set"
hasOpenAI := cfg.Providers.OpenAI.APIKey != "" if hasKey {
hasGemini := cfg.Providers.Gemini.APIKey != "" status = ""
hasZhipu := cfg.Providers.Zhipu.APIKey != ""
hasGroq := cfg.Providers.Groq.APIKey != ""
hasVLLM := cfg.Providers.VLLM.APIBase != ""
status := func(enabled bool) string {
if enabled {
return "✓"
}
return "not set"
}
fmt.Println("OpenRouter API:", status(hasOpenRouter))
fmt.Println("Anthropic API:", status(hasAnthropic))
fmt.Println("OpenAI API:", status(hasOpenAI))
fmt.Println("Gemini API:", status(hasGemini))
fmt.Println("Zhipu API:", status(hasZhipu))
fmt.Println("Groq API:", status(hasGroq))
if hasVLLM {
fmt.Printf("vLLM/Local: ✓ %s\n", cfg.Providers.VLLM.APIBase)
} else {
fmt.Println("vLLM/Local: not set")
} }
fmt.Printf("CLIProxyAPI Key: %s\n", status)
} }
} }
@@ -1181,91 +1162,28 @@ func loginCmd() {
os.Exit(1) os.Exit(1)
} }
providersList := []string{"OpenAI", "Anthropic", "Gemini", "OpenRouter", "Zhipu", "Groq", "VLLM"} fmt.Println("Configuring CLIProxyAPI...")
fmt.Printf("Current Base: %s\n", cfg.Providers.Proxy.APIBase)
if len(os.Args) > 2 { fmt.Print("Enter CLIProxyAPI Base URL (e.g. http://localhost:8080/v1): ")
provider := os.Args[2] reader := bufio.NewReader(os.Stdin)
found := false line, _ := reader.ReadString('\n')
for _, p := range providersList { apiBase := strings.TrimSpace(line)
if strings.EqualFold(p, provider) { if apiBase != "" {
configureProvider(cfg, p) cfg.Providers.Proxy.APIBase = apiBase
found = true
break
}
}
if !found {
fmt.Printf("Unknown provider: %s\n", provider)
fmt.Println("Available providers:", strings.Join(providersList, ", "))
}
return
} }
fmt.Println("Select provider to configure:") fmt.Print("Enter API Key (optional): ")
for i, p := range providersList { fmt.Scanln(&cfg.Providers.Proxy.APIKey)
fmt.Printf("%d. %s\n", i+1, p)
}
fmt.Print("\nEnter choice (number): ")
var choice int
_, err = fmt.Scanln(&choice)
if err != nil || choice < 1 || choice > len(providersList) {
fmt.Println("Invalid choice.")
return
}
selected := providersList[choice-1]
configureProvider(cfg, selected)
}
func configureProvider(cfg *config.Config, provider string) {
fmt.Printf("\nConfiguring %s...\n", provider)
fmt.Print("Enter API Key: ")
var apiKey string
fmt.Scanln(&apiKey)
var apiBase string
// For VLLM or OpenAI, user might want custom base
if provider == "VLLM" || provider == "OpenAI" || provider == "OpenRouter" {
fmt.Print("Enter API Base URL (optional, press Enter to skip): ")
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
apiBase = strings.TrimSpace(line)
}
switch provider {
case "OpenAI":
cfg.Providers.OpenAI.APIKey = apiKey
if apiBase != "" {
cfg.Providers.OpenAI.APIBase = apiBase
}
case "Anthropic":
cfg.Providers.Anthropic.APIKey = apiKey
if apiBase != "" {
cfg.Providers.Anthropic.APIBase = apiBase
}
case "Gemini":
cfg.Providers.Gemini.APIKey = apiKey
case "OpenRouter":
cfg.Providers.OpenRouter.APIKey = apiKey
if apiBase != "" {
cfg.Providers.OpenRouter.APIBase = apiBase
}
case "Zhipu":
cfg.Providers.Zhipu.APIKey = apiKey
case "Groq":
cfg.Providers.Groq.APIKey = apiKey
case "VLLM":
cfg.Providers.VLLM.APIKey = apiKey
if apiBase != "" {
cfg.Providers.VLLM.APIBase = apiBase
}
}
if err := config.SaveConfig(getConfigPath(), cfg); err != nil { if err := config.SaveConfig(getConfigPath(), cfg); err != nil {
fmt.Printf("Error saving config: %v\n", err) fmt.Printf("Error saving config: %v\n", err)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("✓ %s configuration saved.\n", provider) fmt.Println("✓ CLIProxyAPI configuration saved.")
}
func configureProvider(cfg *config.Config, provider string) {
// Deprecated: Migrated to CLIProxyAPI logic in loginCmd
} }

View File

@@ -89,13 +89,7 @@ type DingTalkConfig struct {
} }
type ProvidersConfig struct { type ProvidersConfig struct {
Anthropic ProviderConfig `json:"anthropic"` Proxy ProviderConfig `json:"proxy"`
OpenAI ProviderConfig `json:"openai"`
OpenRouter ProviderConfig `json:"openrouter"`
Groq ProviderConfig `json:"groq"`
Zhipu ProviderConfig `json:"zhipu"`
VLLM ProviderConfig `json:"vllm"`
Gemini ProviderConfig `json:"gemini"`
} }
type ProviderConfig struct { type ProviderConfig struct {
@@ -203,13 +197,9 @@ func DefaultConfig() *Config {
}, },
}, },
Providers: ProvidersConfig{ Providers: ProvidersConfig{
Anthropic: ProviderConfig{}, Proxy: ProviderConfig{
OpenAI: ProviderConfig{}, APIBase: "http://localhost:8080/v1",
OpenRouter: ProviderConfig{}, },
Groq: ProviderConfig{},
Zhipu: ProviderConfig{},
VLLM: ProviderConfig{},
Gemini: ProviderConfig{},
}, },
Gateway: GatewayConfig{ Gateway: GatewayConfig{
Host: "0.0.0.0", Host: "0.0.0.0",
@@ -274,46 +264,13 @@ func (c *Config) WorkspacePath() string {
func (c *Config) GetAPIKey() string { func (c *Config) GetAPIKey() string {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
if c.Providers.OpenRouter.APIKey != "" { return c.Providers.Proxy.APIKey
return c.Providers.OpenRouter.APIKey
}
if c.Providers.Anthropic.APIKey != "" {
return c.Providers.Anthropic.APIKey
}
if c.Providers.OpenAI.APIKey != "" {
return c.Providers.OpenAI.APIKey
}
if c.Providers.Gemini.APIKey != "" {
return c.Providers.Gemini.APIKey
}
if c.Providers.Zhipu.APIKey != "" {
return c.Providers.Zhipu.APIKey
}
if c.Providers.Groq.APIKey != "" {
return c.Providers.Groq.APIKey
}
if c.Providers.VLLM.APIKey != "" {
return c.Providers.VLLM.APIKey
}
return ""
} }
func (c *Config) GetAPIBase() string { func (c *Config) GetAPIBase() string {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
if c.Providers.OpenRouter.APIKey != "" { return c.Providers.Proxy.APIBase
if c.Providers.OpenRouter.APIBase != "" {
return c.Providers.OpenRouter.APIBase
}
return "https://openrouter.ai/api/v1"
}
if c.Providers.Zhipu.APIKey != "" {
return c.Providers.Zhipu.APIBase
}
if c.Providers.VLLM.APIKey != "" && c.Providers.VLLM.APIBase != "" {
return c.Providers.VLLM.APIBase
}
return ""
} }
func expandHome(path string) string { func expandHome(path string) string {

View File

@@ -175,87 +175,12 @@ func (p *HTTPProvider) GetDefaultModel() string {
} }
func CreateProvider(cfg *config.Config) (LLMProvider, error) { func CreateProvider(cfg *config.Config) (LLMProvider, error) {
model := cfg.Agents.Defaults.Model apiKey := cfg.Providers.Proxy.APIKey
apiBase := cfg.Providers.Proxy.APIBase
var apiKey, apiBase, authMode string authMode := cfg.Providers.Proxy.Auth
lowerModel := strings.ToLower(model)
switch {
case strings.HasPrefix(model, "openrouter/") || strings.HasPrefix(model, "anthropic/") || strings.HasPrefix(model, "openai/") || strings.HasPrefix(model, "meta-llama/") || strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "google/"):
apiKey = cfg.Providers.OpenRouter.APIKey
authMode = cfg.Providers.OpenRouter.Auth
if cfg.Providers.OpenRouter.APIBase != "" {
apiBase = cfg.Providers.OpenRouter.APIBase
} else {
apiBase = "https://openrouter.ai/api/v1"
}
case strings.Contains(lowerModel, "claude") || strings.HasPrefix(model, "anthropic/"):
apiKey = cfg.Providers.Anthropic.APIKey
authMode = cfg.Providers.Anthropic.Auth
apiBase = cfg.Providers.Anthropic.APIBase
if apiBase == "" {
apiBase = "https://api.anthropic.com/v1"
}
case strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/") || strings.Contains(lowerModel, "codex"):
apiKey = cfg.Providers.OpenAI.APIKey
authMode = cfg.Providers.OpenAI.Auth
apiBase = cfg.Providers.OpenAI.APIBase
if apiBase == "" {
apiBase = "https://api.openai.com/v1"
}
case strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/"):
apiKey = cfg.Providers.Gemini.APIKey
authMode = cfg.Providers.Gemini.Auth
apiBase = cfg.Providers.Gemini.APIBase
if apiBase == "" {
apiBase = "https://generativelanguage.googleapis.com/v1beta"
}
case strings.Contains(lowerModel, "glm") || strings.Contains(lowerModel, "zhipu") || strings.Contains(lowerModel, "zai"):
apiKey = cfg.Providers.Zhipu.APIKey
authMode = cfg.Providers.Zhipu.Auth
apiBase = cfg.Providers.Zhipu.APIBase
if apiBase == "" {
apiBase = "https://open.bigmodel.cn/api/paas/v4"
}
case strings.Contains(lowerModel, "groq") || strings.HasPrefix(model, "groq/"):
apiKey = cfg.Providers.Groq.APIKey
authMode = cfg.Providers.Groq.Auth
apiBase = cfg.Providers.Groq.APIBase
if apiBase == "" {
apiBase = "https://api.groq.com/openai/v1"
}
case cfg.Providers.VLLM.APIBase != "":
apiKey = cfg.Providers.VLLM.APIKey
authMode = cfg.Providers.VLLM.Auth
apiBase = cfg.Providers.VLLM.APIBase
default:
if cfg.Providers.OpenRouter.APIKey != "" {
apiKey = cfg.Providers.OpenRouter.APIKey
authMode = cfg.Providers.OpenRouter.Auth
if cfg.Providers.OpenRouter.APIBase != "" {
apiBase = cfg.Providers.OpenRouter.APIBase
} else {
apiBase = "https://openrouter.ai/api/v1"
}
} else {
return nil, fmt.Errorf("no API key configured for model: %s", model)
}
}
if apiKey == "" && !strings.HasPrefix(model, "bedrock/") {
return nil, fmt.Errorf("no API key configured for provider (model: %s)", model)
}
if apiBase == "" { if apiBase == "" {
return nil, fmt.Errorf("no API base configured for provider (model: %s)", model) return nil, fmt.Errorf("no API base (CLIProxyAPI) configured")
} }
return NewHTTPProvider(apiKey, apiBase, authMode), nil return NewHTTPProvider(apiKey, apiBase, authMode), nil