Add E2B PTY, LSP, and code runtime providers

This commit is contained in:
Tianyi Cui
2026-08-08 22:19:10 +08:00
parent e7b682f1f6
commit 6667102890
82 changed files with 5462 additions and 644 deletions
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/code-runtime/code-runtime-e2b/README.md
README.md: bf26174c59c1df18f63278acb7923fd409860b44
README.zh.md: c87945da30a6f8d77eb3722879510bb6159b3ee8
@@ -0,0 +1,44 @@
# @deepseek-ai/dsh-code-runtime-e2b
English | [中文](README.zh.md)
E2B implementation of [`ctx.codeRuntime`](../code-runtime/README.md). Each run executes one model-written TypeScript program in a fresh remote Node worker while binding functions, type stripping, output accounting, and lifecycle orchestration remain on the host.
## Configuration
| Key | Default | Meaning |
|---|---|---|
| `computeMs` | `60000` | Remote worker event-loop busy-time budget. |
| `maxWallMs` | `600000` | Host-observed wall-clock ceiling. |
| `maxOutputBytes` | `67108864` | Combined serialized outer logs/value/diagnostic cap. |
| `maxOldGenerationSizeMb` | `512` | Remote worker old-generation heap cap in MiB. |
| `maxFrameBytes` | `268435456` | Largest decoded bridge frame, including binding traffic. |
| `killGraceMs` | `2000` | Remote process-group TERM-to-KILL grace. |
Every value is a positive safe integer. `maxOutputBytes` is at least four bytes, `maxWallMs` cannot exceed Node's maximum timer delay, and `maxFrameBytes` cannot be smaller than `maxOutputBytes`. The service requires the concrete `dsh-subprocess-e2b` backend so run cleanup has remote process-group semantics.
## Execution and bridge contract
Setup uploads one dependency-free runner under `ctx.e2b.runtimeRoot` and resolves remote Node. For each run, the host wraps and type-strips erasable TypeScript with Node's `stripTypeScriptTypes`, then starts the runner in `ctx.e2b.cwd`. The runner creates a fresh worker thread with an empty environment and heap limit, measures active event-loop time, and destroys that worker after one completion. The enclosing E2B subprocess group is terminated and awaited after every result, timeout, abort, or disposal, so ordinary child processes in that group stop with the run.
The bridge uses validated newline-delimited base64 JSON frames because E2B subprocess callbacks expose decoded text. Binding arguments and resolutions use the worker runtime's iterative lossless-JSON wire shape; binding functions execute on the host and typed rejection classes are materialized inside the remote worker. The worker captures the JavaScript intrinsics that its adapter boundary invokes before model code runs, hardening binding transport, output accounting, and completion validation against mutation of those references. The host repeats message validation, call-id deduplication, lossless-JSON checks, and the outer-output ledger.
Program failures resolve as `CodeRunResult.error`; only seam misuse rejects. `isolation` is reported as `container`, which is a deployment descriptor rather than a security claim.
## Model Experience
Indirectly, through Code Mode in `dsh-tools`, which returns program logs, values, or typed failures through the existing `run_code` result contract.
#### KV Cache effect
No direct invalidation; Code Mode owns request-prefix changes.
## Known Limitations and Deferred Work
- **Not a whole-agent runtime** — Cordis, sessions, LLM calls, binding dispatch, TypeScript stripping, output ledgers, and E2B SDK state remain on the host.
- **No reconnectable runs** — retaining a sandbox preserves files but not worker/subprocess handles, binding calls, timers, or output cursors.
- **Node worker internals share the model realm** — mutating realm-wide globals or prototypes that Node itself uses can terminate the worker; captured adapter intrinsics are not a separate JavaScript realm or a security boundary.
- **Deliberate process-group escape is not captured** — model code can create a new POSIX session; that unmanaged process is outside this backend's cleanup identity.
- **Intermediate binding traffic is memory-bounded only per frame** — it does not enter model context or the outer-output ledger, but aggregate host/remote process memory remains the limit.
- **Experimental type stripping** — the backend shares the worker implementation's reliance on Node's experimental erasable-syntax API.
- **Sandbox policy is template-owned** — this package adds no network, volume, snapshot, or workspace-synchronization policy.
@@ -0,0 +1,44 @@
# @deepseek-ai/dsh-code-runtime-e2b
[English](README.md) | 中文
[`ctx.codeRuntime`](../code-runtime/README.md) 的 E2B 实现。每次运行都会在全新的远程 Node worker 中执行一段模型编写的 TypeScript 程序;绑定函数、类型剥离、输出记账和生命周期编排仍保留在宿主侧。
## 配置
| 配置键 | 默认值 | 含义 |
|---|---|---|
| `computeMs` | `60000` | 远程 worker 的事件循环忙碌时间预算。 |
| `maxWallMs` | `600000` | 宿主观测到的墙钟时间上限。 |
| `maxOutputBytes` | `67108864` | 外层日志、值和诊断合计的序列化上限。 |
| `maxOldGenerationSizeMb` | `512` | 远程 worker 的老生代堆上限(MiB)。 |
| `maxFrameBytes` | `268435456` | 已解码桥接帧的最大大小,包括绑定流量。 |
| `killGraceMs` | `2000` | 远程进程组 TERM 到 KILL 的宽限期。 |
每个值都必须是正的安全整数。`maxOutputBytes` 必须至少为 4 字节,`maxWallMs` 不得超过 Node 的最大定时器延迟,且 `maxFrameBytes` 不得小于 `maxOutputBytes`。本服务要求使用具体的 `dsh-subprocess-e2b` 后端,使运行清理具备远程进程组语义。
## 执行与桥接契约
设置阶段会在 `ctx.e2b.runtimeRoot` 下上传一个无依赖的 runner,并解析远程 Node。每次运行时,宿主会包装仅使用可擦除语法的 TypeScript,再用 Node 的 `stripTypeScriptTypes` 剥离类型,然后在 `ctx.e2b.cwd` 中启动 runner。runner 会创建一个具有空环境与堆上限的全新 worker 线程,测量事件循环活跃时间,并在一次运行结算后销毁该 worker。每当运行返回结果、超时、中止或因资源释放终止时,系统都会终止外围的 E2B 进程组并等待其退出,因此组内的普通子进程会随本次运行一同停止。
由于 E2B 进程管理回调公开的是已解码文本,桥接层使用经过验证、以换行分隔的 base64 JSON 帧。绑定参数与 resolve 值使用 worker 运行时的迭代式无损 JSON wire 形状;绑定函数在宿主执行,类型化的 reject 类则在远程 worker 内物化。worker 会在模型代码运行前捕获其适配器边界调用的 JavaScript intrinsic,从而增强绑定传输、输出记账与完成值验证对这些引用修改的抵御能力。宿主会再次执行消息验证、调用 id 去重和无损 JSON 检查,并用外层输出账本再次计量。
程序失败会 resolve 为 `CodeRunResult.error`;只有 seam 误用才会 reject。`isolation` 报告为 `container`;这是部署描述符,不构成安全声明。
## 模型体验
通过 `dsh-tools` 中的 Code Mode 间接影响模型;它会通过现有 `run_code` 结果契约返回程序日志、值或类型化失败。
#### KV Cache 影响
不会直接失效;请求前缀变更由 Code Mode 负责。
## 已知限制与暂缓工作
- **并非完整的 agent(智能体)运行时**:Cordis、会话、LLM(大语言模型)调用、绑定分发、TypeScript 类型剥离、输出账本和 E2B SDK 状态仍保留在宿主侧。
- **运行不可重连**:保留沙箱会保留文件,但不会保留 worker/进程管理句柄、绑定调用、定时器或输出游标。
- **Node worker 内部机制与模型共享同一 realm**:修改 Node 自身使用、影响整个 realm 的全局对象或原型可能会终止 worker;已捕获的适配器 intrinsic 并不构成独立的 JavaScript realm 或安全边界。
- **不会捕获有意逃逸进程组的行为**:模型代码可以创建新的 POSIX 会话;该非受管进程不属于此后端的清理身份范围。
- **中间绑定流量的内存边界仅适用于单帧**:它不会进入模型上下文或外层输出账本,但其总量仍只受宿主/远程进程内存限制。
- **实验性类型剥离**:该后端与 worker 实现一样,依赖 Node 的实验性可擦除语法 API。
- **沙箱策略归模板负责**:本包不会额外增加网络、卷、快照或工作区同步策略。
@@ -0,0 +1,52 @@
{
"name": "@deepseek-ai/dsh-code-runtime-e2b",
"description": "E2B code-runtime implementation for DeepSeek Harness",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-code-runtime": "^0.0.1",
"@deepseek-ai/dsh-code-runtime-worker": "^0.0.1",
"@deepseek-ai/dsh-e2b": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-subprocess-e2b": "^0.0.1",
"@deepseek-ai/dsh-timeout": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"dependencies": {
"schemastery": "^3.18.0"
},
"devDependencies": {
"@deepseek-ai/dsh-code-runtime": "workspace:^",
"@deepseek-ai/dsh-code-runtime-worker": "workspace:^",
"@deepseek-ai/dsh-e2b": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-subprocess-e2b": "workspace:^",
"@deepseek-ai/dsh-timeout": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}
@@ -0,0 +1,441 @@
/** E2B process/worker implementation of the harness code-runtime seam. */
import { posix } from 'node:path'
import { stripTypeScriptTypes } from 'node:module'
import type { Context } from 'cordis'
import z from 'schemastery'
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import type {
CodeBindingNamespace,
CodeJsonValue,
CodeRunFailure,
CodeRunRequest,
CodeRunResult,
} from '@deepseek-ai/dsh-code-runtime'
import {
E2BFrameDecoder,
encodeE2BFrame,
quoteE2BShellArg,
resolveE2BExecutable,
} from '@deepseek-ai/dsh-e2b'
import {
decodeWorkerJson,
encodeWorkerJson,
OutputLedger,
} from '@deepseek-ai/dsh-code-runtime-worker'
import type { WorkerJsonWire } from '@deepseek-ai/dsh-code-runtime-worker'
import { snapshotJsonValue } from '@deepseek-ai/dsh-session'
import E2BSubprocessService from '@deepseek-ai/dsh-subprocess-e2b'
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { CODE_RUNNER_SOURCE } from './runner-source.ts'
/** Runtime configuration; every execution and bridge bound is deployment-tunable. */
export interface Config {
/** Remote worker measured event-loop busy-time budget. */
computeMs?: number
/** Host-observed wall-clock ceiling. */
maxWallMs?: number
/** Combined serialized outer logs/value/diagnostic cap. */
maxOutputBytes?: number
/** Remote worker old-generation heap cap in MiB. */
maxOldGenerationSizeMb?: number
/** Largest decoded bridge frame, including binding traffic. */
maxFrameBytes?: number
/** Remote process-group TERM-to-KILL grace. */
killGraceMs?: number
}
type ResolvedConfig = Required<Config>
interface LiveRun {
settle(failure: CodeRunFailure): void
finished: Promise<void>
}
interface CallMessage {
type: 'call'
id: number
global: string
name: string
args: WorkerJsonWire
}
interface LogMessage {
type: 'log'
text: string
}
interface DoneMessage {
type: 'done'
value?: WorkerJsonWire
error?: CodeRunFailure
}
type RunnerMessage = CallMessage | LogMessage | DoneMessage | { type: 'output-limit' }
const STRIP_WRAP = { prefix: 'async function __dsh_program__() {\n', suffix: '\n}' } as const
const MIN_OUTPUT_BYTES = 4
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
/* jscpd:ignore-start -- Backends enforce the same injected-global vocabulary without coupling lifecycle implementations. */
const RESERVED_WORDS = new Set([
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in',
'instanceof', 'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof',
'var', 'void', 'while', 'with', 'yield', 'let', 'static', 'implements', 'interface', 'package',
'private', 'protected', 'public', 'arguments', 'eval',
])
const RESERVED_ERROR_PROPERTIES = new Set(['name', 'message', 'stack'])
/* jscpd:ignore-end */
const FAILURE_KINDS = new Set<CodeRunFailure['kind']>([
'exception', 'timeout', 'abort', 'worker-exit', 'invalid-output', 'output-limit',
])
function messageOf(error: unknown): string {
return error instanceof Error ? error.message : String(error)
}
function parseRunnerMessage(raw: unknown): RunnerMessage | undefined {
if (typeof raw !== 'object' || raw === null) return undefined
const record = raw as Record<string, unknown>
if (record.type === 'output-limit') return { type: 'output-limit' }
if (record.type === 'log') return typeof record.text === 'string' ? { type: 'log', text: record.text } : undefined
if (record.type === 'call') {
if (!Number.isSafeInteger(record.id) || (record.id as number) < 1 || typeof record.global !== 'string' || typeof record.name !== 'string' || !Array.isArray(record.args)) return undefined
return { type: 'call', id: record.id as number, global: record.global, name: record.name, args: record.args as WorkerJsonWire }
}
if (record.type !== 'done') return undefined
if (record.error === undefined) {
return { type: 'done', ...record.value === undefined ? {} : { value: record.value as WorkerJsonWire } }
}
if (typeof record.error !== 'object' || record.error === null) return undefined
const error = record.error as Record<string, unknown>
if (typeof error.kind !== 'string' || !FAILURE_KINDS.has(error.kind as CodeRunFailure['kind']) || typeof error.message !== 'string') return undefined
return { type: 'done', error: { kind: error.kind as CodeRunFailure['kind'], message: error.message } }
}
/** E2B-backed runtime: host-side type stripping, remote worker execution, host binding dispatch. */
export class E2BCodeRuntime extends CodeRuntime {
static inject = ['e2b', 'subprocess']
static Config: z<Config> = z.object({
computeMs: z.number().default(60_000),
maxWallMs: z.number().default(600_000),
maxOutputBytes: z.number().default(67_108_864),
maxOldGenerationSizeMb: z.number().default(512),
maxFrameBytes: z.number().default(268_435_456),
killGraceMs: z.number().default(2_000),
})
readonly language = 'typescript'
readonly isolation = 'container'
private readonly config: ResolvedConfig
private readonly ready: Promise<{ node: string; runner: string }>
private readonly live = new Set<LiveRun>()
private readonly subprocess: E2BSubprocessService
private disposed = false
constructor(ctx: Context, config: Config) {
super(ctx)
if (!(ctx.subprocess instanceof E2BSubprocessService)) {
throw new Error('code-runtime-e2b requires @deepseek-ai/dsh-subprocess-e2b as ctx.subprocess')
}
this.subprocess = ctx.subprocess
this.config = config as ResolvedConfig
for (const [key, value] of Object.entries(this.config)) {
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`code-runtime-e2b: config.${key} must be a positive safe integer`)
}
}
if (this.config.maxOutputBytes < MIN_OUTPUT_BYTES) {
throw new Error(`code-runtime-e2b: config.maxOutputBytes must be at least ${MIN_OUTPUT_BYTES}`)
}
if (this.config.maxWallMs > MAX_TIMER_DELAY_MS) {
throw new Error(`code-runtime-e2b: config.maxWallMs must be at most ${MAX_TIMER_DELAY_MS}`)
}
if (this.config.maxFrameBytes < this.config.maxOutputBytes) {
throw new Error('code-runtime-e2b: config.maxFrameBytes must be at least maxOutputBytes')
}
this.ready = this.prepare()
void this.ready.catch(() => {})
ctx.effect(() => () => this.teardown(), 'E2B code-runtime teardown')
}
/* jscpd:ignore-start -- Seam-level abort and type-strip results remain identical across execution substrates. */
/** Execute one type-stripped program in a fresh E2B worker process. */
async run(request: CodeRunRequest): Promise<CodeRunResult> {
if (this.disposed) throw new Error('code-runtime-e2b: run() after disposal')
const bindings = this.validateBindings(request)
if (request.signal?.aborted === true) {
return this.failure({ kind: 'abort', message: String(request.signal.reason) })
}
let code: string
try {
const stripped = stripTypeScriptTypes(STRIP_WRAP.prefix + request.program + STRIP_WRAP.suffix)
code = stripped.slice(STRIP_WRAP.prefix.length, stripped.length - STRIP_WRAP.suffix.length)
} catch (error: unknown) {
return this.failure({ kind: 'exception', message: messageOf(error) })
}
let runtime: Awaited<typeof this.ready>
try {
runtime = await this.ready
} catch (error: unknown) {
return this.failure({ kind: 'worker-exit', message: `E2B runtime setup failed: ${messageOf(error)}` })
}
// Disposal can race the awaited remote setup after the pre-await check.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this.disposed) return this.failure({ kind: 'abort', message: 'runtime disposed' })
return await this.execute(request, code, bindings, runtime)
}
/* jscpd:ignore-end */
private async prepare(): Promise<{ node: string; runner: string }> {
const sandbox = await this.ctx.e2b.getSandbox()
const runner = posix.join(this.ctx.e2b.runtimeRoot, 'code-runtime-runner.mjs')
await sandbox.files.write([{ path: runner, data: CODE_RUNNER_SOURCE }])
await sandbox.commands.run(`chmod 600 -- ${quoteE2BShellArg(runner)}`)
const node = await resolveE2BExecutable(sandbox, 'node')
return { node, runner }
}
private failure(error: CodeRunFailure): CodeRunResult {
return new OutputLedger(this.config.maxOutputBytes).failure([], error)
}
/* jscpd:ignore-start -- Binding names have one seam contract while dispatch and teardown remain backend-owned. */
private validateBindings(request: CodeRunRequest): Map<string, CodeBindingNamespace> {
const bindings = new Map<string, CodeBindingNamespace>()
for (const namespace of request.bindings) {
if (!IDENTIFIER.test(namespace.global) || RESERVED_WORDS.has(namespace.global)) {
throw new Error(`code-runtime-e2b: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`)
}
if (namespace.global === 'console' || bindings.has(namespace.global)) {
throw new Error(`code-runtime-e2b: duplicate binding global ${JSON.stringify(namespace.global)}`)
}
bindings.set(namespace.global, namespace)
}
const errorClassNames = new Set<string>()
for (const namespace of request.bindings) {
const descriptor = namespace.errorClass
if (descriptor === undefined) continue
if (!IDENTIFIER.test(descriptor.name) || RESERVED_WORDS.has(descriptor.name)) {
throw new Error(`code-runtime-e2b: binding error class ${JSON.stringify(descriptor.name)} is not a usable identifier`)
}
if (descriptor.name === 'console' || bindings.has(descriptor.name) || errorClassNames.has(descriptor.name)) {
throw new Error(`code-runtime-e2b: duplicate injected global ${JSON.stringify(descriptor.name)}`)
}
if (descriptor.memberNameProperty.length === 0 || RESERVED_ERROR_PROPERTIES.has(descriptor.memberNameProperty)) {
throw new Error(`code-runtime-e2b: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`)
}
errorClassNames.add(descriptor.name)
}
return bindings
}
/* jscpd:ignore-end */
private async execute(
request: CodeRunRequest,
code: string,
bindings: Map<string, CodeBindingNamespace>,
runtime: { node: string; runner: string },
): Promise<CodeRunResult> {
const handle = this.subprocess.spawn({
argv: [runtime.node, runtime.runner],
cwd: this.ctx.e2b.cwd,
stdio: { stdin: 'pipe', stdout: 'pipe', stderr: { maxBytes: this.config.maxOutputBytes } },
graceMs: this.config.killGraceMs,
...request.signal === undefined ? {} : { signal: request.signal },
env: {},
})
if (handle.stdin === undefined || handle.stdout === undefined) {
handle.terminate()
await Promise.allSettled([handle.done])
try {
await handle.waitForExit()
} catch (error: unknown) {
return this.failure({ kind: 'worker-exit', message: `E2B runtime cleanup failed: ${messageOf(error)}` })
}
return this.failure({ kind: 'worker-exit', message: 'E2B subprocess dropped a piped runtime stream' })
}
const stdin = handle.stdin
const stdout = handle.stdout
return new Promise<CodeRunResult>((resolve) => {
const output = new OutputLedger(this.config.maxOutputBytes)
const logs: string[] = []
const answered = new Set<number>()
const decoder = new E2BFrameDecoder(this.config.maxFrameBytes)
let settled = false
let finishResolve!: () => void
const finished = new Promise<void>((done) => { finishResolve = done })
const wallTimer: { current: NodeJS.Timeout | undefined } = { current: undefined }
const live: LiveRun = {
finished,
settle: (failure) => { finish(() => output.failure(logs, failure)) },
}
const finish = (result: CodeRunResult | (() => CodeRunResult)): void => {
if (settled) return
settled = true
clearTimeout(wallTimer.current)
request.signal?.removeEventListener('abort', onAbort)
this.live.delete(live)
void new Promise<void>((resume) => { setImmediate(resume) }).then(async () => {
handle.terminate()
await handle.done.catch(() => {})
let cleanupError: unknown
try {
await handle.waitForExit()
} catch (error: unknown) {
cleanupError = error
}
try {
decoder.finish()
} catch (error: unknown) {
result = output.failure(logs, { kind: 'worker-exit', message: messageOf(error) })
}
if (cleanupError !== undefined) {
result = output.failure(logs, { kind: 'worker-exit', message: `E2B runtime cleanup failed: ${messageOf(cleanupError)}` })
}
const final = typeof result === 'function' ? result() : result
finishResolve()
resolve(final)
})
}
const sendReply = (message: unknown): void => {
if (settled) return
stdin.write(encodeE2BFrame(message), (error?: Error | null) => {
if (error !== undefined && error !== null) {
finish(() => output.failure(logs, { kind: 'worker-exit', message: `E2B runtime bridge write failed: ${error.message}` }))
}
})
}
/* jscpd:ignore-start -- Host binding resolution mirrors worker semantics over a different transport. */
const onCall = (message: CallMessage): void => {
if (answered.has(message.id)) return
answered.add(message.id)
const functions = bindings.get(message.global)?.functions
const fn = functions !== undefined && Object.hasOwn(functions, message.name) ? functions[message.name] : undefined
if (typeof fn !== 'function') {
sendReply({ type: 'reply', id: message.id, ok: false, message: `unknown binding ${JSON.stringify(`${message.global}.${message.name}`)}` })
return
}
const args = decodeWorkerJson(message.args)
if (args === undefined) {
sendReply({ type: 'reply', id: message.id, ok: false, message: 'binding arguments must be lossless JSON' })
return
}
void (async () => {
try {
const resolved = await fn(args)
let value: CodeJsonValue | undefined
try {
value = snapshotJsonValue(resolved)
} catch {
value = undefined
}
if (value === undefined) {
sendReply({ type: 'reply', id: message.id, ok: false, message: 'binding resolution must be lossless JSON' })
} else {
sendReply({ type: 'reply', id: message.id, ok: true, value: encodeWorkerJson(value) })
}
} catch (error: unknown) {
sendReply({ type: 'reply', id: message.id, ok: false, message: messageOf(error) })
}
})()
}
/* jscpd:ignore-end */
const onMessage = (raw: unknown): void => {
if (settled) return
const message = parseRunnerMessage(raw)
if (message === undefined) return
if (message.type === 'log') {
if (!output.admit(message.text, logs)) finish(output.limit([...logs, message.text]))
return
}
if (message.type === 'output-limit') {
finish(output.limit(logs))
return
}
if (message.type === 'call') {
onCall(message)
return
}
if (message.error !== undefined) {
finish(() => output.failure(logs, message.error as CodeRunFailure))
} else if (message.value === undefined) {
finish(() => output.success(logs))
} else {
const value = decodeWorkerJson(message.value)
if (value === undefined) finish(() => output.failure(logs, { kind: 'invalid-output', message: 'program completion must be lossless JSON' }))
else finish(() => output.success(logs, value))
}
}
stdout.on('data', (chunk: Buffer) => {
if (settled) return
try {
for (const frame of decoder.push(chunk.toString('utf8'))) onMessage(frame)
} catch (error: unknown) {
finish(() => output.failure(logs, { kind: 'worker-exit', message: `E2B runtime bridge failed: ${messageOf(error)}` }))
}
})
stdout.on('error', (error: Error) => {
finish(() => output.failure(logs, { kind: 'worker-exit', message: `E2B runtime stdout failed: ${error.message}` }))
})
stdin.on('error', (error: Error) => {
finish(() => output.failure(logs, { kind: 'worker-exit', message: `E2B runtime stdin failed: ${error.message}` }))
})
void handle.done.then(
() => {
if (!settled) {
const stderr = handle.collected.stderr?.readFrom(0).text.trim()
finish(() => output.failure(logs, { kind: 'worker-exit', message: stderr === undefined || stderr === '' ? 'E2B runtime exited before completing' : `E2B runtime exited before completing: ${stderr}` }))
}
},
(error: unknown) => {
finish(() => output.failure(logs, { kind: 'worker-exit', message: `E2B runtime spawn failed: ${messageOf(error)}` }))
},
)
const onAbort = (): void => {
finish(() => output.failure(logs, { kind: 'abort', message: String(request.signal?.reason) }))
}
request.signal?.addEventListener('abort', onAbort, { once: true })
wallTimer.current = setTimeout(() => {
finish(() => output.failure(logs, { kind: 'timeout', message: `wall-clock ceiling reached (${this.config.maxWallMs}ms)` }))
}, this.config.maxWallMs)
this.live.add(live)
if (request.signal?.aborted === true) {
onAbort()
return
}
sendReply({
type: 'boot',
code,
namespaces: [...bindings].map(([global, namespace]) => ({
global,
names: Object.keys(namespace.functions),
...namespace.errorClass === undefined ? {} : { errorClass: namespace.errorClass },
})),
computeMs: this.config.computeMs,
maxOutputBytes: this.config.maxOutputBytes,
maxOldGenerationSizeMb: this.config.maxOldGenerationSizeMb,
})
})
}
/* jscpd:ignore-start -- Code-runtime backends share the service lifecycle but own different child identities. */
private async teardown(): Promise<void> {
this.disposed = true
const runs = [...this.live]
for (const run of runs) run.settle({ kind: 'abort', message: 'runtime disposed' })
await Promise.all(runs.map(run => run.finished))
}
/* jscpd:ignore-end */
}
export default E2BCodeRuntime
@@ -0,0 +1,20 @@
/** Package-owned invariant companion for `@deepseek-ai/dsh-code-runtime-e2b`. */
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-code-runtime-e2b'
/** Cordis companion plugin name. */
export const name = 'code-runtime-e2b-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** No runtime invariant: the service owns every one-shot remote run. */
const install: InvariantInstaller = () => {}
/** Register this package's invariant companion. */
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */
@@ -0,0 +1,460 @@
/** Dependency-free remote code runner installed inside the E2B sandbox. */
/** Node program that runs one model program in a fresh remote worker thread. */
export const CODE_RUNNER_SOURCE = String.raw`import { Buffer } from 'node:buffer'
import { inspect } from 'node:util'
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads'
import { createInterface } from 'node:readline'
const emitFrame = message => {
process.stdout.write(Buffer.from(JSON.stringify(message)).toString('base64') + '\n')
}
const parseFrame = line => JSON.parse(Buffer.from(line, 'base64').toString('utf8'))
if (isMainThread) {
const input = createInterface({ input: process.stdin, crlfDelay: Infinity })
let worker
let finished = false
let computeTimer
const finish = message => {
if (finished) return
finished = true
clearInterval(computeTimer)
emitFrame(message)
const current = worker
worker = undefined
Promise.resolve(current ? current.terminate() : undefined).finally(() => {
input.close()
process.stdin.destroy()
})
}
input.on('line', line => {
let message
try {
message = parseFrame(line)
} catch (error) {
process.stderr.write('code-runtime-e2b frame error: ' + String(error) + '\n')
finish({ type: 'done', error: { kind: 'worker-exit', message: 'remote runner received a malformed frame' } })
return
}
if (!worker) {
if (!message || message.type !== 'boot' || typeof message.code !== 'string' || !Array.isArray(message.namespaces)) {
finish({ type: 'done', error: { kind: 'worker-exit', message: 'remote runner received an invalid boot frame' } })
return
}
worker = new Worker(new URL(import.meta.url), {
workerData: message,
env: {},
stdout: true,
stderr: true,
resourceLimits: { maxOldGenerationSizeMb: message.maxOldGenerationSizeMb },
})
worker.stdout.on('data', data => { emitFrame({ type: 'log', text: data.toString('utf8') }) })
worker.stderr.on('data', data => { emitFrame({ type: 'log', text: data.toString('utf8') }) })
worker.on('message', raw => {
if (!raw || typeof raw !== 'object') return
if (raw.type === 'call' && typeof raw.id === 'number' && typeof raw.global === 'string' && typeof raw.name === 'string' && Array.isArray(raw.args)) {
emitFrame({ type: 'call', id: raw.id, global: raw.global, name: raw.name, args: raw.args })
} else if (raw.type === 'log' && typeof raw.text === 'string') {
emitFrame({ type: 'log', text: raw.text })
} else if (raw.type === 'output-limit') {
finish({ type: 'output-limit' })
} else if (raw.type === 'done') {
if (raw.error && typeof raw.error === 'object' && typeof raw.error.kind === 'string' && typeof raw.error.message === 'string') {
finish({ type: 'done', error: { kind: raw.error.kind, message: raw.error.message } })
} else if (raw.value === undefined || Array.isArray(raw.value)) {
finish({ type: 'done', ...(raw.value === undefined ? {} : { value: raw.value }) })
}
}
})
worker.on('error', error => {
finish({ type: 'done', error: { kind: 'worker-exit', message: 'remote worker error: ' + error.message } })
})
worker.on('exit', code => {
if (!finished) finish({ type: 'done', error: { kind: 'worker-exit', message: 'remote worker exited with code ' + code + ' before completing' } })
})
computeTimer = setInterval(() => {
if (!worker) return
if (worker.performance.eventLoopUtilization().active > message.computeMs) {
finish({ type: 'done', error: { kind: 'timeout', message: 'compute budget exhausted (' + message.computeMs + 'ms busy)' } })
}
}, 25)
return
}
if (message && message.type === 'reply' && typeof message.id === 'number' && typeof message.ok === 'boolean') {
worker.postMessage(message.ok
? { type: 'reply', id: message.id, ok: true, value: message.value }
: { type: 'reply', id: message.id, ok: false, message: String(message.message) })
}
})
input.on('close', () => { if (worker && !finished) void worker.terminate() })
} else {
const port = parentPort
if (!port) throw new Error('remote worker requires parentPort')
const CapturedError = Error
const ArrayIsArray = Array.isArray
const ArrayPrototype = Array.prototype
const ObjectPrototype = Object.prototype
const ObjectCreate = Object.create
const ObjectDefineProperty = Object.defineProperty
const ObjectGetPrototypeOf = Object.getPrototypeOf
const ObjectHasOwn = Object.hasOwn
const ObjectKeys = Object.keys
const ObjectIs = Object.is
const ObjectPropertyIsEnumerable = Object.prototype.propertyIsEnumerable
const ReflectOwnKeys = Reflect.ownKeys
const ReflectApply = Reflect.apply
const NumberIsFinite = Number.isFinite
const NumberIsSafeInteger = Number.isSafeInteger
const PromiseCtor = Promise
const PromiseReject = Promise.reject
const QueueMicrotask = queueMicrotask
const BufferByteLength = Buffer.byteLength
const SetCtor = Set
const SetAdd = Set.prototype.add
const SetDelete = Set.prototype.delete
const SetHas = Set.prototype.has
const MapDelete = Map.prototype.delete
const MapGet = Map.prototype.get
const MapSet = Map.prototype.set
const ArrayJoin = Array.prototype.join
const ArrayPop = Array.prototype.pop
const StringCharCodeAt = String.prototype.charCodeAt
const StringSlice = String.prototype.slice
const JSONStringify = JSON.stringify
const StringValue = String
const define = (target, key, value) => {
const descriptor = ObjectCreate(null)
descriptor.value = value
descriptor.enumerable = true
descriptor.configurable = true
descriptor.writable = true
ObjectDefineProperty(target, key, descriptor)
}
const append = (target, value) => { define(target, target.length, value) }
const pop = target => ReflectApply(ArrayPop, target, [])
const setAdd = (target, value) => { ReflectApply(SetAdd, target, [value]) }
const setDelete = (target, value) => { ReflectApply(SetDelete, target, [value]) }
const setHas = (target, value) => ReflectApply(SetHas, target, [value])
const mapDelete = (target, key) => { ReflectApply(MapDelete, target, [key]) }
const mapGet = (target, key) => ReflectApply(MapGet, target, [key])
const mapSet = (target, key, value) => { ReflectApply(MapSet, target, [key, value]) }
const plainObject = value => {
const prototype = ObjectGetPrototypeOf(value)
return prototype === null || prototype === ObjectPrototype
}
const ownEnumerableStringKeys = value => {
const keys = ReflectOwnKeys(value)
for (let index = 0; index < keys.length; index++) {
const key = keys[index]
if (typeof key !== 'string' || !ReflectApply(ObjectPropertyIsEnumerable, value, [key])) return undefined
}
return keys
}
const assign = (destination, value) => {
if (destination.kind === 'root') destination.holder.value = value
else define(destination.target, destination.key, value)
}
const snapshot = input => {
const active = new SetCtor()
const holder = ObjectCreate(null)
const tasks = [{ kind: 'visit', value: input, destination: { kind: 'root', holder } }]
while (tasks.length) {
const task = pop(tasks)
if (task.kind === 'leave') { setDelete(active, task.source); continue }
const candidate = task.value
if (candidate === null || typeof candidate === 'boolean' || typeof candidate === 'string') {
assign(task.destination, candidate); continue
}
if (typeof candidate === 'number') {
if (!NumberIsFinite(candidate) || ObjectIs(candidate, -0)) return undefined
assign(task.destination, candidate); continue
}
if (typeof candidate !== 'object' || setHas(active, candidate)) return undefined
if (ArrayIsArray(candidate)) {
if (ObjectGetPrototypeOf(candidate) !== ArrayPrototype || ReflectOwnKeys(candidate).length !== candidate.length + 1) return undefined
const target = []
assign(task.destination, target)
setAdd(active, candidate)
append(tasks, { kind: 'leave', source: candidate })
for (let index = candidate.length - 1; index >= 0; index--) {
if (!ObjectHasOwn(candidate, index)) return undefined
append(tasks, { kind: 'visit', value: candidate[index], destination: { kind: 'slot', target, key: index } })
}
continue
}
if (!plainObject(candidate)) return undefined
const keys = ownEnumerableStringKeys(candidate)
if (!keys) return undefined
const target = {}
assign(task.destination, target)
setAdd(active, candidate)
append(tasks, { kind: 'leave', source: candidate })
for (let index = keys.length - 1; index >= 0; index--) {
const key = keys[index]
append(tasks, { kind: 'visit', value: candidate[key], destination: { kind: 'slot', target, key } })
}
}
return holder.value
}
const encodeWire = value => {
const wire = []
const pending = [value]
while (pending.length) {
const current = pop(pending)
if (current === null || typeof current === 'boolean' || typeof current === 'number' || typeof current === 'string') {
append(wire, current); continue
}
if (ArrayIsArray(current)) {
append(wire, { kind: 'array', length: current.length })
for (let index = current.length - 1; index >= 0; index--) append(pending, current[index])
} else {
const keys = ObjectKeys(current)
append(wire, { kind: 'object', keys })
for (let index = keys.length - 1; index >= 0; index--) append(pending, current[keys[index]])
}
}
return wire
}
const decodeWire = wire => {
if (!ArrayIsArray(wire) || wire.length === 0) return undefined
const frames = []
let root
let assigned = false
const attach = value => {
const parent = frames[frames.length - 1]
if (!parent) {
if (assigned) return false
root = value; assigned = true; return true
}
if (parent.kind === 'array') append(parent.target, value)
else define(parent.target, parent.keys[parent.index], value)
parent.index += 1
return true
}
for (let tokenIndex = 0; tokenIndex < wire.length; tokenIndex++) {
const token = wire[tokenIndex]
let value
let frame
if (token === null || typeof token === 'boolean' || typeof token === 'string') value = token
else if (typeof token === 'number') {
if (!NumberIsFinite(token) || ObjectIs(token, -0)) return undefined
value = token
} else {
if (!plainObject(token)) return undefined
const keys = ownEnumerableStringKeys(token)
if (!keys || keys.length !== 2 || keys[0] !== 'kind') return undefined
if (token.kind === 'array' && keys[1] === 'length' && NumberIsSafeInteger(token.length) && token.length >= 0) {
value = []
if (token.length > wire.length - tokenIndex - 1) return undefined
if (token.length) frame = { kind: 'array', target: value, length: token.length, index: 0 }
} else if (token.kind === 'object' && keys[1] === 'keys' && ArrayIsArray(token.keys)) {
const unique = new SetCtor()
const objectKeys = []
for (const key of token.keys) {
if (typeof key !== 'string' || setHas(unique, key)) return undefined
setAdd(unique, key); append(objectKeys, key)
}
if (objectKeys.length > wire.length - tokenIndex - 1) return undefined
value = {}
if (objectKeys.length) frame = { kind: 'object', target: value, keys: objectKeys, index: 0 }
} else return undefined
}
if (!attach(value)) return undefined
if (frame) append(frames, frame)
while (frames.length) {
const current = frames[frames.length - 1]
const length = current.kind === 'array' ? current.length : current.keys.length
if (current.index < length) break
pop(frames)
}
}
return frames.length === 0 ? root : undefined
}
const byteLength = text => ReflectApply(BufferByteLength, Buffer, [text])
const jsonStringBytes = text => byteLength(JSONStringify(text))
const jsonValueBytes = value => {
let bytes = 0
const tasks = [{ kind: 'value', value }]
while (tasks.length) {
const task = pop(tasks)
if (task.kind === 'separator') { bytes += 1; continue }
if (task.kind === 'key') { bytes += jsonStringBytes(task.value) + 1; continue }
const current = task.value
if (current === null) bytes += 4
else if (typeof current === 'string') bytes += jsonStringBytes(current)
else if (typeof current === 'number' || typeof current === 'boolean') bytes += byteLength(StringValue(current))
else if (ArrayIsArray(current)) {
bytes += 2
for (let index = current.length - 1; index >= 0; index--) {
append(tasks, { kind: 'value', value: current[index] })
if (index > 0) append(tasks, { kind: 'separator' })
}
} else {
bytes += 2
const keys = ObjectKeys(current)
for (let index = keys.length - 1; index >= 0; index--) {
const key = keys[index]
append(tasks, { kind: 'value', value: current[key] })
append(tasks, { kind: 'key', value: key })
if (index > 0) append(tasks, { kind: 'separator' })
}
}
}
return bytes
}
const truncate = (text, available) => {
if (available < 2) return ''
let result = ''
let bytes = 2
let index = 0
while (index < text.length) {
const first = ReflectApply(StringCharCodeAt, text, [index])
let end = index + 1
if (first >= 0xd800 && first <= 0xdbff && end < text.length) {
const second = ReflectApply(StringCharCodeAt, text, [end])
if (second >= 0xdc00 && second <= 0xdfff) end += 1
}
const character = ReflectApply(StringSlice, text, [index, end])
const cost = jsonStringBytes(character) - 2
if (bytes + cost > available) break
bytes += cost
result += character
index = end
}
return result
}
let logBytes = 2
let logEntries = 0
let limited = false
const pushLog = text => {
if (limited) return
const separator = logEntries > 0 ? 1 : 0
const available = workerData.maxOutputBytes - logBytes - separator
const cost = jsonStringBytes(text)
if (cost > available) {
const prefix = truncate(text, available)
if (prefix) {
logBytes += jsonStringBytes(prefix) + separator
logEntries += 1
port.postMessage({ type: 'log', text: prefix })
}
limited = true
port.postMessage({ type: 'output-limit' })
return
}
logBytes += cost + separator
logEntries += 1
port.postMessage({ type: 'log', text })
}
const originalStdout = process.stdout.write
const originalStderr = process.stderr.write
process.stdout.write = (chunk, ...rest) => {
pushLog(typeof chunk === 'string' ? chunk : StringValue(chunk))
let callback
for (let index = 0; index < rest.length; index++) {
if (typeof rest[index] === 'function') { callback = rest[index]; break }
}
if (callback) QueueMicrotask(() => { callback(null) })
return true
}
process.stderr.write = process.stdout.write
const consoleShim = ObjectCreate(null)
for (const level of ['log', 'info', 'warn', 'error', 'debug']) {
define(consoleShim, level, (...args) => {
const rendered = []
for (let index = 0; index < args.length; index++) {
const value = args[index]
append(rendered, typeof value === 'string' ? value : inspect(value, { depth: 4, maxArrayLength: 100, maxStringLength: 10000 }))
}
pushLog(ReflectApply(ArrayJoin, rendered, [' ']))
})
}
const pending = new Map()
let nextId = 1
const errorClasses = new Map()
for (const namespace of workerData.namespaces) {
if (!namespace.errorClass) continue
const descriptor = namespace.errorClass
mapSet(errorClasses, namespace.global, class BindingCallError extends CapturedError {
constructor(memberName, message) {
super(message)
ObjectDefineProperty(this, 'name', { value: descriptor.name, enumerable: true })
ObjectDefineProperty(this, descriptor.memberNameProperty, { value: memberName, enumerable: true })
}
})
}
port.on('message', message => {
if (!message || message.type !== 'reply' || typeof message.id !== 'number') return
const entry = mapGet(pending, message.id)
if (!entry) return
mapDelete(pending, message.id)
if (!message.ok) { entry.reject(new CapturedError(StringValue(message.message))); return }
const value = decodeWire(message.value)
if (value === undefined) entry.reject(new CapturedError('binding resolution must be lossless JSON'))
else entry.resolve(value)
})
const namespaces = workerData.namespaces.map(namespace => {
const target = ObjectCreate(null)
const ErrorClass = mapGet(errorClasses, namespace.global)
for (const name of namespace.names) {
define(target, name, args => {
const detached = snapshot(args)
if (detached === undefined) {
return ReflectApply(PromiseReject, PromiseCtor, [ErrorClass ? new ErrorClass(name, 'binding arguments must be lossless JSON') : new CapturedError('binding arguments must be lossless JSON')])
}
return new PromiseCtor((resolve, reject) => {
const id = nextId++
mapSet(pending, id, {
resolve,
reject: error => { reject(ErrorClass ? new ErrorClass(name, error.message) : error) },
})
port.postMessage({ type: 'call', id, global: namespace.global, name, args: encodeWire(detached) })
})
})
}
return target
})
const errorClassNames = []
const errorClassValues = []
for (const namespace of workerData.namespaces) {
if (!namespace.errorClass) continue
append(errorClassNames, namespace.errorClass.name)
append(errorClassValues, mapGet(errorClasses, namespace.global))
}
const AsyncFunction = ObjectGetPrototypeOf(async function () {}).constructor
try {
const fn = new AsyncFunction(...workerData.namespaces.map(value => value.global), ...errorClassNames, 'console', '"use strict";\n' + workerData.code)
const value = await fn(...namespaces, ...errorClassValues, consoleShim)
if (!limited) {
if (value === undefined) port.postMessage({ type: 'done' })
else {
const detached = snapshot(value)
if (detached === undefined) {
const message = 'program completion must be lossless JSON'
if (jsonStringBytes(message) > workerData.maxOutputBytes - logBytes) port.postMessage({ type: 'output-limit' })
else port.postMessage({ type: 'done', error: { kind: 'invalid-output', message } })
} else if (jsonValueBytes(detached) > workerData.maxOutputBytes - logBytes) {
port.postMessage({ type: 'output-limit' })
} else {
port.postMessage({ type: 'done', value: encodeWire(detached) })
}
}
}
} catch (error) {
if (!limited) {
let message
try { message = error instanceof CapturedError ? error.stack || error.message : StringValue(error) }
catch { message = 'program threw an unrenderable value' }
if (jsonStringBytes(message) > workerData.maxOutputBytes - logBytes) port.postMessage({ type: 'output-limit' })
else port.postMessage({ type: 'done', error: { kind: 'exception', message } })
}
} finally {
process.stdout.write = originalStdout
process.stderr.write = originalStderr
}
}
`
@@ -0,0 +1,460 @@
import { PassThrough, Writable } from 'node:stream'
import { Context } from 'cordis'
import { describe, expect, it, vi } from 'vitest'
import type { Sandbox } from '@deepseek-ai/dsh-e2b'
import {
E2BFrameDecoder,
encodeE2BFrame,
} from '@deepseek-ai/dsh-e2b'
import type E2BSandboxService from '@deepseek-ai/dsh-e2b'
import type {
SubprocessHandle,
SubprocessOutcome,
SubprocessSpawnSpec,
} from '@deepseek-ai/dsh-subprocess'
import E2BSubprocessService from '@deepseek-ai/dsh-subprocess-e2b'
import {
encodeWorkerJson,
} from '@deepseek-ai/dsh-code-runtime-worker'
import E2BCodeRuntime from '@deepseek-ai/dsh-code-runtime-e2b'
import * as E2BCodeRuntimeInvariant from '../src/invariant.ts'
import { CODE_RUNNER_SOURCE } from '../src/runner-source.ts'
import InvariantService from '@deepseek-ai/dsh-invariants'
class FakeHandle implements SubprocessHandle {
readonly pid = 123
readonly stdin: Writable | undefined
readonly stdout: PassThrough | undefined
readonly stderr = undefined
readonly collected: SubprocessHandle['collected']
readonly done: Promise<SubprocessOutcome>
readonly writes: unknown[] = []
readonly result = Promise.withResolvers<SubprocessOutcome>()
terminated = 0
waitCalls = 0
private readonly decoder = new E2BFrameDecoder(10_000_000)
private readonly waitError: Error | undefined
private settled = false
constructor(
private readonly onMessage: (message: unknown, handle: FakeHandle) => void = () => {},
options: { stdin?: boolean; stdout?: boolean; stderr?: string; writeError?: Error; waitError?: Error } = {},
) {
this.waitError = options.waitError
this.stdin = options.stdin === false
? undefined
: options.writeError === undefined
? new PassThrough()
: new Writable({ write: (_chunk, _encoding, callback) => { callback(options.writeError) } })
this.stdout = options.stdout === false ? undefined : new PassThrough()
this.collected = options.stderr === undefined
? {}
: { stderr: { readFrom: () => ({ text: options.stderr as string, nextOffset: 0, lossy: false }) } }
this.done = this.result.promise
this.stdin?.on('data', (chunk: Buffer) => {
for (const message of this.decoder.push(chunk.toString('ascii'))) {
this.writes.push(message)
this.onMessage(message, this)
}
})
}
emit(message: unknown): void {
this.stdout?.write(encodeE2BFrame(message))
}
emitRaw(text: string): void {
this.stdout?.write(text)
}
exit(outcome: SubprocessOutcome = { exitCode: 0, signal: null }): void {
if (this.settled) return
this.settled = true
this.stdout?.end()
this.result.resolve(outcome)
}
crash(error: unknown): void {
if (this.settled) return
this.settled = true
this.stdout?.end()
this.result.reject(error)
}
terminate(): void {
this.terminated += 1
this.exit({ exitCode: null, signal: 'SIGTERM' })
}
async waitForExit(): Promise<boolean> {
this.waitCalls += 1
if (this.waitError !== undefined) throw this.waitError
return true
}
}
interface RuntimeFixture {
ctx: Context
fiber: Awaited<ReturnType<Context['plugin']>>
runtime: E2BCodeRuntime
sandbox: Sandbox
spawn: ReturnType<typeof vi.fn<(spec: SubprocessSpawnSpec) => SubprocessHandle>>
write: ReturnType<typeof vi.fn>
run: ReturnType<typeof vi.fn>
}
async function setup(
handles: FakeHandle[] = [],
config: Record<string, number> = {},
sandboxOverrides: Partial<Sandbox> = {},
getSandbox?: () => Promise<Sandbox>,
): Promise<RuntimeFixture> {
const write = vi.fn().mockResolvedValue([])
const run = vi.fn().mockImplementation(async (command: string) => ({
exitCode: 0,
stdout: command.startsWith('command -v') ? '/usr/bin/node\n' : '',
stderr: '',
}))
const sandbox = {
files: { write },
commands: { run },
...sandboxOverrides,
} as unknown as Sandbox
const e2b = {
cwd: '/workspace',
runtimeRoot: '/workspace/.dsh-e2b',
getSandbox: getSandbox ?? (async () => sandbox),
} as unknown as E2BSandboxService
const spawn = vi.fn<(spec: SubprocessSpawnSpec) => SubprocessHandle>(() => {
const handle = handles.shift()
if (handle === undefined) throw new Error('no fake handle queued')
return handle
})
const subprocess = Object.create(E2BSubprocessService.prototype) as E2BSubprocessService
Object.defineProperty(subprocess, 'spawn', { value: spawn })
const ctx = new Context()
ctx.provide('e2b', e2b)
ctx.provide('subprocess', subprocess)
const fiber = await ctx.plugin(E2BCodeRuntime, config)
return { ctx, fiber, runtime: ctx.codeRuntime as E2BCodeRuntime, sandbox, spawn, write, run }
}
function request(program = 'return 1') {
return { program, bindings: [] }
}
describe('E2BCodeRuntime', () => {
it('prepares the remote runner and returns logs and a lossless completion', async () => {
const handle = new FakeHandle((message, current) => {
if ((message as { type?: string }).type !== 'boot') return
current.emit({ type: 'log', text: 'remote 你好' })
current.emitRaw(
encodeE2BFrame({ type: 'done', value: encodeWorkerJson({ answer: 42 }) })
+ encodeE2BFrame({ type: 'log', text: 'ignored after done' }),
)
current.emit({ type: 'log', text: 'also ignored after done' })
})
const fixture = await setup([handle])
await expect(fixture.runtime.run(request('const answer: number = 42; return { answer }')))
.resolves.toEqual({ logs: ['remote 你好'], value: { answer: 42 } })
expect(fixture.runtime.language).toBe('typescript')
expect(fixture.runtime.isolation).toBe('container')
expect(fixture.write).toHaveBeenCalledWith([{ path: '/workspace/.dsh-e2b/code-runtime-runner.mjs', data: CODE_RUNNER_SOURCE }])
expect(fixture.run).toHaveBeenCalledWith("chmod 600 -- '/workspace/.dsh-e2b/code-runtime-runner.mjs'")
expect(fixture.spawn).toHaveBeenCalledWith(expect.objectContaining({
argv: ['/usr/bin/node', '/workspace/.dsh-e2b/code-runtime-runner.mjs'],
cwd: '/workspace',
env: {},
}))
expect(handle.terminated).toBe(1)
expect(handle.waitCalls).toBe(1)
await fixture.fiber.dispose()
})
it('bridges binding success, host rejection, unknown members, and invalid values', async () => {
const replies: unknown[] = []
const handle = new FakeHandle((message, current) => {
const record = message as { type?: string; id?: number; ok?: boolean }
if (record.type === 'boot') {
current.emit({ type: 'call', id: 1, global: 'bridge', name: 'double', args: encodeWorkerJson({ value: 4 }) })
current.emit({ type: 'call', id: 2, global: 'bridge', name: 'fail', args: encodeWorkerJson(null) })
current.emit({ type: 'call', id: 3, global: 'bridge', name: 'missing', args: encodeWorkerJson(null) })
current.emit({ type: 'call', id: 4, global: 'bridge', name: 'double', args: [] })
current.emit({ type: 'call', id: 5, global: 'bridge', name: 'invalid', args: encodeWorkerJson(null) })
current.emit({ type: 'call', id: 6, global: 'bridge', name: 'throwing', args: encodeWorkerJson(null) })
current.emit({ type: 'call', id: 1, global: 'bridge', name: 'double', args: encodeWorkerJson({ value: 99 }) })
return
}
if (record.type === 'reply') {
replies.push(message)
if (replies.length === 6) current.emit({ type: 'done', value: encodeWorkerJson('done') })
}
})
const fixture = await setup([handle])
const result = await fixture.runtime.run({
program: 'return await bridge.double({ value: 4 })',
bindings: [
{
global: 'bridge',
errorClass: { name: 'BridgeError', memberNameProperty: 'member' },
functions: {
double: async args => (args as { value: number }).value * 2,
fail: async () => { throw 'nope' },
invalid: (async () => undefined) as never,
throwing: async () => Object.defineProperty({}, 'value', {
enumerable: true,
get: () => { throw new Error('getter failed') },
}),
},
},
{ global: 'plain', functions: {} },
],
})
expect(result).toEqual({ logs: [], value: 'done' })
expect(replies.sort((left, right) => (left as { id: number }).id - (right as { id: number }).id)).toEqual([
{ type: 'reply', id: 1, ok: true, value: encodeWorkerJson(8) },
{ type: 'reply', id: 2, ok: false, message: 'nope' },
{ type: 'reply', id: 3, ok: false, message: 'unknown binding "bridge.missing"' },
{ type: 'reply', id: 4, ok: false, message: 'binding arguments must be lossless JSON' },
{ type: 'reply', id: 5, ok: false, message: 'binding resolution must be lossless JSON' },
{ type: 'reply', id: 6, ok: false, message: 'binding resolution must be lossless JSON' },
])
await fixture.fiber.dispose()
})
it('ignores malformed runner traffic and classifies terminal runner messages', async () => {
const ignored = [
null, 1, {}, { type: 'log' }, { type: 'call' },
{ type: 'call', id: 0, global: 'x', name: 'y', args: [] },
{ type: 'call', id: 1, global: 1, name: 'y', args: [] },
{ type: 'call', id: 1, global: 'x', name: 1, args: [] },
{ type: 'call', id: 1, global: 'x', name: 'y', args: {} },
{ type: 'done', error: null },
{ type: 'done', error: { kind: 'invented', message: 'x' } },
{ type: 'done', error: { kind: 'exception', message: 1 } },
]
const handles = [
new FakeHandle((message, current) => {
if ((message as { type?: string }).type !== 'boot') return
for (const item of ignored) current.emit(item)
current.emit({ type: 'done' })
}),
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emit({ type: 'done', error: { kind: 'exception', message: 'boom' } })
}),
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emit({ type: 'done', value: [] })
}),
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emit({ type: 'output-limit' })
}),
]
const fixture = await setup(handles, { maxOutputBytes: 64, maxFrameBytes: 128 })
await expect(fixture.runtime.run(request())).resolves.toEqual({ logs: [] })
await expect(fixture.runtime.run(request())).resolves.toEqual({ logs: [], error: { kind: 'exception', message: 'boom' } })
await expect(fixture.runtime.run(request())).resolves.toEqual({ logs: [], error: { kind: 'invalid-output', message: 'program completion must be lossless JSON' } })
await expect(fixture.runtime.run(request())).resolves.toEqual({ logs: [], error: { kind: 'output-limit', message: 'outer output exceeded 64 bytes' } })
await fixture.fiber.dispose()
})
it('enforces the host output ledger and catches malformed bridge output', async () => {
const handles = [
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emit({ type: 'log', text: 'x'.repeat(1_000) })
}),
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emitRaw('not-base64\n')
}),
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emitRaw('é')
}),
new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.stdout?.emit('error', new Error('stdout broke'))
}),
]
const fixture = await setup(handles, { maxOutputBytes: 128, maxFrameBytes: 4_096 })
expect((await fixture.runtime.run(request())).error?.kind).toBe('output-limit')
const malformed = (await fixture.runtime.run(request())).error
expect(malformed?.kind).toBe('worker-exit')
expect(malformed?.message).toContain('bridge failed')
expect((await fixture.runtime.run(request())).error?.message).toContain('non-ASCII')
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime stdout failed: stdout broke' })
await fixture.fiber.dispose()
})
it('contains stdin errors, process exits, spawn failures, and missing pipes', async () => {
const writeError = new FakeHandle(() => {}, { writeError: new Error('write callback broke') })
const stdinError = new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.stdin?.emit('error', new Error('stdin broke'))
})
const earlyExit = new FakeHandle(() => {}, { stderr: 'remote diagnostic' })
const quietExit = new FakeHandle()
const emptyStderrExit = new FakeHandle(() => {}, { stderr: '' })
const spawnFailure = new FakeHandle()
const missingStdin = new FakeHandle(() => {}, { stdin: false })
const missingStdout = new FakeHandle(() => {}, { stdout: false, waitError: new Error('missing-stream process query failed') })
const truncated = new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') {
current.emitRaw('YQ==')
setImmediate(() => { current.exit() })
}
})
const cleanupFailure = new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') current.emit({ type: 'done' })
}, { waitError: new Error('process query failed') })
const fixture = await setup([
writeError, stdinError, earlyExit, quietExit, emptyStderrExit,
spawnFailure, missingStdin, missingStdout, truncated, cleanupFailure,
])
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime bridge write failed: write callback broke' })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime stdin failed: stdin broke' })
setImmediate(() => { earlyExit.exit() })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime exited before completing: remote diagnostic' })
setImmediate(() => { quietExit.exit() })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime exited before completing' })
setImmediate(() => { emptyStderrExit.exit() })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime exited before completing' })
setImmediate(() => { spawnFailure.crash('spawn rejected') })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime spawn failed: spawn rejected' })
expect((await fixture.runtime.run(request())).error?.message).toContain('dropped a piped runtime stream')
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime cleanup failed: missing-stream process query failed' })
expect(missingStdin.terminated).toBe(1)
expect(missingStdin.waitCalls).toBe(1)
expect(missingStdout.terminated).toBe(1)
expect(missingStdout.waitCalls).toBe(1)
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B frame stream ended mid-frame' })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'worker-exit', message: 'E2B runtime cleanup failed: process query failed' })
await fixture.fiber.dispose()
})
it('reports wall timeout, abort, pre-abort, type-strip failure, and disposal', async () => {
const timeout = new FakeHandle()
const abort = new FakeHandle()
const disposing = new FakeHandle()
const fixture = await setup([timeout, abort, disposing], { maxWallMs: 20 })
expect((await fixture.runtime.run(request())).error).toEqual({ kind: 'timeout', message: 'wall-clock ceiling reached (20ms)' })
const controller = new AbortController()
const aborting = fixture.runtime.run({ ...request(), signal: controller.signal })
controller.abort('stop')
expect((await aborting).error).toEqual({ kind: 'abort', message: 'stop' })
expect((await fixture.runtime.run({ ...request(), signal: AbortSignal.abort('already') })).error)
.toEqual({ kind: 'abort', message: 'already' })
expect((await fixture.runtime.run(request('enum E { A }'))).error?.kind).toBe('exception')
const live = fixture.runtime.run(request())
await new Promise(resolve => setImmediate(resolve))
await fixture.fiber.dispose()
expect((await live).error).toEqual({ kind: 'abort', message: 'runtime disposed' })
await expect(fixture.runtime.run(request())).rejects.toThrow('after disposal')
})
it('drops binding replies that settle after abort', async () => {
const controller = new AbortController()
const resolution = Promise.withResolvers<string>()
const invoked = Promise.withResolvers<undefined>()
const handle = new FakeHandle((message, current) => {
if ((message as { type?: string }).type === 'boot') {
current.emit({ type: 'call', id: 1, global: 'bridge', name: 'late', args: encodeWorkerJson(null) })
}
})
const fixture = await setup([handle])
const running = fixture.runtime.run({
program: 'return await bridge.late(null)',
bindings: [{
global: 'bridge',
functions: {
late: async () => {
invoked.resolve(undefined)
return await resolution.promise
},
},
}],
signal: controller.signal,
})
await invoked.promise
controller.abort('stop')
expect((await running).error).toEqual({ kind: 'abort', message: 'stop' })
resolution.resolve('late')
await new Promise(resolve => setImmediate(resolve))
expect(handle.writes).toHaveLength(1)
await fixture.fiber.dispose()
})
it('validates binding and runtime configuration before remote execution', async () => {
const fixture = await setup([])
const invalidRequests = [
{ global: 'not-valid!', functions: {} },
{ global: 'await', functions: {} },
{ global: 'console', functions: {} },
{ global: 'same', functions: {} },
{ global: 'same', functions: {} },
{ global: 'ok', functions: {}, errorClass: { name: 'not-valid!', memberNameProperty: 'member' } },
{ global: 'ok', functions: {}, errorClass: { name: 'await', memberNameProperty: 'member' } },
{ global: 'Clash', functions: {}, errorClass: { name: 'Clash', memberNameProperty: 'member' } },
{ global: 'one', functions: {}, errorClass: { name: 'Err', memberNameProperty: 'member' } },
{ global: 'two', functions: {}, errorClass: { name: 'Err', memberNameProperty: 'member' } },
{ global: 'ok', functions: {}, errorClass: { name: 'Err', memberNameProperty: '' } },
{ global: 'ok', functions: {}, errorClass: { name: 'Err', memberNameProperty: 'message' } },
]
for (const bindings of [
[invalidRequests[0]], [invalidRequests[1]], [invalidRequests[2]],
invalidRequests.slice(3, 5), [invalidRequests[5]], [invalidRequests[6]],
[invalidRequests[7]], invalidRequests.slice(8, 10), [invalidRequests[10]], [invalidRequests[11]],
]) {
await expect(fixture.runtime.run({ program: 'return 1', bindings: bindings as never })).rejects.toThrow()
}
await fixture.fiber.dispose()
for (const config of [
{ computeMs: 0 }, { computeMs: 1.5 }, { maxOutputBytes: 3 },
{ maxWallMs: 2_147_483_648 }, { maxFrameBytes: 10, maxOutputBytes: 20 },
]) {
const ctx = new Context()
const subprocess = Object.create(E2BSubprocessService.prototype) as E2BSubprocessService
ctx.provide('e2b', { getSandbox: async () => ({}) } as never)
ctx.provide('subprocess', subprocess)
await expect(ctx.plugin(E2BCodeRuntime, config)).rejects.toThrow()
}
const wrong = new Context()
wrong.provide('e2b', { getSandbox: async () => ({}) } as never)
wrong.provide('subprocess', {} as never)
await expect(wrong.plugin(E2BCodeRuntime, {})).rejects.toThrow('dsh-subprocess-e2b')
})
it('turns asynchronous runtime preparation failure into a run result', async () => {
const sandbox = {
files: { write: vi.fn().mockRejectedValue(new Error('upload failed')) },
commands: { run: vi.fn() },
} as unknown as Sandbox
const fixture = await setup([], {}, sandbox)
expect((await fixture.runtime.run(request())).error).toEqual({
kind: 'worker-exit',
message: 'E2B runtime setup failed: upload failed',
})
await fixture.fiber.dispose()
})
it('returns disposal when remote preparation completes after teardown', async () => {
const gate = Promise.withResolvers<Sandbox>()
const fixture = await setup([], {}, {}, () => gate.promise)
const running = fixture.runtime.run(request())
await (fixture.runtime as unknown as { teardown(): Promise<void> }).teardown()
gate.resolve(fixture.sandbox)
expect((await running).error).toEqual({ kind: 'abort', message: 'runtime disposed' })
await fixture.fiber.dispose()
})
it('registers the package-owned invariant companion', async () => {
const ctx = new Context()
await ctx.plugin(InvariantService, { enabled: true })
const fiber = await ctx.plugin(E2BCodeRuntimeInvariant).await()
await fiber.dispose()
})
})
@@ -0,0 +1,20 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": ["src"],
"references": [
{ "path": "../../../vendor/cosmokit" },
{ "path": "../../../vendor/cordis" },
{ "path": "../../../vendor/schemastery" },
{ "path": "../code-runtime" },
{ "path": "../code-runtime-worker" },
{ "path": "../../e2b/e2b" },
{ "path": "../../core/session" },
{ "path": "../../subprocess/subprocess-e2b" },
{ "path": "../../util/timeout" },
{ "path": "../../support/invariants" }
]
}