feat: multi-provider support — Ollama Cloud + OpenCode Go
- Account.Provider field (ollama-cloud | opencode-go), backward compatible - Model-based routing: common models served by combined pool, unique models routed to their provider only - /go/v1/* path forces OpenCode Go provider (prefix stripped upstream) - Merged /v1/models endpoint returns union of both catalogs (44 models) - Failover: 429/402 → cooldown + failover; 5xx → retry without cooldown - CLI: accounts add --provider flag, list shows provider column - Body buffering: request body buffered (8 MiB cap) for failover replay - opencode integration: unified provider 'oc' with all merged models - 64 tests pass (unit + integration) - Verified: glm-5 → ollama, mimo-v2.5 → go, gpt-oss:20b → ollama
This commit is contained in:
parent
98bbc96bf5
commit
4fe15324c8
11 changed files with 973 additions and 125 deletions
|
|
@ -37,16 +37,21 @@ func RunAccounts(args []string) int {
|
|||
|
||||
func printAccountsUsage() {
|
||||
fmt.Print(`Usage:
|
||||
ollama-proxy accounts add [API_KEY] [--name <alias>]
|
||||
ollama-proxy accounts add [API_KEY] [--name <alias>] [--provider ollama-cloud|opencode-go]
|
||||
ollama-proxy accounts list
|
||||
ollama-proxy accounts remove <id|name>
|
||||
ollama-proxy accounts set-base-url <url>
|
||||
|
||||
Providers:
|
||||
ollama-cloud (default) Ollama Cloud at https://ollama.com — native + OpenAI API
|
||||
opencode-go OpenCode Go at https://opencode.ai/zen/go/v1 — OpenAI API only
|
||||
`)
|
||||
}
|
||||
|
||||
// accountsAdd implements `accounts add [API_KEY] [--name <alias>]`.
|
||||
// accountsAdd implements `accounts add [API_KEY] [--name <alias>] [--provider P]`.
|
||||
func accountsAdd(args []string) int {
|
||||
var apiKey, name string
|
||||
provider := config.ProviderOllamaCloud
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--name":
|
||||
|
|
@ -56,8 +61,15 @@ func accountsAdd(args []string) int {
|
|||
}
|
||||
name = args[i+1]
|
||||
i++
|
||||
case "--provider":
|
||||
if i+1 >= len(args) {
|
||||
fmt.Fprintln(os.Stderr, "--provider requires an argument")
|
||||
return 2
|
||||
}
|
||||
provider = config.ProviderType(args[i+1])
|
||||
i++
|
||||
case "-h", "--help":
|
||||
fmt.Println("usage: accounts add [API_KEY] [--name <alias>]")
|
||||
fmt.Println("usage: accounts add [API_KEY] [--name <alias>] [--provider ollama-cloud|opencode-go]")
|
||||
return 0
|
||||
default:
|
||||
if apiKey == "" {
|
||||
|
|
@ -69,6 +81,11 @@ func accountsAdd(args []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
if !config.ValidProvider(provider) {
|
||||
fmt.Fprintf(os.Stderr, "unknown provider %q (use ollama-cloud or opencode-go)\n", provider)
|
||||
return 2
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = promptString("Account name (alias)", "acct"+config.NewID()[:4])
|
||||
}
|
||||
|
|
@ -78,7 +95,14 @@ func accountsAdd(args []string) int {
|
|||
}
|
||||
|
||||
if apiKey == "" {
|
||||
fmt.Print("Enter Ollama Cloud API key (input hidden): ")
|
||||
label := "API key"
|
||||
switch provider {
|
||||
case config.ProviderOpenCodeGo:
|
||||
label = "OpenCode Go API key"
|
||||
case config.ProviderOllamaCloud:
|
||||
label = "Ollama Cloud API key"
|
||||
}
|
||||
fmt.Printf("Enter %s (input hidden): ", label)
|
||||
b, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
|
|
@ -106,17 +130,19 @@ func accountsAdd(args []string) int {
|
|||
return 2
|
||||
}
|
||||
acct := config.Account{
|
||||
ID: config.NewID(),
|
||||
Name: name,
|
||||
APIKey: apiKey,
|
||||
Created: nowFn(),
|
||||
ID: config.NewID(),
|
||||
Name: name,
|
||||
Provider: provider,
|
||||
APIKey: apiKey,
|
||||
Created: nowFn(),
|
||||
}
|
||||
af.Accounts = append(af.Accounts, acct)
|
||||
if err := af.Save(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "save accounts:", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Printf("added account %s (name=%s key=%s)\n", acct.ID[:8], acct.Name, maskKey(acct.APIKey))
|
||||
fmt.Printf("added account %s (name=%s provider=%s key=%s)\n",
|
||||
acct.ID[:8], acct.Name, acct.Provider, maskKey(acct.APIKey))
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -133,15 +159,21 @@ func accountsList(args []string) int {
|
|||
}
|
||||
w := bufio.NewWriter(os.Stdout)
|
||||
defer w.Flush()
|
||||
fmt.Fprintf(w, "%-10s %-16s %-18s %-22s\n", "ID", "NAME", "KEY", "CREATED")
|
||||
fmt.Fprintf(w, "%-10s %-16s %-18s %-22s\n", strings.Repeat("-", 8), strings.Repeat("-", 14), strings.Repeat("-", 16), strings.Repeat("-", 20))
|
||||
fmt.Fprintf(w, "%-10s %-14s %-16s %-18s %-22s\n", "ID", "PROVIDER", "NAME", "KEY", "CREATED")
|
||||
fmt.Fprintf(w, "%-10s %-14s %-16s %-18s %-22s\n",
|
||||
strings.Repeat("-", 8), strings.Repeat("-", 12), strings.Repeat("-", 14),
|
||||
strings.Repeat("-", 16), strings.Repeat("-", 20))
|
||||
for _, a := range af.Accounts {
|
||||
id := a.ID
|
||||
if len(id) > 8 {
|
||||
id = id[:8]
|
||||
}
|
||||
prov := string(a.Provider)
|
||||
if prov == "" {
|
||||
prov = string(config.ProviderOllamaCloud)
|
||||
}
|
||||
created := a.Created.Format("2006-01-02 15:04 MST")
|
||||
fmt.Fprintf(w, "%-10s %-16s %-18s %-22s\n", id, a.Name, maskKey(a.APIKey), created)
|
||||
fmt.Fprintf(w, "%-10s %-14s %-16s %-18s %-22s\n", id, prov, a.Name, maskKey(a.APIKey), created)
|
||||
}
|
||||
if af.BaseURL != "" {
|
||||
fmt.Fprintf(w, "\nDefault upstream: %s\n", af.BaseURL)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue