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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user