Rhys Sullivanimprove parsing0517f306 months agoHistory
159 lines · 4.2 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { assertInclude } from "@effect/vitest/utils";import * as Data from "effect/Data";import * as Effect from "effect/Effect"; import type { SandboxToolInvoker } from "@executor/codemode-core";import { makeSecureExecExecutor } from "./index"; class UnknownToolError extends Data.TaggedError("UnknownToolError")<{  readonly path: string;}> {} class ToolHandlerError extends Data.TaggedError("ToolHandlerError")<{  readonly path: string;  readonly cause: unknown;}> {  override get message() {    return this.cause instanceof Error ? this.cause.message : String(this.cause);  }} const makeTestInvoker = (  handlers: Record<string, (args: unknown) => unknown | Promise<unknown>>,): SandboxToolInvoker => ({  invoke: ({ path, args }) => {    const handler = handlers[path];    if (!handler) {      return Effect.fail(new UnknownToolError({ path }));    }    return Effect.tryPromise({      try: () => Promise.resolve(handler(args)),      catch: (cause) => new ToolHandlerError({ path, cause }),    });  },}); const executor = makeSecureExecExecutor({ timeoutMs: 5_000 }); describe("secure-exec executor", () => {  it.effect("runs plain code", () =>    Effect.gen(function* () {      const result = yield* executor.execute("return 1 + 2", makeTestInvoker({}));       expect(result.result).toBe(3);      expect(result.error).toBeUndefined();    }),  );   it.effect("recovers prose-wrapped fenced async arrow input", () =>    Effect.gen(function* () {      const result = yield* executor.execute(        ["Use this snippet.", "", "```ts", "async () => 42", "```"].join("\n"),        makeTestInvoker({}),      );       expect(result.result).toBe(42);      expect(result.error).toBeUndefined();    }),  );   it.effect("invokes a tool and returns its result", () =>    Effect.gen(function* () {      const invoker = makeTestInvoker({        "math.add": (args) => {          const { a, b } = args as { a: number; b: number };          return { sum: a + b };        },      });       const result = yield* executor.execute(        `        const out = await tools.math.add({ a: 7, b: 5 });        return out.sum;        `,        invoker,      );       expect(result.error).toBeUndefined();      expect(result.result).toBe(12);    }),  );   it.effect("captures console output as logs", () =>    Effect.gen(function* () {      const result = yield* executor.execute(        `        console.log("hello");        console.warn("warn");        return "done";        `,        makeTestInvoker({}),      );       expect(result.error).toBeUndefined();      expect(result.result).toBe("done");      expect(result.logs).toContainEqual("[log] hello");      expect(result.logs).toContainEqual("[warn] warn");    }),  );   it.effect("supports async arrow-function source input", () =>    Effect.gen(function* () {      const result = yield* executor.execute(        `        async () => {          return 42;        }        `,        makeTestInvoker({}),      );       expect(result.error).toBeUndefined();      expect(result.result).toBe(42);    }),  );   it.effect("supports catching tool invocation failures in user code", () =>    Effect.gen(function* () {      const invoker = makeTestInvoker({        "db.query": () => {          throw new Error("connection refused");        },      });       const result = yield* executor.execute(        `        try {          await tools.db.query({ sql: "SELECT 1" });          return "unexpected";        } catch (e) {          return "caught: " + e.message;        }        `,        invoker,      );       expect(result.error).toBeUndefined();      expect(result.result).toContain("caught:");      expect(result.result).toContain("connection refused");    }),  );   it.live("enforces timeout", () =>    Effect.gen(function* () {      const shortTimeoutExecutor = makeSecureExecExecutor({ timeoutMs: 150 });       const result = yield* shortTimeoutExecutor.execute(        `        await new Promise((resolve) => setTimeout(resolve, 10_000));        return "done";        `,        makeTestInvoker({}),      );       expect(result.result).toBeNull();      assertInclude(result.error, "timed out");    }),  );});