feat: return typed values from Code Mode

This commit is contained in:
Tianyi Cui
2026-07-21 04:37:09 +08:00
parent 66c36e7325
commit c1d7b0df81
50 changed files with 2155 additions and 580 deletions
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { EventEmitter } from 'node:events'
import { LogBuffer, makeConsoleShim, makeNamespaces, captureStreamWrites, prepareValue, runWorkerMain, truncateUtf8Bytes, wireReplies } from '../src/bootstrap.ts'
import { LogBuffer, makeConsoleShim, makeNamespaces, captureStreamWrites, prepareCompletion, runWorkerMain, ToolCallError, truncateUtf8Bytes, wireReplies } from '../src/bootstrap.ts'
import type { BootstrapPort, PatchableStream, PendingCall } from '../src/bootstrap.ts'
import type { ReplyMessage, WorkerToHost } from '../src/protocol.ts'
@@ -43,19 +43,34 @@ function fakeStreams(): { stdout: PatchableStream; stderr: PatchableStream } {
return { stdout: { write: () => true }, stderr: { write: () => true } }
}
const BOOT = { maxLogBytes: 65_536, maxValueBytes: 32_768 }
/** Capture one promise rejection without Vitest's intentionally `any` matcher channel. */
async function rejectionOf(promise: Promise<unknown>): Promise<unknown> {
try {
await promise
return undefined
} catch (error: unknown) {
return error
}
}
const BOOT = { maxOutputBytes: 65_536 }
describe('LogBuffer', () => {
it('streams entries to the sink until the byte budget, then emits one marker and drops the rest', () => {
it('streams entries to the sink until the byte budget, then emits one fitting prefix and reports the limit once', () => {
const seen: string[] = []
const buffer = new LogBuffer(10, text => seen.push(text))
let limits = 0
const buffer = new LogBuffer(10, text => seen.push(text), () => { limits += 1 })
buffer.push('12345')
buffer.push('123456')
buffer.push('dropped')
expect(seen).toEqual([
'12345',
'[dsh-code-runtime-worker] log capture truncated at 10 bytes',
])
expect(seen).toEqual(['12345', '12345'])
expect(limits).toBe(1)
const exactlyFull: string[] = []
const fullBuffer = new LogBuffer(4, text => exactlyFull.push(text))
fullBuffer.push('1234')
fullBuffer.push('no-prefix-fits')
expect(exactlyFull).toEqual(['1234'])
})
})
@@ -109,45 +124,42 @@ describe('captureStreamWrites', () => {
})
})
describe('prepareValue', () => {
it('omits undefined, passes small cloneable values raw', () => {
expect(prepareValue(undefined, 100)).toEqual({})
expect(prepareValue({ a: [1, 'two'] }, 100)).toEqual({ value: { a: [1, 'two'] } })
describe('prepareCompletion', () => {
it('omits undefined and passes lossless JSON values exactly', () => {
expect(prepareCompletion(undefined, 100)).toEqual({})
expect(prepareCompletion({ a: [1, 'two'] }, 100)).toEqual({ value: { a: [1, 'two'] } })
})
it('replaces a non-cloneable value with its rendering', () => {
const { value } = prepareValue({ fn: () => 1 }, 1_000)
expect(typeof value).toBe('string')
expect(value).toContain('fn')
it('turns every lossy completion shape into invalid-output', () => {
const cyclic: Record<string, unknown> = {}
cyclic.self = cyclic
const sparse = Array(2)
class Exotic { readonly marker = true }
for (const value of [{ fn: () => 1 }, -0, Number.POSITIVE_INFINITY, sparse, cyclic, new Exotic()]) {
expect(prepareCompletion(value, 1_000)).toEqual({
error: { kind: 'invalid-output', message: 'program completion must be lossless JSON' },
})
}
})
it('replaces an oversized value with a truncation-marked capped rendering', () => {
const { value } = prepareValue('x'.repeat(50), 10)
expect(value).toBe(`${'x'.repeat(10)}… [truncated]`)
it('reports an oversized value instead of substituting rendered text', () => {
expect(prepareCompletion('x'.repeat(50), 10)).toEqual({
error: { kind: 'output-limit', message: 'outer output exceeded 10 bytes' },
})
})
it('measures a container by its structured-clone wire size, not its bounded rendering', () => {
// The bounded inspect rendering of a huge array is tiny ("... N more
// items"), but its real cross-boundary size is not — the cap must catch
// it, replacing the value with that bounded rendering.
const huge = new Array(50_000).fill(7)
const { value } = prepareValue(huge, 1_000)
expect(typeof value).toBe('string')
expect(value).toContain('more items')
it('measures the exact JSON serialization at and over the boundary', () => {
expect(prepareCompletion('€', 5)).toEqual({ value: '€' })
expect(prepareCompletion('€', 4)).toEqual({
error: { kind: 'output-limit', message: 'outer output exceeded 4 bytes' },
})
})
it('caps a multibyte string by UTF-8 bytes, not UTF-16 length', () => {
// 4 code units but 12 UTF-8 bytes: a length-counting cap would pass the
// full string through untruncated.
expect(prepareValue('€€€€', 4)).toEqual({ value: '€… [truncated]' })
})
it('caps a multibyte rendering by UTF-8 bytes too', () => {
// Wire size (24-byte string inside an array) exceeds the cap, so the
// value crosses as its rendering — whose truncation must also be
// byte-exact: "[ '" (3 bytes) + two € (6 bytes) = 9; a third € would
// overflow the 10-byte budget.
expect(prepareValue(['€€€€€€€€'], 10)).toEqual({ value: "[ '€€… [truncated]" })
it('contains a getter failure as invalid-output', () => {
const value = Object.defineProperty({}, 'x', { enumerable: true, get() { throw new Error('getter exploded') } })
expect(prepareCompletion(value, 1_000)).toEqual({
error: { kind: 'invalid-output', message: 'program completion must be lossless JSON' },
})
})
})
@@ -191,10 +203,35 @@ describe('makeNamespaces', () => {
}
const pending = new Map<number, PendingCall>()
const [tools] = makeNamespaces({ namespaces: [{ global: 'tools', names: ['x'] }] }, throwingPort, pending, { value: 1 }) as [Record<string, (args: unknown) => Promise<unknown>>]
await expect(tools.x?.(() => 1)).rejects.toThrow(/structured-cloneable: DataCloneError-ish/)
await expect(tools.x?.(() => 1)).rejects.toThrow(/structured-cloneable: raw-clone-failure/)
const first = await rejectionOf(tools.x?.(() => 1) ?? Promise.resolve())
const second = await rejectionOf(tools.x?.(() => 1) ?? Promise.resolve())
expect(first).toMatchObject({ name: 'ToolCallError', toolName: 'x' })
expect(second).toMatchObject({ name: 'ToolCallError', toolName: 'x' })
expect(first).toBeInstanceOf(ToolCallError)
expect(second).toBeInstanceOf(ToolCallError)
expect((first as Error).message).toMatch(/DataCloneError-ish/)
expect((second as Error).message).toMatch(/raw-clone-failure/)
expect(pending.size).toBe(0)
})
it('uses ordinary Error for non-tools namespace failures', async () => {
const deniedPort = new FakePort()
deniedPort.respond = message => message.type === 'call'
? { type: 'reply', id: message.id, ok: false, message: 'helper denied' }
: undefined
const deniedPending = new Map<number, PendingCall>()
wireReplies(deniedPort, deniedPending)
const [helpers] = makeNamespaces({ namespaces: [{ global: 'helpers', names: ['x'] }] }, deniedPort, deniedPending, { value: 1 }) as [Record<string, (args: unknown) => Promise<unknown>>]
const denied = await rejectionOf(helpers.x?.({}) ?? Promise.resolve())
expect(denied).toBeInstanceOf(Error)
expect(denied).not.toBeInstanceOf(ToolCallError)
const clonePort: BootstrapPort = { postMessage: () => { throw new Error('clone failed') }, on: () => {} }
const [cloneHelpers] = makeNamespaces({ namespaces: [{ global: 'helpers', names: ['x'] }] }, clonePort, new Map(), { value: 1 }) as [Record<string, (args: unknown) => Promise<unknown>>]
const cloneFailure = await rejectionOf(cloneHelpers.x?.(() => 1) ?? Promise.resolve())
expect(cloneFailure).toBeInstanceOf(Error)
expect(cloneFailure).not.toBeInstanceOf(ToolCallError)
})
})
describe('runWorkerMain', () => {
@@ -210,11 +247,24 @@ describe('runWorkerMain', () => {
expect(port.done()).toEqual({ type: 'done', value: { doubled: 42 } })
})
it('reports worker-side log capture overflow before completing', async () => {
const port = new FakePort()
await runWorkerMain(port, {
maxOutputBytes: 4,
code: 'console.log("12345"); return null',
namespaces: [],
}, fakeStreams())
expect(port.sent).toContainEqual({ type: 'log', text: '1234' })
expect(port.sent).toContainEqual({ type: 'output-limit' })
expect(port.done()).toEqual({ type: 'done', value: null })
})
it('reports a thrown program error on the done message', async () => {
const port = new FakePort()
await runWorkerMain(port, { ...BOOT, code: 'throw new Error("boom")', namespaces: [] }, fakeStreams())
const done = port.done()
expect(done?.type).toBe('done')
expect(done?.type === 'done' ? done.error?.kind : undefined).toBe('exception')
expect(done?.type === 'done' ? done.error?.message : undefined).toContain('boom')
expect(done?.type === 'done' ? done.value : undefined).toBeUndefined()
})
@@ -222,11 +272,11 @@ describe('runWorkerMain', () => {
it('renders non-Error throws and stack-less Errors on the done message', async () => {
const rawPort = new FakePort()
await runWorkerMain(rawPort, { ...BOOT, code: 'throw "raw-throw"', namespaces: [] }, fakeStreams())
expect(rawPort.done()).toEqual({ type: 'done', error: { message: 'raw-throw' } })
expect(rawPort.done()).toEqual({ type: 'done', error: { kind: 'exception', message: 'raw-throw' } })
const barePort = new FakePort()
await runWorkerMain(barePort, { ...BOOT, code: 'const e = new Error("bare"); e.stack = undefined; throw e', namespaces: [] }, fakeStreams())
expect(barePort.done()).toEqual({ type: 'done', error: { message: 'bare' } })
expect(barePort.done()).toEqual({ type: 'done', error: { kind: 'exception', message: 'bare' } })
})
it('surfaces a host failure reply as a program-side rejection it can catch', async () => {
@@ -234,10 +284,14 @@ describe('runWorkerMain', () => {
port.respond = message => message.type === 'call' ? { type: 'reply', id: message.id, ok: false, message: 'denied by host' } : undefined
await runWorkerMain(port, {
...BOOT,
code: 'try { await tools.x({}) } catch (error) { return `caught: ${error.message}` }',
code: 'try { await tools.x({}) } catch (error) { return { caught: error instanceof ToolCallError, name: error.name, toolName: error.toolName, message: error.message } }',
namespaces: [{ global: 'tools', names: ['x'] }],
}, fakeStreams())
expect(port.done()).toEqual({ type: 'done', value: 'caught: denied by host' })
expect(port.done()).toEqual({
type: 'done',
value: { caught: true, name: 'ToolCallError', toolName: 'x', message: 'denied by host' },
})
expect(new ToolCallError('x', 'nope')).toMatchObject({ name: 'ToolCallError', toolName: 'x', message: 'nope' })
})
it('ignores replies for unknown pending ids', async () => {
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { WorkerCodeRuntime } from '@deepseek-ai/dsh-code-runtime-worker'
import type { Config } from '@deepseek-ai/dsh-code-runtime-worker'
import type { CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import type { CodeBindingFunction, CodeBindingNamespace, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
/**
* Integration suite over REAL worker threads (no mocks — workers are cheap
@@ -17,8 +17,8 @@ async function setup(config: Config = {}) {
}
/** Convenience: one namespace `tools` with the given functions. */
function tools(functions: Record<string, (args: unknown) => Promise<unknown>>) {
return [{ global: 'tools', functions }]
function tools(functions: Record<string, (args: unknown) => Promise<unknown>>): CodeBindingNamespace[] {
return [{ global: 'tools', functions: functions as Record<string, CodeBindingFunction> }]
}
describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
@@ -52,10 +52,10 @@ describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
const result = await runtime.run({
program: `
const first = await tools.echo({ n: 1 });
let caught = '';
try { await tools.fail({}) } catch (error) { caught = error.message }
let caughtRaw = '';
try { await tools.failRaw({}) } catch (error) { caughtRaw = error.message }
let caught = {};
try { await tools.fail({}) } catch (error) { caught = { isTyped: error instanceof ToolCallError, name: error.name, toolName: error.toolName, message: error.message } }
let caughtRaw = {};
try { await tools.failRaw({}) } catch (error) { caughtRaw = { name: error.name, toolName: error.toolName, message: error.message } }
return { first, caught, caughtRaw };
`,
bindings: tools({
@@ -66,7 +66,11 @@ describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
}),
})
expect(result.error).toBeUndefined()
expect(result.value).toEqual({ first: { echoed: { n: 1 } }, caught: 'nope', caughtRaw: 'raw-nope' })
expect(result.value).toEqual({
first: { echoed: { n: 1 } },
caught: { isTyped: true, name: 'ToolCallError', toolName: 'fail', message: 'nope' },
caughtRaw: { name: 'ToolCallError', toolName: 'failRaw', message: 'raw-nope' },
})
expect(calls).toEqual([{ n: 1 }])
})
@@ -90,10 +94,11 @@ describe('WorkerCodeRuntime — programs and bindings (real workers)', () => {
expect(result.value).toBe('{}')
})
it('replaces a non-cloneable return value with a string rendering', async () => {
it('rejects a non-lossless completion instead of replacing it with rendered text', async () => {
const { runtime } = await setup()
const result = await runtime.run({ program: 'return { f: () => 1 }', bindings: [] })
expect(typeof result.value).toBe('string')
expect(result.value).toBeUndefined()
expect(result.error).toEqual({ kind: 'invalid-output', message: 'program completion must be lossless JSON' })
})
it('completes a program that returns nothing with no value at all', async () => {
@@ -201,30 +206,47 @@ describe('WorkerCodeRuntime — budgets and containment (real workers)', () => {
expect(after.value).toBe('alive')
}, 30_000)
it('truncates runaway log output at the byte budget with an in-band marker', async () => {
const { runtime } = await setup({ maxLogBytes: 300 })
it('fails runaway log output explicitly while retaining a bounded prefix', async () => {
const { runtime } = await setup({ maxOutputBytes: 300 })
const result = await runtime.run({
program: 'for (let i = 0; i < 1000; i++) console.log("spam line", i); return 1',
bindings: [],
})
expect(result.logs.at(-1)).toContain('truncated at 300 bytes')
const total = result.logs.reduce((sum, text) => sum + Buffer.byteLength(text, 'utf8'), 0)
expect(total).toBeLessThan(1_000)
expect(result.error).toEqual({ kind: 'output-limit', message: 'outer output exceeded 300 bytes' })
expect(result.value).toBeUndefined()
expect(result.logs.length).toBeGreaterThan(0)
expect(Buffer.byteLength(JSON.stringify(result.logs), 'utf8')).toBeLessThan(300)
})
it('caps an oversized return value with a truncation marker', async () => {
const { runtime } = await setup({ maxValueBytes: 64 })
it('fails an oversized return value without substituting a string', async () => {
const { runtime } = await setup({ maxOutputBytes: 64 })
const result = await runtime.run({ program: 'return "y".repeat(10_000)', bindings: [] })
expect(result.value).toBe(`${'y'.repeat(64)}… [truncated]`)
expect(result.value).toBeUndefined()
expect(result.error).toEqual({ kind: 'output-limit', message: 'outer output exceeded 64 bytes' })
})
it('caps a multibyte return value by UTF-8 bytes, not string length', async () => {
// 4 code units, 12 UTF-8 bytes: a length-counting cap would let the full
// string cross. The worker's byte-exact capped rendering then passes the
// host re-cap unchanged (cap + marker is exactly the granted slack).
const { runtime } = await setup({ maxValueBytes: 4 })
const result = await runtime.run({ program: 'return "€€€€"', bindings: [] })
expect(result.value).toBe('€… [truncated]')
it('uses UTF-8 serialized bytes at the exact completion boundary', async () => {
const exact = await setup({ maxOutputBytes: 7 })
const exactResult = await exact.runtime.run({ program: 'return "€"', bindings: [] })
// [] costs two bytes and JSON serialization of "€" costs five.
expect(exactResult).toEqual({ logs: [], value: '€' })
const over = await setup({ maxOutputBytes: 6 })
const overResult = await over.runtime.run({ program: 'return "€"', bindings: [] })
expect(overResult.error?.kind).toBe('output-limit')
})
it('accounts logs and completion in one exact combined ledger', async () => {
// JSON(["abc"]) is seven bytes and JSON("xy") is four.
const exact = await setup({ maxOutputBytes: 11 })
expect(await exact.runtime.run({ program: 'console.log("abc"); return "xy"', bindings: [] }))
.toEqual({ logs: ['abc'], value: 'xy' })
const over = await setup({ maxOutputBytes: 10 })
const result = await over.runtime.run({ program: 'console.log("abc"); return "xy"', bindings: [] })
expect(result.value).toBeUndefined()
expect(result.error?.kind).toBe('output-limit')
expect(Buffer.byteLength(JSON.stringify(result.logs), 'utf8') + Buffer.byteLength(JSON.stringify(result.error?.message), 'utf8')).toBeLessThanOrEqual(10)
})
it('completes a program that awaits its write callback, capturing the chunk', async () => {
@@ -241,32 +263,48 @@ describe('WorkerCodeRuntime — budgets and containment (real workers)', () => {
expect(result.logs).toContain('flushed')
})
it('caps a huge container whose bounded rendering is small (wire size, not rendering, is what counts)', async () => {
it('returns a large JSON container exactly when the outer cap permits it', async () => {
const { runtime } = await setup()
const result = await runtime.run({ program: 'return new Array(50_000).fill(7)', bindings: [] })
expect(result.error).toBeUndefined()
expect(typeof result.value).toBe('string')
expect(result.value).toContain('more items')
expect(result.value).toEqual(new Array(50_000).fill(7))
})
it('captures pipe writes that bypass the patched write slot as stray logs, capped by the same budget', async () => {
const { runtime } = await setup({ maxLogBytes: 4 })
it('returns an exact completion at the default 64 MiB combined boundary', async () => {
const { runtime } = await setup()
// [] costs two bytes and the JSON string contributes two quotes, leaving
// exactly this many payload bytes under the 67_108_864-byte default.
const result = await runtime.run({ program: 'return "x".repeat(67_108_860)', bindings: [] })
expect(result.error).toBeUndefined()
expect(result.logs).toEqual([])
expect(result.value).toHaveLength(67_108_860)
}, 60_000)
it('fails one byte over the default 64 MiB combined boundary', async () => {
const { runtime } = await setup()
const result = await runtime.run({ program: 'return "x".repeat(67_108_861)', bindings: [] })
expect(result.value).toBeUndefined()
expect(result.error).toEqual({ kind: 'output-limit', message: 'outer output exceeded 67108864 bytes' })
}, 60_000)
it('accounts pipe writes that bypass the patched write slot in the same outer ledger', async () => {
const { runtime } = await setup({ maxOutputBytes: 80 })
const result = await runtime.run({
// The prototype write bypasses the patched instance and reaches the real pipe. Pauses keep
// writes in separate chunks and let both reach the host before settlement.
program: `
const write = (text) => Object.getPrototypeOf(process.stdout).write.call(process.stdout, text);
write('abcd');
write('a'.repeat(20));
await new Promise(resolve => setTimeout(resolve, 150));
write('ef');
write('b'.repeat(100));
await new Promise(resolve => setTimeout(resolve, 100));
return 1;
`,
bindings: [],
})
expect(result.error).toBeUndefined()
expect(result.logs).toContain('abcd')
expect(result.logs).not.toContain('ef')
expect(result.error?.kind).toBe('output-limit')
expect(result.logs).toContain('a'.repeat(20))
expect(result.logs).not.toContain('b'.repeat(100))
}, 15_000)
})
@@ -305,7 +343,8 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
{ type: 'log', text: 7 },
{ type: 'log', text: {} },
{ type: 'done', error: 5 },
{ type: 'done', error: { message: 5 } },
{ type: 'done', error: { kind: 'exception', message: 5 } },
{ type: 'done', error: { kind: 'invented', message: 'bad kind' } },
]) parentPort.postMessage(junk);
return await tools.real({});
`,
@@ -316,10 +355,10 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
expect(result.logs).toEqual([])
})
it('caps forged log floods and forged done values at the configured budgets, dropping forged extra fields', async () => {
const { runtime } = await setup({ maxLogBytes: 200, maxValueBytes: 64 })
it('fails forged log floods and forged done values through the same outer cap', async () => {
const { runtime } = await setup({ maxOutputBytes: 200 })
const result = await runtime.run({
// Forged messages bypass the worker-side LogBuffer and prepareValue
// Forged messages bypass the worker-side LogBuffer and completion check
// entirely — only the host-side ledger and re-cap stand between model
// code and an unbounded result.
program: `
@@ -330,53 +369,79 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
`,
bindings: [],
})
expect(typeof result.value).toBe('string')
const value = result.value as string
expect(value.startsWith('V'.repeat(64))).toBe(true)
expect(value.endsWith('… [truncated]')).toBe(true)
expect(value.length).toBeLessThan(120)
const marker = '[dsh-code-runtime-worker] log capture truncated at 200 bytes'
const total = result.logs.reduce((sum, text) => sum + Buffer.byteLength(text, 'utf8'), 0)
expect(total).toBeLessThanOrEqual(200 + Buffer.byteLength(marker, 'utf8'))
expect(result.logs.at(-1)).toBe(marker)
expect(result.value).toBeUndefined()
expect(result.error).toEqual({ kind: 'output-limit', message: 'outer output exceeded 200 bytes' })
expect(Buffer.byteLength(JSON.stringify(result.logs), 'utf8')).toBeLessThan(200)
})
it('accepts a forged done carrying both value and error (self-sabotage, contained)', async () => {
it('drops a malformed forged done carrying both value and error', async () => {
const { runtime } = await setup()
const result = await runtime.run({
program: `
const { parentPort } = await import('node:worker_threads');
parentPort.postMessage({ type: 'done', value: 'lied', error: { message: 'fake failure' } });
for (;;) {}
parentPort.postMessage({ type: 'done', value: 'lied', error: { kind: 'exception', message: 'fake failure' } });
return 'honest';
`,
bindings: [],
})
expect(result.value).toBe('lied')
expect(result.error).toEqual({ kind: 'exception', message: 'fake failure' })
expect(result).toEqual({ logs: [], error: { kind: 'exception', message: 'fake failure' } })
})
it('byte-bounds forged multibyte error text at the host', async () => {
// Forged error text bypasses the worker entirely; the host bound is a
// BYTE bound (two € = 6 bytes fit an 8-byte cap, a third would not).
const { runtime } = await setup({ maxValueBytes: 8 })
it('turns forged over-limit error text into output-limit at the host', async () => {
const { runtime } = await setup({ maxOutputBytes: 64 })
const result = await runtime.run({
program: `
const { parentPort } = await import('node:worker_threads');
parentPort.postMessage({ type: 'done', error: { message: '€'.repeat(1000) } });
parentPort.postMessage({ type: 'done', error: { kind: 'exception', message: '€'.repeat(1000) } });
for (;;) {}
`,
bindings: [],
})
expect(result.error).toEqual({ kind: 'exception', message: '€€' })
expect(result.error).toEqual({ kind: 'output-limit', message: 'outer output exceeded 64 bytes' })
})
it('answers a binding whose resolution cannot be cloned with a failure reply', async () => {
it('answers a binding whose resolution is not lossless JSON with a typed failure reply', async () => {
const { runtime } = await setup()
const result = await runtime.run({
program: 'try { await tools.bad({}) } catch (error) { return error.message }',
program: 'try { await tools.bad({}) } catch (error) { return { name: error.name, toolName: error.toolName, message: error.message } }',
bindings: tools({ bad: async () => (() => 1) }),
})
expect(result.value).toContain('not structured-cloneable')
expect(result.value).toEqual({ name: 'ToolCallError', toolName: 'bad', message: 'binding resolution must be lossless JSON' })
})
it('contains throwing getters while snapshotting binding resolutions', async () => {
const { runtime } = await setup()
const result = await runtime.run({
program: 'try { await tools.bad({}) } catch (error) { return { name: error.name, toolName: error.toolName, message: error.message } }',
bindings: tools({ bad: async () => Object.defineProperty({}, 'bad', { enumerable: true, get() { throw new Error('getter exploded') } }) }),
})
expect(result.value).toEqual({ name: 'ToolCallError', toolName: 'bad', message: 'binding resolution must be lossless JSON' })
})
it('revalidates a forged lossy completion at the host boundary', async () => {
const { runtime } = await setup()
const result = await runtime.run({
program: `
const { parentPort } = await import('node:worker_threads');
parentPort.postMessage({ type: 'done', value: -0 });
for (;;) {}
`,
bindings: [],
})
expect(result).toEqual({ logs: [], error: { kind: 'invalid-output', message: 'program completion must be lossless JSON' } })
})
it('honors a forged worker-side output-limit signal', async () => {
const { runtime } = await setup()
const result = await runtime.run({
program: `
const { parentPort } = await import('node:worker_threads');
parentPort.postMessage({ type: 'output-limit' });
for (;;) {}
`,
bindings: [],
})
expect(result).toEqual({ logs: [], error: { kind: 'output-limit', message: 'outer output exceeded 67108864 bytes' } })
})
it('exposes binding names that collide with Object.prototype as ordinary functions', async () => {
@@ -392,12 +457,13 @@ describe('WorkerCodeRuntime — hostile programs (real workers)', () => {
})
describe('WorkerCodeRuntime — seam misuse and lifecycle', () => {
it('rejects invalid binding globals loudly (identifier, reserved word, duplicate, console)', async () => {
it('rejects invalid binding globals loudly (identifier, reserved word, duplicate, reserved injected globals)', async () => {
const { runtime } = await setup()
const cases: [string, RegExp][] = [
['not valid!', /not a usable identifier/],
['await', /not a usable identifier/],
['console', /duplicate binding global/],
['ToolCallError', /duplicate binding global/],
]
for (const [global, message] of cases) {
await expect(runtime.run({ program: 'return 1', bindings: [{ global, functions: {} }] })).rejects.toThrow(message)
@@ -413,6 +479,12 @@ describe('WorkerCodeRuntime — seam misuse and lifecycle', () => {
await expect(ctx.plugin(WorkerCodeRuntime, { computeMs: -1 })).rejects.toThrow(/positive number/)
})
it('requires maxOutputBytes to fit the smallest outer failure envelope', async () => {
const ctx = new Context()
await expect(ctx.plugin(WorkerCodeRuntime, { maxOutputBytes: 3 })).rejects.toThrow(/safe integer of at least 4/)
await expect(ctx.plugin(WorkerCodeRuntime, { maxOutputBytes: 4.5 })).rejects.toThrow(/safe integer of at least 4/)
})
it('keeps runs isolated: no state survives from one run to the next', async () => {
const { runtime } = await setup()
await runtime.run({ program: 'globalThis.leak = "value"; return 1', bindings: [] })