pi-mono/packages/coding-agent/test/sdk-session-manager.test.ts
Armin Ronacher 9e05370b29
Mid conversation system messages (#9548)
This change makes system prompt text and tool changes part of the transcript rather than silently rewriting its starting conditions. This lets Pi record when instructions changed or tools became available, restore that state after resuming or navigating branches, and preserve cached prompt prefixes where the upstream supports it.
2026-09-16 15:54:07 +02:00

131 lines
4.1 KiB
TypeScript

import { existsSync, mkdirSync, realpathSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getModel } from "@earendil-works/pi-ai/compat";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createAgentSession } from "../src/core/sdk.ts";
import { SessionManager } from "../src/core/session-manager.ts";
describe("createAgentSession session manager defaults", () => {
let tempDir: string;
let cwd: string;
let agentDir: string;
beforeEach(() => {
tempDir = join(tmpdir(), `pi-sdk-session-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
cwd = join(tempDir, "project");
agentDir = join(tempDir, "agent");
mkdirSync(cwd, { recursive: true });
mkdirSync(agentDir, { recursive: true });
});
afterEach(() => {
if (tempDir && existsSync(tempDir)) {
rmSync(tempDir, { recursive: true, force: true });
}
});
it("uses agentDir for the default persisted session path", async () => {
const model = getModel("anthropic", "claude-sonnet-4-5");
expect(model).toBeTruthy();
const { session } = await createAgentSession({
cwd,
agentDir,
model: model!,
});
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
const expectedSessionDir = join(agentDir, "sessions", safePath);
const sessionDir = session.sessionManager.getSessionDir();
const sessionFile = session.sessionManager.getSessionFile();
expect(sessionDir).toBe(expectedSessionDir);
expect(sessionFile?.startsWith(`${expectedSessionDir}/`)).toBe(true);
session.dispose();
});
it("keeps an explicit sessionManager override", async () => {
const model = getModel("anthropic", "claude-sonnet-4-5");
expect(model).toBeTruthy();
const sessionManager = SessionManager.inMemory(cwd);
const { session } = await createAgentSession({
cwd,
agentDir,
model: model!,
sessionManager,
});
expect(session.sessionManager).toBe(sessionManager);
expect(session.sessionManager.isPersisted()).toBe(false);
session.dispose();
});
it("derives cwd from an explicit sessionManager when cwd is omitted", async () => {
const model = getModel("anthropic", "claude-sonnet-4-5");
expect(model).toBeTruthy();
const sessionCwd = join(tempDir, "session-project");
mkdirSync(sessionCwd, { recursive: true });
const sessionManager = SessionManager.inMemory(sessionCwd);
const { session } = await createAgentSession({
agentDir,
model: model!,
sessionManager,
});
expect(session.sessionManager).toBe(sessionManager);
expect(session.systemPrompt).toContain(`<cwd>\n${sessionCwd}\n</cwd>`);
const bashTool = session.agent.state.tools.find((tool) => tool.name === "bash");
expect(bashTool).toBeTruthy();
const result = await bashTool!.execute("test", { command: "pwd" });
const output = result.content
.filter((item): item is { type: "text"; text: string } => item.type === "text")
.map((item) => item.text)
.join("");
expect(realpathSync(output.trim())).toBe(realpathSync(sessionCwd));
session.dispose();
});
it("exposes current session state to the built-in bash tool", async () => {
const model = getModel("anthropic", "claude-sonnet-4-5");
expect(model).toBeTruthy();
const { session } = await createAgentSession({
cwd,
agentDir,
model: model!,
thinkingLevel: "high",
});
expect(session.sessionFile).toBeTruthy();
expect(session.systemPrompt).toContain(
"You can inspect PI_* environment variables for current model and session details.",
);
const bashTool = session.agent.state.tools.find((tool) => tool.name === "bash");
expect(bashTool).toBeTruthy();
const result = await bashTool!.execute("test", {
command: `printf '%s\\n' "$PI_SESSION_ID" "$PI_SESSION_FILE" "$PI_PROVIDER" "$PI_MODEL" "$PI_REASONING_LEVEL"`,
});
const output = result.content
.filter((item): item is { type: "text"; text: string } => item.type === "text")
.map((item) => item.text)
.join("");
expect(output.trim().split("\n")).toEqual([
session.sessionId,
session.sessionFile,
model!.provider,
model!.id,
session.thinkingLevel,
]);
session.dispose();
});
});