evOo-local/packages/testkit/src/pi-offline.postgres.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

161 lines
5.8 KiB
TypeScript

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<Response> };
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<void>) | 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<T>(
app: App,
cookie: string,
procedure: string,
input: unknown = {},
): Promise<T> {
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;
}