mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-08-21 06:33:48 +00:00
225 lines
7.2 KiB
TypeScript
225 lines
7.2 KiB
TypeScript
import type { Usage } from "@earendil-works/pi-ai";
|
|
import { Container } from "@earendil-works/pi-tui";
|
|
import { describe, expect, test, vi } from "vitest";
|
|
import type { SessionEntry } from "../src/core/session-manager.ts";
|
|
import { InteractiveMode } from "../src/modes/interactive/interactive-mode.ts";
|
|
import { initTheme } from "../src/modes/interactive/theme/theme.ts";
|
|
import { stripAnsi } from "../src/utils/ansi.ts";
|
|
|
|
describe("InteractiveMode compaction events", () => {
|
|
test("uses the cache miss notice setting for compaction and branch summary costs", () => {
|
|
const usage: Usage = {
|
|
input: 10,
|
|
output: 20,
|
|
cacheRead: 30,
|
|
cacheWrite: 40,
|
|
totalTokens: 100,
|
|
cost: { input: 0.01, output: 0.02, cacheRead: 0.03, cacheWrite: 0.065, total: 0.125 },
|
|
};
|
|
const addCompactionCostNotice = Reflect.get(InteractiveMode.prototype, "addCompactionCostNotice") as (
|
|
this: { chatContainer: Container; settingsManager: { getShowCacheMissNotices(): boolean } },
|
|
notice: {
|
|
type: "compaction_cost";
|
|
kind: "compaction" | "branch_summary";
|
|
usage: Usage;
|
|
},
|
|
) => void;
|
|
|
|
initTheme("dark");
|
|
const enabled = {
|
|
chatContainer: new Container(),
|
|
settingsManager: { getShowCacheMissNotices: () => true },
|
|
};
|
|
addCompactionCostNotice.call(enabled, { type: "compaction_cost", kind: "compaction", usage });
|
|
addCompactionCostNotice.call(enabled, {
|
|
type: "compaction_cost",
|
|
kind: "branch_summary",
|
|
usage,
|
|
});
|
|
const output = stripAnsi(enabled.chatContainer.render(120).join("\n"));
|
|
expect(output).toContain("Compaction: 100 tokens billed (~$0.13)");
|
|
expect(output).toContain("Branch summary: 100 tokens billed (~$0.13)");
|
|
|
|
const disabled = {
|
|
chatContainer: new Container(),
|
|
settingsManager: { getShowCacheMissNotices: () => false },
|
|
};
|
|
addCompactionCostNotice.call(disabled, { type: "compaction_cost", kind: "compaction", usage });
|
|
expect(disabled.chatContainer.children).toHaveLength(0);
|
|
});
|
|
|
|
test("renders each compaction cost after its summary", () => {
|
|
const currentUsage: Usage = {
|
|
input: 10,
|
|
output: 20,
|
|
cacheRead: 30,
|
|
cacheWrite: 40,
|
|
totalTokens: 100,
|
|
cost: { input: 0.01, output: 0.02, cacheRead: 0.03, cacheWrite: 0.04, total: 0.1 },
|
|
};
|
|
const previousUsage: Usage = {
|
|
input: 1,
|
|
output: 2,
|
|
cacheRead: 3,
|
|
cacheWrite: 4,
|
|
totalTokens: 10,
|
|
cost: { input: 0.001, output: 0.002, cacheRead: 0.003, cacheWrite: 0.004, total: 0.01 },
|
|
};
|
|
const entries: SessionEntry[] = [
|
|
{
|
|
type: "compaction",
|
|
id: "current",
|
|
parentId: "previous",
|
|
timestamp: "2025-01-02T00:00:00Z",
|
|
summary: "current summary",
|
|
firstKeptEntryId: "kept",
|
|
tokensBefore: 200,
|
|
usage: currentUsage,
|
|
},
|
|
{
|
|
type: "compaction",
|
|
id: "previous",
|
|
parentId: null,
|
|
timestamp: "2025-01-01T00:00:00Z",
|
|
summary: "previous summary",
|
|
firstKeptEntryId: "kept",
|
|
tokensBefore: 100,
|
|
usage: previousUsage,
|
|
},
|
|
];
|
|
const fakeThis = { renderSessionItems: vi.fn() };
|
|
const renderSessionEntries = Reflect.get(InteractiveMode.prototype, "renderSessionEntries") as (
|
|
this: typeof fakeThis,
|
|
entries: SessionEntry[],
|
|
) => void;
|
|
|
|
renderSessionEntries.call(fakeThis, entries);
|
|
|
|
expect(fakeThis.renderSessionItems).toHaveBeenCalledWith(
|
|
[
|
|
expect.objectContaining({ role: "compactionSummary", summary: "current summary" }),
|
|
{ type: "compaction_cost", kind: "compaction", usage: currentUsage },
|
|
expect.objectContaining({ role: "compactionSummary", summary: "previous summary" }),
|
|
{ type: "compaction_cost", kind: "compaction", usage: previousUsage },
|
|
],
|
|
{},
|
|
);
|
|
});
|
|
|
|
test("renders retained entries and appends the latest summary cost at the bottom", async () => {
|
|
const usage: Usage = {
|
|
input: 10,
|
|
output: 20,
|
|
cacheRead: 30,
|
|
cacheWrite: 40,
|
|
totalTokens: 100,
|
|
cost: { input: 0.01, output: 0.02, cacheRead: 0.03, cacheWrite: 0.065, total: 0.125 },
|
|
};
|
|
const latestCompaction: SessionEntry = {
|
|
type: "compaction",
|
|
id: "latest",
|
|
parentId: "previous",
|
|
timestamp: "2025-01-02T00:00:00Z",
|
|
summary: "summary",
|
|
firstKeptEntryId: "kept",
|
|
tokensBefore: 123,
|
|
usage,
|
|
};
|
|
const previousCompaction: SessionEntry = {
|
|
type: "compaction",
|
|
id: "previous",
|
|
parentId: null,
|
|
timestamp: "2025-01-01T00:00:00Z",
|
|
summary: "previous summary",
|
|
firstKeptEntryId: "kept",
|
|
tokensBefore: 100,
|
|
usage,
|
|
};
|
|
const fakeThis = {
|
|
isInitialized: true,
|
|
footer: { invalidate: vi.fn() },
|
|
autoCompactionEscapeHandler: undefined as (() => void) | undefined,
|
|
autoCompactionLoader: undefined,
|
|
defaultEditor: {},
|
|
statusContainer: { clear: vi.fn() },
|
|
chatContainer: { clear: vi.fn() },
|
|
sessionManager: { buildContextEntries: vi.fn().mockReturnValue([latestCompaction, previousCompaction]) },
|
|
renderSessionEntries: vi.fn(),
|
|
addMessageToChat: vi.fn(),
|
|
addCompactionCostNotice: vi.fn(),
|
|
showError: vi.fn(),
|
|
showStatus: vi.fn(),
|
|
clearStatusIndicator: vi.fn(),
|
|
flushCompactionQueue: vi.fn().mockResolvedValue(undefined),
|
|
settingsManager: { getShowTerminalProgress: () => false },
|
|
ui: { requestRender: vi.fn(), terminal: { setProgress: vi.fn() } },
|
|
};
|
|
|
|
const handleEvent = Reflect.get(InteractiveMode.prototype, "handleEvent") as (
|
|
this: typeof fakeThis,
|
|
event: {
|
|
type: "compaction_end";
|
|
reason: "manual" | "threshold" | "overflow";
|
|
result: { tokensBefore: number; summary: string; usage?: Usage } | undefined;
|
|
aborted: boolean;
|
|
willRetry: boolean;
|
|
errorMessage?: string;
|
|
},
|
|
) => Promise<void>;
|
|
|
|
await handleEvent.call(fakeThis, {
|
|
type: "compaction_end",
|
|
reason: "manual",
|
|
result: {
|
|
tokensBefore: 123,
|
|
summary: "summary",
|
|
usage,
|
|
},
|
|
aborted: false,
|
|
willRetry: false,
|
|
});
|
|
|
|
expect(fakeThis.chatContainer.clear).toHaveBeenCalledTimes(1);
|
|
expect(fakeThis.renderSessionEntries).toHaveBeenCalledWith([previousCompaction]);
|
|
expect(fakeThis.addMessageToChat).toHaveBeenCalledTimes(1);
|
|
expect(fakeThis.addMessageToChat).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
role: "compactionSummary",
|
|
tokensBefore: 123,
|
|
summary: "summary",
|
|
}),
|
|
);
|
|
expect(fakeThis.addCompactionCostNotice).toHaveBeenCalledWith({
|
|
type: "compaction_cost",
|
|
kind: "compaction",
|
|
usage,
|
|
});
|
|
expect(fakeThis.flushCompactionQueue).toHaveBeenCalledWith({ willRetry: false });
|
|
});
|
|
|
|
test("preserves steering behavior when flushing into an active agent run", async () => {
|
|
const fakeThis = {
|
|
compactionQueuedMessages: [{ text: "change direction", mode: "steer" as const }],
|
|
session: {
|
|
clearQueue: vi.fn(),
|
|
prompt: vi.fn().mockResolvedValue(undefined),
|
|
steer: vi.fn().mockResolvedValue(undefined),
|
|
followUp: vi.fn().mockResolvedValue(undefined),
|
|
},
|
|
isExtensionCommand: vi.fn().mockReturnValue(false),
|
|
updatePendingMessagesDisplay: vi.fn(),
|
|
showError: vi.fn(),
|
|
};
|
|
|
|
const flushCompactionQueue = Reflect.get(InteractiveMode.prototype, "flushCompactionQueue") as (
|
|
this: typeof fakeThis,
|
|
options?: { willRetry?: boolean },
|
|
) => Promise<void>;
|
|
|
|
await flushCompactionQueue.call(fakeThis, { willRetry: false });
|
|
|
|
expect(fakeThis.session.prompt).toHaveBeenCalledWith("change direction", { streamingBehavior: "steer" });
|
|
expect(fakeThis.compactionQueuedMessages).toEqual([]);
|
|
expect(fakeThis.showError).not.toHaveBeenCalled();
|
|
});
|
|
});
|