pi-mono/packages/coding-agent/test/scoped-models-selector.test.ts
Petr Houška 606af8662e feat(coding-agent): show model names in model selectors when they add information
/model and /scoped-models listed only `id [provider]`, so renamed
overrides from models.json were invisible in the list. Rows now show
the name in parentheses when it has words the id lacks, e.g.
`minimax/minimax-m3 (MiniMax M3 [parasail]) [openrouter]`. Names that
only restate the id with different casing, separators, or a dropped
namespace/version suffix stay hidden.

closes #5916
2026-10-23 21:19:51 +02:00

141 lines
4.9 KiB
TypeScript

import { setKeybindings } from "@earendil-works/pi-tui";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { KeybindingsManager } from "../src/core/keybindings.ts";
import { ScopedModelsSelectorComponent } from "../src/modes/interactive/components/scoped-models-selector.ts";
import { initTheme } from "../src/modes/interactive/theme/theme.ts";
import { stripAnsi } from "../src/utils/ansi.ts";
import { createHarness, type Harness } from "./suite/harness.ts";
interface ModelState {
id: string;
name: string;
enabled: boolean;
}
function getMarkerStates(selector: ScopedModelsSelectorComponent, models: ModelState[]): boolean[] {
const lines = stripAnsi(selector.render(120).join("\n")).split("\n");
return models.map((model) => {
const line = lines.find((candidate) => candidate.includes(`${model.id} [`));
if (!line) throw new Error(`Expected rendered row for ${model.id}`);
return line.slice(2).startsWith("✓ ");
});
}
describe("scoped models selector", () => {
let harness: Harness | undefined;
beforeAll(() => {
initTheme("dark");
});
beforeEach(() => {
setKeybindings(new KeybindingsManager());
});
afterEach(() => {
harness?.cleanup();
harness = undefined;
});
async function createSelector(models: ModelState[]) {
harness = await createHarness({ models });
const provider = harness.models[0].provider;
const selector = new ScopedModelsSelectorComponent(
{
allModels: [...harness.models],
enabledModelIds: models.filter((model) => model.enabled).map((model) => `${provider}/${model.id}`),
},
{
onChange: (enabledModelIds) => {
for (const model of models) {
model.enabled = enabledModelIds === null || enabledModelIds.includes(`${provider}/${model.id}`);
}
},
onPersist: () => {},
onCancel: () => {},
},
);
return selector;
}
// #5916
it("shows model names that differ from the ID", async () => {
const selector = await createSelector([
{ id: "model-a", name: "Model A", enabled: true },
{ id: "model-b", name: "Renamed B", enabled: true },
]);
const provider = harness!.models[0].provider;
const rows = stripAnsi(selector.render(120).join("\n"))
.split("\n")
.map((line) => line.slice(4).trimEnd());
expect(rows).toContain(`model-a [${provider}]`);
expect(rows).toContain(`model-b (Renamed B) [${provider}]`);
});
it("marks every model after enabling all", async () => {
const models = [
{ id: "model-a", name: "Model A", enabled: true },
{ id: "model-b", name: "Model B", enabled: false },
{ id: "model-c", name: "Model C", enabled: false },
];
const selector = await createSelector(models);
selector.handleInput("\x01");
expect(models.map((model) => model.enabled)).toEqual([true, true, true]);
expect(getMarkerStates(selector, models)).toEqual([true, true, true]);
expect(stripAnsi(selector.render(120).join("\n"))).toContain("all enabled");
});
it("disables only the selected model after enabling all", async () => {
const models = [
{ id: "model-a", name: "Model A", enabled: true },
{ id: "model-b", name: "Model B", enabled: false },
{ id: "model-c", name: "Model C", enabled: false },
];
const selector = await createSelector(models);
selector.handleInput("\x01");
selector.handleInput("\r");
expect(models.map((model) => model.enabled)).toEqual([false, true, true]);
expect(getMarkerStates(selector, models)).toEqual([false, true, true]);
});
it("enables only the selected model after clearing all", async () => {
const models = [
{ id: "model-a", name: "Model A", enabled: true },
{ id: "model-b", name: "Model B", enabled: true },
{ id: "model-c", name: "Model C", enabled: true },
];
const selector = await createSelector(models);
selector.handleInput("\x18");
expect(models.map((model) => model.enabled)).toEqual([false, false, false]);
expect(getMarkerStates(selector, models)).toEqual([false, false, false]);
selector.handleInput("\r");
expect(models.map((model) => model.enabled)).toEqual([true, false, false]);
expect(getMarkerStates(selector, models)).toEqual([true, false, false]);
});
it("restores the all-enabled state after re-enabling the last disabled model", async () => {
const models = [
{ id: "model-a", name: "Model A", enabled: true },
{ id: "model-b", name: "Model B", enabled: false },
{ id: "model-c", name: "Model C", enabled: false },
];
const selector = await createSelector(models);
selector.handleInput("\x01"); // enable all -> null
selector.handleInput("\r"); // disable model-a; enabled models re-sort first: [b, c, a]
selector.handleInput("\x1b[B");
selector.handleInput("\x1b[B"); // move selection back to model-a
selector.handleInput("\r"); // re-enable model-a
expect(models.map((model) => model.enabled)).toEqual([true, true, true]);
expect(getMarkerStates(selector, models)).toEqual([true, true, true]);
expect(stripAnsi(selector.render(120).join("\n"))).toContain("all enabled");
});
});