import { randomUUID } from "node:crypto"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { ComposioEmulator } from "@rakazo/adapters"; import { describe, expect, it } from "vitest"; import { sessionCookieHeader } from "./index.js"; import { startModelEmulator } from "./model-emulator.js"; type App = { request: (input: string, init?: RequestInit) => Promise }; const databaseAvailable = process.env.VERIFY_DATABASE === "1" && Boolean(process.env.DATABASE_URL); const fixtureOrigin = "http://127.0.0.1:5173"; describe.skipIf(!databaseAvailable)("offline Pi product journey", () => { it("uses a saved model connection and the real executor to persist a file and completed run", async () => { const fixtureKey = "offline-product-fixture-key"; const model = await startModelEmulator({ apiKey: fixtureKey, steps: [ { expect(request) { expect(JSON.stringify(request.messages)).toContain("Save hello to notes/result.txt."); expect(request.tools).toContainEqual( expect.objectContaining({ function: expect.objectContaining({ name: "write_file" }), }), ); }, response: { type: "tool", id: "product-write", name: "write_file", arguments: { path: "notes/result.txt", content: "hello" }, }, }, { expect(request) { const result = request.messages.findLast((message) => message.role === "tool"); expect(result?.tool_call_id).toBe("product-write"); expect(JSON.parse(String(result?.content))).toMatchObject({ ok: true, path: "notes/result.txt", }); }, response: { type: "text", text: "Saved notes/result.txt." }, }, ], }); const dataDir = await mkdtemp(path.join(tmpdir(), "rakazo-offline-product-")); let stop: (() => Promise) | undefined; try { const { createApp } = await import("../../../apps/api/src/app.ts"); const handles = await createApp({ databaseUrl: process.env.DATABASE_URL!, realtimeDatabaseUrl: process.env.DATABASE_URL!, authUrl: fixtureOrigin, webOrigin: fixtureOrigin, dataDir, sandboxProvider: "fake", agentRuntime: "pi", wakeupDriver: "memory", signupsEnabled: "true", composio: new ComposioEmulator(), encryptionKey: "offline-model-fixture-encryption-key", }); stop = handles.stop; const signup = await handles.app.request("/api/auth/sign-up/email", { method: "POST", headers: { "content-type": "application/json", origin: fixtureOrigin }, body: JSON.stringify({ email: `offline-pi-${randomUUID()}@rakazo.test`, password: "password12", name: "Offline fixture", }), }); expect(signup.status).toBeLessThan(400); const cookie = sessionCookieHeader(signup); await rpc(handles.app, cookie, "models/connect", { provider: model.model.provider, modelId: model.model.id, baseUrl: model.baseUrl, apiKey: fixtureKey, }); const bot = await rpc<{ id: string }>(handles.app, cookie, "bots/create", { name: "File fixture", title: "", description: "", instructions: "Complete the task.", notifyOnFinish: false, }); await rpc(handles.app, cookie, "bots/update", { botId: bot.id, modelProvider: model.model.provider, modelId: model.model.id, }); const sent = await rpc<{ runId: string }>(handles.app, cookie, "threads/send", { botId: bot.id, text: "Save hello to notes/result.txt.", }); await expect .poll( async () => { const run = await handles.prisma.run.findUnique({ where: { id: sent.runId }, select: { status: true }, }); if (run?.status === "failed") model.assertComplete(); return run?.status; }, { timeout: 15_000, interval: 100 }, ) .toBe("completed"); model.assertComplete(); const file = await rpc<{ content: string }>(handles.app, cookie, "computer/readFile", { botId: bot.id, path: "notes/result.txt", }); expect(file.content).toBe("hello"); const thread = await rpc<{ messages: Array<{ role: string; blocks: unknown[] }> }>( handles.app, cookie, "threads/get", { botId: bot.id }, ); const messages = JSON.stringify(thread.messages); expect(messages).toContain("Saved notes/result.txt."); expect(messages).not.toContain(fixtureKey); const tools = await handles.prisma.event.findMany({ where: { botId: bot.id, type: "agent.tool.called" }, select: { payload: true }, }); expect(tools).toHaveLength(1); expect(JSON.stringify(tools[0])).toContain("write_file"); } finally { try { await stop?.(); } finally { await model.close(); await rm(dataDir, { recursive: true, force: true }); } } }, 30_000); }); async function rpc( app: App, cookie: string, procedure: string, input: unknown = {}, ): Promise { const response = await app.request(`/rpc/${procedure}`, { method: "POST", headers: { "content-type": "application/json", cookie, origin: fixtureOrigin }, body: JSON.stringify({ json: input }), }); const body = (await response.json()) as { json?: T; error?: { message?: string } }; if (response.status >= 400 || body.error) throw new Error(`${procedure}: ${body.error?.message ?? response.status}`); return body.json as T; }