Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions providerroutes/stitch.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,42 @@ func stitchRequestURL(req *http.Request, target *url.URL, basePath, version stri
default:
reqPath = trimLeadingVersionSegment(reqPath)
}
} else if dup := trailingVersionSegment(basePath); dup != "" {
// πŸ”΄ 2026-08-15: the ONE unambiguous case on the degraded path.
//
// The block above deliberately leaves rowKnown=false alone, and that
// stays true: for an unknown vendor we cannot tell whether a stored
// path is a mount prefix (https://gw.example/proxy serving
// /proxy/v1/chat/completions) or a complete API root, so we must not
// decide its shape. This branch decides nothing about shape.
//
// It only refuses to emit the SAME version segment twice. When the
// stored base_url already ends in the exact segment the client is
// sending, literal-prepend produced:
// https://www.cun.ai/v1 + /v1/chat/completions
// -> https://www.cun.ai/v1/v1/chat/completions
// which is wrong under every reading of the vendor's shape β€” there is
// no gateway for which /v1/v1 is the intended path. Nothing is
// swallowed: the segment survives, it is contributed by basePath
// instead of by reqPath, so the bare-host case the note above protects
// (https://gw.example + /v1/chat/completions, basePath "") is
// untouched β€” there is no trailing segment to duplicate.
//
// Why this matters in practice: most relay vendors DOCUMENT their
// endpoint with the version on it ("base_url: https://xxx/v1"), so the
// user who pastes the documented URL got the broken shape and the one
// who happened to trim it got the working one. Same /v1/v1 poison the
// OAuth path was fixed for (stitchOAuthRequestURL) and known rows were
// fixed for on 2026-08-03; the unknown-host join was the last one left.
//
// Uses the same digits-only strictness as trimLeadingVersionSegment, so
// "/v1beta" or "/v1abc" is never treated as a duplicate of "/v1".
switch {
case strings.HasPrefix(reqPath, dup+"/"):
reqPath = strings.TrimPrefix(reqPath, dup)
case reqPath == dup:
reqPath = ""
}
}

stitched := basePath + version + reqPath
Expand Down Expand Up @@ -188,6 +224,32 @@ func trimLeadingVersionSegment(p string) string {
return p[len(seg)+1:]
}

// trailingVersionSegment returns the leading-slash version segment a path ENDS
// with ("/v1", "/v3"), or "" when it ends with anything else. It is the mirror
// of trimLeadingVersionSegment and deliberately shares its strictness: "v"
// followed by DIGITS ONLY, so "/v1beta" and "/v1abc" return "" and can never be
// mistaken for a duplicate of a client's "/v1".
//
// Only used for the degraded-path duplicate-join guard in stitchRequestURL; it
// answers "does basePath already end in this exact version segment?", never
// "what version does this vendor use?".
func trailingVersionSegment(p string) string {
i := strings.LastIndexByte(p, '/')
if i < 0 {
return ""
}
seg := p[i:] // includes the leading '/'
if trimLeadingVersionSegment(seg) != "" {
// trimLeadingVersionSegment returns the REMAINDER; a bare version
// segment leaves "" behind. Anything else means this is not one.
return ""
}
if !strings.HasPrefix(seg, "/v") || len(seg) < 3 {
return ""
}
return seg
}

// PathDiscarded reports whether resolving storedBaseURL through this table
// would THROW AWAY path information the stored URL carries β€” and returns the
// row that swallowed it.
Expand Down
145 changes: 145 additions & 0 deletions providerroutes/stitch_unknown_host_dedup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package providerroutes

import (
"net/http"
"testing"
)

// The degraded (unknown-host) stitch must never emit the same version segment
// twice β€” and must change nothing else (2026-08-15).
//
// Context: a host absent from provider_routes takes the literal-prepend branch
// (stitchRequestURL with rowKnown=false). That branch is deliberately left
// shape-agnostic: for a vendor the table does not know we cannot tell a mount
// prefix (https://gw.example/proxy serving /proxy/v1/chat/completions) from a
// complete API root, so we must not decide. See the long 🚫 note in
// stitchRequestURL β€” that decision stands.
//
// What was still wrong: when the STORED base_url already ended in the exact
// segment the client sends, literal-prepend produced /v1/v1/…, which is wrong
// under every reading of the vendor's shape. Most relays document their
// endpoint WITH the version ("base_url: https://xxx/v1"), so the user who
// pasted the documented URL got a broken route and the one who trimmed it got a
// working one. Same /v1/v1 poison already fixed on the OAuth path
// (stitchOAuthRequestURL) and for known rows (2026-08-03).
//
// 能纒: delete the `else if dup := trailingVersionSegment(basePath)` branch in
// stitchRequestURL β€” the duplicate cases below go back to /v1/v1.

func stitched(t *testing.T, base, clientPath string) string {
t.Helper()
req, err := http.NewRequest("POST", "http://ignored"+clientPath, nil)
if err != nil {
t.Fatalf("build request: %v", err)
}
if err := Default().Stitch(req, base); err != nil {
t.Fatalf("stitch %s: %v", base, err)
}
return req.URL.Host + req.URL.Path
}

func TestUnknownHostStitch_DoesNotDuplicateVersionSegment(t *testing.T) {
cases := []struct{ name, base, client, want string }{
{
// The documented-URL shape. This is the bug.
name: "stored base already carries the client's version",
base: "https://www.cun.ai/v1", client: "/v1/chat/completions",
want: "www.cun.ai/v1/chat/completions",
},
{
name: "trailing slash on the stored version",
base: "https://www.cun.ai/v1/", client: "/v1/chat/completions",
want: "www.cun.ai/v1/chat/completions",
},
{
name: "non-/v1 version, still a duplicate",
base: "https://relay.example/api/v3", client: "/v3/chat/completions",
want: "relay.example/api/v3/chat/completions",
},
{
name: "client sends the bare version and nothing else",
base: "https://relay.example/v1", client: "/v1",
want: "relay.example/v1",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := stitched(t, c.base, c.client); got != c.want {
t.Errorf("got %s, want %s\nA stored base_url that already ends in the "+
"client's version segment must not emit it twice β€” /v1/v1/... is "+
"wrong for every gateway.", got, c.want)
}
})
}
}

// The other half of the fence, and the more important one: everything the
// degraded path did before MUST still happen. These are the shapes the
// stitchRequestURL note protects β€” where the vendor's shape is genuinely
// unknowable and we must not decide for it.
func TestUnknownHostStitch_LeavesNonDuplicateShapesAlone(t *testing.T) {
cases := []struct{ name, base, client, want string }{
{
// The note's own counter-example: swallowing this /v1 would break a
// private gateway that serves /v1/chat/completions off a bare host.
name: "bare host keeps the client's version",
base: "https://gw.example", client: "/v1/chat/completions",
want: "gw.example/v1/chat/completions",
},
{
name: "mount prefix keeps the client's version",
base: "https://relay.example/proxy", client: "/v1/chat/completions",
want: "relay.example/proxy/v1/chat/completions",
},
{
// Different version segments are NOT duplicates β€” we do not know
// whether /api/v3 is a root or a prefix, so nothing is decided.
name: "different version segment is not a duplicate",
base: "https://relay.example/api/v3", client: "/v1/chat/completions",
want: "relay.example/api/v3/v1/chat/completions",
},
{
// Strictness shared with trimLeadingVersionSegment: digits only.
name: "v1beta is not a duplicate of v1",
base: "https://gw.example/v1beta", client: "/v1/models",
want: "gw.example/v1beta/v1/models",
},
{
name: "v1abc is a real path segment",
base: "https://gw.example/v1abc", client: "/v1abc/x",
want: "gw.example/v1abc/v1abc/x",
},
{
name: "the exact shape the user runs today stays byte-identical",
base: "https://www.cun.ai", client: "/v1/chat/completions",
want: "www.cun.ai/v1/chat/completions",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := stitched(t, c.base, c.client); got != c.want {
t.Errorf("got %s, want %s\nThe degraded path must stay shape-agnostic "+
"for unknown vendors: only an exact duplicate of the client's own "+
"version segment may be collapsed.", got, c.want)
}
})
}
}

func TestTrailingVersionSegment(t *testing.T) {
cases := []struct{ in, want string }{
{"/v1", "/v1"},
{"/api/v3", "/v3"},
{"", ""},
{"/proxy", ""},
{"/v1beta", ""}, // not digits-only
{"/v1abc", ""},
{"/v", ""},
{"/version", ""},
}
for _, c := range cases {
if got := trailingVersionSegment(c.in); got != c.want {
t.Errorf("trailingVersionSegment(%q) = %q, want %q", c.in, got, c.want)
}
}
}
Loading