Files
oh-my-pi/packages/coding-agent/test/compaction-prefer-current-model.test.ts
T
can1357 1b9d9d0851 refactor(catalog)!: split model catalog from pi-ai
Move bundled models, model cache/manager, thinking metadata, effort helpers,
provider descriptors/discovery, wire constants, and model identity utilities
into the new @oh-my-pi/pi-catalog package.

Update pi-ai to keep provider runtime/auth concerns, move catalog provider
metadata into CATALOG_PROVIDERS, and migrate coding-agent, agent, stats, docs,
and tests to import catalog values from pi-catalog.

Split coding-agent model registry helpers into discovery, roles, and models
config modules while preserving registry orchestration.

BREAKING CHANGE: @oh-my-pi/pi-ai no longer exports catalog subpaths such as
/models, /model-cache, /model-manager, /model-thinking, /effort,
/provider-models*, discovery helpers, and provider wire constants; use the
matching @oh-my-pi/pi-catalog subpaths instead.
2026-06-10 04:06:57 +02:00

101 lines
3.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import * as path from "node:path";
import { Agent } from "@oh-my-pi/pi-agent-core";
import * as compactionModule from "@oh-my-pi/pi-agent-core/compaction";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { TempDir } from "@oh-my-pi/pi-utils";
import { assistantMsg, userMsg } from "./utilities";
/**
* Regression: when the user sets `modelRoles.default` to a model on a different
* provider than the current chat, compaction must still pick the active chat's
* model first. Otherwise an Anthropic chat would route compaction through the
* OpenAI remote-compaction endpoint (gated by `shouldUseOpenAiRemoteCompaction`),
* even though the live conversation never used OpenAI.
*/
describe("compaction prefers the current session model over modelRoles.default", () => {
let tempDir: TempDir;
let authStorage: AuthStorage;
let session: AgentSession;
let modelRegistry: ModelRegistry;
beforeEach(() => {
tempDir = TempDir.createSync("@pi-compact-current-");
});
afterEach(async () => {
vi.restoreAllMocks();
if (session) {
await session.dispose();
}
authStorage?.close();
tempDir.removeSync();
});
it("uses the active Anthropic chat model when modelRoles.default points at an OpenAI model", async () => {
const currentModel = getBundledModel("anthropic", "claude-sonnet-4-5");
const defaultRoleModel = getBundledModel("openai", "gpt-5");
if (!currentModel || !defaultRoleModel) {
throw new Error("Expected bundled test models to exist");
}
const settings = Settings.isolated({ "compaction.keepRecentTokens": 1 });
settings.setModelRole("default", `${defaultRoleModel.provider}/${defaultRoleModel.id}`);
const agent = new Agent({
initialState: {
model: currentModel,
systemPrompt: ["Test"],
tools: [],
messages: [],
},
});
authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db"));
// Both providers have credentials so an "auth failure" wouldn't be the
// reason a candidate is skipped — order alone must drive the choice.
authStorage.setRuntimeApiKey(currentModel.provider, "anthropic-token");
authStorage.setRuntimeApiKey(defaultRoleModel.provider, "openai-token");
modelRegistry = new ModelRegistry(authStorage, path.join(tempDir.path(), "models.yml"));
session = new AgentSession({
agent,
sessionManager: SessionManager.inMemory(),
settings,
modelRegistry,
});
session.subscribe(() => {});
for (const [userText, assistantText] of [
["first question", "first answer"],
["second question", "second answer"],
] as const) {
const user = userMsg(userText);
const assistant = assistantMsg(assistantText);
session.agent.appendMessage(user);
session.sessionManager.appendMessage(user);
session.agent.appendMessage(assistant);
session.sessionManager.appendMessage(assistant);
}
const compactSpy = vi.spyOn(compactionModule, "compact").mockImplementation(async (preparation, model) => ({
summary: "ok",
shortSummary: "ok short",
firstKeptEntryId: preparation.firstKeptEntryId,
tokensBefore: 1,
details: { provider: model.provider },
}));
await session.compact();
expect(compactSpy).toHaveBeenCalled();
const [, firstCandidate] = compactSpy.mock.calls[0]!;
expect(`${firstCandidate.provider}/${firstCandidate.id}`).toBe(`${currentModel.provider}/${currentModel.id}`);
});
});