The reporter's "OpenCode Go 1x" had only chat=https://opencode.ai/zen/go/v1 and no models.dev catalog (its native_endpoints were /v1/chat/completions for every model). Go serves each model on one API of three, as models.dev's per-model provider.npm says and OpenCode itself asks it (@ai-sdk/openai -> /responses, @ai-sdk/anthropic -> /messages, the rest chat), so with chat the only URL, gpt-6-luna, gpt-5.6-luna, grok-4.6/4.7, minimax-m2.7 and muse-spark-1.2/1.3-contributor had nowhere else to go and Go answered 400 on all three of magpie's entry points. - normalize fills in the other two of Zen's or Go's APIs (/v1 for chat and Responses, the root for Messages) and its catalog, only where the provider has none of its own (openCodeEndpoints); other hosts unchanged. - ListedAPIs reads the per-model API from the list of the gateway the URL is at (opencode-go for /zen/go, opencode for /zen), whatever catalog was saved: Go and Zen serve MiniMax M2.7 on different APIs, and an import at /zen/v1 could be saved with opencode-go's. - A wrong-endpoint error with no other API left now ends "(asked at <url>; <model> is served on /v1/responses, which <provider> has no URL for)". Verified: TestOpenCodeGoModelsGoOnTheirOwnAPI (gateway, all 3 client APIs x 5 models through the handler) and TestOpenCodeEndpointsFilledIn fail without the change with the reporter's exact "OpenCode Go 1x: Model does not support this protocol."; TestOtherRelayKeepsItsOneAPI and the relay cases keep other providers as they were. go test -tags nogui ./... green under a temp HOME; go vet, go build, GOOS=linux/windows builds. Real upstream: probed https://opencode.ai/zen/go/v1 with no key only (grok-4.7 is "not supported for format oa-compat" on chat and "format anthropic" on messages, passes to the key check on responses); no keyed request was made.
71 lines
2.7 KiB
Go
71 lines
2.7 KiB
Go
package provider
|
|
|
|
import (
|
|
"cmp"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// openCodeRoot is the root of OpenCode's gateway a base URL is at — Zen's
|
|
// (https://opencode.ai/zen) or Go's (…/zen/go), given with /v1 or without —
|
|
// and the models.dev provider that lists its models: opencode or
|
|
// opencode-go. Both are "" for any other URL.
|
|
func openCodeRoot(base string) (root, catalogID string) {
|
|
u, err := url.Parse(strings.TrimRight(strings.TrimSpace(base), "/"))
|
|
if err != nil || u.Scheme == "" {
|
|
return "", ""
|
|
}
|
|
if h := strings.ToLower(u.Hostname()); h != "opencode.ai" && !strings.HasSuffix(h, ".opencode.ai") {
|
|
return "", ""
|
|
}
|
|
switch path := strings.TrimSuffix(strings.TrimRight(u.Path, "/"), "/v1"); path {
|
|
case "/zen/go":
|
|
return u.Scheme + "://" + u.Host + path, "opencode-go"
|
|
case "/zen":
|
|
return u.Scheme + "://" + u.Host + path, "opencode"
|
|
}
|
|
return "", ""
|
|
}
|
|
|
|
// openCodeEndpoints fills in the APIs of OpenCode's gateway a provider at
|
|
// it was saved without. Zen and Go serve each model on one API of three,
|
|
// all under one root — chat completions and Responses at /v1, Anthropic's
|
|
// Messages at the root — and turn it away on the other two. OpenCode asks
|
|
// a model through the AI SDK package models.dev's per-model provider.npm
|
|
// names: @ai-sdk/openai (Responses) for GPT, Grok and Muse Spark,
|
|
// @ai-sdk/anthropic (Messages) for Claude, MiniMax and some Qwen, and
|
|
// @ai-sdk/openai-compatible (chat) for the rest. A provider at Go added by
|
|
// hand or brought from another app with its chat URL alone had nowhere
|
|
// but chat to send GPT, Grok, MiniMax or Muse Spark, which Go answers
|
|
// with "Model does not support this protocol" (#1215). A URL the user gave
|
|
// an API keeps it, and so does a catalog they named.
|
|
func (p *Provider) openCodeEndpoints() {
|
|
root, catalogID := "", ""
|
|
for _, u := range []string{p.Chat, p.Responses, p.Anthropic} {
|
|
if root, catalogID = openCodeRoot(u); root != "" {
|
|
break
|
|
}
|
|
}
|
|
if root == "" {
|
|
return
|
|
}
|
|
p.Chat = cmp.Or(p.Chat, root+"/v1")
|
|
p.Responses = cmp.Or(p.Responses, root+"/v1")
|
|
p.Anthropic = cmp.Or(p.Anthropic, root)
|
|
p.Catalog = cmp.Or(p.Catalog, catalogID)
|
|
}
|
|
|
|
// openCodeCatalogs are the models.dev providers that say which API a model
|
|
// of OpenCode's gateway is served on: the list of the gateway its URL is
|
|
// at (Go's for Go, Zen's for Zen) alone, whatever catalog the provider was
|
|
// saved with — the two serve one model on different APIs (MiniMax M2.7 on
|
|
// Messages at Go, on chat at Zen) — or, at another path, its own.
|
|
func (p Provider) openCodeCatalogs() []string {
|
|
for _, u := range []string{p.Chat, p.Responses, p.Anthropic} {
|
|
if _, id := openCodeRoot(u); id != "" {
|
|
return []string{id}
|
|
}
|
|
}
|
|
return p.Catalogs()
|
|
}
|