fix: keep model selection on local normalization paths

This commit is contained in:
Peter Steinberger
2026-03-28 09:57:32 +00:00
parent c1ae49e306
commit cec1703734
2 changed files with 26 additions and 17 deletions

View File

@@ -16,25 +16,25 @@ describe("model-selection plugin runtime normalization", () => {
it("delegates provider-owned model id normalization to plugin runtime hooks", async () => {
normalizeProviderModelIdWithPluginMock.mockImplementation(({ provider, context }) => {
if (
provider === "xai" &&
(context as { modelId?: string }).modelId === "grok-4.20-experimental-beta-0304-reasoning"
provider === "custom-provider" &&
(context as { modelId?: string }).modelId === "custom-legacy-model"
) {
return "grok-4.20-beta-latest-reasoning";
return "custom-modern-model";
}
return undefined;
});
const { parseModelRef } = await import("./model-selection.js");
expect(parseModelRef("grok-4.20-experimental-beta-0304-reasoning", "xai")).toEqual({
provider: "xai",
model: "grok-4.20-beta-latest-reasoning",
expect(parseModelRef("custom-legacy-model", "custom-provider")).toEqual({
provider: "custom-provider",
model: "custom-modern-model",
});
expect(normalizeProviderModelIdWithPluginMock).toHaveBeenCalledWith({
provider: "xai",
provider: "custom-provider",
context: {
provider: "xai",
modelId: "grok-4.20-experimental-beta-0304-reasoning",
provider: "custom-provider",
modelId: "custom-legacy-model",
},
});
});

View File

@@ -6,6 +6,8 @@ import {
toAgentModelListLike,
} from "../config/model-input.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { normalizeGoogleModelId } from "../plugin-sdk/google.js";
import { normalizeXaiModelId } from "../plugin-sdk/xai.js";
import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js";
import { normalizeProviderModelIdWithPlugin } from "../plugins/provider-runtime.js";
import { sanitizeForLog } from "../terminal/ansi.js";
@@ -117,6 +119,18 @@ function normalizeProviderModelId(provider: string, model: string): string {
if (provider === "anthropic") {
return normalizeAnthropicModelId(model);
}
if (provider === "google" || provider === "google-vertex") {
return normalizeGoogleModelId(model);
}
if (provider === "openai") {
return model;
}
if (provider === "openrouter") {
return model.includes("/") ? model : `openrouter/${model}`;
}
if (provider === "xai") {
return normalizeXaiModelId(model);
}
if (provider === "vercel-ai-gateway" && !model.includes("/")) {
// Allow Vercel-specific Claude refs without an upstream prefix.
const normalizedAnthropicModel = normalizeAnthropicModelId(model);
@@ -124,13 +138,6 @@ function normalizeProviderModelId(provider: string, model: string): string {
return `anthropic/${normalizedAnthropicModel}`;
}
}
// OpenRouter-native models (e.g. "openrouter/aurora-alpha") need the full
// "openrouter/<name>" as the model ID sent to the API. Models from external
// providers already contain a slash (e.g. "anthropic/claude-sonnet-4-5") and
// are passed through as-is (#12924).
if (provider === "openrouter" && !model.includes("/")) {
return `openrouter/${model}`;
}
return (
normalizeProviderModelIdWithPlugin({
provider,
@@ -201,7 +208,9 @@ export function inferUniqueProviderFromConfiguredModels(params: {
if (!ref || !ref.includes("/")) {
continue;
}
const parsed = parseModelRef(ref, DEFAULT_PROVIDER);
const parsed = parseModelRef(ref, DEFAULT_PROVIDER, {
allowPluginNormalization: false,
});
if (!parsed) {
continue;
}