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

View File

@@ -7,7 +7,7 @@
- **💰 Ultra-Low Cost**: Perfect for $10 SBCs like LicheeRV Nano or Orange Pi Zero.
- **🔌 Plug & Play**: Single binary. No complex dependencies.
- **🧩 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
@@ -16,14 +16,10 @@
clawgo onboard
```
**2. Configure Provider**
Interactively set up your API key (OpenAI, Anthropic, Gemini, Zhipu, etc.):
**2. Configure CLIProxyAPI**
Ensure [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) is running.
```bash
clawgo login
# Or specify provider directly:
# clawgo login openai
# clawgo login anthropic
# clawgo login gemini
```
**3. Chat!**

View File

@@ -171,7 +171,7 @@ func printHelp() {
fmt.Println(" gateway Start clawgo gateway")
fmt.Println(" status Show clawgo status")
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(" version Show version information")
}
@@ -205,8 +205,8 @@ func onboard() {
fmt.Printf("%s clawgo is ready!\n", logo)
fmt.Println("\nNext steps:")
fmt.Println(" 1. Add your API key to", configPath)
fmt.Println(" Get one at: https://openrouter.ai/keys")
fmt.Println(" 1. Configure CLIProxyAPI at", configPath)
fmt.Println(" Ensure CLIProxyAPI is running: https://github.com/router-for-me/CLIProxyAPI")
fmt.Println(" 2. Chat: clawgo agent -m \"Hello!\"")
}
@@ -681,32 +681,13 @@ func statusCmd() {
if _, err := os.Stat(configPath); err == nil {
fmt.Printf("Model: %s\n", cfg.Agents.Defaults.Model)
hasOpenRouter := cfg.Providers.OpenRouter.APIKey != ""
hasAnthropic := cfg.Providers.Anthropic.APIKey != ""
hasOpenAI := cfg.Providers.OpenAI.APIKey != ""
hasGemini := cfg.Providers.Gemini.APIKey != ""
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 Base: %s\n", cfg.Providers.Proxy.APIBase)
hasKey := cfg.Providers.Proxy.APIKey != ""
status := "not set"
if hasKey {
status = ""
}
fmt.Printf("CLIProxyAPI Key: %s\n", status)
}
}
@@ -1181,91 +1162,28 @@ func loginCmd() {
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 {
provider := os.Args[2]
found := false
for _, p := range providersList {
if strings.EqualFold(p, provider) {
configureProvider(cfg, p)
found = true
break
}
}
if !found {
fmt.Printf("Unknown provider: %s\n", provider)
fmt.Println("Available providers:", strings.Join(providersList, ", "))
}
return
fmt.Print("Enter CLIProxyAPI Base URL (e.g. http://localhost:8080/v1): ")
reader := bufio.NewReader(os.Stdin)
line, _ := reader.ReadString('\n')
apiBase := strings.TrimSpace(line)
if apiBase != "" {
cfg.Providers.Proxy.APIBase = apiBase
}
fmt.Println("Select provider to configure:")
for i, p := range providersList {
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
}
}
fmt.Print("Enter API Key (optional): ")
fmt.Scanln(&cfg.Providers.Proxy.APIKey)
if err := config.SaveConfig(getConfigPath(), cfg); err != nil {
fmt.Printf("Error saving config: %v\n", err)
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 {
Anthropic ProviderConfig `json:"anthropic"`
OpenAI ProviderConfig `json:"openai"`
OpenRouter ProviderConfig `json:"openrouter"`
Groq ProviderConfig `json:"groq"`
Zhipu ProviderConfig `json:"zhipu"`
VLLM ProviderConfig `json:"vllm"`
Gemini ProviderConfig `json:"gemini"`
Proxy ProviderConfig `json:"proxy"`
}
type ProviderConfig struct {
@@ -203,13 +197,9 @@ func DefaultConfig() *Config {
},
},
Providers: ProvidersConfig{
Anthropic: ProviderConfig{},
OpenAI: ProviderConfig{},
OpenRouter: ProviderConfig{},
Groq: ProviderConfig{},
Zhipu: ProviderConfig{},
VLLM: ProviderConfig{},
Gemini: ProviderConfig{},
Proxy: ProviderConfig{
APIBase: "http://localhost:8080/v1",
},
},
Gateway: GatewayConfig{
Host: "0.0.0.0",
@@ -274,46 +264,13 @@ func (c *Config) WorkspacePath() string {
func (c *Config) GetAPIKey() string {
c.mu.RLock()
defer c.mu.RUnlock()
if c.Providers.OpenRouter.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 ""
return c.Providers.Proxy.APIKey
}
func (c *Config) GetAPIBase() string {
c.mu.RLock()
defer c.mu.RUnlock()
if c.Providers.OpenRouter.APIKey != "" {
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 ""
return c.Providers.Proxy.APIBase
}
func expandHome(path string) string {

View File

@@ -175,87 +175,12 @@ func (p *HTTPProvider) GetDefaultModel() string {
}
func CreateProvider(cfg *config.Config) (LLMProvider, error) {
model := cfg.Agents.Defaults.Model
var apiKey, apiBase, authMode string
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)
}
apiKey := cfg.Providers.Proxy.APIKey
apiBase := cfg.Providers.Proxy.APIBase
authMode := cfg.Providers.Proxy.Auth
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