import { describe, expect, test } from "bun:test" import { Effect, Schema } from "effect" import { CodeMode, Extension, Tool } from "../src/index.js" const held: Array = [] const config = { retries: 3, nested: { deep: true } } const requests: Array = [] const extension = Extension.make({ name: "web", globals: { keep: (value: unknown) => { held.push(value) return value }, settings: () => config, later: async (value: number) => value + 1, first: (map: Map) => map.get("k"), fetch: async (url: string, init?: { method?: string }) => { requests.push([url, init]) const bytes = new TextEncoder().encode(`{"url":"${url}"}`) return { status: 200, ok: true, headers: { get: (name: string) => (name === "content-type" ? "application/json" : null) }, text: () => new TextDecoder().decode(bytes), json: () => JSON.parse(new TextDecoder().decode(bytes)), bytes: () => bytes, handlers: [(step: number) => step + 1], } }, }, }) const runtime = CodeMode.make({ tools: {}, extensions: [extension] }) const value = async (code: string, target = runtime) => { const result = await Effect.runPromise(target.execute(code)) if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`) return result.value } const failure = async (code: string, target = runtime) => { const result = await Effect.runPromise(target.execute(code)) if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`) return result.error } describe("extension functions", () => { test("a global is callable, awaitable, and not constructible", async () => { expect(await value(`return await later(1)`)).toBe(2) expect(await value(`return [typeof later, later.name, later.length]`)).toEqual(["function", "later", 1]) expect((await failure(`new later()`)).message).toContain("new later(...) is not supported") }) test("a function inside a result is callable and crosses the same way", async () => { requests.length = 0 expect( await value( `const res = await fetch("https://a.test/", { method: "GET" }); return [res.status, res.ok, res.headers.get("content-type"), res.text(), res.json(), [...res.bytes()].length, typeof res.json, res.json.name, res.handlers[0](1)]`, ), ).toEqual([ 200, true, "application/json", '{"url":"https://a.test/"}', { url: "https://a.test/" }, 25, "function", "json", 2, ]) expect(requests).toEqual([["https://a.test/", { method: "GET" }]]) }) test("a function inside a result is named by its path in diagnostics", async () => { expect((await failure(`const res = await fetch("https://a.test/"); res.handlers[0](() => 1)`)).message).toContain( "Argument 1 to fetch.handlers[0] contains a function", ) const target = CodeMode.make({ extensions: [Extension.make({ name: "odd", globals: { make: () => ({ sym: () => Symbol("s") }) } })], }) expect((await failure(`make().sym()`, target)).message).toContain("make.sym produced a symbol") }) test("a function is invisible to the data boundary like any program function", async () => { expect(await value(`return await fetch("https://a.test/")`)).toEqual({ status: 200, ok: true, headers: {}, handlers: [null], }) expect(await value(`return JSON.stringify((await fetch("https://a.test/")).headers)`)).toBe("{}") }) test("a class global is only a function; calling it throws the host TypeError", async () => { const target = CodeMode.make({ extensions: [Extension.make({ name: "cls", globals: { Bag: class Bag {} } })] }) expect((await failure(`Bag()`, target)).message).toContain("without") expect((await failure(`new Bag()`, target)).message).toContain("new Bag(...) is not supported") }) }) describe("values are converted at the boundary, never shared", () => { test("plain data passed in is a copy the program cannot change afterwards", async () => { held.length = 0 await value( `const o = { n: 1, list: [1], d: new Date(0), u: new URL("https://a.test/") }; keep(o); o.n = 2; o.list.push(2)`, ) expect(held[0]).toEqual({ n: 1, list: [1], d: new Date(0), u: new URL("https://a.test/") }) }) test("plain data returned is a copy; program writes never reach the host", async () => { expect(await value(`const s = settings(); s.retries = 0; s.nested.deep = false; return s`)).toEqual({ retries: 0, nested: { deep: false }, }) expect(config).toEqual({ retries: 3, nested: { deep: true } }) }) test("the same host value returned twice is two program values", async () => { expect(await value(`return settings() === settings()`)).toBe(false) expect(await value(`return keep(settings()) === settings()`)).toBe(false) }) test("Map and Set contents are converted element-wise", async () => { expect(await value(`return first(new Map([["k", { z: 1 }]]))`)).toEqual({ z: 1 }) held.length = 0 await value(`const inner = { z: 1 }; keep(new Set([inner])); inner.z = 2`) expect([...(held[0] as Set<{ z: number }>)][0]).toEqual({ z: 1 }) }) test("Headers cross as copies in both directions", async () => { const stored = new Headers({ "X-A": "1" }) const target = CodeMode.make({ extensions: [ Extension.make({ name: "http", globals: { headers: () => stored, keep: (value: Headers) => { held.push(value) return value }, }, }), ], }) held.length = 0 expect( await value( `const h = headers(); h.set("x-a", "2"); const back = keep(h); back.set("x-a", "3"); return [h instanceof Headers, h.get("x-a"), back === h, back.get("x-a"), [...back]]`, target, ), ).toEqual([true, "2", false, "3", [["x-a", "3"]]]) expect(stored.get("x-a")).toBe("1") expect(held[0]).toBeInstanceOf(Headers) expect((held[0] as Headers).get("x-a")).toBe("2") }) test("bytes cross as copies in both directions; ArrayBuffer comes in as Uint8Array", async () => { const stored = new Uint8Array([1, 2, 3]) const target = CodeMode.make({ extensions: [ Extension.make({ name: "bin", globals: { stored: () => stored, buffer: () => stored.buffer, wide: () => new Uint16Array(1), first: (bytes: Uint8Array) => { bytes[0] = 99 return bytes.constructor.name }, }, }), ], }) expect( await value( `const b = stored(); b[0] = 42; const mine = new Uint8Array([5]); const name = first(mine); return [[...b], [...stored()], [...buffer()], b instanceof Uint8Array, name, mine[0]]`, target, ), ).toEqual([[42, 2, 3], [1, 2, 3], [1, 2, 3], true, "Uint8Array", 5]) expect([...stored]).toEqual([1, 2, 3]) expect((await failure(`wide()`, target)).message).toContain("produced a Uint16Array, which the program cannot hold") }) test("a __proto__ key never reaches the host object", async () => { held.length = 0 await value(`keep({ __proto__: { polluted: true }, a: 1 })`) expect(Object.assign({}, held[0] as object)).not.toHaveProperty("polluted") expect(held[0]).toEqual({ a: 1 }) }) test("a program Error crosses as a host Error with its name, message, cause, and own data", async () => { held.length = 0 await value(`keep(new TypeError("bad"))`) expect(held[0]).toBeInstanceOf(TypeError) expect((held[0] as Error).message).toBe("bad") expect(Object.keys(held[0] as object)).toEqual([]) held.length = 0 await value(` const e = new Error("m", { cause: new RangeError("root") }) e.code = "ENOENT"; e.detail = { path: "x" } e.stack = "chosen"; e.toString = 1; e.constructor = 2; e.fn = () => 1 keep(e)`) const crossed = held[0] as Error & Record expect(crossed.cause).toBeInstanceOf(RangeError) expect((crossed.cause as Error).message).toBe("root") expect(Object.keys(crossed)).toEqual(["code", "detail"]) expect(crossed.detail).toEqual({ path: "x" }) expect(crossed.stack).not.toBe("chosen") expect(String(crossed)).toBe("Error: m") expect(crossed.constructor).toBe(Error) }) test("an Error with an unknown name crosses as a plain Error", async () => { held.length = 0 await value(`const e = new Error("x"); e.name = "constructor"; keep(e); e.name = "__proto__"; keep(e)`) expect(held[0]).toBeInstanceOf(Error) expect(held[1]).toBeInstanceOf(Error) }) test("functions, promises, iterators, and symbols cannot be passed in", async () => { expect((await failure(`keep(() => 1)`)).message).toContain("Argument 1 to keep contains a function") expect((await failure(`keep([1].keys())`)).message).toContain("Argument 1 to keep contains an iterator") expect((await failure(`keep(later(1))`)).message).toContain("un-awaited Promise") expect((await failure(`keep(Symbol.iterator)`)).message).toContain("Argument 1 to keep contains a symbol") }) test("only interpreter primitives come out", async () => { const target = CodeMode.make({ extensions: [Extension.make({ name: "odd", globals: { sym: () => Symbol("s"), big: () => 10n } })], }) expect((await failure(`sym()`, target)).message).toContain("sym produced a symbol") expect((await failure(`big()`, target)).message).toContain("big produced a bigint") }) test("a value that cannot come out carries a location like any built-in error, sync or async", async () => { const target = CodeMode.make({ extensions: [ Extension.make({ name: "odd", globals: { sym: () => Symbol("s"), later: async () => Symbol("s") } }), ], }) expect((await failure(`sym()`, target)).location).toEqual((await failure(`JSON.parse("{")`, target)).location) expect((await failure(`await later()`, target)).location).toBeDefined() }) test("a class instance cannot come out", async () => { class Other {} const target = CodeMode.make({ extensions: [Extension.make({ name: "odd", globals: { detached: () => new Other() } })], }) expect((await failure(`detached()`, target)).message).toContain("produced a Other, which the program cannot hold") }) }) describe("host errors", () => { test("a synchronous throw becomes the matching program error", async () => { const target = CodeMode.make({ extensions: [ Extension.make({ name: "odd", globals: { fail: () => { throw new RangeError("boom") }, }, }), ], }) expect(await value(`try { fail() } catch (e) { return [e instanceof RangeError, e.message] }`, target)).toEqual([ true, "boom", ]) }) test("a host Error arrives with its cause and own data; what cannot cross is left behind", async () => { class Handle {} const target = CodeMode.make({ extensions: [ Extension.make({ name: "fs", globals: { open: () => { const error = Object.assign(new Error("ENOENT: no such file or directory, open 'x'"), { code: "ENOENT", errno: -2, path: "x", detail: { retried: true }, handle: new Handle(), retry: () => 1, }) throw new Error("open failed", { cause: error }) }, }, }), ], }) expect( await value( `try { open() } catch (e) { const c = e.cause return [e.message, Object.keys(e), c instanceof Error, c.code, c.errno, c.path, c.detail, Object.keys(c), "stack" in c] }`, target, ), ).toEqual([ "open failed", [], true, "ENOENT", -2, "x", { retried: true }, ["code", "errno", "path", "detail"], false, ]) }) test("a thrown or rejected value crosses like a return, so the program catches what was thrown", async () => { const reason = { status: 404, nested: { a: 1 } } const target = CodeMode.make({ extensions: [ Extension.make({ name: "api", globals: { reject: async (reason: unknown) => { throw reason }, get: async () => Promise.reject(reason), boom: () => { throw reason }, }, }), ], }) expect( await value( `try { await reject(new TypeError("bad")) } catch (e) { return [e instanceof TypeError, e.message] }`, target, ), ).toEqual([true, "bad"]) expect(await value(`try { await reject("plain") } catch (e) { return e }`, target)).toBe("plain") expect(await value(`try { await get() } catch (e) { e.status = 0; return e }`, target)).toEqual({ status: 0, nested: { a: 1 }, }) expect(await value(`try { boom() } catch (e) { return e.status }`, target)).toBe(404) expect(reason.status).toBe(404) expect((await failure(`await get()`, target)).message).toBe('Uncaught: {"status":404,"nested":{"a":1}}') }) }) describe("configuration", () => { test("extension calls are not tool calls", async () => { const limited = CodeMode.make({ extensions: [extension], limits: { maxToolCalls: 0 } }) const result = await Effect.runPromise( limited.execute(`(await fetch("https://a.test/")).json(); return await later(1)`), ) expect(result.ok).toBe(true) expect(result.toolCalls).toEqual([]) }) test("a result handed to a tool is plain data", async () => { const tools = CodeMode.make({ extensions: [extension], tools: { echo: Tool.make({ description: "Echo", input: Schema.Struct({ v: Schema.Unknown }), output: Schema.Unknown, execute: (input) => Effect.succeed(input.v), }), }, }) expect(await value(`return await tools.echo({ v: await fetch("https://a.test/") })`, tools)).toEqual({ status: 200, ok: true, headers: {}, handlers: [null], }) }) test("a global must be a function", () => { expect(() => Extension.make({ name: "bad", globals: { n: 1 as never } })).toThrow( 'Extension "bad" global "n" must be a function.', ) }) test("a global may not shadow a built-in or another extension", () => { expect(() => CodeMode.make({ extensions: [Extension.make({ name: "web", globals: { URL: () => 1 } })] })).toThrow( 'Extension "web" global "URL" is already defined.', ) expect(() => CodeMode.make({ extensions: [extension, Extension.make({ name: "again", globals: { fetch: () => 1 } })] }), ).toThrow('Extension "again" global "fetch" is already defined.') }) })