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
|
|
@ -77,6 +77,60 @@ func TestValidateName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProviderType_Constants(t *testing.T) {
|
||||
if ProviderOllamaCloud != "ollama-cloud" {
|
||||
t.Errorf("ProviderOllamaCloud = %q", ProviderOllamaCloud)
|
||||
}
|
||||
if ProviderOpenCodeGo != "opencode-go" {
|
||||
t.Errorf("ProviderOpenCodeGo = %q", ProviderOpenCodeGo)
|
||||
}
|
||||
if !ValidProvider(ProviderOllamaCloud) || !ValidProvider(ProviderOpenCodeGo) {
|
||||
t.Error("ValidProvider returned false for known providers")
|
||||
}
|
||||
if ValidProvider(ProviderType("bogus")) {
|
||||
t.Error("ValidProvider returned true for bogus provider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultProviderBaseURL(t *testing.T) {
|
||||
if DefaultProviderBaseURL(ProviderOllamaCloud) != "https://ollama.com" {
|
||||
t.Errorf("ollama base url = %q", DefaultProviderBaseURL(ProviderOllamaCloud))
|
||||
}
|
||||
if DefaultProviderBaseURL(ProviderOpenCodeGo) != "https://opencode.ai/zen/go" {
|
||||
t.Errorf("go base url = %q", DefaultProviderBaseURL(ProviderOpenCodeGo))
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadAccounts_BackwardCompatProvider verifies that accounts.json entries
|
||||
// without a "provider" field are normalised to ollama-cloud on load.
|
||||
func TestLoadAccounts_BackwardCompatProvider(t *testing.T) {
|
||||
p := withTempAccountsPath(t)
|
||||
// Write an old-style file with no provider field.
|
||||
old := `{
|
||||
"base_url": "https://ollama.com",
|
||||
"accounts": [
|
||||
{"id":"abc","name":"legacy","api_key":"sk-xyz","created":"2026-06-19T10:00:00Z"},
|
||||
{"id":"def","name":"go1","provider":"opencode-go","api_key":"sk-go","created":"2026-06-19T10:00:00Z"}
|
||||
]
|
||||
}`
|
||||
if err := os.WriteFile(p, []byte(old), 0o600); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
af, err := LoadAccounts()
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if len(af.Accounts) != 2 {
|
||||
t.Fatalf("len = %d", len(af.Accounts))
|
||||
}
|
||||
if af.Accounts[0].Name != "legacy" || af.Accounts[0].Provider != ProviderOllamaCloud {
|
||||
t.Errorf("legacy account provider = %q, want ollama-cloud", af.Accounts[0].Provider)
|
||||
}
|
||||
if af.Accounts[1].Provider != ProviderOpenCodeGo {
|
||||
t.Errorf("go account provider = %q, want opencode-go", af.Accounts[1].Provider)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAccounts_MissingFile(t *testing.T) {
|
||||
withTempAccountsPath(t)
|
||||
af, err := LoadAccounts()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue