ollama-proxy/internal/proxy/model_router.go
Atte149 4fe15324c8 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
2026-06-24 15:37:28 +03:00

116 lines
3.4 KiB
Go

package proxy
import (
"sort"
"github.com/Atte149/ollama-proxy/internal/config"
)
// ollamaCloudModels is the set of model IDs served by Ollama Cloud.
// These overlap partly with the OpenCode Go catalog.
var ollamaCloudModels = []string{
"deepseek-v3.1:671b", "deepseek-v3.2", "deepseek-v4-flash", "deepseek-v4-pro",
"devstral-2:123b", "devstral-small-2:24b",
"gemini-3-flash-preview", "gemma3:12b", "gemma3:27b", "gemma3:4b", "gemma4:31b",
"glm-4.7", "glm-5", "glm-5.1", "glm-5.2",
"gpt-oss:120b", "gpt-oss:20b",
"kimi-k2.5", "kimi-k2.6", "kimi-k2.7-code",
"minimax-m2.1", "minimax-m2.5", "minimax-m2.7", "minimax-m3",
"ministral-3:14b", "ministral-3:3b", "ministral-3:8b",
"mistral-large-3:675b",
"nemotron-3-nano:30b", "nemotron-3-super", "nemotron-3-ultra",
"qwen3-coder:480b", "qwen3-coder-next", "qwen3.5:397b",
"rnj-1:8b",
}
// openCodeGoModels is the set of model IDs served by OpenCode Go.
var openCodeGoModels = []string{
"deepseek-v4-flash", "deepseek-v4-pro",
"glm-5", "glm-5.1", "glm-5.2",
"hy3-preview",
"kimi-k2.5", "kimi-k2.6", "kimi-k2.7-code",
"mimo-v2-omni", "mimo-v2-pro", "mimo-v2.5", "mimo-v2.5-pro",
"minimax-m2.5", "minimax-m2.7", "minimax-m3",
"qwen3.5-plus", "qwen3.6-plus", "qwen3.7-max", "qwen3.7-plus",
}
// modelProviders maps a model ID to the providers that can serve it.
// Built once from the two catalogs above.
var modelProviders = func() map[string][]config.ProviderType {
m := make(map[string][]config.ProviderType, len(ollamaCloudModels)+len(openCodeGoModels))
add := func(id string, p config.ProviderType) {
m[id] = append(m[id], p)
}
for _, id := range ollamaCloudModels {
add(id, config.ProviderOllamaCloud)
}
for _, id := range openCodeGoModels {
// Deduplicate: if already present (common model), append; else create.
found := false
for _, ex := range m[id] {
if ex == config.ProviderOpenCodeGo {
found = true
break
}
}
if !found {
add(id, config.ProviderOpenCodeGo)
}
}
return m
}()
// ProvidersForModel returns the providers eligible to serve the given model.
// Unknown models default to Ollama Cloud (the original behaviour).
func ProvidersForModel(model string) []config.ProviderType {
if ps, ok := modelProviders[model]; ok {
return ps
}
return []config.ProviderType{config.ProviderOllamaCloud}
}
// AllModels returns the deduplicated, sorted list of all model IDs known to the
// router (union of both catalogs).
func AllModels() []string {
seen := make(map[string]struct{})
for id := range modelProviders {
seen[id] = struct{}{}
}
out := make([]string, 0, len(seen))
for id := range seen {
out = append(out, id)
}
sort.Strings(out)
return out
}
// IsCommonModel reports whether the model is served by both providers.
func IsCommonModel(model string) bool {
ps := ProvidersForModel(model)
if len(ps) < 2 {
return false
}
hasOllama, hasGo := false, false
for _, p := range ps {
switch p {
case config.ProviderOllamaCloud:
hasOllama = true
case config.ProviderOpenCodeGo:
hasGo = true
}
}
return hasOllama && hasGo
}
// ProviderLabel returns a short human label for a provider, used to annotate
// unique models in the merged /v1/models response and in opencode config names.
func ProviderLabel(p config.ProviderType) string {
switch p {
case config.ProviderOpenCodeGo:
return "go"
case config.ProviderOllamaCloud:
return "ollama"
default:
return string(p)
}
}