refactor(e2b): group remote providers
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user