import type { AgentMessage } from "@earendil-works/pi-agent-core";
import { describe, expect, it } from "vitest";
import {
type AgentSessionController,
createAgentSessionHostHandlers,
createAgentSessionTurnSummaries,
normalizeAgentSessionMaxChars,
normalizeAgentSessionTurnCount,
} from "../src/core/agent-sessions.js";
const summary = {
activeSessionId: "active-root",
sessionId: "session-root",
cwd: "/repo",
status: "idle",
isCurrent: true,
isStreaming: false,
isCompacting: false,
attachedClients: 1,
messageCount: 4,
queuedCount: 0,
isSessionActive: false,
capabilities: ["send", "recent_turns", "stop", "restart"] as const,
};
function user(text: string, timestamp: number): AgentMessage {
return { role: "user", content: [{ type: "text", text }], timestamp } as AgentMessage;
}
function assistant(text: string, timestamp: number): AgentMessage {
return { role: "assistant", content: [{ type: "text", text }], timestamp } as AgentMessage;
}
describe("agent session helpers", () => {
it("groups bounded recent turns without exposing unbounded transcript text", () => {
const result = createAgentSessionTurnSummaries(
[
user("first question", 1),
assistant("first answer", 2),
user("second question", 3),
assistant("second answer", 4),
],
1,
6,
);
expect(result).toEqual({
turns: [
{
user: "second",
assistant: "second",
toolCalls: 0,
startedAt: 3,
endedAt: 4,
messageCount: 2,
truncated: true,
},
],
truncated: true,
});
});
it("normalizes the recent-turn bounds", () => {
expect(normalizeAgentSessionTurnCount(undefined)).toBe(3);
expect(normalizeAgentSessionTurnCount(20)).toBe(20);
expect(normalizeAgentSessionMaxChars(undefined)).toBe(6_000);
expect(normalizeAgentSessionMaxChars(12_000)).toBe(12_000);
expect(() => normalizeAgentSessionTurnCount(0)).toThrow("between 1 and 20");
expect(() => normalizeAgentSessionMaxChars(79)).toThrow("between 80 and 12000");
});
it("validates and routes the typed host handlers", async () => {
const controller: AgentSessionController = {
listSessions: () => [summary],
getSession: (target) => ({ ...summary, activeSessionId: target }),
recentTurns: () => ({ agent: summary, turns: [], count: 3, maxChars: 6_000, truncated: false }),
send: ({ target, delivery }) => ({
operationId: "op-1",
status: "queued",
delivery,
target: { activeSessionId: target, sessionId: "session-root" },
}),
stop: () => ({ operationId: "op-2", status: "accepted" }),
restart: () => ({ operationId: "op-3", status: "accepted" }),
};
const handlers = createAgentSessionHostHandlers(controller);
expect(await handlers["agent.sessions.list"]({})).toEqual({ sessions: [summary] });
expect(await handlers["agent.sessions.get"]({ target: "root" })).toEqual({
session: { ...summary, activeSessionId: "root" },
});
expect(
await handlers["agent.sessions.send"]({ target: "root", message: "continue", delivery: "follow_up" }),
).toEqual({
operationId: "op-1",
status: "queued",
delivery: "follow_up",
target: { activeSessionId: "root", sessionId: "session-root" },
});
await expect(
handlers["agent.sessions.send"]({ target: "root", message: "continue", delivery: "bad" }),
).rejects.toThrow("delivery must be 'steer' or 'follow_up'");
expect(await handlers["agent.sessions.stop"]({ target: "root" })).toEqual({
operationId: "op-2",
status: "accepted",
});
expect(await handlers["agent.sessions.restart"]({ target: "root" })).toEqual({
operationId: "op-3",
status: "accepted",
});
});
});