@watasu/sdk 0.1.6 → 0.1.25
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 +122 -8
- package/dist/codeInterpreter.d.ts +100 -0
- package/dist/codeInterpreter.js +241 -0
- package/dist/commands.d.ts +26 -4
- package/dist/commands.js +54 -15
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +8 -0
- package/dist/filesystem.d.ts +53 -13
- package/dist/filesystem.js +128 -12
- package/dist/git.d.ts +27 -0
- package/dist/git.js +43 -2
- package/dist/index.d.ts +15 -6
- package/dist/index.js +8 -3
- package/dist/process.d.ts +56 -0
- package/dist/process.js +137 -0
- package/dist/processSocket.d.ts +1 -0
- package/dist/processSocket.js +3 -0
- package/dist/pty.d.ts +5 -1
- package/dist/pty.js +9 -7
- package/dist/sandbox.d.ts +121 -20
- package/dist/sandbox.js +254 -43
- package/dist/template.d.ts +232 -0
- package/dist/template.js +624 -0
- package/dist/terminal.d.ts +41 -0
- package/dist/terminal.js +74 -0
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +3 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@ Set `WATASU_API_KEY` before using the SDK.
|
|
|
16
16
|
import { Sandbox } from '@watasu/sdk'
|
|
17
17
|
|
|
18
18
|
const sbx = await Sandbox.create()
|
|
19
|
-
await sbx.
|
|
20
|
-
const result = await sbx.
|
|
19
|
+
await sbx.filesystem.write('/home/user/a.js', 'console.log(2 + 2)')
|
|
20
|
+
const result = await sbx.process.startAndWait('node /home/user/a.js')
|
|
21
21
|
console.log(result.stdout)
|
|
22
22
|
console.log(await sbx.isRunning())
|
|
23
23
|
await sbx.kill()
|
|
@@ -26,11 +26,67 @@ await sbx.kill()
|
|
|
26
26
|
`Sandbox.create` and `Sandbox.connect` return only after the Watasu API supplies
|
|
27
27
|
a usable data-plane session. The SDK does not poll sandbox readiness.
|
|
28
28
|
|
|
29
|
+
```ts
|
|
30
|
+
await sbx.betaPause()
|
|
31
|
+
await sbx.resume({ timeoutMs: 300_000 })
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Code Interpreter
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Sandbox } from '@watasu/sdk/code-interpreter'
|
|
38
|
+
|
|
39
|
+
const sbx = await Sandbox.create()
|
|
40
|
+
const execution = await sbx.runCode("print('hello')\n2 + 3", {
|
|
41
|
+
language: 'python',
|
|
42
|
+
onStdout: (message) => console.log(message.line),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
console.log(execution.text)
|
|
46
|
+
await sbx.kill()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`@watasu/sdk/code-interpreter` starts the `code-interpreter` template by default
|
|
50
|
+
and returns structured `results`, `logs`, and `error` fields for each execution.
|
|
51
|
+
|
|
52
|
+
## MCP Gateway
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const sbx = await Sandbox.create({
|
|
56
|
+
mcp: {
|
|
57
|
+
github: {
|
|
58
|
+
command: 'github-mcp-server',
|
|
59
|
+
args: ['stdio'],
|
|
60
|
+
env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
console.log(sbx.getMcpUrl())
|
|
66
|
+
console.log(await sbx.getMcpToken())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Listing Sandboxes
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { Sandbox } from '@watasu/sdk'
|
|
73
|
+
|
|
74
|
+
const paginator = Sandbox.list({
|
|
75
|
+
query: { metadata: { purpose: 'ci' }, state: ['running'] },
|
|
76
|
+
limit: 20,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
for (const sandbox of await paginator.listItems()) {
|
|
80
|
+
console.log(sandbox.sandboxId, sandbox.state)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
29
84
|
## Git, Watch, PTY, And Signed File URLs
|
|
30
85
|
|
|
31
86
|
```ts
|
|
32
87
|
const sbx = await Sandbox.create()
|
|
33
88
|
|
|
89
|
+
await sbx.git.init('/workspace/new-project', { initialBranch: 'main' })
|
|
34
90
|
await sbx.git.clone('https://github.com/acme/project.git', {
|
|
35
91
|
path: '/workspace/project',
|
|
36
92
|
branch: 'main',
|
|
@@ -52,17 +108,26 @@ await sbx.git.push('/workspace/project', {
|
|
|
52
108
|
branch: 'feature/docs',
|
|
53
109
|
setUpstream: true,
|
|
54
110
|
})
|
|
111
|
+
const remoteUrl = await sbx.git.remoteGet('/workspace/project', 'origin')
|
|
112
|
+
await sbx.git.restore('/workspace/project', { paths: ['README.md'] })
|
|
113
|
+
await sbx.git.reset('/workspace/project', { mode: 'hard', target: 'HEAD' })
|
|
114
|
+
|
|
115
|
+
await sbx.filesystem.writeFiles([
|
|
116
|
+
{ path: '/workspace/project/a.txt', data: 'alpha' },
|
|
117
|
+
{ path: '/workspace/project/b.bin', data: new Uint8Array([0, 1, 2]) },
|
|
118
|
+
])
|
|
55
119
|
|
|
56
|
-
const watcher =
|
|
120
|
+
const watcher = sbx.filesystem.watchDir('/workspace/project')
|
|
121
|
+
watcher.addEventListener((event) => {
|
|
57
122
|
console.log(event.type, event.path)
|
|
58
|
-
}
|
|
123
|
+
})
|
|
124
|
+
await watcher.start({ recursive: true })
|
|
59
125
|
|
|
60
|
-
const terminal = await sbx.
|
|
61
|
-
cols: 100,
|
|
62
|
-
rows: 30,
|
|
126
|
+
const terminal = await sbx.terminal.start({
|
|
127
|
+
size: { cols: 100, rows: 30 },
|
|
63
128
|
onData: (data) => process.stdout.write(data),
|
|
64
129
|
})
|
|
65
|
-
await terminal.
|
|
130
|
+
await terminal.sendData('echo hello\n')
|
|
66
131
|
|
|
67
132
|
const uploadUrl = await sbx.uploadUrl('/workspace/input.bin')
|
|
68
133
|
const downloadUrl = await sbx.downloadUrl('/workspace/output.bin')
|
|
@@ -72,6 +137,54 @@ await terminal.kill()
|
|
|
72
137
|
await sbx.kill()
|
|
73
138
|
```
|
|
74
139
|
|
|
140
|
+
## Network Policy
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const sbx = await Sandbox.create({
|
|
144
|
+
network: {
|
|
145
|
+
allowOut: ['pypi.org:443'],
|
|
146
|
+
denyOut: ['169.254.169.254'],
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await sbx.updateNetwork({
|
|
151
|
+
allowInternetAccess: false,
|
|
152
|
+
allowPackageRegistryAccess: true,
|
|
153
|
+
allowOut: ['pypi.org:443', 'registry.npmjs.org:443'],
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Template Builds
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { Template } from '@watasu/sdk'
|
|
161
|
+
|
|
162
|
+
const template = Template()
|
|
163
|
+
.fromPythonImage('3.12')
|
|
164
|
+
.copy('requirements.txt', '/workspace/requirements.txt')
|
|
165
|
+
.aptInstall(['git'])
|
|
166
|
+
.pipInstall(['pytest'])
|
|
167
|
+
.setEnvs({ PIP_DISABLE_PIP_VERSION_CHECK: '1' })
|
|
168
|
+
.runCmd('echo ready')
|
|
169
|
+
|
|
170
|
+
const build = await Template.buildInBackground(template, 'python-ci:stable', {
|
|
171
|
+
tags: ['stable'],
|
|
172
|
+
cpuCount: 2,
|
|
173
|
+
memoryMB: 2048,
|
|
174
|
+
})
|
|
175
|
+
const status = await Template.getBuildStatus(build)
|
|
176
|
+
|
|
177
|
+
await Template.assignTags('python-ci:stable', ['prod'])
|
|
178
|
+
console.log(await Template.exists('python-ci'))
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Template names resolve server-side. `python-ci` starts the latest ready build;
|
|
182
|
+
`python-ci:stable` starts the tagged build.
|
|
183
|
+
|
|
184
|
+
`Template({ fileContextPath: process.cwd() }).fromDockerfile('Dockerfile')`
|
|
185
|
+
parses common `FROM`, `WORKDIR`, `COPY`, `RUN`, `ENV`, `CMD`, and `ENTRYPOINT`
|
|
186
|
+
instructions into Watasu's package-spec builder.
|
|
187
|
+
|
|
75
188
|
## Metrics And Snapshots
|
|
76
189
|
|
|
77
190
|
```ts
|
|
@@ -81,6 +194,7 @@ const sbx = await Sandbox.create()
|
|
|
81
194
|
const metrics = await sbx.getMetrics()
|
|
82
195
|
const snapshot = await sbx.createSnapshot({ name: 'ready' })
|
|
83
196
|
const snapshots = await sbx.listSnapshots().nextItems()
|
|
197
|
+
const allSnapshots = await Sandbox.listSnapshots({ limit: 100 }).nextItems()
|
|
84
198
|
const restored = await sbx.restore({ snapshotId: snapshot.snapshotId })
|
|
85
199
|
await sbx.deleteSnapshot(snapshot.snapshotId)
|
|
86
200
|
await sbx.kill()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Sandbox as BaseSandbox, SandboxConnectOpts, SandboxCreateOpts } from './sandbox.js';
|
|
2
|
+
export type RunCodeLanguage = 'python' | 'python3' | string;
|
|
3
|
+
export interface RunCodeOpts {
|
|
4
|
+
language?: RunCodeLanguage;
|
|
5
|
+
context?: Context;
|
|
6
|
+
onStdout?: (message: OutputMessage) => void;
|
|
7
|
+
onStderr?: (message: OutputMessage) => void;
|
|
8
|
+
onResult?: (result: Result) => void;
|
|
9
|
+
onError?: (error: ExecutionError) => void;
|
|
10
|
+
envs?: Record<string, string>;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
requestTimeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CreateCodeContextOpts {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
language?: RunCodeLanguage;
|
|
17
|
+
requestTimeoutMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/** One stdout or stderr line emitted by code execution. */
|
|
20
|
+
export declare class OutputMessage {
|
|
21
|
+
readonly line: string;
|
|
22
|
+
readonly timestamp: number;
|
|
23
|
+
readonly error: boolean;
|
|
24
|
+
constructor(line: string, timestamp?: number, error?: boolean);
|
|
25
|
+
toString(): string;
|
|
26
|
+
toJSON(): Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/** Structured exception raised by user code inside the sandbox. */
|
|
29
|
+
export declare class ExecutionError {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly value: string;
|
|
32
|
+
readonly traceback: string;
|
|
33
|
+
constructor(name: string, value: string, traceback: string);
|
|
34
|
+
toJSON(): Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
/** Rich result produced by the last expression of a code execution. */
|
|
37
|
+
export declare class Result {
|
|
38
|
+
readonly text?: string;
|
|
39
|
+
readonly html?: string;
|
|
40
|
+
readonly markdown?: string;
|
|
41
|
+
readonly svg?: string;
|
|
42
|
+
readonly png?: string;
|
|
43
|
+
readonly jpeg?: string;
|
|
44
|
+
readonly pdf?: string;
|
|
45
|
+
readonly latex?: string;
|
|
46
|
+
readonly json?: unknown;
|
|
47
|
+
readonly javascript?: string;
|
|
48
|
+
readonly data?: unknown;
|
|
49
|
+
readonly chart?: unknown;
|
|
50
|
+
readonly extra: Record<string, unknown>;
|
|
51
|
+
readonly isMainResult: boolean;
|
|
52
|
+
constructor(payload?: Record<string, unknown>);
|
|
53
|
+
formats(): string[];
|
|
54
|
+
toJSON(): Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
export interface Logs {
|
|
57
|
+
stdout: OutputMessage[];
|
|
58
|
+
stderr: OutputMessage[];
|
|
59
|
+
}
|
|
60
|
+
/** Complete result of a sandbox code execution. */
|
|
61
|
+
export declare class Execution {
|
|
62
|
+
readonly results: Result[];
|
|
63
|
+
readonly logs: Logs;
|
|
64
|
+
readonly error: ExecutionError | undefined;
|
|
65
|
+
readonly executionCount: number | undefined;
|
|
66
|
+
constructor(results?: Result[], logs?: Logs, error?: ExecutionError | undefined, executionCount?: number | undefined);
|
|
67
|
+
get text(): string | undefined;
|
|
68
|
+
toJSON(): Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
/** Code execution context metadata. */
|
|
71
|
+
export declare class Context {
|
|
72
|
+
readonly id: string;
|
|
73
|
+
readonly language?: string | undefined;
|
|
74
|
+
readonly cwd?: string | undefined;
|
|
75
|
+
constructor(id: string, language?: string | undefined, cwd?: string | undefined);
|
|
76
|
+
toJSON(): Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
/** Sandbox specialized for running Python code. */
|
|
79
|
+
export declare class Sandbox extends BaseSandbox {
|
|
80
|
+
static readonly defaultTemplate = "code-interpreter";
|
|
81
|
+
static create(opts?: SandboxCreateOpts): Promise<Sandbox>;
|
|
82
|
+
static create(template: string, opts?: SandboxCreateOpts): Promise<Sandbox>;
|
|
83
|
+
static connect(sandboxId: string, opts?: SandboxConnectOpts): Promise<Sandbox>;
|
|
84
|
+
/** Run Python code in the sandbox and return structured execution output. */
|
|
85
|
+
runCode(code: string, opts?: RunCodeOpts): Promise<Execution>;
|
|
86
|
+
/** Create a persistent code context. */
|
|
87
|
+
createCodeContext(_opts?: CreateCodeContextOpts): Promise<Context>;
|
|
88
|
+
/** Remove a persistent code context. */
|
|
89
|
+
removeCodeContext(_context: Context, _opts?: {
|
|
90
|
+
requestTimeoutMs?: number;
|
|
91
|
+
}): Promise<boolean>;
|
|
92
|
+
/** List persistent code contexts. */
|
|
93
|
+
listCodeContexts(_opts?: {
|
|
94
|
+
requestTimeoutMs?: number;
|
|
95
|
+
}): Promise<Context[]>;
|
|
96
|
+
/** Restart a persistent code context. */
|
|
97
|
+
restartCodeContext(_context: Context, _opts?: {
|
|
98
|
+
requestTimeoutMs?: number;
|
|
99
|
+
}): Promise<Context>;
|
|
100
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { InvalidArgumentError, NotImplementedError } from './errors.js';
|
|
2
|
+
import { Sandbox as BaseSandbox } from './sandbox.js';
|
|
3
|
+
/** One stdout or stderr line emitted by code execution. */
|
|
4
|
+
export class OutputMessage {
|
|
5
|
+
line;
|
|
6
|
+
timestamp;
|
|
7
|
+
error;
|
|
8
|
+
constructor(line, timestamp = Date.now() / 1000, error = false) {
|
|
9
|
+
this.line = line;
|
|
10
|
+
this.timestamp = timestamp;
|
|
11
|
+
this.error = error;
|
|
12
|
+
}
|
|
13
|
+
toString() {
|
|
14
|
+
return this.line;
|
|
15
|
+
}
|
|
16
|
+
toJSON() {
|
|
17
|
+
return {
|
|
18
|
+
line: this.line,
|
|
19
|
+
timestamp: this.timestamp,
|
|
20
|
+
error: this.error,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Structured exception raised by user code inside the sandbox. */
|
|
25
|
+
export class ExecutionError {
|
|
26
|
+
name;
|
|
27
|
+
value;
|
|
28
|
+
traceback;
|
|
29
|
+
constructor(name, value, traceback) {
|
|
30
|
+
this.name = name;
|
|
31
|
+
this.value = value;
|
|
32
|
+
this.traceback = traceback;
|
|
33
|
+
}
|
|
34
|
+
toJSON() {
|
|
35
|
+
return {
|
|
36
|
+
name: this.name,
|
|
37
|
+
value: this.value,
|
|
38
|
+
traceback: this.traceback,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Rich result produced by the last expression of a code execution. */
|
|
43
|
+
export class Result {
|
|
44
|
+
text;
|
|
45
|
+
html;
|
|
46
|
+
markdown;
|
|
47
|
+
svg;
|
|
48
|
+
png;
|
|
49
|
+
jpeg;
|
|
50
|
+
pdf;
|
|
51
|
+
latex;
|
|
52
|
+
json;
|
|
53
|
+
javascript;
|
|
54
|
+
data;
|
|
55
|
+
chart;
|
|
56
|
+
extra;
|
|
57
|
+
isMainResult;
|
|
58
|
+
constructor(payload = {}) {
|
|
59
|
+
this.text = stringValue(payload.text);
|
|
60
|
+
this.html = stringValue(payload.html);
|
|
61
|
+
this.markdown = stringValue(payload.markdown);
|
|
62
|
+
this.svg = stringValue(payload.svg);
|
|
63
|
+
this.png = stringValue(payload.png);
|
|
64
|
+
this.jpeg = stringValue(payload.jpeg);
|
|
65
|
+
this.pdf = stringValue(payload.pdf);
|
|
66
|
+
this.latex = stringValue(payload.latex);
|
|
67
|
+
this.json = payload.json;
|
|
68
|
+
this.javascript = stringValue(payload.javascript);
|
|
69
|
+
this.data = payload.data;
|
|
70
|
+
this.chart = payload.chart;
|
|
71
|
+
this.extra = record(payload.extra);
|
|
72
|
+
this.isMainResult = Boolean(payload.is_main_result ?? payload.isMainResult);
|
|
73
|
+
}
|
|
74
|
+
formats() {
|
|
75
|
+
const names = ['text', 'html', 'markdown', 'svg', 'png', 'jpeg', 'pdf', 'latex', 'json', 'javascript', 'data', 'chart'];
|
|
76
|
+
return names.filter((name) => this[name] !== undefined);
|
|
77
|
+
}
|
|
78
|
+
toJSON() {
|
|
79
|
+
return compactRecord({
|
|
80
|
+
text: this.text,
|
|
81
|
+
html: this.html,
|
|
82
|
+
markdown: this.markdown,
|
|
83
|
+
svg: this.svg,
|
|
84
|
+
png: this.png,
|
|
85
|
+
jpeg: this.jpeg,
|
|
86
|
+
pdf: this.pdf,
|
|
87
|
+
latex: this.latex,
|
|
88
|
+
json: this.json,
|
|
89
|
+
javascript: this.javascript,
|
|
90
|
+
data: this.data,
|
|
91
|
+
chart: this.chart,
|
|
92
|
+
extra: Object.keys(this.extra).length === 0 ? undefined : this.extra,
|
|
93
|
+
is_main_result: this.isMainResult,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Complete result of a sandbox code execution. */
|
|
98
|
+
export class Execution {
|
|
99
|
+
results;
|
|
100
|
+
logs;
|
|
101
|
+
error;
|
|
102
|
+
executionCount;
|
|
103
|
+
constructor(results = [], logs = { stdout: [], stderr: [] }, error = undefined, executionCount = undefined) {
|
|
104
|
+
this.results = results;
|
|
105
|
+
this.logs = logs;
|
|
106
|
+
this.error = error;
|
|
107
|
+
this.executionCount = executionCount;
|
|
108
|
+
}
|
|
109
|
+
get text() {
|
|
110
|
+
return this.results.find((result) => result.isMainResult && result.text !== undefined)?.text ??
|
|
111
|
+
this.results.find((result) => result.text !== undefined)?.text;
|
|
112
|
+
}
|
|
113
|
+
toJSON() {
|
|
114
|
+
return {
|
|
115
|
+
results: this.results.map((result) => result.toJSON()),
|
|
116
|
+
logs: {
|
|
117
|
+
stdout: this.logs.stdout.map((message) => message.toJSON()),
|
|
118
|
+
stderr: this.logs.stderr.map((message) => message.toJSON()),
|
|
119
|
+
},
|
|
120
|
+
error: this.error?.toJSON() ?? null,
|
|
121
|
+
execution_count: this.executionCount,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Code execution context metadata. */
|
|
126
|
+
export class Context {
|
|
127
|
+
id;
|
|
128
|
+
language;
|
|
129
|
+
cwd;
|
|
130
|
+
constructor(id, language, cwd) {
|
|
131
|
+
this.id = id;
|
|
132
|
+
this.language = language;
|
|
133
|
+
this.cwd = cwd;
|
|
134
|
+
}
|
|
135
|
+
toJSON() {
|
|
136
|
+
return compactRecord({
|
|
137
|
+
id: this.id,
|
|
138
|
+
language: this.language,
|
|
139
|
+
cwd: this.cwd,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Sandbox specialized for running Python code. */
|
|
144
|
+
export class Sandbox extends BaseSandbox {
|
|
145
|
+
static defaultTemplate = 'code-interpreter';
|
|
146
|
+
static async create(templateOrOpts, opts = {}) {
|
|
147
|
+
return await super.create(templateOrOpts, opts);
|
|
148
|
+
}
|
|
149
|
+
static async connect(sandboxId, opts = {}) {
|
|
150
|
+
return await super.connect(sandboxId, opts);
|
|
151
|
+
}
|
|
152
|
+
/** Run Python code in the sandbox and return structured execution output. */
|
|
153
|
+
async runCode(code, opts = {}) {
|
|
154
|
+
if (typeof code !== 'string')
|
|
155
|
+
throw new InvalidArgumentError('code must be a string');
|
|
156
|
+
if (opts.language !== undefined && opts.context !== undefined) {
|
|
157
|
+
throw new InvalidArgumentError('language and context cannot both be set');
|
|
158
|
+
}
|
|
159
|
+
const payload = compactRecord({
|
|
160
|
+
code,
|
|
161
|
+
language: opts.language,
|
|
162
|
+
context_id: contextId(opts.context),
|
|
163
|
+
env_vars: opts.envs,
|
|
164
|
+
timeout_seconds: opts.timeout,
|
|
165
|
+
});
|
|
166
|
+
const response = await this.runtimePostJson('/runtime/v1/code/run', payload, {
|
|
167
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
168
|
+
});
|
|
169
|
+
const execution = executionFromApi(response);
|
|
170
|
+
emitCallbacks(execution, opts);
|
|
171
|
+
return execution;
|
|
172
|
+
}
|
|
173
|
+
/** Create a persistent code context. */
|
|
174
|
+
async createCodeContext(_opts = {}) {
|
|
175
|
+
throw new NotImplementedError('code contexts are not supported by Watasu yet');
|
|
176
|
+
}
|
|
177
|
+
/** Remove a persistent code context. */
|
|
178
|
+
async removeCodeContext(_context, _opts = {}) {
|
|
179
|
+
throw new NotImplementedError('code contexts are not supported by Watasu yet');
|
|
180
|
+
}
|
|
181
|
+
/** List persistent code contexts. */
|
|
182
|
+
async listCodeContexts(_opts = {}) {
|
|
183
|
+
throw new NotImplementedError('code contexts are not supported by Watasu yet');
|
|
184
|
+
}
|
|
185
|
+
/** Restart a persistent code context. */
|
|
186
|
+
async restartCodeContext(_context, _opts = {}) {
|
|
187
|
+
throw new NotImplementedError('code contexts are not supported by Watasu yet');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function executionFromApi(payload) {
|
|
191
|
+
const execution = record(payload.execution ?? payload);
|
|
192
|
+
const logs = record(execution.logs);
|
|
193
|
+
return new Execution(arrayOfRecords(execution.results).map((item) => new Result(item)), {
|
|
194
|
+
stdout: arrayOfUnknown(logs.stdout).map((item) => outputMessageFromApi(item, false)),
|
|
195
|
+
stderr: arrayOfUnknown(logs.stderr).map((item) => outputMessageFromApi(item, true)),
|
|
196
|
+
}, executionErrorFromApi(execution.error), numberValue(execution.execution_count ?? execution.executionCount));
|
|
197
|
+
}
|
|
198
|
+
function outputMessageFromApi(value, error) {
|
|
199
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
200
|
+
const item = value;
|
|
201
|
+
return new OutputMessage(String(item.line ?? ''), numberValue(item.timestamp) ?? Date.now() / 1000, Boolean(item.error ?? error));
|
|
202
|
+
}
|
|
203
|
+
return new OutputMessage(String(value), Date.now() / 1000, error);
|
|
204
|
+
}
|
|
205
|
+
function executionErrorFromApi(value) {
|
|
206
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
207
|
+
return undefined;
|
|
208
|
+
const item = value;
|
|
209
|
+
return new ExecutionError(String(item.name ?? ''), String(item.value ?? ''), String(item.traceback ?? ''));
|
|
210
|
+
}
|
|
211
|
+
function emitCallbacks(execution, opts) {
|
|
212
|
+
for (const message of execution.logs.stdout)
|
|
213
|
+
opts.onStdout?.(message);
|
|
214
|
+
for (const message of execution.logs.stderr)
|
|
215
|
+
opts.onStderr?.(message);
|
|
216
|
+
for (const result of execution.results)
|
|
217
|
+
opts.onResult?.(result);
|
|
218
|
+
if (execution.error !== undefined)
|
|
219
|
+
opts.onError?.(execution.error);
|
|
220
|
+
}
|
|
221
|
+
function contextId(context) {
|
|
222
|
+
return context?.id;
|
|
223
|
+
}
|
|
224
|
+
function compactRecord(payload) {
|
|
225
|
+
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined));
|
|
226
|
+
}
|
|
227
|
+
function record(value) {
|
|
228
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
229
|
+
}
|
|
230
|
+
function arrayOfRecords(value) {
|
|
231
|
+
return Array.isArray(value) ? value.map((item) => record(item)) : [];
|
|
232
|
+
}
|
|
233
|
+
function arrayOfUnknown(value) {
|
|
234
|
+
return Array.isArray(value) ? value : [];
|
|
235
|
+
}
|
|
236
|
+
function stringValue(value) {
|
|
237
|
+
return typeof value === 'string' ? value : undefined;
|
|
238
|
+
}
|
|
239
|
+
function numberValue(value) {
|
|
240
|
+
return typeof value === 'number' ? value : undefined;
|
|
241
|
+
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -29,14 +29,26 @@ export interface ProcessInfo {
|
|
|
29
29
|
export interface CommandStartOpts {
|
|
30
30
|
/** Return a `CommandHandle` immediately instead of waiting for exit. */
|
|
31
31
|
background?: boolean;
|
|
32
|
+
/** Executable to start directly. When omitted, `cmd` strings run through a login shell. */
|
|
33
|
+
cmd?: string;
|
|
34
|
+
/** Arguments for `cmd` when starting a direct executable. */
|
|
35
|
+
args?: string[];
|
|
32
36
|
cwd?: string;
|
|
37
|
+
/** Deprecated alias for `cwd`. */
|
|
38
|
+
rootDir?: string;
|
|
33
39
|
user?: string;
|
|
34
40
|
envs?: Record<string, string>;
|
|
41
|
+
/** Alias for `envs`. */
|
|
42
|
+
envVars?: Record<string, string>;
|
|
35
43
|
onStdout?: (data: string) => void | Promise<void>;
|
|
36
44
|
onStderr?: (data: string) => void | Promise<void>;
|
|
37
45
|
onPty?: (data: Uint8Array) => void | Promise<void>;
|
|
46
|
+
onExit?: (exitCode: number) => void | Promise<void>;
|
|
38
47
|
stdin?: boolean;
|
|
39
48
|
timeoutMs?: number;
|
|
49
|
+
/** Alias for `timeoutMs`. */
|
|
50
|
+
timeout?: number;
|
|
51
|
+
processID?: string;
|
|
40
52
|
requestTimeoutMs?: number;
|
|
41
53
|
}
|
|
42
54
|
/** Live handle for one sandbox process stream. */
|
|
@@ -48,28 +60,31 @@ export declare class CommandHandle implements Partial<CommandResult> {
|
|
|
48
60
|
private readonly onStdout?;
|
|
49
61
|
private readonly onStderr?;
|
|
50
62
|
private readonly onPty?;
|
|
63
|
+
private readonly onExit?;
|
|
51
64
|
private _stdout;
|
|
52
65
|
private _stderr;
|
|
53
66
|
private result?;
|
|
54
67
|
private readonly pending;
|
|
55
|
-
constructor(pid: number | string, socket: ProcessSocket, handleKill: () => Promise<boolean>, events: AsyncIterable<ProcessFrame>, onStdout?: ((data: string) => void | Promise<void>) | undefined, onStderr?: ((data: string) => void | Promise<void>) | undefined, onPty?: ((data: Uint8Array) => void | Promise<void>) | undefined);
|
|
68
|
+
constructor(pid: number | string, socket: ProcessSocket, handleKill: () => Promise<boolean>, events: AsyncIterable<ProcessFrame>, onStdout?: ((data: string) => void | Promise<void>) | undefined, onStderr?: ((data: string) => void | Promise<void>) | undefined, onPty?: ((data: Uint8Array) => void | Promise<void>) | undefined, onExit?: ((exitCode: number) => void | Promise<void>) | undefined);
|
|
56
69
|
get stdout(): string;
|
|
57
70
|
get stderr(): string;
|
|
58
71
|
get exitCode(): number | undefined;
|
|
59
72
|
get error(): string | undefined;
|
|
60
73
|
/** Wait until the process exits and return captured output. */
|
|
61
|
-
wait(): Promise<CommandResult>;
|
|
74
|
+
wait(timeoutMs?: number): Promise<CommandResult>;
|
|
62
75
|
/** Kill the process. */
|
|
63
76
|
kill(): Promise<boolean>;
|
|
64
77
|
/** Send stdin bytes or text to the process. */
|
|
65
78
|
sendStdin(data: string | Uint8Array): Promise<void>;
|
|
79
|
+
/** Close the stdin stream and signal EOF to the process. */
|
|
80
|
+
closeStdin(): Promise<void>;
|
|
66
81
|
/** Resize the attached PTY stream when this handle was created as a PTY. */
|
|
67
82
|
resize(size: {
|
|
68
83
|
cols: number;
|
|
69
84
|
rows: number;
|
|
70
85
|
}): Promise<void>;
|
|
71
86
|
/** Detach the local stream without killing the process. */
|
|
72
|
-
disconnect(): void
|
|
87
|
+
disconnect(): Promise<void>;
|
|
73
88
|
private handleEvents;
|
|
74
89
|
}
|
|
75
90
|
/** Command runner for a sandbox data-plane session. */
|
|
@@ -78,6 +93,8 @@ export declare class Commands {
|
|
|
78
93
|
private readonly config;
|
|
79
94
|
private readonly sandboxEnvs;
|
|
80
95
|
constructor(dataPlane: DataPlaneClient, config: ConnectionConfig, sandboxEnvs?: Record<string, string>);
|
|
96
|
+
/** Whether this runtime supports stdin EOF frames. */
|
|
97
|
+
get supportsStdinClose(): boolean;
|
|
81
98
|
/** List processes currently known by the sandbox runtime. */
|
|
82
99
|
list(opts?: {
|
|
83
100
|
requestTimeoutMs?: number;
|
|
@@ -90,11 +107,16 @@ export declare class Commands {
|
|
|
90
107
|
sendStdin(pid: number | string, data: string | Uint8Array, opts?: {
|
|
91
108
|
requestTimeoutMs?: number;
|
|
92
109
|
}): Promise<void>;
|
|
110
|
+
/** Attach to a process and close stdin, signalling EOF. */
|
|
111
|
+
closeStdin(pid: number | string, opts?: {
|
|
112
|
+
requestTimeoutMs?: number;
|
|
113
|
+
}): Promise<void>;
|
|
93
114
|
run(cmd: string, opts: CommandStartOpts & {
|
|
94
115
|
background: true;
|
|
95
116
|
}): Promise<CommandHandle>;
|
|
96
117
|
run(cmd: string, opts?: CommandStartOpts): Promise<CommandResult>;
|
|
97
118
|
/** Reconnect to a live process stream by pid. */
|
|
98
119
|
connect(pid: number | string, opts?: CommandStartOpts): Promise<CommandHandle>;
|
|
99
|
-
|
|
120
|
+
/** Start a command and return a live handle immediately. */
|
|
121
|
+
start(cmd: string, opts?: CommandStartOpts): Promise<CommandHandle>;
|
|
100
122
|
}
|