@secure-exec/core 0.0.0-nathan-docs-sdk-expansion.c9c2e4e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/binary.d.ts +4 -0
- package/dist/binary.js +25 -0
- package/dist/bytes.d.ts +2 -0
- package/dist/bytes.js +6 -0
- package/dist/callbacks.d.ts +41 -0
- package/dist/callbacks.js +94 -0
- package/dist/cargo.d.ts +2 -0
- package/dist/cargo.js +142 -0
- package/dist/correlation.d.ts +10 -0
- package/dist/correlation.js +49 -0
- package/dist/descriptors.d.ts +34 -0
- package/dist/descriptors.js +37 -0
- package/dist/event-buffer.d.ts +90 -0
- package/dist/event-buffer.js +313 -0
- package/dist/ext.d.ts +7 -0
- package/dist/ext.js +13 -0
- package/dist/filesystem.d.ts +41 -0
- package/dist/filesystem.js +70 -0
- package/dist/frame-payload-codec.d.ts +8 -0
- package/dist/frame-payload-codec.js +14 -0
- package/dist/frame-rpc.d.ts +38 -0
- package/dist/frame-rpc.js +73 -0
- package/dist/frame-stream.d.ts +27 -0
- package/dist/frame-stream.js +99 -0
- package/dist/framing.d.ts +7 -0
- package/dist/framing.js +22 -0
- package/dist/generated/AcpLimitsConfig.d.ts +4 -0
- package/dist/generated/AcpLimitsConfig.js +2 -0
- package/dist/generated/CreateVmConfig.d.ts +19 -0
- package/dist/generated/CreateVmConfig.js +1 -0
- package/dist/generated/FsPermissionRule.d.ts +6 -0
- package/dist/generated/FsPermissionRule.js +1 -0
- package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
- package/dist/generated/FsPermissionRuleSet.js +1 -0
- package/dist/generated/FsPermissionScope.d.ts +3 -0
- package/dist/generated/FsPermissionScope.js +1 -0
- package/dist/generated/HttpLimitsConfig.d.ts +3 -0
- package/dist/generated/HttpLimitsConfig.js +2 -0
- package/dist/generated/JsModuleResolution.d.ts +1 -0
- package/dist/generated/JsModuleResolution.js +2 -0
- package/dist/generated/JsRuntimeConfig.d.ts +26 -0
- package/dist/generated/JsRuntimeConfig.js +1 -0
- package/dist/generated/JsRuntimeLimitsConfig.d.ts +7 -0
- package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
- package/dist/generated/JsRuntimePlatform.d.ts +1 -0
- package/dist/generated/JsRuntimePlatform.js +2 -0
- package/dist/generated/MountPluginDescriptor.d.ts +4 -0
- package/dist/generated/MountPluginDescriptor.js +2 -0
- package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
- package/dist/generated/NativeRootFilesystemConfig.js +1 -0
- package/dist/generated/PatternPermissionRule.d.ts +6 -0
- package/dist/generated/PatternPermissionRule.js +1 -0
- package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
- package/dist/generated/PatternPermissionRuleSet.js +1 -0
- package/dist/generated/PatternPermissionScope.d.ts +3 -0
- package/dist/generated/PatternPermissionScope.js +1 -0
- package/dist/generated/PermissionMode.d.ts +1 -0
- package/dist/generated/PermissionMode.js +2 -0
- package/dist/generated/PermissionsPolicy.d.ts +10 -0
- package/dist/generated/PermissionsPolicy.js +1 -0
- package/dist/generated/PluginLimitsConfig.d.ts +4 -0
- package/dist/generated/PluginLimitsConfig.js +2 -0
- package/dist/generated/PythonLimitsConfig.d.ts +5 -0
- package/dist/generated/PythonLimitsConfig.js +2 -0
- package/dist/generated/ResourceLimitsConfig.d.ts +22 -0
- package/dist/generated/ResourceLimitsConfig.js +2 -0
- package/dist/generated/RootFilesystemConfig.d.ts +9 -0
- package/dist/generated/RootFilesystemConfig.js +1 -0
- package/dist/generated/RootFilesystemEntry.d.ts +13 -0
- package/dist/generated/RootFilesystemEntry.js +1 -0
- package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
- package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
- package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
- package/dist/generated/RootFilesystemEntryKind.js +2 -0
- package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
- package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
- package/dist/generated/RootFilesystemMode.d.ts +1 -0
- package/dist/generated/RootFilesystemMode.js +2 -0
- package/dist/generated/ToolLimitsConfig.d.ts +10 -0
- package/dist/generated/ToolLimitsConfig.js +2 -0
- package/dist/generated/VmDnsConfig.d.ts +6 -0
- package/dist/generated/VmDnsConfig.js +2 -0
- package/dist/generated/VmLimitsConfig.d.ts +18 -0
- package/dist/generated/VmLimitsConfig.js +1 -0
- package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
- package/dist/generated/VmListenPolicyConfig.js +2 -0
- package/dist/generated/WasmLimitsConfig.d.ts +5 -0
- package/dist/generated/WasmLimitsConfig.js +2 -0
- package/dist/generated-protocol.d.ts +1037 -0
- package/dist/generated-protocol.js +2887 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +20 -0
- package/dist/kernel-proxy.d.ts +149 -0
- package/dist/kernel-proxy.js +1733 -0
- package/dist/native-client.d.ts +41 -0
- package/dist/native-client.js +124 -0
- package/dist/node-runtime.d.ts +443 -0
- package/dist/node-runtime.js +569 -0
- package/dist/numbers.d.ts +1 -0
- package/dist/numbers.js +8 -0
- package/dist/ownership.d.ts +18 -0
- package/dist/ownership.js +77 -0
- package/dist/permissions.d.ts +29 -0
- package/dist/permissions.js +68 -0
- package/dist/process.d.ts +35 -0
- package/dist/process.js +125 -0
- package/dist/protocol-client.d.ts +46 -0
- package/dist/protocol-client.js +180 -0
- package/dist/protocol-frames.d.ts +68 -0
- package/dist/protocol-frames.js +139 -0
- package/dist/protocol-maps.d.ts +28 -0
- package/dist/protocol-maps.js +217 -0
- package/dist/protocol-schema.d.ts +10 -0
- package/dist/protocol-schema.js +11 -0
- package/dist/request-payloads.d.ts +137 -0
- package/dist/request-payloads.js +210 -0
- package/dist/response-payloads.d.ts +107 -0
- package/dist/response-payloads.js +161 -0
- package/dist/sidecar-client.d.ts +242 -0
- package/dist/sidecar-client.js +797 -0
- package/dist/state.d.ts +40 -0
- package/dist/state.js +44 -0
- package/dist/test-runtime.d.ts +526 -0
- package/dist/test-runtime.js +2119 -0
- package/dist/vm-config.d.ts +31 -0
- package/dist/vm-config.js +1 -0
- package/fixtures/alpine-defaults.json +520 -0
- package/fixtures/base-filesystem.json +528 -0
- package/package.json +194 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StdioSidecarProcess } from "./process.js";
|
|
2
|
+
import type { LiveEventFrame, LiveResponseFrame, LiveSidecarRequestHandler, ProtocolFramePayloadCodec } from "./protocol-frames.js";
|
|
3
|
+
import type { LiveOwnershipScope } from "./ownership.js";
|
|
4
|
+
import type { LiveRequestPayload } from "./request-payloads.js";
|
|
5
|
+
import type { LiveSidecarEventSelector } from "./event-buffer.js";
|
|
6
|
+
export declare const DEFAULT_SIDECAR_FRAME_TIMEOUT_MS = 120000;
|
|
7
|
+
export declare const DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY = 4096;
|
|
8
|
+
export declare const DEFAULT_SIDECAR_GRACEFUL_EXIT_MS = 5000;
|
|
9
|
+
export declare const DEFAULT_SIDECAR_FORCE_EXIT_MS = 2000;
|
|
10
|
+
export interface StdioSidecarProtocolClientSpawnOptions {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
command?: string;
|
|
13
|
+
args?: string[];
|
|
14
|
+
frameTimeoutMs?: number;
|
|
15
|
+
eventBufferCapacity?: number;
|
|
16
|
+
gracefulExitMs?: number;
|
|
17
|
+
forceExitMs?: number;
|
|
18
|
+
disposedErrorMessage?: string;
|
|
19
|
+
payloadCodec?: ProtocolFramePayloadCodec;
|
|
20
|
+
}
|
|
21
|
+
export declare class StdioSidecarProtocolClient {
|
|
22
|
+
readonly child: StdioSidecarProcess["child"];
|
|
23
|
+
private readonly sidecarProcess;
|
|
24
|
+
private readonly protocolClient;
|
|
25
|
+
private readonly gracefulExitMs;
|
|
26
|
+
private readonly forceExitMs;
|
|
27
|
+
private readonly disposedErrorMessage;
|
|
28
|
+
private constructor();
|
|
29
|
+
static spawn(options?: StdioSidecarProtocolClientSpawnOptions): StdioSidecarProtocolClient;
|
|
30
|
+
setSidecarRequestHandler(handler: LiveSidecarRequestHandler | null): void;
|
|
31
|
+
onEvent(handler: (event: LiveEventFrame) => void): () => void;
|
|
32
|
+
sendRequest(input: {
|
|
33
|
+
ownership: LiveOwnershipScope;
|
|
34
|
+
payload: LiveRequestPayload;
|
|
35
|
+
}): Promise<LiveResponseFrame>;
|
|
36
|
+
waitForEvent(matcher: LiveSidecarEventSelector | ((event: LiveEventFrame) => boolean), timeoutMs?: number, options?: {
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}): Promise<LiveEventFrame>;
|
|
39
|
+
dispose(): Promise<void>;
|
|
40
|
+
failPermanently(error: Error): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { resolvePublishedSidecarBinary } from "./binary.js";
|
|
2
|
+
import { SidecarProcessExited, StdioSidecarProcess, } from "./process.js";
|
|
3
|
+
import { SidecarProtocolClient } from "./protocol-client.js";
|
|
4
|
+
export const DEFAULT_SIDECAR_FRAME_TIMEOUT_MS = 120_000;
|
|
5
|
+
export const DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY = 4_096;
|
|
6
|
+
export const DEFAULT_SIDECAR_GRACEFUL_EXIT_MS = 5_000;
|
|
7
|
+
export const DEFAULT_SIDECAR_FORCE_EXIT_MS = 2_000;
|
|
8
|
+
export class StdioSidecarProtocolClient {
|
|
9
|
+
child;
|
|
10
|
+
sidecarProcess;
|
|
11
|
+
protocolClient;
|
|
12
|
+
gracefulExitMs;
|
|
13
|
+
forceExitMs;
|
|
14
|
+
disposedErrorMessage;
|
|
15
|
+
constructor(sidecarProcess, options) {
|
|
16
|
+
this.sidecarProcess = sidecarProcess;
|
|
17
|
+
this.child = sidecarProcess.child;
|
|
18
|
+
this.gracefulExitMs = options.gracefulExitMs;
|
|
19
|
+
this.forceExitMs = options.forceExitMs;
|
|
20
|
+
this.disposedErrorMessage = options.disposedErrorMessage;
|
|
21
|
+
this.protocolClient = new SidecarProtocolClient({
|
|
22
|
+
stdin: this.child.stdin,
|
|
23
|
+
stdout: this.child.stdout,
|
|
24
|
+
frameTimeoutMs: options.frameTimeoutMs,
|
|
25
|
+
eventBufferCapacity: options.eventBufferCapacity,
|
|
26
|
+
payloadCodec: options.payloadCodec,
|
|
27
|
+
stderrText: () => this.sidecarProcess.stderrText(),
|
|
28
|
+
streamEndedError: () => this.sidecarProcess.currentExitError() ??
|
|
29
|
+
new SidecarProcessExited({
|
|
30
|
+
exitCode: this.child.exitCode,
|
|
31
|
+
signal: this.child.signalCode,
|
|
32
|
+
stderr: this.sidecarProcess.stderrText(),
|
|
33
|
+
}),
|
|
34
|
+
frameError: (error) => this.sidecarProcess.currentExitError() ?? error,
|
|
35
|
+
});
|
|
36
|
+
this.sidecarProcess.onExit((error) => {
|
|
37
|
+
this.failPermanently(error);
|
|
38
|
+
});
|
|
39
|
+
this.sidecarProcess.onError((error) => {
|
|
40
|
+
this.failPermanently(error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
static spawn(options = {}) {
|
|
44
|
+
return new StdioSidecarProtocolClient(StdioSidecarProcess.spawn({
|
|
45
|
+
command: options.command ?? resolvePublishedSidecarBinary(),
|
|
46
|
+
args: options.args ?? [],
|
|
47
|
+
cwd: options.cwd,
|
|
48
|
+
}), {
|
|
49
|
+
frameTimeoutMs: options.frameTimeoutMs ?? DEFAULT_SIDECAR_FRAME_TIMEOUT_MS,
|
|
50
|
+
eventBufferCapacity: options.eventBufferCapacity ??
|
|
51
|
+
DEFAULT_SIDECAR_EVENT_BUFFER_CAPACITY,
|
|
52
|
+
gracefulExitMs: options.gracefulExitMs ?? DEFAULT_SIDECAR_GRACEFUL_EXIT_MS,
|
|
53
|
+
forceExitMs: options.forceExitMs ?? DEFAULT_SIDECAR_FORCE_EXIT_MS,
|
|
54
|
+
disposedErrorMessage: options.disposedErrorMessage ?? "sidecar client disposed",
|
|
55
|
+
payloadCodec: options.payloadCodec ?? "bare",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
setSidecarRequestHandler(handler) {
|
|
59
|
+
this.protocolClient.setSidecarRequestHandler(handler);
|
|
60
|
+
}
|
|
61
|
+
onEvent(handler) {
|
|
62
|
+
return this.protocolClient.onEvent(handler);
|
|
63
|
+
}
|
|
64
|
+
async sendRequest(input) {
|
|
65
|
+
return await this.protocolClient.sendRequest(input);
|
|
66
|
+
}
|
|
67
|
+
async waitForEvent(matcher, timeoutMs, options) {
|
|
68
|
+
return await this.protocolClient.waitForEvent(matcher, timeoutMs, options);
|
|
69
|
+
}
|
|
70
|
+
async dispose() {
|
|
71
|
+
this.protocolClient.failPermanently(new Error(this.disposedErrorMessage));
|
|
72
|
+
if (!this.child.stdin.destroyed) {
|
|
73
|
+
try {
|
|
74
|
+
this.child.stdin.end();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Stdin may already be closing. The child exit watcher will catch up.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const exitCode = await this.sidecarProcess.waitForExit(this.gracefulExitMs);
|
|
81
|
+
if (exitCode === null) {
|
|
82
|
+
try {
|
|
83
|
+
this.child.kill("SIGKILL");
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// The child may have exited between the timeout and the kill attempt.
|
|
87
|
+
}
|
|
88
|
+
await this.sidecarProcess.waitForExit(this.forceExitMs);
|
|
89
|
+
}
|
|
90
|
+
this.protocolClient.dispose();
|
|
91
|
+
try {
|
|
92
|
+
this.child.stdin.destroy();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Best effort. The child is gone so the descriptor will close on its own.
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
this.child.stdout.destroy();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Best effort. The child is gone so the descriptor will close on its own.
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
this.child.stderr.destroy();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Best effort. The child is gone so the descriptor will close on its own.
|
|
108
|
+
}
|
|
109
|
+
if (exitCode !== null &&
|
|
110
|
+
exitCode !== 0 &&
|
|
111
|
+
this.child.signalCode === null) {
|
|
112
|
+
throw new Error(`sidecar exited with code ${exitCode}\nstderr:\n${this.sidecarProcess.stderrText()}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
failPermanently(error) {
|
|
116
|
+
this.protocolClient.failPermanently(error, {
|
|
117
|
+
replaceExisting: (current, next) => current instanceof SidecarProcessExited &&
|
|
118
|
+
current.exitCode === null &&
|
|
119
|
+
current.signal === null &&
|
|
120
|
+
next instanceof SidecarProcessExited &&
|
|
121
|
+
(next.exitCode !== null || next.signal !== null),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodeRuntime — ergonomic façade for running guest JavaScript end-to-end.
|
|
3
|
+
*
|
|
4
|
+
* Boots a fully virtualized VM (via the native sidecar) and runs guest Node
|
|
5
|
+
* programs with minimal boilerplate. All of the sidecar spawn, session
|
|
6
|
+
* handshake, VM creation, root filesystem bootstrap, runtime-driver mounting,
|
|
7
|
+
* and lifecycle waiting are hidden behind `NodeRuntime.create()`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const rt = await NodeRuntime.create();
|
|
11
|
+
* const { stdout, exitCode } = await rt.exec("console.log('hi', 1 + 1)");
|
|
12
|
+
* await rt.dispose();
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Guest code is written to an ESM module inside the VM and executed as
|
|
16
|
+
* `node <file>` through the kernel, so all execution stays inside the kernel
|
|
17
|
+
* isolation boundary — no host escapes, no real Node.js builtins for guest
|
|
18
|
+
* work.
|
|
19
|
+
*/
|
|
20
|
+
import type { HostToolDefinition, Permissions } from "./test-runtime.js";
|
|
21
|
+
export type { HostToolDefinition, HostToolExample } from "./test-runtime.js";
|
|
22
|
+
/** Options for {@link NodeRuntime.create}. */
|
|
23
|
+
export interface NodeRuntimeCreateOptions {
|
|
24
|
+
/** Environment variables visible to guest processes. */
|
|
25
|
+
env?: Record<string, string>;
|
|
26
|
+
/** Initial working directory for guest processes. Defaults to `/home/user`. */
|
|
27
|
+
cwd?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Permission policy for the VM. Merged over a secure default that **denies
|
|
30
|
+
* network access** (guest code cannot reach the network until you opt in);
|
|
31
|
+
* the virtualized filesystem and processes stay enabled so programs run.
|
|
32
|
+
* Because it merges, a partial policy works: `{ network: "allow" }` grants
|
|
33
|
+
* the network while keeping the execution essentials. Pass a fuller policy
|
|
34
|
+
* (rule sets) to further sandbox individual scopes.
|
|
35
|
+
*/
|
|
36
|
+
permissions?: Permissions;
|
|
37
|
+
/**
|
|
38
|
+
* Override the directory containing the WASM command binaries (the source of
|
|
39
|
+
* the guest `sh`). Defaults to the in-repo build output, or the
|
|
40
|
+
* `SECURE_EXEC_WASM_COMMANDS_DIR` environment variable when set.
|
|
41
|
+
*/
|
|
42
|
+
commandsDir?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Files to seed into the VM's virtual filesystem before the guest runs,
|
|
45
|
+
* keyed by absolute guest path. Parent directories are created as needed.
|
|
46
|
+
* Use this to project host assets, npm packages, or fixtures into the
|
|
47
|
+
* sandbox so guest code can `import`/`require`/read them. The bytes are
|
|
48
|
+
* copied into the VM's in-memory filesystem; the host filesystem is never
|
|
49
|
+
* exposed to the guest.
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* const rt = await NodeRuntime.create({
|
|
53
|
+
* files: { "/root/data.json": '{"ok":true}' },
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
files?: Record<string, string | Uint8Array>;
|
|
58
|
+
/**
|
|
59
|
+
* Host directories to project into the VM's virtual filesystem, Docker-style.
|
|
60
|
+
* Each mount makes a host directory readable at a guest path. Files are read
|
|
61
|
+
* lazily from the host as the guest accesses them, so large trees (for
|
|
62
|
+
* example a `node_modules` package such as the TypeScript compiler) are
|
|
63
|
+
* projected without copying their bytes up front. The guest sees only the
|
|
64
|
+
* mounted subtree, never the wider host filesystem.
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* const rt = await NodeRuntime.create({
|
|
68
|
+
* mounts: [
|
|
69
|
+
* {
|
|
70
|
+
* guestPath: "/root/node_modules/typescript",
|
|
71
|
+
* hostPath: "/abs/path/to/node_modules/typescript",
|
|
72
|
+
* readOnly: true,
|
|
73
|
+
* },
|
|
74
|
+
* ],
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
mounts?: HostDirectoryMount[];
|
|
79
|
+
/**
|
|
80
|
+
* Host-side tools the guest can invoke as shell commands. Each entry is
|
|
81
|
+
* registered as a named guest command; when the guest runs it, the
|
|
82
|
+
* invocation round-trips back to the host and runs the tool's `handler`,
|
|
83
|
+
* whose return value is delivered back to the guest. This is how you give
|
|
84
|
+
* sandboxed guest code controlled, named host capabilities (the kind an AI
|
|
85
|
+
* agent calls as tools) without granting it the underlying access directly.
|
|
86
|
+
*
|
|
87
|
+
* The guest invokes a tool by name with JSON input:
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* const rt = await NodeRuntime.create({
|
|
91
|
+
* tools: {
|
|
92
|
+
* add: {
|
|
93
|
+
* description: "Add two numbers",
|
|
94
|
+
* inputSchema: {
|
|
95
|
+
* type: "object",
|
|
96
|
+
* properties: { a: { type: "number" }, b: { type: "number" } },
|
|
97
|
+
* required: ["a", "b"],
|
|
98
|
+
* },
|
|
99
|
+
* handler: ({ a, b }: { a: number; b: number }) => ({ sum: a + b }),
|
|
100
|
+
* },
|
|
101
|
+
* },
|
|
102
|
+
* });
|
|
103
|
+
* await rt.exec(`
|
|
104
|
+
* import { execFileSync } from "node:child_process";
|
|
105
|
+
* const out = execFileSync("add", ["add", "--json", JSON.stringify({ a: 2, b: 3 })]);
|
|
106
|
+
* console.log(out.toString());
|
|
107
|
+
* `);
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* When `tools` is provided and no `tool` permission scope is set, the `tool`
|
|
111
|
+
* scope is granted so the registered tools are invocable; pass your own
|
|
112
|
+
* `permissions.tool` policy to gate individual tools.
|
|
113
|
+
*/
|
|
114
|
+
tools?: Record<string, HostToolDefinition>;
|
|
115
|
+
/**
|
|
116
|
+
* Guest-bound ports that may accept non-loopback connections. By default a
|
|
117
|
+
* guest server is reachable only over loopback inside the VM; listing a port
|
|
118
|
+
* here lifts that restriction for that port, letting connections from outside
|
|
119
|
+
* the loopback interface reach it. Use this for guests that run servers which
|
|
120
|
+
* must accept external connections (for example a dev server you expose
|
|
121
|
+
* beyond loopback).
|
|
122
|
+
*
|
|
123
|
+
* ```ts
|
|
124
|
+
* const rt = await NodeRuntime.create({
|
|
125
|
+
* permissions: { network: "allow" },
|
|
126
|
+
* loopbackExemptPorts: [3000],
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
loopbackExemptPorts?: number[];
|
|
131
|
+
}
|
|
132
|
+
/** A host directory projected into the VM's virtual filesystem. */
|
|
133
|
+
export interface HostDirectoryMount {
|
|
134
|
+
/** Absolute guest path the directory appears at inside the VM. */
|
|
135
|
+
guestPath: string;
|
|
136
|
+
/** Absolute host directory to project (read through the VFS, lazily). */
|
|
137
|
+
hostPath: string;
|
|
138
|
+
/** Mount read-only (the default). Pass `false` to allow guest writes. */
|
|
139
|
+
readOnly?: boolean;
|
|
140
|
+
}
|
|
141
|
+
/** Result of {@link NodeRuntime.exec}. */
|
|
142
|
+
export interface NodeRuntimeExecResult {
|
|
143
|
+
stdout: string;
|
|
144
|
+
stderr: string;
|
|
145
|
+
exitCode: number;
|
|
146
|
+
}
|
|
147
|
+
/** Options for a single {@link NodeRuntime.exec} call. */
|
|
148
|
+
export interface NodeRuntimeExecOptions {
|
|
149
|
+
/** Extra environment variables for this run, merged over the VM env. */
|
|
150
|
+
env?: Record<string, string>;
|
|
151
|
+
/** Working directory for this run. */
|
|
152
|
+
cwd?: string;
|
|
153
|
+
/** Data piped to the guest program's stdin. */
|
|
154
|
+
stdin?: string | Uint8Array;
|
|
155
|
+
/** Abort the run after this many milliseconds. */
|
|
156
|
+
timeout?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Cancel the run when this signal aborts. On abort the guest process is
|
|
159
|
+
* killed inside the VM (the kernel delivers `SIGTERM`) and the call rejects
|
|
160
|
+
* with the signal's abort reason. Use this to cancel an in-flight run from
|
|
161
|
+
* the outside, for example to enforce your own deadline or stop work when a
|
|
162
|
+
* request is canceled.
|
|
163
|
+
*
|
|
164
|
+
* ```ts
|
|
165
|
+
* const controller = new AbortController();
|
|
166
|
+
* const pending = rt.exec("while (true) {}", { signal: controller.signal });
|
|
167
|
+
* controller.abort();
|
|
168
|
+
* await pending.catch((err) => console.log(err.name)); // "AbortError"
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
signal?: AbortSignal;
|
|
172
|
+
/**
|
|
173
|
+
* Called with each chunk the guest writes to stdout as it is produced,
|
|
174
|
+
* letting you observe output incrementally instead of waiting for the run to
|
|
175
|
+
* finish. Chunks arrive as raw bytes; decode with a `TextDecoder` for text.
|
|
176
|
+
* The complete output is still returned as `result.stdout` when the run ends.
|
|
177
|
+
*/
|
|
178
|
+
onStdout?: (chunk: Uint8Array) => void;
|
|
179
|
+
/**
|
|
180
|
+
* Called with each chunk the guest writes to stderr as it is produced,
|
|
181
|
+
* letting you observe output incrementally instead of waiting for the run to
|
|
182
|
+
* finish. Chunks arrive as raw bytes; decode with a `TextDecoder` for text.
|
|
183
|
+
* The complete output is still returned as `result.stderr` when the run ends.
|
|
184
|
+
*/
|
|
185
|
+
onStderr?: (chunk: Uint8Array) => void;
|
|
186
|
+
}
|
|
187
|
+
/** The HTTP request {@link NodeRuntime.fetch} drives into the VM. */
|
|
188
|
+
export interface NodeRuntimeFetchInput {
|
|
189
|
+
/** HTTP method. Defaults to `GET`. */
|
|
190
|
+
method?: string;
|
|
191
|
+
/** Request path (and query), e.g. `/api/users?limit=10`. */
|
|
192
|
+
path: string;
|
|
193
|
+
/** Request headers. */
|
|
194
|
+
headers?: Record<string, string>;
|
|
195
|
+
/** Request body. */
|
|
196
|
+
body?: string | Uint8Array;
|
|
197
|
+
}
|
|
198
|
+
/** The HTTP response {@link NodeRuntime.fetch} returns from the VM. */
|
|
199
|
+
export interface NodeRuntimeFetchResponse {
|
|
200
|
+
/** HTTP status code, e.g. `200`. */
|
|
201
|
+
status: number;
|
|
202
|
+
/** HTTP status text, e.g. `OK`. */
|
|
203
|
+
statusText: string;
|
|
204
|
+
/** Response headers, lower-cased by name. */
|
|
205
|
+
headers: Record<string, string>;
|
|
206
|
+
/** Response body decoded as UTF-8 text. */
|
|
207
|
+
body: string;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Options for {@link NodeRuntime.spawn}. Inherits the streaming `onStdout` /
|
|
211
|
+
* `onStderr` hooks from {@link NodeRuntimeExecOptions}.
|
|
212
|
+
*/
|
|
213
|
+
export interface NodeRuntimeSpawnOptions extends NodeRuntimeExecOptions {
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Describes the guest TCP listener to wait for with
|
|
217
|
+
* {@link NodeRuntime.waitForListener} or look up with
|
|
218
|
+
* {@link NodeRuntime.findListener}. A listener matches when a guest process is
|
|
219
|
+
* accepting connections on the given `port` (and `host`/`path` when supplied).
|
|
220
|
+
*/
|
|
221
|
+
export interface NodeRuntimeListenerQuery {
|
|
222
|
+
/** TCP port the guest listener is bound to, e.g. `3000`. */
|
|
223
|
+
port: number;
|
|
224
|
+
/** Bind host to match, e.g. `127.0.0.1`. Omit to match any host. */
|
|
225
|
+
host?: string;
|
|
226
|
+
/** Unix socket path to match, for path-bound listeners. */
|
|
227
|
+
path?: string;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* A matched guest listener returned by {@link NodeRuntime.waitForListener} and
|
|
231
|
+
* {@link NodeRuntime.findListener}. `processId` identifies the guest process
|
|
232
|
+
* that owns the listening socket; the `host`/`port`/`path` it is bound to are
|
|
233
|
+
* reported when known.
|
|
234
|
+
*/
|
|
235
|
+
export interface NodeRuntimeListener {
|
|
236
|
+
/** The guest process id that owns the listening socket. */
|
|
237
|
+
processId: string;
|
|
238
|
+
/** The host the listener is bound to, when reported. */
|
|
239
|
+
host?: string;
|
|
240
|
+
/** The port the listener is bound to, when reported. */
|
|
241
|
+
port?: number;
|
|
242
|
+
/** The unix socket path the listener is bound to, when reported. */
|
|
243
|
+
path?: string;
|
|
244
|
+
}
|
|
245
|
+
/** Options for {@link NodeRuntime.waitForListener}. */
|
|
246
|
+
export interface NodeRuntimeWaitForListenerOptions {
|
|
247
|
+
/**
|
|
248
|
+
* Give up after this many milliseconds and reject. Defaults to 10000. The
|
|
249
|
+
* wait also rejects if the bound `signal` aborts first.
|
|
250
|
+
*/
|
|
251
|
+
timeoutMs?: number;
|
|
252
|
+
/** Abort the wait early; the returned promise rejects when it fires. */
|
|
253
|
+
signal?: AbortSignal;
|
|
254
|
+
/**
|
|
255
|
+
* How long to wait between listener lookups while polling, in milliseconds.
|
|
256
|
+
* Defaults to 50.
|
|
257
|
+
*/
|
|
258
|
+
pollIntervalMs?: number;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* A live handle to a guest process started with {@link NodeRuntime.spawn}.
|
|
262
|
+
*
|
|
263
|
+
* Unlike {@link NodeRuntime.exec}, which runs a program to completion and
|
|
264
|
+
* returns its captured output, a handle is returned immediately while the
|
|
265
|
+
* process keeps running. Use it to stream stdout/stderr, feed stdin, signal or
|
|
266
|
+
* kill the process, and await its exit. This is the building block for
|
|
267
|
+
* long-running guests such as dev servers: start one here, then drive requests
|
|
268
|
+
* into it with {@link NodeRuntime.fetch}.
|
|
269
|
+
*/
|
|
270
|
+
export interface NodeRuntimeProcess {
|
|
271
|
+
/** The guest process id. */
|
|
272
|
+
readonly pid: number;
|
|
273
|
+
/** Write data to the guest process's stdin. */
|
|
274
|
+
writeStdin(data: string | Uint8Array): void;
|
|
275
|
+
/** Close the guest process's stdin, signalling end-of-input. */
|
|
276
|
+
closeStdin(): void;
|
|
277
|
+
/**
|
|
278
|
+
* Send a signal to the guest process. Defaults to `SIGTERM`. Accepts a
|
|
279
|
+
* signal name (e.g. `"SIGKILL"`) or a raw signal number.
|
|
280
|
+
*/
|
|
281
|
+
kill(signal?: NodeJS.Signals | number): void;
|
|
282
|
+
/** Resolve with the guest process's exit code once it terminates. */
|
|
283
|
+
wait(): Promise<number>;
|
|
284
|
+
/** The exit code once the process has exited, or `null` while it runs. */
|
|
285
|
+
readonly exitCode: number | null;
|
|
286
|
+
}
|
|
287
|
+
/** Result of {@link NodeRuntime.run}. */
|
|
288
|
+
export interface NodeRuntimeRunResult<T = unknown> {
|
|
289
|
+
/** The JSON-decoded value the guest produced, when the run succeeded. */
|
|
290
|
+
value?: T;
|
|
291
|
+
stdout: string;
|
|
292
|
+
stderr: string;
|
|
293
|
+
exitCode: number;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Ergonomic, batteries-included runtime for executing guest JavaScript.
|
|
297
|
+
*
|
|
298
|
+
* Construct one with {@link NodeRuntime.create}, run programs with
|
|
299
|
+
* {@link NodeRuntime.exec} / {@link NodeRuntime.run}, and release the VM with
|
|
300
|
+
* {@link NodeRuntime.dispose}. A single instance can run many programs; each
|
|
301
|
+
* call executes a fresh guest process.
|
|
302
|
+
*/
|
|
303
|
+
export declare class NodeRuntime {
|
|
304
|
+
private readonly kernel;
|
|
305
|
+
private constructor();
|
|
306
|
+
/**
|
|
307
|
+
* Boot a VM and return a ready-to-use runtime. Spawns the sidecar, opens a
|
|
308
|
+
* session, creates the VM with a bootstrapped root filesystem, mounts the
|
|
309
|
+
* shell and Node runtimes, and waits for the VM to report ready.
|
|
310
|
+
*/
|
|
311
|
+
static create(options?: NodeRuntimeCreateOptions): Promise<NodeRuntime>;
|
|
312
|
+
/**
|
|
313
|
+
* Run `code` as a guest Node program and capture its output.
|
|
314
|
+
*
|
|
315
|
+
* The source is written to an ES module inside the VM and executed with
|
|
316
|
+
* `node <file>`; it runs as standard ESM (top-level `await`, `import`).
|
|
317
|
+
*/
|
|
318
|
+
exec(code: string, options?: NodeRuntimeExecOptions): Promise<NodeRuntimeExecResult>;
|
|
319
|
+
/**
|
|
320
|
+
* Run an already-written guest program file to completion and capture its
|
|
321
|
+
* output, honoring a caller-supplied `signal` for cancellation.
|
|
322
|
+
*
|
|
323
|
+
* Without a `signal`, this runs the program through the shell (`node <file>`)
|
|
324
|
+
* exactly as before. With a `signal`, it starts the program as a guest
|
|
325
|
+
* process so the run can be cancelled: when the signal aborts, the process is
|
|
326
|
+
* killed inside the VM (the kernel delivers `SIGTERM`) and the call rejects
|
|
327
|
+
* with the signal's abort reason.
|
|
328
|
+
*/
|
|
329
|
+
private runProgram;
|
|
330
|
+
/**
|
|
331
|
+
* Start `code` as a long-running guest Node program and return a live handle
|
|
332
|
+
* to it, without waiting for it to finish.
|
|
333
|
+
*
|
|
334
|
+
* The source is written to an ES module inside the VM and started with
|
|
335
|
+
* `node <file>`; it runs as standard ESM (top-level `await`, `import`). The
|
|
336
|
+
* returned {@link NodeRuntimeProcess} lets you stream output, write to stdin,
|
|
337
|
+
* signal or kill the process, and await its exit. Pass `onStdout`/`onStderr`
|
|
338
|
+
* to receive output chunks as they are produced.
|
|
339
|
+
*
|
|
340
|
+
* Use this for guests that do not run to completion, such as a dev server you
|
|
341
|
+
* later drive with {@link NodeRuntime.fetch}:
|
|
342
|
+
*
|
|
343
|
+
* ```ts
|
|
344
|
+
* const server = await rt.spawn(`
|
|
345
|
+
* import http from "node:http";
|
|
346
|
+
* http.createServer((_, res) => res.end("ok")).listen(3000);
|
|
347
|
+
* `);
|
|
348
|
+
* const res = await rt.fetch(3000, { path: "/" });
|
|
349
|
+
* server.kill();
|
|
350
|
+
* await server.wait();
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
spawn(code: string, options?: NodeRuntimeSpawnOptions): Promise<NodeRuntimeProcess>;
|
|
354
|
+
/**
|
|
355
|
+
* Run `code` and return the JSON-serializable value it produces.
|
|
356
|
+
*
|
|
357
|
+
* The guest exposes a `__return(value)` function; call it with a
|
|
358
|
+
* JSON-serializable value and that value is decoded on the host as
|
|
359
|
+
* `result.value`. If `__return` is never called the value is `undefined`.
|
|
360
|
+
* stdout/stderr/exitCode are still captured.
|
|
361
|
+
*/
|
|
362
|
+
run<T = unknown>(code: string, options?: NodeRuntimeExecOptions): Promise<NodeRuntimeRunResult<T>>;
|
|
363
|
+
/**
|
|
364
|
+
* Drive an HTTP request to a guest HTTP server listening inside the VM and
|
|
365
|
+
* read the response back on the host.
|
|
366
|
+
*
|
|
367
|
+
* Point this at a port a guest program is serving, for example a dev server
|
|
368
|
+
* started with {@link NodeRuntime.exec}. The
|
|
369
|
+
* request and response never leave the VM: the connection is made to the
|
|
370
|
+
* guest's loopback listener through the kernel socket table, so this works
|
|
371
|
+
* even when guest network egress is denied.
|
|
372
|
+
*
|
|
373
|
+
* ```ts
|
|
374
|
+
* const res = await rt.fetch(3000, { path: "/health" });
|
|
375
|
+
* console.log(res.status, res.body);
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
fetch(port: number, input: NodeRuntimeFetchInput): Promise<NodeRuntimeFetchResponse>;
|
|
379
|
+
/**
|
|
380
|
+
* Look up a guest TCP listener once and return it, or `null` when nothing is
|
|
381
|
+
* listening yet.
|
|
382
|
+
*
|
|
383
|
+
* This is the immediate, non-blocking check behind
|
|
384
|
+
* {@link NodeRuntime.waitForListener}: it asks the kernel socket table
|
|
385
|
+
* whether a guest process is accepting connections on the requested `port`
|
|
386
|
+
* (optionally narrowed by `host`/`path`) and returns the match, or `null` if
|
|
387
|
+
* none is up. Use {@link NodeRuntime.waitForListener} when you want to block
|
|
388
|
+
* until one appears.
|
|
389
|
+
*
|
|
390
|
+
* ```ts
|
|
391
|
+
* const listener = rt.findListener({ port: 3000 });
|
|
392
|
+
* if (listener) console.log("up on pid", listener.processId);
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
findListener(query: NodeRuntimeListenerQuery): NodeRuntimeListener | null;
|
|
396
|
+
/**
|
|
397
|
+
* Block until a guest TCP listener is accepting connections on the requested
|
|
398
|
+
* `port` (optionally narrowed by `host`/`path`), then resolve with it.
|
|
399
|
+
*
|
|
400
|
+
* This is the companion to {@link NodeRuntime.spawn} and
|
|
401
|
+
* {@link NodeRuntime.fetch} for dev-server scenarios: start a server, wait
|
|
402
|
+
* until it is actually listening, then drive requests into it. The kernel
|
|
403
|
+
* socket table is polled until a matching listener appears or the wait is
|
|
404
|
+
* cut short. If `timeoutMs` elapses (default 10000) or the supplied `signal`
|
|
405
|
+
* aborts first, the returned promise rejects.
|
|
406
|
+
*
|
|
407
|
+
* ```ts
|
|
408
|
+
* const server = await rt.spawn(`
|
|
409
|
+
* import http from "node:http";
|
|
410
|
+
* http.createServer((_, res) => res.end("ok")).listen(3000);
|
|
411
|
+
* `);
|
|
412
|
+
* const listener = await rt.waitForListener({ port: 3000 });
|
|
413
|
+
* const res = await rt.fetch(listener.port ?? 3000, { path: "/" });
|
|
414
|
+
* server.kill();
|
|
415
|
+
* await server.wait();
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
waitForListener(query: NodeRuntimeListenerQuery, options?: NodeRuntimeWaitForListenerOptions): Promise<NodeRuntimeListener>;
|
|
419
|
+
/**
|
|
420
|
+
* Register host-side tools the guest can invoke as shell commands, after the
|
|
421
|
+
* VM is already running. Each entry becomes a named guest command; when the
|
|
422
|
+
* guest runs it, the invocation round-trips back to the host and runs the
|
|
423
|
+
* tool's `handler`, whose return value is delivered back to the guest. This
|
|
424
|
+
* is the same capability as the `tools` create option, exposed for adding
|
|
425
|
+
* tools to a live runtime. See `tools` on {@link NodeRuntime.create} for the
|
|
426
|
+
* invocation shape and permission behavior.
|
|
427
|
+
*
|
|
428
|
+
* When registering tools this way, make sure the `tool` permission scope is
|
|
429
|
+
* granted (for example `permissions: { tool: "allow" }` on
|
|
430
|
+
* {@link NodeRuntime.create}) so the tools are invocable.
|
|
431
|
+
*/
|
|
432
|
+
registerTools(tools: Record<string, HostToolDefinition>): Promise<void>;
|
|
433
|
+
/**
|
|
434
|
+
* Write a file into the VM's virtual filesystem, creating parent
|
|
435
|
+
* directories as needed. Use this to project assets or npm packages into
|
|
436
|
+
* the sandbox after boot; the host filesystem is never touched.
|
|
437
|
+
*/
|
|
438
|
+
writeFile(filePath: string, content: string | Uint8Array): Promise<void>;
|
|
439
|
+
/** Read a file from the VM's virtual filesystem as raw bytes. */
|
|
440
|
+
readFile(filePath: string): Promise<Uint8Array>;
|
|
441
|
+
/** Tear down the VM and release the sidecar. */
|
|
442
|
+
dispose(): Promise<void>;
|
|
443
|
+
}
|