/** Shared host mechanics for local and subprocess-hosted TypeScript worker runtimes. */ import { stripTypeScriptTypes } from 'node:module' import type { Readable } from 'node:stream' import type { CodeBindingNamespace, CodeJsonValue, CodeRunFailure, CodeRunRequest, CodeRunResult, } from '@deepseek-ai/dsh-code-runtime' import { jsonStringBytesUpTo, jsonValueBytesUpTo, truncateJsonStringBytes } from './output-json.ts' import { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from './worker-json.ts' import type { WorkerJsonWire } from './worker-json.ts' /** Smallest cap that can represent an empty log array and failure message. */ export const MIN_RUNTIME_OUTPUT_BYTES = 4 /** * Resolve after a worker pipe emits queued data or closes during termination. * @param stream - captured worker or child-process pipe. * @returns after no more queued bytes can arrive. */ export function waitForRuntimePipeDrain(stream: Readable): Promise { if (stream.readableEnded || stream.destroyed) return Promise.resolve() return new Promise((resolve) => { const done = (): void => { stream.off('end', done) stream.off('close', done) stream.off('error', done) resolve() } stream.once('end', done) stream.once('close', done) stream.once('error', done) /* v8 ignore next -- termination can win the adjacent listener-registration race. */ if (stream.readableEnded || stream.destroyed) done() }) } const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/ 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']) const STRIP_WRAP = { prefix: 'async function __dsh_program__() {\n', suffix: '\n}' } as const /** One validated binding call received from an isolated worker. */ export interface RuntimeBindingCall { /** Correlation id supplied by the isolated worker. */ readonly id: number /** Injected namespace global. */ readonly global: string /** Declared namespace function. */ readonly name: string /** Untrusted lossless-JSON wire payload. */ readonly args: unknown } /** One host reply to an isolated worker binding call. */ export type RuntimeBindingReply = | { readonly type: 'reply'; readonly id: number; readonly ok: true; readonly value: WorkerJsonWire } | { readonly type: 'reply'; readonly id: number; readonly ok: false; readonly message: string } /** * Render an unknown thrown value without assuming it is an Error. * @param error - thrown or rejected value. * @returns the caller-facing diagnostic text. */ export function runtimeErrorMessage(error: unknown): string { try { return error instanceof Error ? error.message : String(error) } catch { return 'binding rejected with an unrenderable value' } } /** * Strip erasable TypeScript while preserving the program's body coordinates. * @param program - model-written async-function body. * @returns JavaScript source with the wrapper removed. */ export function stripRuntimeProgram(program: string): string { const stripped = stripTypeScriptTypes(STRIP_WRAP.prefix + program + STRIP_WRAP.suffix) return stripped.slice(STRIP_WRAP.prefix.length, stripped.length - STRIP_WRAP.suffix.length) } /** * Validate binding globals and typed-error declarations shared by worker runtimes. * @param request - code-runtime request carrying the namespaces. * @param implementationName - package name used in seam-misuse diagnostics. * @returns namespaces indexed by their injected global. */ export function validateRuntimeBindings( request: CodeRunRequest, implementationName: string, ): Map { const bindings = new Map() for (const namespace of request.bindings) { if (!IDENTIFIER.test(namespace.global) || RESERVED_WORDS.has(namespace.global)) { throw new Error(`${implementationName}: binding global ${JSON.stringify(namespace.global)} is not a usable identifier`) } if (namespace.global === 'console' || bindings.has(namespace.global)) { throw new Error(`${implementationName}: duplicate binding global ${JSON.stringify(namespace.global)}`) } bindings.set(namespace.global, namespace) } const errorClassNames = new Set() 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(`${implementationName}: 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(`${implementationName}: duplicate injected global ${JSON.stringify(descriptor.name)}`) } if (descriptor.memberNameProperty.length === 0 || RESERVED_ERROR_PROPERTIES.has(descriptor.memberNameProperty)) { throw new Error(`${implementationName}: binding error member property ${JSON.stringify(descriptor.memberNameProperty)} is not usable`) } errorClassNames.add(descriptor.name) } return bindings } /** * Resolve one untrusted worker call through a declared host binding. * @param call - parsed call envelope from the isolated worker. * @param bindings - namespaces returned by {@link validateRuntimeBindings}. * @returns a lossless-JSON success or stable rejection reply. */ export async function invokeRuntimeBinding( call: RuntimeBindingCall, bindings: ReadonlyMap, ): Promise { const functions = bindings.get(call.global)?.functions const fn = functions !== undefined && Object.hasOwn(functions, call.name) ? functions[call.name] : undefined if (typeof fn !== 'function') { return { type: 'reply', id: call.id, ok: false, message: `unknown binding ${JSON.stringify(`${call.global}.${call.name}`)}` } } const args = decodeWorkerJson(call.args) if (args === undefined) { return { type: 'reply', id: call.id, ok: false, message: 'binding arguments must be lossless JSON' } } try { const resolved = await fn(args) let value: CodeJsonValue | undefined try { value = snapshotCodeJsonValue(resolved) } catch { value = undefined } if (value === undefined) { return { type: 'reply', id: call.id, ok: false, message: 'binding resolution must be lossless JSON' } } return { type: 'reply', id: call.id, ok: true, value: encodeWorkerJson(value) } } catch (error: unknown) { return { type: 'reply', id: call.id, ok: false, message: runtimeErrorMessage(error) } } } /** One run's combined outer-output ledger; binding values never enter it. */ export class RuntimeOutputLedger { private bytes = 2 private entries = 0 /** @param maxBytes - hard cap for logs plus completion or failure payload. */ constructor(private readonly maxBytes: number) {} /** * Admit one exact log entry. * @param text - candidate log entry. * @param sink - ordered retained log list. * @returns false when the hard cap was crossed. */ admit(text: string, sink: string[]): boolean { const separatorBytes = this.entries > 0 ? 1 : 0 const stringBytes = jsonStringBytesUpTo(text, this.maxBytes - this.bytes - separatorBytes) if (stringBytes === undefined) return false this.bytes += stringBytes + separatorBytes this.entries += 1 sink.push(text) return true } /** * Finalize a successful completion against the combined cap. * @param logs - retained ordered logs. * @param value - optional lossless-JSON completion. * @returns the completion or output-limit result. */ success(logs: string[], value?: CodeJsonValue): CodeRunResult { if (value !== undefined && jsonValueBytesUpTo(value, this.maxBytes - this.bytes) === undefined) return this.limit(logs) return { logs, ...value !== undefined ? { value } : {} } } /** * Finalize one failure diagnostic against the combined cap. * @param logs - retained ordered logs. * @param error - structured runtime failure. * @returns the failure or output-limit result. */ failure(logs: string[], error: CodeRunFailure): CodeRunResult { if (jsonStringBytesUpTo(error.message, this.maxBytes - this.bytes) === undefined) return this.limit(logs) return { logs, error } } /** * Build an explicit output-limit failure with a fitting log prefix. * @param logs - ordered logs observed before the limit. * @returns bounded output-limit result. */ limit(logs: string[]): CodeRunResult { const fullMessage = `outer output exceeded ${this.maxBytes} bytes` const messageBytes = fullMessage.length + 2 const retained: string[] = [] let retainedBytes = 2 const logBudget = this.maxBytes - messageBytes for (const text of logs) { const separatorBytes = retained.length > 0 ? 1 : 0 const availableBytes = logBudget - retainedBytes - separatorBytes const stringBytes = jsonStringBytesUpTo(text, availableBytes) if (stringBytes !== undefined) { retained.push(text) retainedBytes += stringBytes + separatorBytes continue } const prefix = truncateJsonStringBytes(text, availableBytes) if (prefix.length > 0) { const prefixBytes = jsonStringBytesUpTo(prefix, availableBytes) /* v8 ignore next -- truncateJsonStringBytes guarantees the same bound. */ if (prefixBytes === undefined) throw new Error('output ledger produced an oversized log prefix') retained.push(prefix) retainedBytes += prefixBytes + separatorBytes } break } const message = truncateJsonStringBytes(fullMessage, this.maxBytes - retainedBytes) return { logs: retained, error: { kind: 'output-limit', message } } } } export { decodeWorkerJson, encodeWorkerJson, snapshotCodeJsonValue } from './worker-json.ts' export { jsonStringBytesUpTo, jsonValueBytesUpTo } from './output-json.ts' export { runWorkerMain } from './bootstrap.ts' export type { WorkerJsonWire } from './worker-json.ts'