feat(codemode): add confined execution package (#35079)

This commit is contained in:
Aiden Cline 2026-07-03 00:19:11 -05:00 committed by GitHub
commit 2409c7a3d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 9109 additions and 15 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,147 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode, Tool } from "../src/index.js"
// Key enumeration: Object.keys and for...in share one surface over plain objects, arrays
// (index strings), and tool references (namespace/tool names from the host tool tree), so a
// model can discover what it may call instead of guessing names from the instructions. The
// motivating transcript: `Object.keys(tools)` failed with the generic plain-objects-only
// message and `for (const key in tools)` was unsupported syntax, forcing blind guesses.
const echo = (description: string) =>
Tool.make({
description,
input: Schema.Struct({ value: Schema.String }),
output: Schema.String,
run: ({ value }) => Effect.succeed(value),
})
const tools = {
github: { list_issues: echo("List issues"), get_issue: echo("Get one issue") },
memory: { search: echo("Search memory") },
playwright: { navigate: echo("Navigate somewhere") },
}
const run = (code: string) => Effect.runPromise(CodeMode.execute({ tools, code }))
const value = async (code: string) => {
const result = await run(code)
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const error = async (code: string) => {
const result = await run(code)
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
return result.error
}
describe("Object.keys over tool references", () => {
test("enumerates top-level namespaces (the transcript program)", async () => {
expect(await value(`
const namespaces = Object.keys(tools)
return { namespaces, count: namespaces.length }
`)).toEqual({ namespaces: ["github", "memory", "playwright"], count: 3 })
})
test("enumerates tool names at a nested namespace", async () => {
expect(await value(`return Object.keys(tools.github)`)).toEqual(["list_issues", "get_issue"])
})
test("a callable tool is a leaf and enumerates as []", async () => {
expect(await value(`return Object.keys(tools.github.list_issues)`)).toEqual([])
})
test("the virtual discovery namespace enumerates its callable surface", async () => {
expect(await value(`return Object.keys(tools.$codemode)`)).toEqual(["search"])
})
test("an unknown namespace is an UnknownTool error pointing at the discovery idioms", async () => {
const failure = await error(`return Object.keys(tools.nonexistent)`)
expect(failure.kind).toBe("UnknownTool")
expect(failure.message).toContain("Unknown tool namespace 'nonexistent'")
expect(failure.suggestions?.join(" ")).toContain("Object.keys(tools)")
})
test("Object.values/entries on a tool reference explain the working idioms", async () => {
for (const method of ["values", "entries"] as const) {
const failure = await error(`return Object.${method}(tools)`)
expect(failure.kind).toBe("InvalidDataValue")
expect(failure.message).toContain(
`Object.${method}(...) cannot read tool references: they are not plain data. Use Object.keys(tools) for names, or tools.$codemode.search({ query }) for signatures.`,
)
}
const nested = await error(`return Object.entries(tools.github)`)
expect(nested.message).toContain("Use Object.keys(tools) for names")
})
})
describe("Object.keys over arrays", () => {
test("returns index strings, like JS", async () => {
expect(await value(`return Object.keys(["a", "b", "c"])`)).toEqual(["0", "1", "2"])
expect(await value(`return Object.keys([])`)).toEqual([])
})
test("objects keep their own enumerable keys", async () => {
expect(await value(`return Object.keys({ a: 1, b: 2 })`)).toEqual(["a", "b"])
})
test("non-object inputs still fail clearly", async () => {
const failure = await error(`return Object.keys("nope")`)
expect(failure.message).toContain("Object.keys expects a data object or array")
})
})
describe("for...in", () => {
test("iterates own enumerable keys of a plain object with break/continue", async () => {
expect(await value(`
const seen = []
for (const key in { a: 1, b: 2, c: 3, d: 4 }) {
if (key === "b") continue
if (key === "d") break
seen.push(key)
}
return seen
`)).toEqual(["a", "c"])
})
test("iterates index strings over arrays", async () => {
expect(await value(`
const indexes = []
for (const i in ["x", "y", "z"]) {
if (i === "2") break
indexes.push(i)
}
return indexes
`)).toEqual(["0", "1"])
})
test("supports let declarations and bare identifiers", async () => {
expect(await value(`
let last = ""
for (let key in { a: 1, b: 2 }) last = key
return last
`)).toBe("b")
expect(await value(`
let key = "before"
for (key in { only: 1 }) {}
return key
`)).toBe("only")
})
test("enumerates namespaces and tools from the host tool tree", async () => {
expect(await value(`
const names = []
for (const ns in tools) {
for (const name in tools[ns]) names.push(ns + "." + name)
}
return names
`)).toEqual(["github.list_issues", "github.get_issue", "memory.search", "playwright.navigate"])
})
test("unsupported values fail with a hint at for...of and Object.keys", async () => {
for (const expression of [`"text"`, "new Map([[1, 2]])", "new Set([1])", "42", "null"]) {
const failure = await error(`for (const key in ${expression}) {}; return "no"`)
expect(failure.message).toContain("for...in requires a plain object, array, or tools reference")
expect(failure.message).toContain("Use for...of for arrays/strings/Maps/Sets, or Object.keys(value)")
}
})
})

View file

@ -0,0 +1,380 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode } from "../src/index.js"
import { ToolRuntime } from "../src/tool-runtime.js"
// Runs a CodeMode program with no host tools and returns the ExecuteResult. These tests pin the
// JS-parity behaviors for the "99% of ordinary defensive JavaScript just works" goal: cases where
// a strict interpreter would throw but idiomatic JS yields undefined / succeeds.
//
// Note on the result boundary: this package normalizes a bare `undefined` result to `null` when
// it crosses out of the sandbox (results are JSON data), so tests asserting an in-sandbox
// `undefined` read check `=== undefined` inside the program and `null` at the boundary.
const run = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
const value = async (code: string) => {
const result = await run(code)
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const error = async (code: string) => {
const result = await run(code)
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
return result.error
}
describe("H2: string property access reads as undefined (not a throw)", () => {
test("unknown property on a string is undefined", async () => {
expect(await value(`const s = "hi"; return s.login === undefined`)).toBe(true)
expect(await value(`const s = "hi"; return s.login`)).toBeNull()
})
test("optional chaining + fallback on a string does not throw", async () => {
expect(await value(`const s = "hi"; return s?.login ?? "fallback"`)).toBe("fallback")
})
test("the real MCP pattern: result is a JSON string, defensive read falls through", async () => {
// me.result is a string; me.result?.login is undefined, so we fall back to the raw string.
expect(await value(`const me = { result: '{"login":"x"}' }; return me.result?.login ?? me.result`)).toBe(
'{"login":"x"}',
)
})
test("unknown property on a number is undefined", async () => {
expect(await value(`return (5).foo ?? "n"`)).toBe("n")
})
test("supported string methods still work", async () => {
expect(await value(`return "AB".toLowerCase()`)).toBe("ab")
expect(await value(`return "hello".length`)).toBe(5)
})
})
describe("H3: array property access reads as undefined (not a throw)", () => {
test("unknown property on an array is undefined", async () => {
expect(await value(`return [1,2,3].foo === undefined`)).toBe(true)
expect(await value(`return [1,2,3].foo`)).toBeNull()
})
test("optional chaining on an array does not throw", async () => {
expect(await value(`return [1,2,3]?.foo ?? "fb"`)).toBe("fb")
})
test("unknown property reads stay undefined for methods CodeMode does not implement", async () => {
expect(await value(`return [1,2,3].toSpliced === undefined`)).toBe(true)
})
test("supported array methods and indexing still work", async () => {
expect(await value(`return [1,2,3].map(x => x + 1)`)).toEqual([2, 3, 4])
expect(await value(`return [1,2,3][9] === undefined`)).toBe(true)
expect(await value(`return [1,2,3][9]`)).toBeNull()
})
})
describe("H6: object spread of null/undefined is a no-op", () => {
test("spreading null is a no-op", async () => {
expect(await value(`const o = null; return { ...o, a: 1 }`)).toEqual({ a: 1 })
})
test("spreading an absent argument merges cleanly", async () => {
expect(await value(`function f(opts){ return { ...opts, a: 1 } } return f(undefined)`)).toEqual({ a: 1 })
})
test("spreading a real object still works", async () => {
expect(await value(`const o = { a: 1 }; return { ...o, b: 2 }`)).toEqual({ a: 1, b: 2 })
})
test("spreading an array into an object still errors", async () => {
const err = await error(`return { ...[1,2], a: 1 }`)
expect(err.kind).toBe("InvalidDataValue")
})
})
describe("H4: typeof on an undeclared identifier is 'undefined'", () => {
test("feature-detection guard does not throw", async () => {
expect(await value(`return typeof foo === "undefined" ? "safe" : "no"`)).toBe("safe")
})
test("typeof of a declared binding is unaffected", async () => {
expect(await value(`const x = 5; return typeof x`)).toBe("number")
expect(await value(`const s = "a"; return typeof s`)).toBe("string")
})
test("referencing an undeclared identifier outside typeof still throws", async () => {
const err = await error(`return foo + 1`)
expect(err.message).toContain("foo")
})
})
describe("H1: NaN/Infinity flow as intermediates and normalize to null at the boundary", () => {
test("guards run instead of the program crashing on a transient NaN", async () => {
expect(await value(`return parseInt("abc") || 0`)).toBe(0)
expect(await value(`const x = Number("abc"); return Number.isNaN(x) ? 0 : x`)).toBe(0)
expect(await value(`const o = {}; o.count = (o.count || 0) + 1; return o.count`)).toBe(1)
// average of an empty list, guarded - the classic divide-by-zero that used to throw pre-guard
expect(await value(`const a = []; return a.length ? a.reduce((s,x)=>s+x,0)/a.length : 0`)).toBe(0)
})
test("a non-finite value becomes null when it leaves the sandbox", async () => {
expect(await value(`return 5/0`)).toBeNull()
expect(await value(`return 0/0`)).toBeNull()
expect(await value(`return Math.max()`)).toBeNull()
// nested, too - normalization walks the returned structure
expect(await value(`return { a: Number("x"), b: 2, c: [1/0] }`)).toEqual({ a: null, b: 2, c: [null] })
})
test("NaN and Infinity are usable identifiers and inspectable in-sandbox", async () => {
expect(await value(`return Number.isNaN(NaN)`)).toBe(true)
expect(await value(`return Infinity > 1e9`)).toBe(true)
expect(await value(`return Number.isFinite(1/0)`)).toBe(false)
expect(await value(`return [3,1,2].reduce((a,b)=>Math.max(a,b), -Infinity)`)).toBe(3)
// JSON.stringify inside the sandbox matches JS: non-finite serializes to null
expect(await value(`return JSON.stringify({ x: Number("z") })`)).toBe('{"x":null}')
})
test("copyOut normalizes non-finite numbers to null (the shared return + tool-arg boundary)", () => {
// Tool-call arguments funnel through copyOut too, so this one function pins both boundaries.
expect(ToolRuntime.copyOut(NaN)).toBeNull()
expect(ToolRuntime.copyOut(Infinity)).toBeNull()
expect(ToolRuntime.copyOut(-Infinity)).toBeNull()
expect(ToolRuntime.copyOut(42)).toBe(42)
expect(ToolRuntime.copyOut({ a: NaN, b: [Infinity, 1] })).toEqual({ a: null, b: [null, 1] })
})
})
describe("Error values and instanceof", () => {
test("new Error carries name/message and is instanceof Error", async () => {
expect(await value(`const e = new Error("boom"); return [e instanceof Error, e.name, e.message]`)).toEqual([true, "Error", "boom"])
})
test("Error without new behaves like new Error", async () => {
expect(await value(`const e = Error("plain"); return [e instanceof Error, e.name, e.message]`)).toEqual([true, "Error", "plain"])
expect(await value(`const e = new Error(); return [e.name, e.message, e instanceof Error]`)).toEqual(["Error", "", true])
})
test("specific error types are instanceof themselves and Error, not each other", async () => {
expect(await value(`const e = new TypeError("t"); return [e instanceof TypeError, e instanceof Error, e instanceof RangeError]`)).toEqual([true, true, false])
expect(await value(`return new Error("e") instanceof TypeError`)).toBe(false)
})
test("thrown errors keep instanceof through try/catch", async () => {
expect(await value(`try { throw new Error("x") } catch (e) { return [e instanceof Error, e.message] }`)).toEqual([true, "x"])
})
test("interpreter runtime failures are caught as Error values", async () => {
expect(await value(`try { JSON.parse("nope") } catch (e) { return e instanceof Error }`)).toBe(true)
expect(await value(`try { undeclared() } catch (e) { return e instanceof Error }`)).toBe(true)
})
test("caught failures carry the constructor name the real-JS failure would have", async () => {
// JSON.parse throws SyntaxError: name and specific-instanceof both carry through, and the
// message keeps the engine's position detail.
expect(await value(`
try { JSON.parse("{oops") } catch (e) {
return [e.name, e instanceof SyntaxError, e instanceof Error, e instanceof TypeError, e.message.includes("JSON")]
}
`)).toEqual(["SyntaxError", true, true, false, true])
expect(await value(`try { undeclared() } catch (e) { return [e.name, e instanceof ReferenceError] }`))
.toEqual(["ReferenceError", true])
expect(await value(`try { const c = 1; c = 2 } catch (e) { return [e.name, e instanceof TypeError] }`))
.toEqual(["TypeError", true])
expect(await value(`try { "a".normalize("NOPE") } catch (e) { return [e.name, e instanceof RangeError] }`))
.toEqual(["RangeError", true])
expect(await value(`try { "a".match("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`))
.toEqual(["SyntaxError", true])
expect(await value(`try { new RegExp("(") } catch (e) { return [e.name, e instanceof SyntaxError] }`))
.toEqual(["SyntaxError", true])
})
test("diagnostics without a specific real-JS analogue are named plain Error", async () => {
expect(await value(`try { JSON.parse(5) } catch (e) { return [e.name, e instanceof Error] }`))
.toEqual(["Error", true])
})
test("Promise.allSettled rejection reasons are Error values", async () => {
expect(await value(`
const settled = await Promise.allSettled([Promise.reject(new Error("b"))])
return [settled[0].reason instanceof Error, settled[0].reason.message]
`)).toEqual([true, "b"])
})
test("non-error thrown values are not instanceof Error", async () => {
expect(await value(`try { throw "raw" } catch (e) { return e instanceof Error }`)).toBe(false)
expect(await value(`try { throw { message: "shaped" } } catch (e) { return e instanceof Error }`)).toBe(false)
})
test("plain data is never instanceof Error", async () => {
expect(await value(`return [({}) instanceof Error, "s" instanceof Error, null instanceof Error]`)).toEqual([false, false, false])
})
test("error values still serialize as plain { name, message } data", async () => {
expect(await value(`return new Error("m")`)).toEqual({ name: "Error", message: "m" })
expect(await value(`return JSON.stringify(new Error("m"))`)).toBe('{"name":"Error","message":"m"}')
expect(await value(`try { throw new Error("m") } catch (e) { return Object.keys(e) }`)).toEqual(["name", "message"])
})
test("spreading an error loses the brand, like losing the prototype in JS", async () => {
expect(await value(`const e = new Error("m"); return ({ ...e }) instanceof Error`)).toBe(false)
expect(await value(`const e = new Error("m"); return { ...e }`)).toEqual({ name: "Error", message: "m" })
})
test("typeof Error is function; an unknown instanceof right-hand side is a catchable error", async () => {
expect(await value(`return typeof Error`)).toBe("function")
expect(await value(`try { return 1 instanceof 5 } catch (e) { return "caught" }`)).toBe("caught")
const err = await error(`return 1 instanceof 5`)
expect(err.message).toContain("right-hand side of 'instanceof'")
})
})
describe("array methods: splice, fill, copyWithin, keys/values/entries", () => {
test("splice removes in place and returns the removed elements", async () => {
expect(await value(`const a = [1,2,3,4]; const removed = a.splice(1, 2); return { removed, a }`)).toEqual({ removed: [2, 3], a: [1, 4] })
})
test("splice inserts new elements at the cut", async () => {
expect(await value(`const a = ["a","d"]; a.splice(1, 0, "b", "c"); return a`)).toEqual(["a", "b", "c", "d"])
expect(await value(`const a = [1,2,3]; const removed = a.splice(1, 1, "x"); return { removed, a }`)).toEqual({ removed: [2], a: [1, "x", 3] })
})
test("splice with one argument removes to the end; negative start counts back", async () => {
expect(await value(`const a = [1,2,3]; const removed = a.splice(1); return { removed, a }`)).toEqual({ removed: [2, 3], a: [1] })
expect(await value(`const a = [1,2,3]; const removed = a.splice(-1); return { removed, a }`)).toEqual({ removed: [3], a: [1, 2] })
})
test("splice rejects inserting a container into itself", async () => {
const err = await error(`const a = [1]; a.splice(0, 0, [a]); return a`)
expect(err.kind).toBe("InvalidDataValue")
expect(err.message).toContain("circular")
})
test("fill overwrites a range and returns the mutated array", async () => {
expect(await value(`const a = [1,2,3,4]; return a.fill(0, 1, 3)`)).toEqual([1, 0, 0, 4])
expect(await value(`return [1,2,3].fill("z")`)).toEqual(["z", "z", "z"])
})
test("copyWithin copies a range in place", async () => {
expect(await value(`return [1,2,3,4,5].copyWithin(0, 3)`)).toEqual([4, 5, 3, 4, 5])
})
test("keys/values/entries return arrays usable with for...of and spread", async () => {
expect(await value(`return [...["x","y","z"].keys()]`)).toEqual([0, 1, 2])
expect(await value(`return ["x","y"].values()`)).toEqual(["x", "y"])
expect(await value(`
const out = []
for (const [index, item] of ["a","b"].entries()) out.push(index + ":" + item)
return out
`)).toEqual(["0:a", "1:b"])
expect(await value(`return [...[7].entries()]`)).toEqual([[0, 7]])
})
})
describe("string methods: localeCompare, normalize, trim aliases", () => {
test("localeCompare orders strings for sorting", async () => {
expect(await value(`return ["b","a","c"].sort((x, y) => x.localeCompare(y))`)).toEqual(["a", "b", "c"])
expect(await value(`return "a".localeCompare("a")`)).toBe(0)
})
test("normalize applies unicode normalization forms", async () => {
expect(await value(`return "\\u0065\\u0301".normalize("NFC").length`)).toBe(1)
expect(await value(`return "\\u00e9".normalize("NFD").length`)).toBe(2)
expect(await value(`return "x".normalize() === "x"`)).toBe(true)
})
test("an invalid normalize form is a clear catchable error", async () => {
expect(await value(`try { "x".normalize("nope"); return "no" } catch (e) { return e.message }`)).toContain('"NFC"')
})
test("trimLeft/trimRight alias trimStart/trimEnd", async () => {
expect(await value(`return " x ".trimLeft()`)).toBe("x ")
expect(await value(`return " x ".trimRight()`)).toBe(" x")
})
})
describe("compound assignment matches its binary operator", () => {
// `x op= y` must behave exactly like `x = x op y`, sharing the binary operator's coercion
// semantics (Dates string-coerce for `+` and use their time value for arithmetic; data
// objects/arrays coerce to their JS string form).
const pair = async (compound: string, expanded: string) => {
const [a, b] = await Promise.all([value(compound), value(expanded)])
expect(a).toEqual(b)
return a
}
test("sandbox Date += concatenates its string form, like d = d + 1", async () => {
const result = await pair(
`let d = new Date(1000); d += 1; return d`,
`let d = new Date(1000); d = d + 1; return d`,
)
expect(result).toBe("1970-01-01T00:00:01.000Z1")
})
test("sandbox Date numeric compound ops use its time value", async () => {
expect(await pair(
`let d = new Date(1000); d -= 400; return d`,
`let d = new Date(1000); d = d - 400; return d`,
)).toBe(600)
expect(await pair(
`let d = new Date(1000); d /= 4; return d`,
`let d = new Date(1000); d = d / 4; return d`,
)).toBe(250)
})
test("string += object/array matches x = x + obj", async () => {
expect(await pair(
`let x = "a"; x += { b: 1 }; return x`,
`let x = "a"; x = x + { b: 1 }; return x`,
)).toBe("a[object Object]")
expect(await pair(
`let x = "a"; x += [1, 2]; return x`,
`let x = "a"; x = x + [1, 2]; return x`,
)).toBe("a1,2")
})
test("compound assignment through a member target coerces the same way", async () => {
expect(await pair(
`const o = { s: "t" }; o.s += new Date(0); return o.s`,
`const o = { s: "t" }; o.s = o.s + new Date(0); return o.s`,
)).toBe("t1970-01-01T00:00:00.000Z")
})
test("numeric and string compound operators sweep identically to their expansions", async () => {
const cases: Array<[string, number | string]> = [
[`let x = 7; x += 3; return x`, 7 + 3],
[`let x = 7; x -= 3; return x`, 7 - 3],
[`let x = 7; x *= 3; return x`, 7 * 3],
[`let x = 7; x /= 2; return x`, 7 / 2],
[`let x = 7; x %= 3; return x`, 7 % 3],
[`let x = 7; x **= 2; return x`, 7 ** 2],
[`let x = 7; x &= 3; return x`, 7 & 3],
[`let x = 7; x |= 8; return x`, 7 | 8],
[`let x = 7; x ^= 2; return x`, 7 ^ 2],
[`let x = 7; x <<= 2; return x`, 7 << 2],
[`let x = -7; x >>= 1; return x`, -7 >> 1],
[`let x = -7; x >>>= 1; return x`, -7 >>> 1],
[`let x = "a"; x += "b"; return x`, "ab"],
]
for (const [compound, expected] of cases) {
expect(await value(compound)).toBe(expected)
expect(await value(compound.replace(/x (\S+)= /, (_, op) => `x = x ${op} `))).toBe(expected)
}
})
})
describe("H5: builtin coercion functions work as array callbacks", () => {
test("filter(Boolean) drops falsy values", async () => {
expect(await value(`return [0, 1, "", 2, null, 3].filter(Boolean)`)).toEqual([1, 2, 3])
})
test("map(String) coerces each element", async () => {
expect(await value(`return [1, 2, 3].map(String)`)).toEqual(["1", "2", "3"])
})
test("arrow callbacks still work (no regression)", async () => {
expect(await value(`return [1, 2, 3, 4].filter(x => x % 2 === 0)`)).toEqual([2, 4])
expect(await value(`return [1, 2, 3].reduce((a, b) => a + b, 0)`)).toBe(6)
})
test("a non-callable callback is still rejected", async () => {
const err = await error(`return [1,2,3].map(42)`)
expect(err.message).toContain("callback")
})
})

View file

@ -0,0 +1,426 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode, Tool, toolError, type ExecuteResult, type ExecutionLimits } from "../src/index.js"
// Wave 5 acceptance suite: first-class promise values. Un-awaited tool calls start eagerly on
// supervised fibers, `await` settles them, and Promise.all/allSettled/race/resolve/reject are
// ordinary functions over arbitrary arrays mixing promises and plain values.
type Trace = {
starts: Array<number>
active: number
maxActive: number
completed: number
interrupted: number
}
const makeTrace = (): Trace => ({ starts: [], active: 0, maxActive: 0, completed: 0, interrupted: 0 })
/** Echoes `id` after `ms` milliseconds, recording start order, live concurrency, and interruption. */
const sleepyTool = (trace: Trace) =>
Tool.make({
description: "Echo an id after a delay",
input: Schema.Struct({ id: Schema.Number, ms: Schema.optionalKey(Schema.Number) }),
output: Schema.Number,
run: ({ id, ms }) =>
Effect.gen(function*() {
trace.starts.push(id)
trace.active += 1
trace.maxActive = Math.max(trace.maxActive, trace.active)
yield* Effect.sleep(ms ?? 20)
trace.active -= 1
trace.completed += 1
return id
}).pipe(Effect.onInterrupt(() => Effect.sync(() => {
trace.active -= 1
trace.interrupted += 1
}))),
})
const failingTool = Tool.make({
description: "Always refuse",
input: Schema.Struct({}),
output: Schema.String,
run: () => Effect.fail(toolError("Lookup refused")),
})
const run = (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}): Promise<ExecuteResult> => {
const trace = options.trace ?? makeTrace()
return Effect.runPromise(CodeMode.execute({
tools: { host: { sleepy: sleepyTool(trace), fail: failingTool } },
code,
...(options.limits ? { limits: options.limits } : {}),
}))
}
const value = async (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}) => {
const result = await run(code, options)
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const error = async (code: string, options: { trace?: Trace; limits?: ExecutionLimits } = {}) => {
const result = await run(code, options)
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
return result.error
}
describe("first-class promise values", () => {
test("an un-awaited tool call starts eagerly, in call order, before any await", async () => {
const trace = makeTrace()
const result = await value(
`
const a = tools.host.sleepy({ id: 1, ms: 40 })
const b = tools.host.sleepy({ id: 2, ms: 40 })
const rb = await b
const ra = await a
return [ra, rb]
`,
{ trace },
)
expect(result).toEqual([1, 2])
expect(trace.starts).toEqual([1, 2])
// Both calls overlapped even though they were awaited sequentially.
expect(trace.maxActive).toBeGreaterThan(1)
})
test("awaiting the same promise twice settles once and never re-runs the call", async () => {
const result = await run(`
const p = tools.host.sleepy({ id: 7 })
const x = await p
const y = await p
return [x, y]
`)
expect(result.ok).toBe(true)
if (!result.ok) return
expect(result.value).toEqual([7, 7])
expect(result.toolCalls).toStrictEqual([{ name: "host.sleepy" }])
})
test("await of a non-promise value is a passthrough no-op", async () => {
expect(await value(`return await 42`)).toBe(42)
expect(await value(`const x = await "s"; return x`)).toBe("s")
expect(await value(`return await null`)).toBeNull()
expect(await value(`return (await [1, 2]).length`)).toBe(2)
})
test("returning an un-awaited tool call resolves it (async-function return semantics)", async () => {
expect(await value(`return tools.host.sleepy({ id: 9 })`)).toBe(9)
})
test("typeof a promise is 'object', and console.log renders it sensibly", async () => {
const result = await run(`
const p = Promise.resolve(1)
console.log(p)
return typeof p
`)
expect(result.ok).toBe(true)
if (!result.ok) return
expect(result.value).toBe("object")
expect(result.logs).toStrictEqual(["[Promise (await it to get its value)]"])
})
test("an awaited failure is catchable exactly like a synchronous throw", async () => {
expect(await value(`
const p = tools.host.fail({})
try {
await p
return "no"
} catch (e) {
return e.message
}
`)).toBe("Lookup refused")
})
test("a fire-and-forget call completes before the execution ends", async () => {
const trace = makeTrace()
const result = await value(
`
tools.host.sleepy({ id: 1, ms: 30 })
return "done"
`,
{ trace },
)
expect(result).toBe("done")
expect(trace.completed).toBe(1)
expect(trace.interrupted).toBe(0)
})
test("a never-awaited failing call surfaces as an unhandled-rejection diagnostic", async () => {
const diagnostic = await error(`
tools.host.fail({})
return "done"
`)
expect(diagnostic.kind).toBe("ToolFailure")
expect(diagnostic.message).toContain("Unhandled rejection from an un-awaited tool call")
expect(diagnostic.message).toContain("Lookup refused")
expect(diagnostic.suggestions?.join(" ")).toContain("await tools.ns.tool(...)")
})
})
describe("promises at data boundaries", () => {
test("returning an un-awaited promise inside data is a clear await-hinting diagnostic", async () => {
const diagnostic = await error(`return { result: tools.host.sleepy({ id: 1 }) }`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("un-awaited Promise")
expect(diagnostic.message).toContain("await tools.ns.tool(...)")
})
test("passing an un-awaited promise as a tool argument is a clear diagnostic", async () => {
const diagnostic = await error(`return await tools.host.sleepy({ id: tools.host.sleepy({ id: 1 }) })`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("un-awaited Promise")
})
test("JSON.stringify of a promise is a diagnostic, not '{}'", async () => {
const diagnostic = await error(`return JSON.stringify(Promise.resolve(1))`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("un-awaited Promise")
})
test("operators reject promise operands", async () => {
const diagnostic = await error(`return Promise.resolve(1) + 1`)
expect(diagnostic.kind).toBe("InvalidDataValue")
})
})
describe("Promise.all over arbitrary arrays", () => {
test("mixes promises and plain values, preserving order", async () => {
expect(await value(`
return await Promise.all([tools.host.sleepy({ id: 1 }), "plain", tools.host.sleepy({ id: 2 }), 42])
`)).toEqual([1, "plain", 2, 42])
})
test("accepts arrays built beforehand, passed as identifiers, and spread elements", async () => {
expect(await value(`
const calls = []
calls.push(tools.host.sleepy({ id: 1 }))
calls.push(7)
const more = [tools.host.sleepy({ id: 2 })]
const batch = [...calls, ...more, "x"]
return await Promise.all(batch)
`)).toEqual([1, 7, 2, "x"])
})
test("runs items.map tool calls in parallel", async () => {
const trace = makeTrace()
const result = await value(
`
const ids = [1, 2, 3, 4]
return await Promise.all(ids.map((id) => tools.host.sleepy({ id, ms: 40 })))
`,
{ trace },
)
expect(result).toEqual([1, 2, 3, 4])
// maxActive counts truly-overlapping live executions, so > 1 proves real
// parallelism deterministically - no wall-clock assertion needed.
expect(trace.maxActive).toBeGreaterThan(1)
})
test("caps live tool-call concurrency at the fixed internal constant (8)", async () => {
const trace = makeTrace()
const result = await value(
`
const ids = []
for (let i = 0; i < 20; i += 1) ids.push(i)
const results = await Promise.all(ids.map((id) => tools.host.sleepy({ id, ms: 10 })))
return results.length
`,
{ trace },
)
expect(result).toBe(20)
expect(trace.maxActive).toBeGreaterThan(1)
expect(trace.maxActive).toBeLessThanOrEqual(8)
})
test("resolves the empty array", async () => {
expect(await value(`return await Promise.all([])`)).toEqual([])
})
test("rejects with the first failure, catchable in-program", async () => {
expect(await value(`
try {
await Promise.all([tools.host.sleepy({ id: 1 }), tools.host.fail({})])
return "no"
} catch (e) {
return e.message
}
`)).toBe("Lookup refused")
})
test("a non-collection argument is a clear error", async () => {
const diagnostic = await error(`return await Promise.all(42)`)
expect(diagnostic.message).toContain("Promise.all expects an array")
})
test("exceeding maxToolCalls inside Promise.all is a ToolCallLimitExceeded diagnostic", async () => {
const diagnostic = await error(
`return await Promise.all([tools.host.sleepy({ id: 1 }), tools.host.sleepy({ id: 2 }), tools.host.sleepy({ id: 3 })])`,
{ limits: { maxToolCalls: 2 } },
)
expect(diagnostic.kind).toBe("ToolCallLimitExceeded")
})
})
describe("Promise.allSettled", () => {
test("reports fulfilled and rejected outcomes with catch-normalized reasons", async () => {
expect(await value(`
return await Promise.allSettled([
tools.host.sleepy({ id: 5 }),
tools.host.fail({}),
"plain",
Promise.reject(new Error("boom")),
])
`)).toEqual([
{ status: "fulfilled", value: 5 },
{ status: "rejected", reason: { name: "Error", message: "Lookup refused" } },
{ status: "fulfilled", value: "plain" },
{ status: "rejected", reason: { name: "Error", message: "boom" } },
])
})
test("never rejects for program-level failures", async () => {
const result = await run(`
const settled = await Promise.allSettled([tools.host.fail({}), tools.host.fail({})])
return settled.filter((s) => s.status === "rejected").length
`)
expect(result.ok).toBe(true)
if (result.ok) expect(result.value).toBe(2)
})
})
describe("Promise.race", () => {
test("first settlement wins and losers are interrupted", async () => {
const trace = makeTrace()
const result = await value(
`
const fast = tools.host.sleepy({ id: 1, ms: 10 })
const slow = tools.host.sleepy({ id: 2, ms: 5000 })
return await Promise.race([fast, slow])
`,
{ trace },
)
expect(result).toBe(1)
expect(trace.interrupted).toBe(1)
expect(trace.completed).toBe(1)
})
test("awaiting an interrupted loser afterwards is a catchable program failure", async () => {
expect(await value(`
const fast = tools.host.sleepy({ id: 1, ms: 10 })
const slow = tools.host.sleepy({ id: 2, ms: 5000 })
const winner = await Promise.race([fast, slow])
try {
await slow
return "no"
} catch (e) {
return { winner, caught: e.message }
}
`)).toEqual({ winner: 1, caught: "This tool call was interrupted because another value settled a Promise.race first." })
})
test("a rejection can win the race", async () => {
expect(await value(`
try {
await Promise.race([tools.host.fail({}), tools.host.sleepy({ id: 1, ms: 5000 })])
return "no"
} catch (e) {
return e.message
}
`)).toBe("Lookup refused")
})
test("a plain value wins over pending promises", async () => {
const trace = makeTrace()
expect(await value(`return await Promise.race([tools.host.sleepy({ id: 1, ms: 5000 }), "immediate"])`, { trace })).toBe("immediate")
expect(trace.interrupted).toBe(1)
})
test("an empty race is a clear error instead of hanging", async () => {
const diagnostic = await error(`return await Promise.race([])`)
expect(diagnostic.message).toContain("never settle")
})
})
describe("Promise.resolve / Promise.reject", () => {
test("resolve wraps plain values and passes promises through", async () => {
expect(await value(`return await Promise.resolve(42)`)).toBe(42)
expect(await value(`return await Promise.resolve(Promise.resolve("nested"))`)).toBe("nested")
expect(await value(`return await Promise.resolve(tools.host.sleepy({ id: 3 }))`)).toBe(3)
})
test("reject produces a promise whose await throws the reason", async () => {
expect(await value(`
try {
await Promise.reject("nope")
return "no"
} catch (e) {
return e
}
`)).toBe("nope")
})
})
describe("timeout interruption of forked calls", () => {
test("the execution timeout interrupts in-flight forked fibers", async () => {
const trace = makeTrace()
const result = await run(
`
const a = tools.host.sleepy({ id: 1, ms: 60000 })
const b = tools.host.sleepy({ id: 2, ms: 60000 })
return await a
`,
{ trace, limits: { timeoutMs: 100 } },
)
expect(result.ok).toBe(false)
if (result.ok) return
expect(result.error.kind).toBe("TimeoutExceeded")
// Both calls started; neither escaped the timeout - the awaited one AND the abandoned one.
expect(trace.starts).toEqual([1, 2])
expect(trace.interrupted).toBe(2)
expect(trace.completed).toBe(0)
})
test("the timeout also interrupts calls inside Promise.all", async () => {
const trace = makeTrace()
const result = await run(
`return await Promise.all([tools.host.sleepy({ id: 1, ms: 60000 }), tools.host.sleepy({ id: 2, ms: 60000 })])`,
{ trace, limits: { timeoutMs: 100 } },
)
expect(result.ok).toBe(false)
if (result.ok) return
expect(result.error.kind).toBe("TimeoutExceeded")
expect(trace.interrupted).toBe(2)
})
})
describe("unsupported promise surface", () => {
test(".then/.catch/.finally give a clear await-instead error", async () => {
for (const method of ["then", "catch", "finally"]) {
const diagnostic = await error(`return tools.host.sleepy({ id: 1 }).${method}((x) => x)`)
expect(diagnostic.kind).toBe("UnsupportedSyntax")
expect(diagnostic.message).toContain(`Promise.prototype.${method} is not supported`)
expect(diagnostic.message).toContain("await")
}
})
test("other property reads on a promise hint at the missing await", async () => {
const diagnostic = await error(`return tools.host.sleepy({ id: 1 }).value`)
expect(diagnostic.kind).toBe("InvalidDataValue")
expect(diagnostic.message).toContain("un-awaited Promise")
expect(diagnostic.message).toContain("await it first")
})
test("unknown Promise statics list what is available", async () => {
const diagnostic = await error(`return await Promise.any([tools.host.sleepy({ id: 1 })])`)
expect(diagnostic.message).toContain("Promise.any is not available")
expect(diagnostic.message).toContain("Promise.allSettled")
})
test("new Promise(...) points at tool calls instead", async () => {
const diagnostic = await error(`return new Promise((resolve) => resolve(1))`)
expect(diagnostic.kind).toBe("UnsupportedSyntax")
expect(diagnostic.message).toContain("new Promise(...) is not supported")
expect(diagnostic.message).toContain("already return promises")
})
})

View file

@ -0,0 +1,336 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { CodeMode } from "../src/index.js"
import { Tool, inputTypeScript, jsonSchemaToTypeScript, outputTypeScript } from "../src/tool.js"
// A raw JSON Schema tool in the shape an MCP adapter produces: render-only input schema
// whose property descriptions and constraints must surface as JSDoc in pretty signatures.
const listIssues = Tool.make({
description: "List issues in a repository",
input: {
type: "object",
properties: {
owner: { type: "string", description: "Repository owner" },
after: { type: "string", description: "Cursor from the previous response's pageInfo" },
perPage: { type: "number", description: "Results per page", default: 30 },
labels: { type: "array", items: { type: "string" }, description: "Filter by labels", minItems: 1, maxItems: 10 },
state: { type: "string", enum: ["open", "closed"] },
},
required: ["owner"],
},
run: () => Effect.succeed("[]"),
})
// An Effect Schema tool whose field annotations must flow through the emitted JSON Schema.
const lookupOrder = Tool.make({
description: "Look up an order",
input: Schema.Struct({
id: Schema.String.annotate({ description: "Order identifier" }),
verbose: Schema.optionalKey(Schema.Boolean),
}),
output: Schema.Struct({
status: Schema.String.annotate({ description: "Current order status" }),
}),
run: () => Effect.succeed({ status: "open" }),
})
describe("pretty signature rendering", () => {
test("described fields get JSDoc comments; undescribed and untagged fields get none", () => {
expect(inputTypeScript(listIssues, true)).toBe(
[
"{",
" /** Repository owner */",
" owner: string",
" /** Cursor from the previous response's pageInfo */",
" after?: string",
" /**",
" * Results per page",
" * @default 30",
" */",
" perPage?: number",
" /**",
" * Filter by labels",
" * @minItems 1",
" * @maxItems 10",
" */",
" labels?: Array<string>",
' state?: "open" | "closed"',
"}",
].join("\n"),
)
})
test("compact mode output is unchanged by the pretty machinery", () => {
expect(inputTypeScript(listIssues)).toBe(
'{ owner: string; after?: string; perPage?: number; labels?: Array<string>; state?: "open" | "closed" }',
)
expect(inputTypeScript(lookupOrder)).toBe("{ id: string; verbose?: boolean }")
expect(outputTypeScript(lookupOrder)).toBe("{ status: string }")
})
test("nested objects recurse with increasing indent and their own JSDoc", () => {
const pretty = jsonSchemaToTypeScript(
{
type: "object",
properties: {
filter: {
type: "object",
description: "Search filter",
properties: { state: { type: "string", description: "Issue state" } },
},
},
},
true,
)
expect(pretty).toBe(
[
"{",
" /** Search filter */",
" filter?: {",
" /** Issue state */",
" state?: string",
" }",
"}",
].join("\n"),
)
})
test("Effect Schema annotations become JSDoc on input and output fields", () => {
expect(inputTypeScript(lookupOrder, true)).toBe(
["{", " /** Order identifier */", " id: string", " verbose?: boolean", "}"].join("\n"),
)
expect(outputTypeScript(lookupOrder, true)).toBe(
["{", " /** Current order status */", " status: string", "}"].join("\n"),
)
})
test("constraints TypeScript cannot express surface as JSDoc tags", () => {
const pretty = jsonSchemaToTypeScript(
{
type: "object",
properties: {
legacy: { type: "string", deprecated: true },
homepage: { type: "string", format: "uri" },
tags: { type: "array", items: { type: "string" }, minItems: 2, maxItems: 5, default: ["a", "b"] },
},
},
true,
)
expect(pretty).toContain(" /** @deprecated */\n legacy?: string")
expect(pretty).toContain(" /** @format uri */\n homepage?: string")
expect(pretty).toContain(
[" /**", ' * @default ["a","b"]', " * @minItems 2", " * @maxItems 5", " */", " tags?: Array<string>"].join("\n"),
)
})
test("skips an unserializable default rather than emitting a broken tag", () => {
const pretty = jsonSchemaToTypeScript(
{ type: "object", properties: { size: { type: "number", default: 1n } } },
true,
)
expect(pretty).toBe(["{", " size?: number", "}"].join("\n"))
})
test("neutralizes */ inside descriptions so nothing closes the comment early", () => {
const pretty = jsonSchemaToTypeScript(
{ type: "object", properties: { note: { type: "string", description: "Ends */ early" } } },
true,
)
expect(pretty).toContain(" /** Ends * / early */")
expect(pretty).not.toContain("Ends */")
})
test("multiline descriptions become *-prefixed blocks with blank edges trimmed", () => {
const pretty = jsonSchemaToTypeScript(
{
type: "object",
properties: { query: { type: "string", description: "\nFirst line\n\nSecond line\n" } },
},
true,
)
expect(pretty).toBe(
["{", " /**", " * First line", " *", " * Second line", " */", " query?: string", "}"].join("\n"),
)
})
test("stays total on cyclic $refs and pathological nesting in both modes", () => {
const cyclic = {
$ref: "#/$defs/Node",
$defs: { Node: { type: "object", properties: { child: { $ref: "#/$defs/Node" }, name: { type: "string" } } } },
} as const
expect(jsonSchemaToTypeScript(cyclic)).toBe("{ child?: Node; name?: string }")
expect(jsonSchemaToTypeScript(cyclic, true)).toContain("child?: Node")
let deep: Record<string, unknown> = { type: "string" }
for (let level = 0; level < 12; level += 1) deep = { type: "object", properties: { next: deep } }
for (const pretty of [false, true]) {
const rendered = jsonSchemaToTypeScript(deep, pretty)
expect(rendered).toContain("unknown")
expect(rendered).toContain("next?:")
}
})
})
describe("non-identifier property names render as quoted keys", () => {
// MCP-style schemas routinely carry property names that are not bare TS identifiers
// (`foo-bar`, `@type`, dotted names); the rendered signature must quote them so the
// model sees a valid TypeScript object type. Bare identifiers stay unquoted.
const rawSchema = {
type: "object",
properties: {
"foo-bar": { type: "string" },
"@type": { type: "string" },
"x.y": { type: "number", description: "Dotted name" },
"123": { type: "number" },
plain: { type: "boolean" },
},
required: ["@type"],
} as const
test("compact rendering quotes non-identifier keys and leaves identifiers bare", () => {
expect(jsonSchemaToTypeScript(rawSchema)).toBe(
'{ "123"?: number; "foo-bar"?: string; "@type": string; "x.y"?: number; plain?: boolean }',
)
})
test("pretty rendering quotes non-identifier keys and keeps their JSDoc", () => {
expect(jsonSchemaToTypeScript(rawSchema, true)).toBe(
[
"{",
' "123"?: number',
' "foo-bar"?: string',
' "@type": string',
" /** Dotted name */",
' "x.y"?: number',
" plain?: boolean",
"}",
].join("\n"),
)
})
test("JSON Schema input and output signatures of a tool both quote", () => {
const tool = Tool.make({
description: "Adapter tool with awkward field names",
input: rawSchema,
output: { type: "object", properties: { "content-type": { type: "string" } }, required: ["content-type"] } as const,
run: () => Effect.succeed({ "content-type": "text/plain" }),
})
expect(inputTypeScript(tool)).toContain('"foo-bar"?: string')
expect(outputTypeScript(tool)).toBe('{ "content-type": string }')
expect(outputTypeScript(tool, true)).toBe(["{", ' "content-type": string', "}"].join("\n"))
})
test("Effect Schema structs with non-identifier field names quote too", () => {
const tool = Tool.make({
description: "Schema tool with awkward field names",
input: Schema.Struct({ "foo-bar": Schema.String, plain: Schema.optionalKey(Schema.Number) }),
run: () => Effect.succeed(null),
})
expect(inputTypeScript(tool)).toBe('{ "foo-bar": string; plain?: number }')
expect(inputTypeScript(tool, true)).toBe(["{", ' "foo-bar": string', " plain?: number", "}"].join("\n"))
})
})
describe("union schemas render every alternative", () => {
test("anyOf with a number branch keeps sibling alternatives", () => {
const schema = {
anyOf: [{ type: "string" }, { type: "number" }],
} as const
expect(jsonSchemaToTypeScript(schema)).toBe("string | number")
expect(jsonSchemaToTypeScript(schema, true)).toBe("string | number")
})
test("nullable numeric unions keep null", () => {
const schema = {
oneOf: [{ type: "number" }, { type: "null" }],
} as const
expect(jsonSchemaToTypeScript(schema)).toBe("number | null")
expect(jsonSchemaToTypeScript(schema, true)).toBe("number | null")
})
test("tool input and output signatures preserve numeric unions", () => {
const tool = Tool.make({
description: "Tool with numeric unions",
input: {
type: "object",
properties: {
value: { anyOf: [{ type: "string" }, { type: "number" }] },
},
} as const,
output: { anyOf: [{ type: "number" }, { type: "boolean" }] } as const,
run: () => Effect.succeed(1),
})
expect(inputTypeScript(tool)).toBe("{ value?: string | number }")
expect(outputTypeScript(tool)).toBe("number | boolean")
})
})
describe("pretty signatures in search results", () => {
const runtime = CodeMode.make({ tools: { github: { list_issues: listIssues }, orders: { lookup: lookupOrder } } })
const search = async (query: string) => {
const result = await Effect.runPromise(runtime.execute(
`return await tools.$codemode.search({ query: ${JSON.stringify(query)} })`,
))
expect(result.ok).toBe(true)
if (!result.ok) throw new Error("search failed")
return result.value as { items: Array<{ path: string; signature: string }>; total: number }
}
test("a raw JSON Schema (MCP-style) tool's result signature carries field JSDoc and tags", async () => {
const { items } = await search("list issues repository")
const item = items.find(({ path }) => path === "tools.github.list_issues")!
expect(item.signature).toBe(
[
"tools.github.list_issues(input: {",
" /** Repository owner */",
" owner: string",
" /** Cursor from the previous response's pageInfo */",
" after?: string",
" /**",
" * Results per page",
" * @default 30",
" */",
" perPage?: number",
" /**",
" * Filter by labels",
" * @minItems 1",
" * @maxItems 10",
" */",
" labels?: Array<string>",
' state?: "open" | "closed"',
"}): Promise<unknown>",
].join("\n"),
)
})
test("an annotated Effect Schema tool's result signature carries field JSDoc (exact-path lookup too)", async () => {
for (const query of ["look up order", "tools.orders.lookup"]) {
const { items } = await search(query)
const item = items.find(({ path }) => path === "tools.orders.lookup")!
expect(item.signature).toBe(
[
"tools.orders.lookup(input: {",
" /** Order identifier */",
" id: string",
" verbose?: boolean",
"}): Promise<{",
" /** Current order status */",
" status: string",
"}>",
].join("\n"),
)
}
})
test("the inline catalog line for the same tool stays single-line compact", () => {
const instructions = runtime.instructions()
expect(instructions).toContain(
' - tools.github.list_issues(input: { owner: string; after?: string; perPage?: number; labels?: Array<string>; state?: "open" | "closed" }): Promise<unknown> // List issues in a repository',
)
expect(instructions).toContain(
" - tools.orders.lookup(input: { id: string; verbose?: boolean }): Promise<{ status: string }> // Look up an order",
)
expect(instructions).not.toContain("/**")
})
})

View file

@ -0,0 +1,427 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { CodeMode, Tool } from "../src/index.js"
// Standard-library value types: Date, RegExp, Map, Set. Programs use them as ordinary JS;
// intra-sandbox checkpoints (Object.* helpers, spread, coercion inputs) preserve the live
// values, while at the host boundary (final result, tool arguments, JSON.stringify) they
// serialize exactly as JSON.stringify would: Date -> ISO string (invalid -> null),
// RegExp/Map/Set -> {}.
const run = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
const value = async (code: string) => {
const result = await run(code)
if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
return result.value
}
const error = async (code: string) => {
const result = await run(code)
if (result.ok) throw new Error(`expected failure, got value ${JSON.stringify(result.value)}`)
return result.error
}
describe("Date", () => {
test("Date.now() returns a number", async () => {
expect(await value(`return typeof Date.now()`)).toBe("number")
})
test("epoch construction and ISO rendering", async () => {
expect(await value(`return new Date(0).toISOString()`)).toBe("1970-01-01T00:00:00.000Z")
})
test("string parsing round-trips", async () => {
expect(await value(`return new Date("2024-01-02T03:04:05.000Z").getTime()`)).toBe(1704164645000)
expect(await value(`return Date.parse("2024-01-02T03:04:05.000Z")`)).toBe(1704164645000)
})
test("date arithmetic and comparison use the time value", async () => {
expect(await value(`const a = new Date(1000); const b = new Date(3000); return b - a`)).toBe(2000)
expect(await value(`const a = new Date(1000); const b = new Date(3000); return a < b`)).toBe(true)
expect(await value(`return +new Date(42)`)).toBe(42)
})
test("UTC getters read calendar components", async () => {
expect(await value(`const d = new Date("2024-03-05T06:07:08.009Z"); return [d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()]`)).toEqual([2024, 2, 5, 6, 7, 8, 9])
})
test("invalid dates yield NaN times, guardable in-sandbox", async () => {
expect(await value(`return Number.isNaN(new Date("garbage").getTime())`)).toBe(true)
expect(await value(`return new Date("garbage").toJSON()`)).toBeNull()
})
test("toISOString on an invalid date is a catchable error", async () => {
expect(await value(`try { new Date("garbage").toISOString(); return "no" } catch { return "caught" }`)).toBe("caught")
})
test("template interpolation renders the ISO form", async () => {
expect(await value("return `at ${new Date(0)}`")).toBe("at 1970-01-01T00:00:00.000Z")
})
test("dates serialize to ISO strings at the boundary, direct and nested", async () => {
expect(await value(`return new Date(0)`)).toBe("1970-01-01T00:00:00.000Z")
expect(await value(`return { when: new Date(0), tags: [new Date(1000)] }`)).toEqual({
when: "1970-01-01T00:00:00.000Z",
tags: ["1970-01-01T00:00:01.000Z"],
})
expect(await value(`return JSON.stringify({ d: new Date(0) })`)).toBe('{"d":"1970-01-01T00:00:00.000Z"}')
})
test("coercions: Number is the time, String is ISO, Boolean is true", async () => {
expect(await value(`return Number(new Date(5))`)).toBe(5)
expect(await value(`return String(new Date(0))`)).toBe("1970-01-01T00:00:00.000Z")
expect(await value(`return Boolean(new Date(0))`)).toBe(true)
})
test("sorting dates with a numeric comparator", async () => {
expect(await value(`
const dates = [new Date(3000), new Date(1000), new Date(2000)]
return dates.sort((a, b) => a - b).map((d) => d.getTime())
`)).toEqual([1000, 2000, 3000])
})
test("new Date(year, month, day) accepts component form", async () => {
expect(await value(`const d = new Date(2024, 0, 2); return [d.getFullYear(), d.getMonth(), d.getDate()]`)).toEqual([2024, 0, 2])
})
test("typeof and unknown properties are forgiving", async () => {
expect(await value(`return typeof new Date(0)`)).toBe("object")
expect(await value(`return new Date(0).nope === undefined`)).toBe(true)
})
})
describe("RegExp", () => {
test("literal test", async () => {
expect(await value(`return /ab+c/.test("xabbbc")`)).toBe(true)
expect(await value(`return /ab+c/.test("nope")`)).toBe(false)
})
test("exec exposes captures and index", async () => {
expect(await value(`const m = /a(b+)/.exec("xxabbc"); return { full: m[0], group: m[1], index: m.index }`)).toEqual({
full: "abb",
group: "bb",
index: 2,
})
expect(await value(`return /a/.exec("zzz")`)).toBeNull()
})
test("named groups read through", async () => {
expect(await value(`const m = /(?<word>[a-z]+)-(?<num>\\d+)/.exec("id ab-42"); return m.groups.word + m.groups.num`)).toBe("ab42")
})
test("global exec advances lastIndex across calls", async () => {
expect(await value(`
const r = /\\d+/g
const first = r.exec("a1b22c")
const second = r.exec("a1b22c")
return [first[0], second[0]]
`)).toEqual(["1", "22"])
})
test("string match: non-global carries index, global lists all matches", async () => {
expect(await value(`const m = "a1b22".match(/\\d+/); return [m[0], m.index]`)).toEqual(["1", 1])
expect(await value(`return "a1b22".match(/\\d+/g)`)).toEqual(["1", "22"])
expect(await value(`return "abc".match(/\\d/)`)).toBeNull()
})
test("matchAll materializes match arrays with captures", async () => {
expect(await value(`return "a1b22".matchAll(/(\\d+)/g).map((m) => m[1])`)).toEqual(["1", "22"])
})
test("replace and replaceAll with patterns and $1 substitution", async () => {
expect(await value(`return "a1b2".replace(/\\d/, "#")`)).toBe("a#b2")
expect(await value(`return "a1b2".replace(/\\d/g, "#")`)).toBe("a#b#")
expect(await value(`return "a1b2".replaceAll(/\\d/g, "#")`)).toBe("a#b#")
expect(await value(`return "hi bob".replace(/b(o)b/, "[$1]")`)).toBe("hi [o]")
})
test("replaceAll without the g flag is a catchable error", async () => {
expect(await value(`try { "a".replaceAll(/a/, "b"); return "no" } catch { return "caught" }`)).toBe("caught")
})
test("split and search accept patterns", async () => {
expect(await value(`return "a1b22c".split(/\\d+/)`)).toEqual(["a", "b", "c"])
expect(await value(`return "ab42".search(/\\d/)`)).toBe(2)
expect(await value(`return "ab".search(/\\d/)`)).toBe(-1)
})
test("new RegExp constructs from strings; invalid patterns are catchable", async () => {
expect(await value(`return new RegExp("a+", "i").test("AAA")`)).toBe(true)
expect(await value(`try { new RegExp("("); return "no" } catch { return "caught" }`)).toBe("caught")
expect(await value(`return [/a/ instanceof RegExp, /a/.source]`)).toEqual([true, "a"])
})
test("invalid patterns fail with actionable messages", async () => {
const fromString = await error(`return "abc".match("(")`)
expect(fromString.message).toContain('String.match received the string "("')
expect(fromString.message).toContain("escape them with a backslash")
const fromConstructor = await error(`return new RegExp("(")`)
expect(fromConstructor.message).toContain('new RegExp(...) received "("')
expect(fromConstructor.message).toContain("escape them with a backslash")
const fromFlags = await error(`return new RegExp("a", "xz")`)
expect(fromFlags.message).toContain('invalid flags "xz"')
expect(fromFlags.message).toContain("Valid flags are")
})
test("missing g-flag errors say how to fix the call", async () => {
expect((await error(`return "aa".replaceAll(/a/, "b")`)).message).toContain("write /a/g, or use String.replace")
expect((await error(`return "aa".matchAll(/a/)`)).message).toContain("write /a/g, or use String.match")
})
test("a non-pattern argument names the expected shapes", async () => {
const err = await error(`return "abc".match(42)`)
expect(err.message).toContain("expects a regular expression")
expect(err.message).toContain("not number")
})
test("source and flags properties read through", async () => {
expect(await value(`const r = /ab/gi; return { source: r.source, flags: r.flags, global: r.global }`)).toEqual({
source: "ab",
flags: "gi",
global: true,
})
})
test("regexes serialize to {} at the boundary, like JSON", async () => {
expect(await value(`return /a/`)).toEqual({})
expect(await value(`return JSON.stringify({ r: /a/g })`)).toBe('{"r":{}}')
})
test("template interpolation renders the literal form", async () => {
expect(await value("return `${/ab/g}`")).toBe("/ab/g")
})
})
describe("Map", () => {
test("get/set/has/size with chaining", async () => {
expect(await value(`
const m = new Map()
m.set("a", 1).set("b", 2)
return { a: m.get("a"), b: m.get("b"), has: m.has("a"), miss: m.get("zz") === undefined, size: m.size }
`)).toEqual({ a: 1, b: 2, has: true, miss: true, size: 5 - 3 })
})
test("object keys use identity", async () => {
expect(await value(`
const key = { id: 1 }
const m = new Map()
m.set(key, "hit")
return [m.get(key), m.get({ id: 1 }) === undefined]
`)).toEqual(["hit", true])
})
test("construction from entry pairs and another Map", async () => {
expect(await value(`const m = new Map([["a", 1], ["b", 2]]); return m.get("b")`)).toBe(2)
expect(await value(`const m = new Map([["a", 1]]); const n = new Map(m); n.set("b", 2); return [n.get("a"), n.get("b"), m.has("b")]`)).toEqual([1, 2, false])
expect((await error(`return new Map("nope")`)).message).toMatch(/\[key, value\] pairs/)
expect((await error(`return new Map(["flat"])`)).message).toMatch(/\[key, value\] pairs/)
})
test("keys/values/entries return arrays", async () => {
expect(await value(`
const m = new Map([["a", 1], ["b", 2]])
return { keys: m.keys(), values: m.values(), entries: m.entries() }
`)).toEqual({ keys: ["a", "b"], values: [1, 2], entries: [["a", 1], ["b", 2]] })
})
test("Object.fromEntries(map) and Array.from(map)", async () => {
expect(await value(`return Object.fromEntries(new Map([["a", 1], ["b", 2]]))`)).toEqual({ a: 1, b: 2 })
expect(await value(`return Array.from(new Map([["a", 1]]))`)).toEqual([["a", 1]])
})
test("for...of iterates [key, value] pairs with destructuring", async () => {
expect(await value(`
const m = new Map([["a", 1], ["b", 2]])
let total = 0
let names = ""
for (const [key, count] of m) { names += key; total += count }
return names + total
`)).toBe("ab3")
})
test("spread produces entry pairs", async () => {
expect(await value(`return [...new Map([["a", 1]])]`)).toEqual([["a", 1]])
})
test("forEach passes (value, key)", async () => {
expect(await value(`
const m = new Map([["a", 1], ["b", 2]])
const seen = []
m.forEach((count, key) => seen.push(key + count))
return seen
`)).toEqual(["a1", "b2"])
})
test("delete and clear", async () => {
expect(await value(`
const m = new Map([["a", 1], ["b", 2]])
const removed = m.delete("a")
const missed = m.delete("zz")
const sizeAfterDelete = m.size
m.clear()
return [removed, missed, sizeAfterDelete, m.size]
`)).toEqual([true, false, 1, 0])
})
test("counting idiom: grouped tallies", async () => {
expect(await value(`
const words = ["a", "b", "a", "c", "a"]
const counts = new Map()
for (const word of words) counts.set(word, (counts.get(word) ?? 0) + 1)
return Object.fromEntries(counts)
`)).toEqual({ a: 3, b: 1, c: 1 })
})
test("maps serialize to {} at the boundary, like JSON", async () => {
expect(await value(`return new Map([["a", 1]])`)).toEqual({})
expect(await value(`return JSON.stringify(new Map([["a", 1]]))`)).toBe("{}")
})
test("console.log renders map contents for debugging", async () => {
const result = await run(`console.log(new Map([["a", 1]])); return null`)
expect(result.ok).toBe(true)
expect(result.logs?.[0]).toBe(`Map(1) [["a",1]]`)
})
})
describe("Set", () => {
test("add/has/delete/size with chaining", async () => {
expect(await value(`
const s = new Set()
s.add(1).add(2).add(1)
const removed = s.delete(2)
return [s.size, s.has(1), s.has(2), removed]
`)).toEqual([1, true, false, true])
})
test("dedupe idiom: [...new Set(items)]", async () => {
expect(await value(`return [...new Set([1, 2, 2, 3, 1])]`)).toEqual([1, 2, 3])
})
test("construction from strings and other Sets", async () => {
expect(await value(`return [...new Set("aba")]`)).toEqual(["a", "b"])
expect(await value(`return Array.from(new Set(new Set([1, 2])))`)).toEqual([1, 2])
})
test("SameValueZero: NaN is findable", async () => {
expect(await value(`const s = new Set([NaN]); return s.has(NaN)`)).toBe(true)
})
test("for...of iterates values", async () => {
expect(await value(`
let total = 0
for (const n of new Set([1, 2, 3])) total += n
return total
`)).toBe(6)
})
test("sets serialize to {} at the boundary, like JSON", async () => {
expect(await value(`return { s: new Set([1]) }`)).toEqual({ s: {} })
})
})
describe("stdlib integration", () => {
test("typeof reports constructors as functions and never throws", async () => {
expect(await value(`return typeof Map`)).toBe("function")
expect(await value(`return typeof ((x) => x)`)).toBe("function")
expect(await value(`return typeof Math`)).toBe("object")
expect(await value(`return typeof tools`)).toBe("object")
})
test("negation works on any value", async () => {
expect(await value(`return !new Map()`)).toBe(false)
expect(await value(`const fn = () => 1; return !fn`)).toBe(false)
})
test("object spread of sandbox values is a no-op, like JS", async () => {
expect(await value(`return { ...new Map([["a", 1]]), kept: true }`)).toEqual({ kept: true })
})
test("dates inside Map values survive in-sandbox reads", async () => {
expect(await value(`
const m = new Map([["start", new Date(1000)]])
return m.get("start").getTime()
`)).toBe(1000)
})
test("instanceof recognizes the stdlib value types", async () => {
expect(await value(`return [new Date(0) instanceof Date, /a/ instanceof RegExp, new Map() instanceof Map, new Set() instanceof Set]`)).toEqual([true, true, true, true])
expect(await value(`return [[1] instanceof Array, [1] instanceof Object, ({}) instanceof Object, 5 instanceof Object]`)).toEqual([true, true, true, false])
expect(await value(`return [new Map() instanceof Set, "s" instanceof Date]`)).toEqual([false, false])
expect(await value(`const p = Promise.resolve(1); const isPromise = p instanceof Promise; await p; return isPromise`)).toBe(true)
})
test("realistic pipeline: parse, extract with regex, dedupe, count by day", async () => {
expect(await value(`
const raw = '[{"at":"2024-01-01T05:00:00Z","tag":"a b"},{"at":"2024-01-01T09:00:00Z","tag":"b c"},{"at":"2024-01-02T01:00:00Z","tag":"a"}]'
const rows = JSON.parse(raw)
const tags = new Set()
const byDay = new Map()
for (const row of rows) {
for (const m of row.tag.matchAll(/[a-z]+/g)) tags.add(m[0])
const day = new Date(row.at).toISOString().slice(0, 10)
byDay.set(day, (byDay.get(day) ?? 0) + 1)
}
return { tags: [...tags].sort((a, b) => (a < b ? -1 : 1)), byDay: Object.fromEntries(byDay) }
`)).toEqual({ tags: ["a", "b", "c"], byDay: { "2024-01-01": 2, "2024-01-02": 1 } })
})
})
describe("sandbox values at intra-sandbox checkpoints", () => {
test("Object.values/entries keep Dates usable", async () => {
expect(await value(`return Object.values({ d: new Date(0) })[0].getTime()`)).toBe(0)
expect(await value(`const [key, d] = Object.entries({ d: new Date(0) })[0]; return key + ":" + d.getTime()`)).toBe("d:0")
})
test("Object.assign keeps Maps usable", async () => {
expect(await value(`const merged = Object.assign({}, { m: new Map([["a", 1]]) }); return merged.m.get("a")`)).toBe(1)
})
test("object and array spread keep sandbox values usable", async () => {
expect(await value(`
const src = { m: new Map([["a", 1]]) }
const copy = { ...src }
copy.m.set("b", 2)
return [copy.m.get("a"), src.m.get("b")]
`)).toEqual([1, 2])
expect(await value(`const list = [new Date(1000)]; const copy = [...list]; return copy[0].getTime()`)).toBe(1000)
})
test("Array.from over arrays keeps nested sandbox values usable", async () => {
expect(await value(`return Array.from([new Date(5)])[0].getTime()`)).toBe(5)
})
test("regexes stay callable through Object.values", async () => {
expect(await value(`return Object.values({ r: /ab+/ })[0].test("abb")`)).toBe(true)
})
test("Object.* helpers see sandbox values as empty objects, never internals", async () => {
expect(await value(`return Object.keys(new Map([["a", 1]]))`)).toEqual([])
expect(await value(`return Object.values(new Date(0))`)).toEqual([])
expect(await value(`return Object.entries(new Set([1]))`)).toEqual([])
expect(await value(`return Object.assign({}, new Map([["a", 1]]))`)).toEqual({})
expect(await value(`return Object.hasOwn(new Date(0), "time")`)).toBe(false)
})
test("the host boundary still serializes JSON forms: results, JSON.stringify, and tool arguments", async () => {
expect(await value(`return { d: new Date(0), m: new Map([["a", 1]]) }`)).toEqual({ d: "1970-01-01T00:00:00.000Z", m: {} })
expect(await value(`return JSON.stringify({ d: new Date(0) })`)).toBe('{"d":"1970-01-01T00:00:00.000Z"}')
const observed: Array<unknown> = []
const capture = Tool.make({
description: "Capture the exact input the host receives",
input: { type: "object" },
run: (input) =>
Effect.sync(() => {
observed.push(input)
return "ok"
}),
})
const result = await Effect.runPromise(CodeMode.execute({
tools: { host: { capture } },
code: `return await tools.host.capture({ when: new Date(0), tags: new Map([["a", 1]]) })`,
}))
expect(result.ok).toBe(true)
expect(observed).toStrictEqual([{ when: "1970-01-01T00:00:00.000Z", tags: {} }])
})
})