fix: distinguish model options with duplicate labels

Signed-off-by: Jasper <241105014+jipeng6036-del@users.noreply.github.com>
This commit is contained in:
Jasper
2026-10-09 04:24:40 -07:00
parent 908c994901
commit ba37dd88dc
2 changed files with 77 additions and 3 deletions

View File

@@ -496,12 +496,20 @@ export async function getAvailableModels(
for (const name of displayNames) {
displayNameCounts.set(name, (displayNameCounts.get(name) ?? 0) + 1);
}
const names = models.map((model, index) =>
displayNameCounts.get(displayNames[index]) === 1 ? displayNames[index] : model.displayName,
);
const nameCounts = new Map<string, number>();
for (const name of names) {
nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
}
return {
availableModels: models.map((model, index) => ({
modelId: model.value,
name:
displayNameCounts.get(displayNames[index]) === 1 ? displayNames[index] : model.displayName,
// Allowlisted aliases can inherit the same SDK label. Keep every ID
// selectable, but distinguish the rows for clients that show only names.
name: nameCounts.get(names[index]) === 1 ? names[index] : `${names[index]} (${model.value})`,
description: model.description,
})),
currentModelId: currentModel.value,

View File

@@ -1,6 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { query, type ModelInfo, type Query } from "@anthropic-ai/claude-agent-sdk";
import { buildModelConfigOption, getAvailableModels } from "../session-model.js";
import {
applyAvailableModelsAllowlist,
buildModelConfigOption,
getAvailableModels,
} from "../session-model.js";
const OPUS: ModelInfo = {
value: "opus[1m]",
@@ -151,6 +155,68 @@ describe("versioned model display names", () => {
);
expect(state.availableModels.map((model) => model.name)).toEqual(["Opus (1M context)", "Opus"]);
});
it("distinguishes allowlisted aliases and pins that inherit the same SDK label", async () => {
const sdkModels: ModelInfo[] = [
{ value: "default", displayName: "Default (recommended)", description: "" },
{
value: "opus",
resolvedModel: "claude-opus-5-5",
displayName: "Opus 5.5",
description: "For complex work and everyday tasks",
},
{ value: "haiku", displayName: "Haiku", description: "Fast" },
];
const ids = ["claude-opus-5-5[1m]", "claude-opus-5-5", "opus[1m]", "opus", "haiku"];
const models = applyAvailableModelsAllowlist(sdkModels, ids);
const setModel = vi.fn();
const state = await getAvailableModels(
{ setModel } as unknown as Query,
models,
sdkModels,
{ getSettings: () => ({}) },
{ log: vi.fn(), error: vi.fn() },
false,
"model-presentation-test",
);
const expected = [
"Default (recommended)",
...ids.slice(0, 4).map((id) => `Opus 5.5 (${id})`),
"Haiku",
];
expect(state.availableModels.map((model) => model.name)).toEqual(expected);
expect(state.availableModels.map((model) => model.modelId)).toEqual(["default", ...ids]);
expect(state.currentModelId).toBe("default");
expect(state.availableModels.slice(1, 5).map((model) => model.description)).toEqual(
Array(4).fill(sdkModels[1].description),
);
const option = buildModelConfigOption(state, models, false);
expect(option).toMatchObject({ currentValue: "default" });
expect(option.type === "select" && option.options.map((model) => model.name)).toEqual(expected);
expect(sdkModels[1].displayName).toBe("Opus 5.5");
expect(setModel).not.toHaveBeenCalled();
});
it("distinguishes identical custom labels without changing their model IDs", async () => {
const models: ModelInfo[] = [
{ value: "provider-a", displayName: "My model", description: "First endpoint" },
{ value: "provider-b", displayName: "My model", description: "Second endpoint" },
];
const state = await getAvailableModels(
{} as Query,
models,
models,
{ getSettings: () => ({}) },
{ log: vi.fn(), error: vi.fn() },
false,
"model-presentation-test",
);
expect(state.availableModels).toEqual([
{ modelId: "provider-a", name: "My model (provider-a)", description: "First endpoint" },
{ modelId: "provider-b", name: "My model (provider-b)", description: "Second endpoint" },
]);
});
});
// Account-dependent model availability requires the same authenticated environment