repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/model-info/__tests__/contract.test.ts
Rawimport { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import type { MessageEndEvent } from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it, type MockInstance, vi } from "vitest";
import {
createPiSession,
type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
import { __test } from "../index.ts";
import { shortModelLabel } from "../model-label.ts";
import type { ProbeHooks } from "./probe.ts";
function extensionPath(): string {
return fileURLToPath(new URL("../index.ts", import.meta.url));
}
function probePath(): string {
return fileURLToPath(new URL("./probe.ts", import.meta.url));
}
function setProbe(hooks: ProbeHooks): void {
(globalThis as { __modelInfoProbe?: ProbeHooks }).__modelInfoProbe = hooks;
}
function clearProbe(): void {
delete (globalThis as { __modelInfoProbe?: ProbeHooks }).__modelInfoProbe;
}
function captureStatus(
session: PiSession,
mode: "tui" | "print",
): MockInstance {
const runner = session.session.extensionRunner;
const ui = { ...runner.getUIContext() };
const spy = vi.spyOn(ui, "setStatus");
runner.setUIContext(ui, mode);
return spy;
}
function latestStatus(spy: MockInstance, id: string): unknown {
const calls = spy.mock.calls as Array<[string, unknown?]>;
return calls.filter((call) => call[0] === id).at(-1)?.[1];
}
describe("model-info contract", () => {
let session: PiSession | undefined;
afterEach(async () => {
await session?.dispose();
session = undefined;
clearProbe();
vi.restoreAllMocks();
});
it("loads in real Pi extension runtime", async () => {
session = await createPiSession({ extensionPaths: [extensionPath()] });
expect(session.extensions).toHaveLength(1);
});
it("writes enabled lifecycle debug records in an isolated sandbox", async () => {
const root = mkdtempSync(join(tmpdir(), "pi-ext-model-info-debug-"));
try {
session = await createPiSession({
cwd: root,
extensionPaths: [extensionPath()],
env: {
PI_MODEL_INFO_DEBUG: "1",
XDG_STATE_HOME: join(root, "state"),
LOCALAPPDATA: join(root, "state"),
},
});
const runner = session.session.extensionRunner;
runner.setUIContext({ ...runner.getUIContext() }, "tui");
await runner.emit({ type: "session_start", reason: "startup" });
await runner.emit({ type: "session_shutdown", reason: "quit" });
const debugDir =
process.platform === "darwin"
? join(root, ".home", "Library", "Logs", "pi-ext", "model-info")
: join(root, "state", "pi-ext", "debug", "model-info");
const events = readdirSync(debugDir).flatMap((file) =>
readFileSync(join(debugDir, file), "utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line)),
);
expect(events).toEqual(
expect.arrayContaining([
expect.objectContaining({
extension: "model-info",
event: "session.start",
}),
expect.objectContaining({
extension: "model-info",
event: "session.shutdown",
}),
expect.objectContaining({
event: "route.refresh",
outcome: "fallback",
}),
]),
);
} finally {
await session?.dispose();
session = undefined;
rmSync(root, { recursive: true, force: true });
}
});
it("uses acknowledged footer segments without transient fallback status", async () => {
let accepted = 0;
setProbe({
onFooterSegment: (data: unknown) => {
const accept = (data as { accept?: unknown })?.accept;
if (typeof accept !== "function") return;
(accept as () => void)();
accepted++;
},
});
session = await createPiSession({
extensionPaths: [probePath(), extensionPath()],
});
const spy = captureStatus(session, "tui");
await session.session.extensionRunner.emit({
type: "session_start",
reason: "startup",
});
expect(accepted).toBeGreaterThan(0);
expect(latestStatus(spy, "model-info")).toBeUndefined();
});
it("keeps fallback status when only removals are acknowledged", async () => {
let removals = 0;
setProbe({
onFooterSegment: (data: unknown) => {
const segment = data as { text?: unknown; accept?: unknown };
if (segment.text !== undefined || typeof segment.accept !== "function")
return;
(segment.accept as () => void)();
removals++;
},
});
session = await createPiSession({
extensionPaths: [probePath(), extensionPath()],
});
const spy = captureStatus(session, "tui");
await session.session.extensionRunner.emit({
type: "session_start",
reason: "startup",
});
expect(removals).toBeGreaterThan(0);
expect(latestStatus(spy, "model-info")).toMatch(/:/);
});
it("replaces producer fallbacks with the combined route", async () => {
setProbe({
onSessionStart: (ctx, pi) => {
ctx.ui.setStatus("fast", "fast fallback");
pi.events.emit("model-route:fast", { active: true });
ctx.ui.setStatus("angel", "angel fallback");
pi.events.emit("model-route:angel", {
advisor: { modelId: "gpt-6-astra", thinkingLevel: "max" },
});
},
});
session = await createPiSession({
extensionPaths: [probePath(), extensionPath()],
});
const spy = captureStatus(session, "print");
await session.session.extensionRunner.emit({
type: "session_start",
reason: "startup",
});
expect(latestStatus(spy, "fast")).toBeUndefined();
expect(latestStatus(spy, "angel")).toBeUndefined();
expect(latestStatus(spy, "model-info")).toMatch(/→ astra-6:max$/u);
});
it("shows the model a router resolved to after a reply, until the model changes", async () => {
session = await createPiSession({ extensionPaths: [extensionPath()] });
const runner = session.session.extensionRunner;
const spy = captureStatus(session, "print");
const model = runner.createContext().model;
if (!model) throw new Error("fixture model missing");
await runner.emitMessageEnd({
type: "message_end",
message: {
role: "assistant",
provider: model.provider,
model: model.id,
responseModel: "kilo/dots-studio/dots-3-note-preview:free",
} as unknown as MessageEndEvent["message"],
});
expect(latestStatus(spy, "model-info")).toMatch(
/ → dots-3-note-preview:free$/,
);
await runner.emit({
type: "model_select",
model,
previousModel: model,
source: "set",
});
expect(latestStatus(spy, "model-info")).not.toMatch(/→/);
});
it("reports native virtual dispatch without synthetic responseModel metadata", () => {
const ctx = {
model: { api: "pi-virtual", provider: "grts", id: "auto" },
} as never;
const reply = {
role: "assistant",
provider: "google",
model: "gemma-4-31b-it",
stopReason: "stop",
};
expect(__test.servedModel(reply, ctx)).toBe("google/gemma-4-31b-it");
expect(
__test.servedModel({ ...reply, stopReason: "error" }, ctx),
).toBeUndefined();
expect(
__test.servedModel({ ...reply, stopReason: "aborted" }, ctx),
).toBeUndefined();
expect(
__test.servedModel({ ...reply, provider: "grts", model: "auto" }, ctx),
).toBeUndefined();
expect(
__test.servedModel(
{
...reply,
provider: "openrouter",
model: "openrouter/free",
responseModel: "qwen/test:free",
},
ctx,
),
).toBe("openrouter/qwen/test:free");
});
it("reports a served model only when it differs from the selected one", () => {
const ctx = { model: { provider: "grts", id: "auto" } } as never;
const reply = (extra: object) => ({
role: "assistant",
provider: "grts",
model: "auto",
...extra,
});
expect(
__test.servedModel(reply({ responseModel: "kilo/x:free" }), ctx),
).toBe("kilo/x:free");
expect(__test.servedModel(reply({}), ctx)).toBeUndefined();
expect(
__test.servedModel(reply({ responseModel: "auto" }), ctx),
).toBeUndefined();
expect(
__test.servedModel(
reply({ provider: "openrouter", responseModel: "a/b" }),
ctx,
),
).toBeUndefined();
expect(
__test.servedModel({ role: "user", responseModel: "a/b" }, ctx),
).toBeUndefined();
});
it("restores the served model from the last assistant reply in the branch", () => {
const ctx = (branch: object[]) =>
({
model: { provider: "grts", id: "auto" },
sessionManager: { getBranch: () => branch },
}) as never;
const assistant = (responseModel?: string) => ({
type: "message",
message: {
role: "assistant",
provider: "grts",
model: "auto",
responseModel,
},
});
expect(
__test.servedFromBranch(
ctx([
assistant("kilo/old:free"),
assistant("google/gemini-3.8-flash"),
{ type: "message", message: { role: "user" } },
]),
),
).toBe("google/gemini-3.8-flash");
expect(__test.servedFromBranch(ctx([assistant()]))).toBeUndefined();
});
it("formats model and thinking", () => {
const ctx = {
cwd: "/tmp",
model: { provider: "test", id: "openai-codex/gpt-5.6-sol" },
} as any;
expect(__test.modelInfoLabel(ctx, "high")).toBe("sol-5.6:high");
expect(__test.providerLabel(ctx)).toBe("test");
expect(__test.providerIcon(ctx)).toBeUndefined();
});
it.each([
["openai-codex", ""],
["anthropic", ""],
["klaus", ""],
["github-copilot", ""],
["google-gemini-cli", ""],
["hetzner", ""],
["grts", ""],
])("uses the verified brand icon for %s", (provider, icon) => {
expect(__test.providerIcon({ model: { provider } } as any)).toBe(icon);
});
it("detects declared OAuth subscriptions and Kimi Coding", () => {
const modelRegistry = {
isUsingOAuth: () => true,
getProvider: () => ({ auth: { oauth: { isSubscription: true } } }),
};
expect(
__test.usesSubscription({
model: { provider: "anthropic" },
modelRegistry,
} as any),
).toBe(true);
expect(
__test.usesSubscription({
model: { provider: "kimi-coding" },
modelRegistry: {},
} as any),
).toBe(true);
});
it("combines executor, transport, and advisor into one route", () => {
expect(
__test.statusLabel("sol-5.6:medium", true, {
modelId: "gpt-6-astra",
thinkingLevel: "max",
}),
).toBe("sol-5.6:medium → astra-6:max");
});
it.each([
["gpt-6-astra", "astra-6"],
["gpt-5.6-sol", "sol-5.6"],
["gpt-5.5", "gpt-5.5"],
["claude-fable-5-1", "fable-5.1"],
["claude-opus-5", "opus-5"],
["claude-opus-5-5", "opus-5.5"],
["claude-opus-4-5-20251101", "opus-4.5"],
["gemini-3.1-pro-preview", "3.1-pro"],
["grok-4.6", "grok-4.6"],
["deepseek-v4-flash-vision-exp", "v4-flash"],
["MiniMax-M3", "M3"],
["kimi-k2.7-code-highspeed", "k2.7-code"],
["glm-5.3-highspeed", "glm-5.3"],
["mistral-medium-2604", "mistral-medium"],
["new-family-7-alpha", "new-family-7-alpha"],
])("shortens %s by model-family grammar", (modelId, expected) => {
expect(shortModelLabel(modelId)).toBe(expected);
});
});