import type { AgentMessage, AgentTool } from "@mariozechner/pi-agent-core"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import { Type } from "@sinclair/typebox"; import { describe, expect, it, vi } from "vitest"; import { applyGoogleTurnOrderingFix, buildEmbeddedSandboxInfo, createSystemPromptOverride, resolveSkillsPrompt, splitSdkTools, } from "./pi-embedded-runner.js"; import type { SandboxContext } from "./sandbox.js"; import type { SkillEntry } from "./skills.js"; describe("buildEmbeddedSandboxInfo", () => { it("returns undefined when sandbox is missing", () => { expect(buildEmbeddedSandboxInfo()).toBeUndefined(); }); it("maps sandbox context into prompt info", () => { const sandbox = { enabled: true, sessionKey: "session:test", workspaceDir: "/tmp/clawdbot-sandbox", agentWorkspaceDir: "/tmp/clawdbot-workspace", workspaceAccess: "none", containerName: "clawdbot-sbx-test", containerWorkdir: "/workspace", docker: { image: "clawdbot-sandbox:bookworm-slim", containerPrefix: "clawdbot-sbx-", workdir: "/workspace", readOnlyRoot: true, tmpfs: ["/tmp"], network: "none", user: "1000:1000", capDrop: ["ALL"], env: { LANG: "C.UTF-8" }, }, tools: { allow: ["bash"], deny: ["browser"], }, browser: { controlUrl: "http://localhost:9222", noVncUrl: "http://localhost:6080", containerName: "clawdbot-sbx-browser-test", }, } satisfies SandboxContext; expect(buildEmbeddedSandboxInfo(sandbox)).toEqual({ enabled: true, workspaceDir: "/tmp/clawdbot-sandbox", workspaceAccess: "none", agentWorkspaceMount: undefined, browserControlUrl: "http://localhost:9222", browserNoVncUrl: "http://localhost:6080", }); }); }); function createStubTool(name: string): AgentTool { return { name, label: name, description: "", parameters: Type.Object({}), execute: async () => ({ content: [], details: {} }), }; } describe("splitSdkTools", () => { // Tool names are now capitalized (Bash, Read, etc.) to bypass Anthropic OAuth blocking const tools = [ createStubTool("Read"), createStubTool("Bash"), createStubTool("Edit"), createStubTool("Write"), createStubTool("browser"), ]; it("routes all tools to customTools when sandboxed", () => { const { builtInTools, customTools } = splitSdkTools({ tools, sandboxEnabled: true, }); expect(builtInTools).toEqual([]); expect(customTools.map((tool) => tool.name)).toEqual([ "Read", "Bash", "Edit", "Write", "browser", ]); }); it("routes all tools to customTools even when not sandboxed (for OAuth compatibility)", () => { // All tools are now passed as customTools to bypass pi-coding-agent's // built-in tool filtering, which expects lowercase names. const { builtInTools, customTools } = splitSdkTools({ tools, sandboxEnabled: false, }); expect(builtInTools).toEqual([]); expect(customTools.map((tool) => tool.name)).toEqual([ "Read", "Bash", "Edit", "Write", "browser", ]); }); }); describe("createSystemPromptOverride", () => { it("returns the override prompt regardless of default prompt", () => { const override = createSystemPromptOverride("OVERRIDE"); expect(override("DEFAULT")).toBe("OVERRIDE"); }); it("returns an empty string for blank overrides", () => { const override = createSystemPromptOverride(" \n "); expect(override("DEFAULT")).toBe(""); }); }); describe("resolveSkillsPrompt", () => { it("prefers snapshot prompt when available", () => { const prompt = resolveSkillsPrompt({ skillsSnapshot: { prompt: "SNAPSHOT", skills: [] }, workspaceDir: "/tmp/clawd", }); expect(prompt).toBe("SNAPSHOT"); }); it("builds prompt from entries when snapshot is missing", () => { const entry: SkillEntry = { skill: { name: "demo-skill", description: "Demo", filePath: "/app/skills/demo-skill/SKILL.md", baseDir: "/app/skills/demo-skill", source: "clawdbot-bundled", }, frontmatter: {}, }; const prompt = resolveSkillsPrompt({ skillEntries: [entry], workspaceDir: "/tmp/clawd", }); expect(prompt).toContain(""); expect(prompt).toContain("/app/skills/demo-skill/SKILL.md"); }); }); describe("applyGoogleTurnOrderingFix", () => { const makeAssistantFirst = () => [ { role: "assistant", content: [ { type: "toolCall", id: "call_1", name: "bash", arguments: {} }, ], }, ] satisfies AgentMessage[]; it("prepends a bootstrap once and records a marker for Google models", () => { const sessionManager = SessionManager.inMemory(); const warn = vi.fn(); const input = makeAssistantFirst(); const first = applyGoogleTurnOrderingFix({ messages: input, modelApi: "google-generative-ai", sessionManager, sessionId: "session:1", warn, }); expect(first.messages[0]?.role).toBe("user"); expect(first.messages[1]?.role).toBe("assistant"); expect(warn).toHaveBeenCalledTimes(1); expect( sessionManager .getEntries() .some( (entry) => entry.type === "custom" && entry.customType === "google-turn-ordering-bootstrap", ), ).toBe(true); applyGoogleTurnOrderingFix({ messages: input, modelApi: "google-generative-ai", sessionManager, sessionId: "session:1", warn, }); expect(warn).toHaveBeenCalledTimes(1); }); it("skips non-Google models", () => { const sessionManager = SessionManager.inMemory(); const warn = vi.fn(); const input = makeAssistantFirst(); const result = applyGoogleTurnOrderingFix({ messages: input, modelApi: "openai", sessionManager, sessionId: "session:2", warn, }); expect(result.messages).toBe(input); expect(warn).not.toHaveBeenCalled(); }); });