fix openrouter model picker refs (#63416)

* fix openrouter model picker refs

Signed-off-by: sallyom <somalley@redhat.com>

* test(ui): cover openrouter slash-id /model resolution

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: Vignesh Natarajan <vignesh.natarajan92@gmail.com>
This commit is contained in:
Sally O'Malley
2026-04-08 20:10:20 -04:00
committed by GitHub
parent b706301b44
commit 5f8de8c3f4
3 changed files with 84 additions and 2 deletions

View File

@@ -35,6 +35,37 @@ describe("chat-model-ref helpers", () => {
);
});
it("prefixes provider-native model ids that already contain slashes", () => {
expect(
buildChatModelOption({
id: "google/gemma-4-26b-a4b-it",
provider: "openrouter",
} as never),
).toEqual({
value: "openrouter/google/gemma-4-26b-a4b-it",
label: "google/gemma-4-26b-a4b-it · openrouter",
});
expect(
resolvePreferredServerChatModel("Google/Gemma-4-26b-a4b-it", "openrouter", [
{ id: "google/gemma-4-26b-a4b-it", provider: "openrouter" } as never,
]),
).toEqual({
value: "openrouter/google/gemma-4-26b-a4b-it",
source: "server",
});
});
it("reuses already-qualified catalog ids without double-prefixing them", () => {
expect(
resolvePreferredServerChatModel("OpenAI/GPT-4O", "openai", [
{ id: "openai/gpt-4o", provider: "openai" } as never,
]),
).toEqual({
value: "openai/gpt-4o",
source: "server",
});
});
it("normalizes raw overrides when the catalog match is unique", () => {
expect(normalizeChatModelOverrideValue(createChatModelOverride("gpt-5-mini"), catalog)).toBe(
"openai/gpt-5-mini",
@@ -102,7 +133,6 @@ describe("chat-model-ref helpers", () => {
).toEqual({
value: "openai/gpt-5-mini",
source: "server",
reason: "ambiguous",
});
});
});

View File

@@ -113,6 +113,20 @@ export function resolvePreferredServerChatModel(
if (!trimmedModel) {
return { value: "", source: "empty", reason: "empty" };
}
const trimmedProvider = provider?.trim();
if (trimmedProvider) {
const providerMatch = catalog.find(
(entry) =>
entry.provider?.trim().toLowerCase() === trimmedProvider.toLowerCase() &&
entry.id.trim().toLowerCase() === trimmedModel.toLowerCase(),
);
if (providerMatch) {
return {
value: buildChatModelOption(providerMatch).value,
source: "server",
};
}
}
const overrideResolution = resolveChatModelOverride(
createChatModelOverride(trimmedModel),
@@ -151,8 +165,15 @@ export function formatChatModelDisplay(value: string): string {
export function buildChatModelOption(entry: ModelCatalogEntry): { value: string; label: string } {
const provider = entry.provider?.trim();
const value = (() => {
if (!provider) {
return entry.id;
}
const providerPrefix = `${provider.toLowerCase()}/`;
return entry.id.toLowerCase().startsWith(providerPrefix) ? entry.id : `${provider}/${entry.id}`;
})();
return {
value: buildQualifiedChatModelValue(entry.id, provider),
value,
label: provider ? `${entry.id} · ${provider}` : entry.id,
};
}

View File

@@ -359,6 +359,37 @@ describe("executeSlashCommand directives", () => {
});
});
it("keeps openrouter-prefixed refs when patched model ids include slashes", async () => {
const request = vi.fn(async (method: string, _payload?: unknown) => {
if (method === "sessions.patch") {
return createResolvedModelPatch("google/gemma-4-26b-a4b-it", "openrouter");
}
throw new Error(`unexpected method: ${method}`);
});
const result = await executeSlashCommand(
{ request } as unknown as GatewayBrowserClient,
"main",
"model",
"google/gemma-4-26b-a4b-it",
{
chatModelCatalog: [
{
id: "google/gemma-4-26b-a4b-it",
name: "Gemma 4 26B",
provider: "openrouter",
},
],
},
);
expect(result.sessionPatch?.modelOverride).toEqual({
kind: "qualified",
value: "openrouter/google/gemma-4-26b-a4b-it",
});
expect(request).toHaveBeenCalledTimes(1);
});
it("falls back to the patched server provider when catalog lookup fails", async () => {
const request = vi.fn(async (method: string, _payload?: unknown) => {
if (method === "sessions.patch") {