import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { Agent } from "@oh-my-pi/pi-agent-core"; import type { Model } from "@oh-my-pi/pi-ai"; 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 { buildSystemPrompt } from "@oh-my-pi/pi-coding-agent/system-prompt"; import { cleanupTempHome } from "./helpers/temp-home-cleanup"; const EMPTY_TREE = { rootPath: "", rendered: "", truncated: false, totalLines: 0, agentsMdFiles: [], }; describe("system prompt model identifier", () => { let tempDir = ""; let tempHomeDir = ""; let originalHome: string | undefined; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-model-")); tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-model-home-")); originalHome = process.env.HOME; process.env.HOME = tempHomeDir; }); afterEach(cleanupTempHome(() => ({ tempDir, tempHomeDir, originalHome }))); it("renders the model identifier into the workstation block when provided", async () => { const { systemPrompt } = await buildSystemPrompt({ cwd: tempDir, contextFiles: [], skills: [], rules: [], toolNames: [], workspaceTree: { ...EMPTY_TREE, rootPath: tempDir }, model: "anthropic/claude-opus-4", }); expect(systemPrompt.join("\n\n")).toContain("Model: anthropic/claude-opus-4"); }); it("omits the model line when no model is provided", async () => { const { systemPrompt } = await buildSystemPrompt({ cwd: tempDir, contextFiles: [], skills: [], rules: [], toolNames: [], workspaceTree: { ...EMPTY_TREE, rootPath: tempDir }, }); expect(systemPrompt.join("\n\n")).not.toContain("Model:"); }); }); describe("AgentSession model-change prompt refresh", () => { let authStorage: AuthStorage; let modelRegistry: ModelRegistry; let tempDir: string; let session: AgentSession | undefined; beforeEach(async () => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-prompt-model-session-")); authStorage = await AuthStorage.create(path.join(tempDir, "auth.db")); modelRegistry = new ModelRegistry(authStorage); }); afterEach(async () => { if (session) { await session.dispose(); session = undefined; } authStorage.close(); fs.rmSync(tempDir, { recursive: true, force: true }); }); function pickTwoModels(): [Model, Model] { const all = modelRegistry.getAll(); const first = all[0]; const second = all.find(m => m.provider !== first.provider || m.id !== first.id); if (!first || !second) throw new Error("Expected at least two distinct models in the registry"); return [first, second]; } function newSession( model: Model, settings: Settings, rebuild: () => Promise<{ systemPrompt: string[] }>, ): AgentSession { const agent = new Agent({ getApiKey: () => "test-key", initialState: { model, systemPrompt: ["initial"], tools: [], messages: [] }, }); const created = new AgentSession({ agent, sessionManager: SessionManager.inMemory(), settings, modelRegistry, toolRegistry: new Map(), rebuildSystemPrompt: async () => rebuild(), }); return created; } it("rebuilds the prompt with the new model when includeModelInPrompt is enabled", async () => { const [modelA, modelB] = pickTwoModels(); authStorage.setRuntimeApiKey(modelA.provider, "key-a"); authStorage.setRuntimeApiKey(modelB.provider, "key-b"); let rebuildCount = 0; session = newSession(modelA, Settings.isolated({ "compaction.enabled": false }), async () => { rebuildCount++; const active = session?.model; return { systemPrompt: [`model:${active ? `${active.provider}/${active.id}` : ""}`] }; }); await session.setModel(modelB); expect(rebuildCount).toBe(1); expect(session.agent.state.systemPrompt).toEqual([`model:${modelB.provider}/${modelB.id}`]); // Re-selecting the same model leaves the rendered model unchanged → no rebuild. await session.setModel(modelB); expect(rebuildCount).toBe(1); }); it("does not rebuild on model change when includeModelInPrompt is disabled", async () => { const [modelA, modelB] = pickTwoModels(); authStorage.setRuntimeApiKey(modelA.provider, "key-a"); authStorage.setRuntimeApiKey(modelB.provider, "key-b"); let rebuildCount = 0; session = newSession( modelA, Settings.isolated({ "compaction.enabled": false, includeModelInPrompt: false }), async () => { rebuildCount++; return { systemPrompt: ["unchanged"] }; }, ); await session.setModel(modelB); expect(rebuildCount).toBe(0); expect(session.agent.state.systemPrompt).toEqual(["initial"]); }); });