import { ui } from "../tree/tree"; import { defineApp } from "../define/define"; import { createTestWorkspace } from "./test-workspace"; import { describe, it, expect } from "vitest"; import { fakeAi } from "./fake-ai"; import { watchAiJob, type ColineAiRequest } from "../capabilities/ai"; const models = [ { id: "model", name: "Model", kind: "language" as const, priceMicrousd: 100, supports: { streaming: true }, }, ]; const request: ColineAiRequest = { kind: "language", input: { model: "model", prompt: "Hello", idempotencyKey: "same", maxCostMicrousd: 100 }, }; describe("durable AI contract", () => { it("requires media permissions before invoking a fake provider", async () => { const app = defineApp({ key: "studio", name: "Studio", permissions: ["ai.generate"], surfaces: { home: { tier: "tree" } }, handlers: { renderHome: async () => ui.text("Studio") }, }); const workspace = createTestWorkspace(app, { aiModels: [{ id: "image", name: "Image", kind: "image", priceMicrousd: 100, supports: {} }], onAiJob: async () => ({ images: [] }), }); await expect( workspace.client().ai.images.generate({ model: "image", prompt: "A photo", driveId: "drive", idempotencyKey: "image", maxCostMicrousd: 100, }), ).rejects.toThrow("files.write"); }); it("deduplicates a request and rejects reuse for different input", async () => { let calls = 0; const ai = fakeAi({}, "owner", models, async () => { calls++; return { text: "Hi" }; }); const first = await ai.run(request); const second = await ai.run(request); expect(first.id).toBe(second.id); expect(calls).toBe(1); await expect( ai.run({ ...request, input: { ...request.input, prompt: "Other" } }), ).rejects.toThrow("idempotency_conflict"); }); it("prevents another actor from reading or cancelling a job", async () => { const state = {}; const owner = fakeAi(state, "owner", models, async () => ({ text: "private" })); const outsider = fakeAi(state, "outsider", models); const job = await owner.run(request); await expect(outsider.jobs.get(job.id)).rejects.toThrow("job_not_found"); await expect(outsider.jobs.cancel(job.id)).rejects.toThrow("job_not_found"); expect(await outsider.jobs.list()).toEqual([]); }); it("does not resurrect cancelled jobs when the provider finishes", async () => { let finish: (value: { text: string }) => void = () => {}; const ai = fakeAi( {}, "owner", models, () => new Promise((resolve) => { finish = resolve; }), ); const job = await ai.run(request); await ai.jobs.cancel(job.id); finish({ text: "Late" }); await Promise.resolve(); expect((await ai.jobs.get(job.id)).status).toBe("cancelled"); expect((await ai.jobs.get(job.id)).chargeMicrousd).toBe(0); }); it("enforces the caller budget before invoking a provider", async () => { const ai = fakeAi({}, "owner", models, async () => { throw Error("must not run"); }); await expect( ai.run({ ...request, input: { ...request.input, maxCostMicrousd: 1 } }), ).rejects.toThrow("ai_budget_exceeded"); }); it("replays events in order after a persisted cursor", async () => { const ai = fakeAi({}, "owner", models, async (_request, context) => { await context.event("text-delta", { text: "One" }); await context.event("text-delta", { text: "Two" }); return { text: "OneTwo" }; }); const job = await ai.run(request); const updates = []; for await (const update of watchAiJob(ai, job.id, { afterSequence: 1 })) if (update.type === "event") updates.push(update.event); expect(updates).toEqual([{ sequence: 2, type: "text-delta", data: { text: "Two" } }]); }); it("can observe an already-completed job", async () => { const ai = fakeAi({}, "owner", models, async () => ({ text: "Hi" })); const job = await ai.run(request); await Promise.resolve(); const updates = []; for await (const update of watchAiJob(ai, job.id)) updates.push(update); expect( updates.some((update) => update.type === "job" && update.job.status === "succeeded"), ).toBe(true); }); });