mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-17 21:17:28 +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 级别的单板机。
|
- **💰 极低成本**:完美适配 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. 开始聊天!**
|
||||||
|
|||||||
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.
|
- **💰 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!**
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user