Skip to content

Commit 395a3a1

Browse files
authored
fix(proxy): fall back to OpenRouter for vendor-prefixed models (#1)
1 parent 47ae50f commit 395a3a1

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

internal/proxy/handler.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,19 @@ func (h *Handler) handleOpenAI(w http.ResponseWriter, r *http.Request, agentID s
128128

129129
prov, err := h.registry.Get(providerName)
130130
if err != nil {
131-
h.fail(w, http.StatusBadGateway, "unknown provider", agentID, requestedModel, start, err)
132-
return
131+
// Compatibility: some clients targeting OpenRouter via an OpenAI-compatible
132+
// provider config send vendor-prefixed model IDs like "anthropic/claude-*"
133+
// instead of "openrouter/anthropic/claude-*". If no first-party provider is
134+
// configured for that prefix, route through OpenRouter and preserve the full
135+
// vendor/model path as the upstream model.
136+
bridge, bridgeErr := h.registry.Get("openrouter")
137+
if bridgeErr != nil || strings.EqualFold(providerName, "openrouter") {
138+
h.fail(w, http.StatusBadGateway, "unknown provider", agentID, requestedModel, start, err)
139+
return
140+
}
141+
upstreamModel = providerName + "/" + upstreamModel
142+
providerName = bridge.Name
143+
prov = bridge
133144
}
134145

135146
// Format bridge: if the resolved provider uses Anthropic format but

internal/proxy/handler_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,50 @@ func TestHandlerRejectsUnknownProvider(t *testing.T) {
8585
}
8686
}
8787

88+
func TestHandlerFallsBackToOpenRouterForVendorPrefixedModel(t *testing.T) {
89+
var gotAuth string
90+
var gotBody []byte
91+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
92+
gotAuth = r.Header.Get("Authorization")
93+
var err error
94+
gotBody, err = io.ReadAll(r.Body)
95+
if err != nil {
96+
t.Fatalf("read body: %v", err)
97+
}
98+
w.Header().Set("Content-Type", "application/json")
99+
_, _ = w.Write([]byte(`{"id":"chatcmpl-1","choices":[{"message":{"content":"hello"}}]}`))
100+
}))
101+
defer backend.Close()
102+
103+
reg := provider.NewRegistry("")
104+
reg.Set("openrouter", &provider.Provider{
105+
Name: "openrouter", BaseURL: backend.URL + "/v1", APIKey: "sk-real", Auth: "bearer",
106+
})
107+
108+
h := NewHandler(reg, stubContextLoaderWithToken("tiverton", "tiverton:dummy123"), nil)
109+
body := `{"model":"anthropic/claude-sonnet-4","messages":[{"role":"user","content":"hi"}]}`
110+
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", bytes.NewBufferString(body))
111+
req.Header.Set("Authorization", "Bearer tiverton:dummy123")
112+
req.Header.Set("Content-Type", "application/json")
113+
w := httptest.NewRecorder()
114+
115+
h.ServeHTTP(w, req)
116+
117+
if w.Code != http.StatusOK {
118+
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
119+
}
120+
if gotAuth != "Bearer sk-real" {
121+
t.Fatalf("expected openrouter auth forwarded, got %q", gotAuth)
122+
}
123+
var payload map[string]any
124+
if err := json.Unmarshal(gotBody, &payload); err != nil {
125+
t.Fatalf("unmarshal backend body: %v", err)
126+
}
127+
if payload["model"] != "anthropic/claude-sonnet-4" {
128+
t.Fatalf("expected vendor-prefixed model preserved for openrouter fallback, got %#v", payload["model"])
129+
}
130+
}
131+
88132
func TestHandlerRejectsWrongSecret(t *testing.T) {
89133
reg := provider.NewRegistry("")
90134
reg.Set("openai", &provider.Provider{Name: "openai", BaseURL: "https://api.openai.com/v1", APIKey: "sk-real", Auth: "bearer"})

0 commit comments

Comments
 (0)