import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { AssistantMessage } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; import { buildBootstrapContextFiles, formatAssistantErrorText, isContextOverflowError, sanitizeGoogleTurnOrdering, } from "./pi-embedded-helpers.js"; import { DEFAULT_AGENTS_FILENAME, type WorkspaceBootstrapFile, } from "./workspace.js"; const makeFile = ( overrides: Partial, ): WorkspaceBootstrapFile => ({ name: DEFAULT_AGENTS_FILENAME, path: "/tmp/AGENTS.md", content: "", missing: false, ...overrides, }); describe("buildBootstrapContextFiles", () => { it("keeps missing markers", () => { const files = [makeFile({ missing: true, content: undefined })]; expect(buildBootstrapContextFiles(files)).toEqual([ { path: DEFAULT_AGENTS_FILENAME, content: "[MISSING] Expected at: /tmp/AGENTS.md", }, ]); }); it("skips empty or whitespace-only content", () => { const files = [makeFile({ content: " \n " })]; expect(buildBootstrapContextFiles(files)).toEqual([]); }); it("truncates large bootstrap content", () => { const head = `HEAD-${"a".repeat(6000)}`; const tail = `${"b".repeat(3000)}-TAIL`; const long = `${head}${tail}`; const files = [makeFile({ content: long })]; const [result] = buildBootstrapContextFiles(files); expect(result?.content).toContain( "[...truncated, read AGENTS.md for full content...]", ); expect(result?.content.length).toBeLessThan(long.length); expect(result?.content.startsWith(long.slice(0, 120))).toBe(true); expect(result?.content.endsWith(long.slice(-120))).toBe(true); }); }); describe("isContextOverflowError", () => { it("matches known overflow hints", () => { const samples = [ "request_too_large", "Request exceeds the maximum size", "context length exceeded", "Maximum context length", "413 Request Entity Too Large", ]; for (const sample of samples) { expect(isContextOverflowError(sample)).toBe(true); } }); it("ignores unrelated errors", () => { expect(isContextOverflowError("rate limit exceeded")).toBe(false); }); }); describe("formatAssistantErrorText", () => { const makeAssistantError = (errorMessage: string): AssistantMessage => ({ stopReason: "error", errorMessage, }) as AssistantMessage; it("returns a friendly message for context overflow", () => { const msg = makeAssistantError("request_too_large"); expect(formatAssistantErrorText(msg)).toContain("Context overflow"); }); }); describe("sanitizeGoogleTurnOrdering", () => { it("prepends a synthetic user turn when history starts with assistant", () => { const input = [ { role: "assistant", content: [ { type: "toolCall", id: "call_1", name: "bash", arguments: {} }, ], }, ] satisfies AgentMessage[]; const out = sanitizeGoogleTurnOrdering(input); expect(out[0]?.role).toBe("user"); expect(out[1]?.role).toBe("assistant"); }); it("is a no-op when history starts with user", () => { const input = [{ role: "user", content: "hi" }] satisfies AgentMessage[]; const out = sanitizeGoogleTurnOrdering(input); expect(out).toBe(input); }); });