Files
yetone 050e629b0a gateway, provider: a Claude model is asked on Anthropic's Messages where its provider has an Anthropic URL, not Chat (#997, wlj521: 自动好像只会走chat; ReturnTrue on Discord: a relay with an OpenAI and an Anthropic URL had 0% cache hits from Claude models until the OpenAI URL was left empty) — every API's order was Chat first. A request on an API the provider serves was already relayed as it is (a fake relay recording each path: Messages → /v1/messages, Responses → /v1/responses, Chat → /v1/chat/completions, for GLM, GPT and Claude alike, with one URL for three APIs and with an OpenAI URL plus an Anthropic one), but two places fell back to Chat for Claude: usable(), which a request on an API the provider lacks is translated to (Codex's Responses for a Claude model at a Chat+Anthropic relay went to /v1/chat/completions, losing the cache breakpoints the translation to Messages adds), and Provider.Native, the API Pi, omp and droid are wired to ask each model on (route.go's magpieModels), which named Chat for every Claude model at such a relay, so those agents spoke Chat and their cache_control never reached the relay. MessagesFirst (a Claude model by name, where the provider has an Anthropic URL) now puts Messages first in both, beside ResponsesFirst for OpenAI's models. A model the user set "Asked on" for, or one the vendor's list names its APIs for, is as before; GPT and other models keep Chat first; Copilot's Claude, which its list serves on Chat and Messages, is now native on Messages (TestNative updated), the API Claude Code already reaches it on. Not reproduced: a Claude Code /v1/messages request translated to Chat — with either relay layout, a list as new-api answers it, and a key per protocol, it went to /v1/messages with cache_control kept; ReturnTrue is asked for the request's Protocol row. TestClaudeGoesOnMessagesWhereTheRelayHasThem and TestNativeClaudeIsMessagesWhereServed fail without the change and pass; TestAutoRelaysOnTheClientsOwnAPI guards the pass-through for the three APIs; -race -count=5 on them. go test ./internal/gateway ./internal/provider ./internal/catalog ./internal/agent, go vet, darwin, linux (nogui) and windows builds pass. docs/subsystems/gateway-routing.md says the order.
2026-10-06 15:33:16 +08:00

52 lines
2.3 KiB
Go

package provider
import (
"path/filepath"
"testing"
"github.com/yetone/magpie/internal/catalog"
)
// Native is the API a client does best to ask a model on, for the gateway
// to relay it as it is.
func TestNative(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
catalog.Reset()
t.Cleanup(catalog.Reset)
// Copilot's model list says which APIs serve each model
if err := catalog.SaveLive("copilot", "https://api.githubcopilot.com", []catalog.Model{
{ID: "gpt-5.5", APIs: []string{"responses"}}, {ID: "claude-sonnet-5", APIs: []string{"chat", "anthropic"}}}); err != nil {
t.Fatal(err)
}
openai := Provider{ID: "openai", Chat: "https://api.openai.com/v1", Responses: "https://api.openai.com/v1"}
copilot := Provider{ID: "copilot", Chat: "https://api.githubcopilot.com", Responses: "https://api.githubcopilot.com", Anthropic: "https://api.githubcopilot.com"}
for _, c := range []struct {
name string
p Provider
model string
want Protocol
}{
{"chat only", Provider{ID: "relay", Chat: "https://relay.test/v1"}, "glm-4.6", Chat},
{"responses only", Provider{ID: "resp", Responses: "https://resp.test/v1"}, "m", Responses},
{"ChatGPT sign-in", Provider{ID: "codex", Responses: CodexBase, Account: &Account{Agent: "codex"}}, "gpt-5.5", Responses},
{"GPT on OpenAI's API", openai, "gpt-5.5", Responses},
{"o-series on OpenAI's API", openai, "o3", Responses},
{"other on OpenAI's API", openai, "whisper-x", Chat},
{"Chat and Responses elsewhere", Provider{ID: "both", Chat: "https://x.test/v1", Responses: "https://x.test/v1"}, "gpt-5.5", Chat},
{"anthropic only", Provider{ID: "anthropic", Anthropic: "https://api.anthropic.com"}, "claude-sonnet-5", Anthropic},
{"Copilot GPT", copilot, "gpt-5.5", Responses},
{"Copilot Claude", copilot, "claude-sonnet-5", Anthropic},
{"Cursor", Provider{ID: "cursor", Chat: "https://cursor.test/v1", Account: &Account{Agent: "cursor"}}, "auto", ""},
{"Claude Code", Provider{ID: "claude", Anthropic: "https://api.anthropic.com", Account: &Account{Agent: "claude"}}, "claude-sonnet-5", ""},
{"nothing", Provider{ID: "none"}, "m", ""},
} {
if got := c.p.Native(c.model); got != c.want {
t.Errorf("%s: %q, want %q", c.name, got, c.want)
}
}
}