Files
agent/packages/harness/tests/pi-event-adapter.test.ts
T

90 lines
5.5 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import * as harness from "../src/index.js";
describe("PiEventAdapter", () => {
it.each([
{ stopReason: "stop", content: [], code: "MODEL_EMPTY_RESPONSE" },
{ stopReason: "stop", content: [{ type: "thinking", thinking: "private reasoning" }], code: "MODEL_EMPTY_RESPONSE" },
{ stopReason: "length", content: [{ type: "text", text: "partial output" }], code: "MODEL_OUTPUT_LIMIT" },
])("rejects incomplete assistant replies ($stopReason, $code)", async ({ stopReason, content, code }) => {
const store = new harness.InMemoryEventStore();
const adapter = new harness.PiEventAdapter({ sessionId: "ses_1", runId: "run_1", eventStore: store });
await expect(adapter.observe({ type: "message_end", message: { role: "assistant", stopReason, content } })).rejects.toMatchObject({ code });
expect(store.listAfter("ses_1").events).toHaveLength(0);
});
it("keeps licensed frame bytes and subtitles out of event history", async () => {
const store = new harness.InMemoryEventStore();
const adapter = new harness.PiEventAdapter({ sessionId: "ses_1", runId: "run_1", eventStore: store });
await adapter.observe({ type: "tool_execution_end", toolName: "read_drama_frames", toolCallId: "call_1", isError: false,
result: { content: [{ type: "image", data: "PRIVATE_IMAGE_BYTES", mimeType: "image/jpeg" }, { type: "text", text: "PRIVATE_SUBTITLES" }], details: { jobId: "job_1", imageCount: 9 } } });
expect(store.listAfter("ses_1").events[0]?.payload).toMatchObject({ result: { jobId: "job_1", imageCount: 9 } });
expect(JSON.stringify(store.listAfter("ses_1"))).not.toContain("PRIVATE_");
});
it("does not announce completion between tool turns or turn provider errors into success", async () => {
const store = new harness.InMemoryEventStore();
const adapter = new harness.PiEventAdapter({ sessionId: "ses_1", runId: "run_1", eventStore: store });
await adapter.observe({ type: "message_end", message: { role: "assistant", stopReason: "toolUse" } });
expect(store.listAfter("ses_1").events).toHaveLength(0);
await expect(adapter.observe({ type: "message_end", message: { role: "assistant", stopReason: "error", errorMessage: "provider secret" } })).rejects.toThrow("Model request failed");
expect(store.listAfter("ses_1").events).toHaveLength(0);
});
it("unwraps real Pi custom-tool details for the metric card", async () => {
const store = new harness.InMemoryEventStore();
const adapter = new harness.PiEventAdapter({ sessionId: "ses_1", runId: "run_1", eventStore: store });
await adapter.observe({
type: "tool_execution_end", toolName: "mock_ads_metrics", toolCallId: "call_1", isError: false,
result: { content: [{ type: "text", text: '{"spend":120,"revenue":360,"roas":3}' }], details: { spend: 120, revenue: 360, roas: 3 } },
});
expect(store.listAfter("ses_1").events[0]?.payload).toEqual({
toolCallId: "call_1", toolName: "mock_ads_metrics", result: { spend: 120, revenue: 360, roas: 3 },
});
});
it("maps assistant deltas and completion with one stable message id", async () => {
const store = new harness.InMemoryEventStore();
const Adapter = Reflect.get(harness, "PiEventAdapter") as unknown as new (options: Record<string, unknown>) => {
observe(event: unknown): Promise<void>;
};
expect(Adapter, "PiEventAdapter must be exported").toBeDefined();
const adapter = new Adapter({ sessionId: "ses_1", runId: "run_1", eventStore: store });
await adapter.observe({ type: "message_update", message: { role: "assistant" }, assistantMessageEvent: { type: "text_delta", delta: "你好" } });
await adapter.observe({ type: "message_end", message: { role: "assistant" } });
expect(store.listAfter("ses_1").events.map(({ type, payload }) => ({ type, payload }))).toEqual([
{ type: "assistant.delta", payload: { messageId: "msg_run_1", delta: "你好" } },
{ type: "assistant.completed", payload: { messageId: "msg_run_1" } },
]);
});
it("maps tool lifecycle, redacts payloads, and correlates successful reads to Skill inference", async () => {
const store = new harness.InMemoryEventStore();
const requested = vi.fn(() => Promise.resolve());
const completed = vi.fn();
const Adapter = Reflect.get(harness, "PiEventAdapter") as unknown as new (options: Record<string, unknown>) => {
observe(event: unknown): Promise<void>;
};
const adapter = new Adapter({
sessionId: "ses_1",
runId: "run_1",
eventStore: store,
skillInference: { onReadRequested: requested, onReadCompleted: completed },
});
await adapter.observe({ type: "tool_execution_start", toolCallId: "call_1", toolName: "read", args: { path: "/skill/SKILL.md", token: "hidden" } });
await adapter.observe({ type: "tool_execution_end", toolCallId: "call_1", toolName: "read", result: { content: "ok", apiKey: "hidden" }, isError: false });
await adapter.observe({ type: "tool_execution_end", toolCallId: "call_2", toolName: "grep", result: "failed", isError: true });
await expect(adapter.observe({ type: "future_event", anything: true })).resolves.toBeUndefined();
expect(requested).toHaveBeenCalledWith({ toolCallId: "call_1", path: "/skill/SKILL.md" });
expect(completed).toHaveBeenCalledWith({ toolCallId: "call_1", success: true });
expect(store.listAfter("ses_1").events.map((event) => event.type)).toEqual([
"tool.requested",
"tool.completed",
"tool.failed",
]);
expect(JSON.stringify(store.listAfter("ses_1"))).not.toContain("hidden");
});
});