import { describe, expect, it } from "@effect/vitest"; import { Effect, Exit } from "effect"; import { bundleEntry } from "../pipeline/bundle"; import { makeQuickjsToolSandbox } from "./quickjs-tool-sandbox"; import { ISSUES_SYNC_TS } from "../testing/daily-brief"; import { InputValidationError, type HandleBridge } from "../seams/tool-sandbox"; const run = (effect: Effect.Effect): Promise => Effect.runPromise(effect); describe("QuickJS ToolSandbox", () => { const sandbox = makeQuickjsToolSandbox(); it("bundles + collects a tool descriptor (deterministic)", async () => { const files = new Map([["tools/issues-sync.ts", ISSUES_SYNC_TS]]); const { code } = await run(bundleEntry({ files, entry: "tools/issues-sync.ts" })); const result = await run(sandbox.collect(code)); const descriptor = result.artifacts.default.descriptor as { kind: string; integrations: Record; inputJsonSchema: { type: string; properties: Record }; hasHandler: boolean; }; expect(descriptor.kind).toBe("tool"); expect(descriptor.integrations.github).toEqual( expect.objectContaining({ integration: "github" }), ); expect(descriptor.hasHandler).toBe(true); expect(descriptor.inputJsonSchema.type).toBe("object"); expect(descriptor.inputJsonSchema.properties).toHaveProperty("repos"); expect(descriptor.inputJsonSchema.properties).toHaveProperty("since"); }); it("invokes a tool, routing injected-client calls through the bridge", async () => { const files = new Map([["tools/issues-sync.ts", ISSUES_SYNC_TS]]); const { code } = await run(bundleEntry({ files, entry: "tools/issues-sync.ts" })); const calls: { root: string; path: readonly string[]; args: readonly unknown[] }[] = []; const bridge: HandleBridge = { call: ({ root, path, args }) => Effect.sync(() => { calls.push({ root, path, args }); const key = `${root}.${path.join(".")}`; if (key === "github.repos.listForAuthenticatedUser") { return [{ full_name: "acme/app" }]; } if (key === "github.issues.listForRepo") { return [ { number: 1, title: "Bug", labels: [{ name: "bug" }], assignee: { login: "rhys" }, updated_at: "2026-01-01T00:00:00Z", html_url: "https://github.com/acme/app/issues/1", }, ]; } return []; }), }; const result = await run( sandbox.invoke( code, { artifact: "issues-sync", kind: "tool", input: {}, roots: { github: { kind: "single" } }, }, bridge, ), ); expect(result.output).toEqual({ synced: 1, repos: 1, issues: [{ repo: "acme/app", number: 1, title: "Bug" }], }); expect(calls.some((c) => c.root === "github")).toBe(true); }); it("reports storage as unavailable when an old handler calls db.sql", async () => { const source = `import { defineTool } from "executor:app"; import { z } from "zod"; export default defineTool({ description: "legacy storage", input: z.object({}), async handler(_input, { db }) { await db.sql\`SELECT 1\`; return { ok: true }; }, });`; const files = new Map([["tools/storage.ts", source]]); const { code } = await run(bundleEntry({ files, entry: "tools/storage.ts" })); const bridge: HandleBridge = { call: () => Effect.succeed(null) }; const exit = await Effect.runPromiseExit( sandbox.invoke(code, { artifact: "storage", kind: "tool", input: {}, roots: {} }, bridge), ); expect(Exit.isFailure(exit)).toBe(true); expect(JSON.stringify(exit)).toContain("storage is not available yet"); }); it("surfaces Standard Schema validation issues before the handler runs", async () => { const source = `import { defineTool } from "executor:app"; import { z } from "zod"; export default defineTool({ description: "validates", input: z.object({ q: z.string() }), async handler(){ return { ok: true }; }, });`; const files = new Map([["tools/validate.ts", source]]); const { code } = await run(bundleEntry({ files, entry: "tools/validate.ts" })); const bridge: HandleBridge = { call: () => Effect.succeed(null) }; const exit = await Effect.runPromiseExit( sandbox.invoke( code, { artifact: "validate", kind: "tool", input: { q: 123 }, roots: {} }, bridge, ), ); expect(Exit.isFailure(exit)).toBe(true); expect(JSON.stringify(exit)).toContain("InputValidationError"); expect(JSON.stringify(exit)).toContain("q"); }); it("invokes raw JSON Schema tools without sandbox Standard Schema validation", async () => { const source = `import { defineTool } from "executor:app"; export default defineTool({ description: "raw", input: { type: "object", properties: { q: { type: "string" } }, required: ["q"] }, async handler(input){ return { q: input.q }; }, });`; const files = new Map([["tools/raw.ts", source]]); const { code } = await run(bundleEntry({ files, entry: "tools/raw.ts" })); const bridge: HandleBridge = { call: () => Effect.succeed(null) }; const result = await run( sandbox.invoke( code, { artifact: "raw", kind: "tool", input: { q: "ok" }, roots: {} }, bridge, ), ); expect(result.output).toEqual({ q: "ok" }); }); it("rejects a non-deterministic descriptor (Math.random at describe time)", async () => { const nondeterministic = `import { defineTool } from "executor:app"; import { z } from "zod"; export default defineTool({ description: "x-" + Math.random(), input: z.object({}), async handler() { return {}; }, });`; const files = new Map([["tools/rng.ts", nondeterministic]]); const { code } = await run(bundleEntry({ files, entry: "tools/rng.ts" })); const exit = await Effect.runPromiseExit(sandbox.collect(code)); expect(Exit.isFailure(exit)).toBe(true); }); it("denies network (fetch throws in the sandbox)", async () => { const usesFetch = `import { defineTool } from "executor:app"; import { z } from "zod"; export default defineTool({ description: "fetches", input: z.object({}), async handler() { await fetch("https://example.com"); return {}; }, });`; const files = new Map([["tools/net.ts", usesFetch]]); const { code } = await run(bundleEntry({ files, entry: "tools/net.ts" })); const bridge: HandleBridge = { call: () => Effect.succeed(null) }; const exit = await Effect.runPromiseExit( sandbox.invoke(code, { artifact: "net", kind: "tool", input: {}, roots: {} }, bridge), ); expect(Exit.isFailure(exit)).toBe(true); }); it("returns typed InputValidationError from promise rejection", async () => { const source = `import { defineTool } from "executor:app"; import { z } from "zod"; export default defineTool({ description: "typed", input: z.object({ name: z.string() }), async handler(){ return {}; }, });`; const files = new Map([["tools/typed.ts", source]]); const { code } = await run(bundleEntry({ files, entry: "tools/typed.ts" })); const bridge: HandleBridge = { call: () => Effect.succeed(null) }; await expect( run(sandbox.invoke(code, { artifact: "typed", kind: "tool", input: {}, roots: {} }, bridge)), ).rejects.toBeInstanceOf(InputValidationError); }); });