fix(coding-agent): limit @upstream fuzzy bypass to aggregators

The routing bypass in matchModel() returned early for any provider whose
post-slash id contained a valid @slug, so fuzzy provider-qualified patterns
over non-aggregator ids that legitimately end in @ (e.g. google-vertex/opus@default
-> claude-opus-4-8@default) resolved to nothing. Gate the bypass on
providerModels.some(supportsUpstreamRouting) so only OpenRouter / Vercel Gateway
short-circuit to the routing fallback. Addresses Codex review feedback on #2710.
This commit is contained in:
can1357
2026-06-16 14:25:53 +02:00
parent 8b17764e08
commit 6b6e9aaffc
2 changed files with 27 additions and 2 deletions
@@ -481,8 +481,11 @@ function matchModel(
// The prefix is not a known provider in this candidate set, so treat the
// slash as part of the raw model ID and continue with generic matching.
} else {
// Let the routing fallback apply `@upstream` before fuzzy matching can consume the slug.
if (splitUpstreamRouting(modelId)) {
// Let the routing fallback apply `@upstream` before fuzzy matching can consume the
// slug — but only for aggregator providers (OpenRouter / Vercel Gateway). Other
// providers have ids that legitimately end in `@` (Vertex `claude-opus-4-8@default`),
// and the fallback never routes them, so they must keep fuzzy matching.
if (splitUpstreamRouting(modelId) && providerModels.some(supportsUpstreamRouting)) {
return undefined;
}
const scored = providerModels
@@ -1020,6 +1020,28 @@ describe("provider routing selector (@upstream)", () => {
expect(openRouterOnly(result.model)).toBeUndefined();
});
test("keeps fuzzy matching a non-aggregator provider id that ends in @ (Vertex)", () => {
const vertexModel: Model<"anthropic-messages"> = buildModel({
id: "claude-opus-4-8@default",
name: "Claude Opus 4.8",
api: "anthropic-messages",
provider: "google-vertex",
baseUrl: "https://us-aiplatform.googleapis.com",
reasoning: true,
input: ["text"],
cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
contextWindow: 200000,
maxTokens: 32000,
});
// `opus@default` is a fuzzy provider-qualified pattern: the `@upstream` bypass must not
// swallow it, because google-vertex is not an aggregator and the routing fallback would
// never resolve it, leaving the selector unmatched.
const result = parseModelPattern("google-vertex/opus@default", [vertexModel]);
expect(result.model?.id).toBe("claude-opus-4-8@default");
expect(result.upstream).toBeUndefined();
expect(openRouterOnly(result.model)).toBeUndefined();
});
test("ignores @slug on a non-aggregator model (no silent routing)", () => {
const result = parseModelPattern("gpt-4o@cerebras", allModels);
expect(result.model).toBeUndefined();