mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-13 18:07:36 +08:00
refactor: enforce CLIProxyAPI as mandatory upstream and remove individual providers
This commit is contained in:
10
README.md
10
README.md
@@ -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. 开始聊天!**
|
||||
|
||||
10
README_EN.md
10
README_EN.md
@@ -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!**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user