@watasu/sdk 0.1.6 → 0.1.24

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.
@@ -1,6 +1,6 @@
1
1
  import { withQuery } from './transport.js';
2
- import { FileNotFoundError } from './errors.js';
3
- import { ProcessSocket } from './processSocket.js';
2
+ import { FileNotFoundError, InvalidArgumentError } from './errors.js';
3
+ import { ProcessSocket, base64Encode } from './processSocket.js';
4
4
  export var FileType;
5
5
  (function (FileType) {
6
6
  /** Regular file. */
@@ -50,27 +50,93 @@ export class WatchHandle {
50
50
  }
51
51
  }
52
52
  }
53
+ /** Lazy filesystem watcher. Add listeners, then call `start()`. */
54
+ export class FilesystemWatcher {
55
+ dataPlane;
56
+ path;
57
+ opts;
58
+ handle;
59
+ listeners = [];
60
+ constructor(dataPlane, path, opts = {}) {
61
+ this.dataPlane = dataPlane;
62
+ this.path = path;
63
+ this.opts = opts;
64
+ }
65
+ async start(opts = {}) {
66
+ if (this.handle)
67
+ return;
68
+ const nextOpts = { ...this.opts, ...opts };
69
+ const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, withQuery('/runtime/v1/files/watch', { path: this.path, recursive: nextOpts.recursive ?? false, include_entry: nextOpts.includeEntry }), nextOpts.requestTimeoutMs).connect();
70
+ this.handle = new WatchHandle(socket, socket, async (event) => {
71
+ for (const listener of this.listeners)
72
+ await listener(event);
73
+ }, nextOpts.onExit);
74
+ }
75
+ async stop() {
76
+ this.handle?.stop();
77
+ }
78
+ addEventListener(listener) {
79
+ this.listeners.push(listener);
80
+ return () => {
81
+ const index = this.listeners.indexOf(listener);
82
+ if (index === -1)
83
+ return false;
84
+ this.listeners.splice(index, 1);
85
+ return true;
86
+ };
87
+ }
88
+ wait() {
89
+ return this.handle?.wait() ?? Promise.resolve();
90
+ }
91
+ }
53
92
  /** Filesystem helper for a sandbox data-plane session. */
54
93
  export class Filesystem {
55
94
  dataPlane;
56
95
  constructor(dataPlane) {
57
96
  this.dataPlane = dataPlane;
58
97
  }
59
- /** Read a file as UTF-8 text, bytes, or a one-chunk async byte stream. */
60
98
  async read(path, opts = {}) {
61
99
  const bytes = await this.dataPlane.getBytes(withQuery('/runtime/v1/files', { path, gzip: opts.gzip }), opts);
62
100
  if (opts.format === 'bytes')
63
101
  return bytes;
102
+ if (opts.format === 'blob')
103
+ return new Blob([toArrayBuffer(bytes)]);
64
104
  if (opts.format === 'stream')
65
- return (async function* () { yield bytes; })();
105
+ return new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } });
66
106
  return new TextDecoder().decode(bytes);
67
107
  }
68
- /** Write UTF-8 text or bytes to a file. */
69
- async write(path, data, opts = {}) {
70
- const body = typeof data === 'string' ? new TextEncoder().encode(data) : data;
71
- const payload = await this.dataPlane.putJson(withQuery('/runtime/v1/files', { path, gzip: opts.gzip }), body, opts);
108
+ /** Read a file as raw bytes. */
109
+ async readBytes(path, opts = {}) {
110
+ return this.read(path, { ...opts, format: 'bytes' });
111
+ }
112
+ async write(pathOrFiles, dataOrOpts, opts = {}) {
113
+ if (Array.isArray(pathOrFiles)) {
114
+ return this.writeFiles(pathOrFiles, dataOrOpts);
115
+ }
116
+ const body = await writeDataToBytes(dataOrOpts);
117
+ const payload = await this.dataPlane.putJson(withQuery('/runtime/v1/files', { path: pathOrFiles, gzip: opts.gzip }), body, opts);
72
118
  return entryInfo(payload.file ?? payload);
73
119
  }
120
+ /** Write raw bytes to a file. */
121
+ async writeBytes(path, data, opts = {}) {
122
+ return this.write(path, data, opts);
123
+ }
124
+ /** Write several files in one runtime API call. */
125
+ async writeFiles(files, opts = {}) {
126
+ if (files.length === 0)
127
+ return [];
128
+ const payload = await this.dataPlane.postJson('/runtime/v1/files/write_files', {
129
+ ...opts,
130
+ json: {
131
+ files: await Promise.all(files.map(async (file) => ({
132
+ path: file.path,
133
+ data_base64: base64Encode(await writeDataToBytes(file.data)),
134
+ }))),
135
+ },
136
+ });
137
+ const written = Array.isArray(payload.files) ? payload.files : [];
138
+ return written.map(entryInfo);
139
+ }
74
140
  /** List directory entries below `path`. */
75
141
  async list(path, opts = {}) {
76
142
  const payload = await this.dataPlane.getJson(withQuery('/runtime/v1/directories', { path, depth: opts.depth }), opts);
@@ -111,11 +177,61 @@ export class Filesystem {
111
177
  await this.dataPlane.postJson(withQuery('/runtime/v1/directories', { path }), opts);
112
178
  return true;
113
179
  }
114
- /** Start watching a directory for filesystem events. */
115
- async watchDir(path, onEvent, opts = {}) {
116
- const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, withQuery('/runtime/v1/files/watch', { path, recursive: opts.recursive ?? false, include_entry: opts.includeEntry }), opts.requestTimeoutMs).connect();
117
- return new WatchHandle(socket, socket, onEvent, opts.onExit);
180
+ watchDir(path, onEvent, opts = {}) {
181
+ const watcher = new FilesystemWatcher(this.dataPlane, path, opts);
182
+ if (!onEvent)
183
+ return watcher;
184
+ watcher.addEventListener(onEvent);
185
+ return watcher.start().then(() => watcher);
186
+ }
187
+ }
188
+ async function writeDataToBytes(data) {
189
+ if (typeof data === 'string')
190
+ return new TextEncoder().encode(data);
191
+ if (data instanceof Uint8Array)
192
+ return data;
193
+ if (data instanceof ArrayBuffer)
194
+ return new Uint8Array(data);
195
+ if (typeof Blob !== 'undefined' && data instanceof Blob)
196
+ return new Uint8Array(await data.arrayBuffer());
197
+ if (isReadableStream(data))
198
+ return readStreamToBytes(data);
199
+ throw new InvalidArgumentError(`Unsupported file data type: ${Object.prototype.toString.call(data)}`);
200
+ }
201
+ function toArrayBuffer(bytes) {
202
+ const buffer = new ArrayBuffer(bytes.byteLength);
203
+ new Uint8Array(buffer).set(bytes);
204
+ return buffer;
205
+ }
206
+ function isReadableStream(value) {
207
+ return Boolean(value && typeof value === 'object' && typeof value.getReader === 'function');
208
+ }
209
+ async function readStreamToBytes(stream) {
210
+ const reader = stream.getReader();
211
+ const chunks = [];
212
+ let total = 0;
213
+ try {
214
+ for (;;) {
215
+ const { done, value } = await reader.read();
216
+ if (done)
217
+ break;
218
+ if (!(value instanceof Uint8Array)) {
219
+ throw new InvalidArgumentError('ReadableStream file data must yield Uint8Array chunks');
220
+ }
221
+ chunks.push(value);
222
+ total += value.byteLength;
223
+ }
224
+ }
225
+ finally {
226
+ reader.releaseLock();
227
+ }
228
+ const bytes = new Uint8Array(total);
229
+ let offset = 0;
230
+ for (const chunk of chunks) {
231
+ bytes.set(chunk, offset);
232
+ offset += chunk.byteLength;
118
233
  }
234
+ return bytes;
119
235
  }
120
236
  function entryInfo(value) {
121
237
  const item = value && typeof value === 'object' ? value : {};
package/dist/git.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface GitAuthOpts {
18
18
  username?: string;
19
19
  password?: string;
20
20
  envs?: Record<string, string>;
21
+ user?: string;
22
+ cwd?: string;
21
23
  timeout?: number;
22
24
  timeoutMs?: number;
23
25
  requestTimeoutMs?: number;
@@ -32,6 +34,10 @@ export interface GitCloneOpts extends GitAuthOpts {
32
34
  }
33
35
  export interface GitRequestOpts extends GitAuthOpts {
34
36
  }
37
+ export interface GitInitOpts extends GitRequestOpts {
38
+ bare?: boolean;
39
+ initialBranch?: string;
40
+ }
35
41
  export interface GitPullOpts extends GitAuthOpts {
36
42
  branch?: string;
37
43
  remote?: string;
@@ -54,12 +60,25 @@ export interface GitBranchOpts extends GitRequestOpts {
54
60
  }
55
61
  export interface GitAddOpts extends GitRequestOpts {
56
62
  files?: string[];
63
+ all?: boolean;
57
64
  }
58
65
  export interface GitCommitOpts extends GitRequestOpts {
59
66
  authorName?: string;
60
67
  authorEmail?: string;
61
68
  allowEmpty?: boolean;
62
69
  }
70
+ export type GitResetMode = 'soft' | 'mixed' | 'hard' | 'merge' | 'keep';
71
+ export interface GitResetOpts extends GitRequestOpts {
72
+ mode?: GitResetMode;
73
+ target?: string;
74
+ paths?: string[];
75
+ }
76
+ export interface GitRestoreOpts extends GitRequestOpts {
77
+ paths: string[];
78
+ staged?: boolean;
79
+ worktree?: boolean;
80
+ source?: string;
81
+ }
63
82
  export interface GitRemoteAddOpts extends GitRequestOpts {
64
83
  fetch?: boolean;
65
84
  overwrite?: boolean;
@@ -114,6 +133,8 @@ export declare class Git {
114
133
  }): Promise<GitCommandResult>;
115
134
  /** Configure Git author identity globally or for one repository. */
116
135
  configureUser(name: string, email: string, opts?: GitConfigureUserOpts): Promise<GitCommandResult>;
136
+ /** Initialize a Git repository. */
137
+ init(path: string, opts?: GitInitOpts): Promise<GitCommandResult>;
117
138
  /** Return parsed repository status for `path`. */
118
139
  status(path: string, opts?: GitRequestOpts): Promise<GitStatus>;
119
140
  /** Return branches and the current branch for `path`. */
@@ -126,6 +147,10 @@ export declare class Git {
126
147
  add(path: string, opts?: GitAddOpts): Promise<GitCommandResult>;
127
148
  /** Commit staged files. */
128
149
  commit(path: string, message: string, opts?: GitCommitOpts): Promise<GitCommandResult>;
150
+ /** Reset the current HEAD to a specified state. */
151
+ reset(path: string, opts?: GitResetOpts): Promise<GitCommandResult>;
152
+ /** Restore working tree files or unstage changes. */
153
+ restore(path: string, opts: GitRestoreOpts): Promise<GitCommandResult>;
129
154
  /** Pull the current branch with a fast-forward-only merge. */
130
155
  pull(path: string, opts?: GitPullOpts): Promise<GitCommandResult>;
131
156
  /** Push the current branch or a selected branch. */
@@ -136,6 +161,8 @@ export declare class Git {
136
161
  checkoutBranch(path: string, branch: string, opts?: GitRequestOpts): Promise<GitCommandResult>;
137
162
  /** Add a remote. */
138
163
  remoteAdd(path: string, name: string, url: string, opts?: GitRemoteAddOpts): Promise<GitCommandResult>;
164
+ /** Return a remote URL, or undefined when the remote does not exist. */
165
+ remoteGet(path: string, name: string, opts?: GitRequestOpts): Promise<string | undefined>;
139
166
  /** Set a Git config value. */
140
167
  setConfig(key: string, value: string, opts?: GitConfigOpts): Promise<GitCommandResult>;
141
168
  /** Read a Git config value. */
package/dist/git.js CHANGED
@@ -33,6 +33,15 @@ export class Git {
33
33
  path: opts.path,
34
34
  }, opts);
35
35
  }
36
+ /** Initialize a Git repository. */
37
+ async init(path, opts = {}) {
38
+ return this.run('/runtime/v1/git/init', {
39
+ path,
40
+ bare: opts.bare,
41
+ initial_branch: opts.initialBranch,
42
+ ...gitOpts(opts),
43
+ }, opts);
44
+ }
36
45
  /** Return parsed repository status for `path`. */
37
46
  async status(path, opts = {}) {
38
47
  const result = await this.run('/runtime/v1/git/status', { path, ...gitOpts(opts) }, opts);
@@ -58,7 +67,7 @@ export class Git {
58
67
  }
59
68
  /** Stage files. Defaults to all files. */
60
69
  async add(path, opts = {}) {
61
- return this.run('/runtime/v1/git/add', { path, files: opts.files, ...gitOpts(opts) }, opts);
70
+ return this.run('/runtime/v1/git/add', { path, files: opts.files, all: opts.all, ...gitOpts(opts) }, opts);
62
71
  }
63
72
  /** Commit staged files. */
64
73
  async commit(path, message, opts = {}) {
@@ -71,6 +80,27 @@ export class Git {
71
80
  ...gitOpts(opts),
72
81
  }, opts);
73
82
  }
83
+ /** Reset the current HEAD to a specified state. */
84
+ async reset(path, opts = {}) {
85
+ return this.run('/runtime/v1/git/reset', {
86
+ path,
87
+ mode: opts.mode,
88
+ target: opts.target,
89
+ paths: opts.paths,
90
+ ...gitOpts(opts),
91
+ }, opts);
92
+ }
93
+ /** Restore working tree files or unstage changes. */
94
+ async restore(path, opts) {
95
+ return this.run('/runtime/v1/git/restore', {
96
+ path,
97
+ paths: opts.paths,
98
+ staged: opts.staged,
99
+ worktree: opts.worktree,
100
+ source: opts.source,
101
+ ...gitOpts(opts),
102
+ }, opts);
103
+ }
74
104
  /** Pull the current branch with a fast-forward-only merge. */
75
105
  async pull(path, opts = {}) {
76
106
  return this.run('/runtime/v1/git/pull', { path, ...gitOpts(opts), ...pick(opts, ['remote', 'branch', 'username', 'password']) }, opts);
@@ -81,7 +111,7 @@ export class Git {
81
111
  path,
82
112
  ...gitOpts(opts),
83
113
  ...pick(opts, ['remote', 'branch', 'username', 'password']),
84
- set_upstream: opts.setUpstream,
114
+ set_upstream: opts.setUpstream ?? true,
85
115
  }, opts);
86
116
  }
87
117
  /** Check out an arbitrary ref in a repository. */
@@ -103,6 +133,15 @@ export class Git {
103
133
  ...gitOpts(opts),
104
134
  }, opts);
105
135
  }
136
+ /** Return a remote URL, or undefined when the remote does not exist. */
137
+ async remoteGet(path, name, opts = {}) {
138
+ const result = await this.run('/runtime/v1/git/remote_get', {
139
+ path,
140
+ name,
141
+ ...gitOpts(opts),
142
+ }, opts);
143
+ return result.value ?? result.url;
144
+ }
106
145
  /** Set a Git config value. */
107
146
  async setConfig(key, value, opts = {}) {
108
147
  return this.run('/runtime/v1/git/set_config', {
@@ -131,6 +170,8 @@ export class Git {
131
170
  function gitOpts(opts) {
132
171
  return {
133
172
  env_vars: opts.envs,
173
+ user: opts.user,
174
+ cwd: opts.cwd,
134
175
  timeout_seconds: opts.timeout ?? (opts.timeoutMs === undefined ? undefined : Math.ceil(opts.timeoutMs / 1000)),
135
176
  };
136
177
  }
package/dist/index.d.ts CHANGED
@@ -1,13 +1,19 @@
1
- export { ApiError, AuthenticationError, FileNotFoundError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, NotImplementedError, RateLimitError, SandboxError, TimeoutError, } from './errors.js';
1
+ export { ApiError, AuthenticationError, ConflictError, FileNotFoundError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, NotImplementedError, RateLimitError, SandboxError, TimeoutError, } from './errors.js';
2
2
  export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
3
- export { Sandbox, SnapshotPaginator } from './sandbox.js';
4
- export type { CreateSnapshotOpts, RestoreSnapshotOpts, SandboxCreateOpts, SandboxConnectOpts, SandboxInfo, SandboxMetrics, SandboxUrlOpts, SnapshotInfo, FileUrlInfo, } from './sandbox.js';
3
+ export { Sandbox, SandboxPaginator, SnapshotPaginator } from './sandbox.js';
4
+ export type { CreateSnapshotOpts, RestoreSnapshotOpts, SandboxCreateOpts, SandboxConnectOpts, SandboxInfo, SandboxListOpts, SandboxMetrics, McpServer, McpServerName, SandboxNetworkSelector, SandboxNetworkUpdate, SandboxNetworkUpdateOpts, SandboxUrlOpts, SnapshotInfo, FileUrlInfo, } from './sandbox.js';
5
5
  export { CommandExitError, CommandHandle, Commands } from './commands.js';
6
6
  export type { CommandResult, CommandStartOpts, ProcessInfo } from './commands.js';
7
- export { FileType, Filesystem, WatchHandle } from './filesystem.js';
8
- export type { EntryInfo, FilesystemEvent, WatchOpts, WriteInfo } from './filesystem.js';
7
+ export { Process, ProcessManager, ProcessMessage, ProcessOutput } from './process.js';
8
+ export type { ProcessOpts } from './process.js';
9
+ export { FileType, Filesystem, FilesystemWatcher, WatchHandle } from './filesystem.js';
10
+ export type { EntryInfo, FilesystemEvent, FilesystemReadOpts, FilesystemRequestOpts, FilesystemWriteOpts, WatchOpts, WriteData, WriteEntry, WriteInfo, } from './filesystem.js';
9
11
  export { Git } from './git.js';
10
- export type { GitAddOpts, GitAuthOpts, GitBranches, GitBranchOpts, GitCloneOpts, GitCommandResult, GitConfigOpts, GitConfigureUserOpts, GitCredentialOpts, GitCommitOpts, GitFileStatus, GitPullOpts, GitPushOpts, GitRemoteAddOpts, GitRequestOpts, GitStatus, } from './git.js';
12
+ export type { GitAddOpts, GitAuthOpts, GitBranches, GitBranchOpts, GitCloneOpts, GitCommandResult, GitConfigOpts, GitConfigureUserOpts, GitCredentialOpts, GitCommitOpts, GitFileStatus, GitInitOpts, GitPullOpts, GitPushOpts, GitRemoteAddOpts, GitResetMode, GitResetOpts, GitRestoreOpts, GitRequestOpts, GitStatus, } from './git.js';
11
13
  export { Pty } from './pty.js';
12
14
  export type { PtyConnectOpts, PtyCreateOpts, PtySize } from './pty.js';
15
+ export { Terminal, TerminalManager, TerminalOutput } from './terminal.js';
16
+ export type { TerminalOpts } from './terminal.js';
13
17
  export { ProcessSocket, base64DecodeBytes, base64DecodeText, base64Encode } from './processSocket.js';
18
+ export { ReadyCmd, Template, TemplateBase, waitForFile, waitForPort, waitForProcess, waitForTimeout, waitForURL, waitForUrl, } from './template.js';
19
+ export type { BuildInfo, BuildOptions, BuildStatusReason, CopyItem, GetBuildStatusOptions, LogEntry, ReadyCommand, TemplateBuildStatus, TemplateBuildStatusResponse, TemplateBuilder, TemplateClass, TemplateFactory, TemplateFinal, TemplateFromImage, TemplateOptions, TemplateTag, TemplateTagInfo, } from './template.js';
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
- export { ApiError, AuthenticationError, FileNotFoundError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, NotImplementedError, RateLimitError, SandboxError, TimeoutError, } from './errors.js';
1
+ export { ApiError, AuthenticationError, ConflictError, FileNotFoundError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, NotImplementedError, RateLimitError, SandboxError, TimeoutError, } from './errors.js';
2
2
  export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
3
- export { Sandbox, SnapshotPaginator } from './sandbox.js';
3
+ export { Sandbox, SandboxPaginator, SnapshotPaginator } from './sandbox.js';
4
4
  export { CommandExitError, CommandHandle, Commands } from './commands.js';
5
- export { FileType, Filesystem, WatchHandle } from './filesystem.js';
5
+ export { Process, ProcessManager, ProcessMessage, ProcessOutput } from './process.js';
6
+ export { FileType, Filesystem, FilesystemWatcher, WatchHandle } from './filesystem.js';
6
7
  export { Git } from './git.js';
7
8
  export { Pty } from './pty.js';
9
+ export { Terminal, TerminalManager, TerminalOutput } from './terminal.js';
8
10
  export { ProcessSocket, base64DecodeBytes, base64DecodeText, base64Encode } from './processSocket.js';
11
+ export { ReadyCmd, Template, TemplateBase, waitForFile, waitForPort, waitForProcess, waitForTimeout, waitForURL, waitForUrl, } from './template.js';
@@ -0,0 +1,56 @@
1
+ import { CommandHandle, CommandResult, Commands, CommandStartOpts } from './commands.js';
2
+ /** A message emitted by a sandbox process. */
3
+ export declare class ProcessMessage {
4
+ readonly line: string;
5
+ /** Unix epoch in nanoseconds. */
6
+ readonly timestamp: number;
7
+ readonly error: boolean;
8
+ constructor(line: string,
9
+ /** Unix epoch in nanoseconds. */
10
+ timestamp: number, error: boolean);
11
+ toString(): string;
12
+ }
13
+ /** Captured output from a sandbox process. */
14
+ export declare class ProcessOutput {
15
+ private messages;
16
+ private _finished;
17
+ private _error;
18
+ private _exitCode;
19
+ get error(): boolean;
20
+ get exitCode(): number | undefined;
21
+ get stdout(): string;
22
+ get stderr(): string;
23
+ addStdout(message: ProcessMessage): void;
24
+ addStderr(message: ProcessMessage): void;
25
+ setExitCode(exitCode: number): void;
26
+ replace(result: CommandResult): void;
27
+ }
28
+ export interface ProcessOpts extends Omit<CommandStartOpts, 'cmd' | 'args' | 'onStdout' | 'onStderr' | 'onExit'> {
29
+ cmd: string;
30
+ onStdout?: (out: ProcessMessage) => Promise<void> | void;
31
+ onStderr?: (out: ProcessMessage) => Promise<void> | void;
32
+ onExit?: ((exitCode: number) => Promise<void> | void) | (() => Promise<void> | void);
33
+ }
34
+ /** A running sandbox process. */
35
+ export declare class Process {
36
+ readonly processID: string;
37
+ private readonly handle;
38
+ readonly output: ProcessOutput;
39
+ private readonly onExit?;
40
+ readonly finished: Promise<ProcessOutput>;
41
+ private waitPromise?;
42
+ constructor(processID: string, handle: CommandHandle, output: ProcessOutput, onExit?: ((exitCode: number) => Promise<void> | void) | undefined);
43
+ kill(): Promise<void>;
44
+ wait(timeout?: number): Promise<ProcessOutput>;
45
+ private waitOnce;
46
+ sendStdin(data: string): Promise<void>;
47
+ }
48
+ /** Manager for starting and interacting with sandbox processes. */
49
+ export declare class ProcessManager {
50
+ private readonly commands;
51
+ constructor(commands: Commands);
52
+ start(cmd: string): Promise<Process>;
53
+ start(opts: ProcessOpts): Promise<Process>;
54
+ startAndWait(cmd: string): Promise<ProcessOutput>;
55
+ startAndWait(opts: ProcessOpts): Promise<ProcessOutput>;
56
+ }
@@ -0,0 +1,137 @@
1
+ import { CommandExitError } from './commands.js';
2
+ import { TimeoutError } from './errors.js';
3
+ /** A message emitted by a sandbox process. */
4
+ export class ProcessMessage {
5
+ line;
6
+ timestamp;
7
+ error;
8
+ constructor(line,
9
+ /** Unix epoch in nanoseconds. */
10
+ timestamp, error) {
11
+ this.line = line;
12
+ this.timestamp = timestamp;
13
+ this.error = error;
14
+ }
15
+ toString() {
16
+ return this.line;
17
+ }
18
+ }
19
+ /** Captured output from a sandbox process. */
20
+ export class ProcessOutput {
21
+ messages = [];
22
+ _finished = false;
23
+ _error = false;
24
+ _exitCode;
25
+ get error() { return this._error; }
26
+ get exitCode() { return this._exitCode; }
27
+ get stdout() { return this.messages.filter((message) => !message.error).map(String).join(''); }
28
+ get stderr() { return this.messages.filter((message) => message.error).map(String).join(''); }
29
+ addStdout(message) {
30
+ this.messages.push(message);
31
+ }
32
+ addStderr(message) {
33
+ this.messages.push(message);
34
+ this._error = true;
35
+ }
36
+ setExitCode(exitCode) {
37
+ this._exitCode = exitCode;
38
+ this._finished = true;
39
+ if (exitCode !== 0)
40
+ this._error = true;
41
+ }
42
+ replace(result) {
43
+ this.messages = [];
44
+ if (result.stdout)
45
+ this.addStdout(processMessage(result.stdout, false));
46
+ if (result.stderr)
47
+ this.addStderr(processMessage(result.stderr, true));
48
+ this.setExitCode(result.exitCode);
49
+ }
50
+ }
51
+ /** A running sandbox process. */
52
+ export class Process {
53
+ processID;
54
+ handle;
55
+ output;
56
+ onExit;
57
+ finished;
58
+ waitPromise;
59
+ constructor(processID, handle, output, onExit) {
60
+ this.processID = processID;
61
+ this.handle = handle;
62
+ this.output = output;
63
+ this.onExit = onExit;
64
+ this.waitPromise = this.waitOnce();
65
+ this.finished = this.waitPromise;
66
+ }
67
+ async kill() {
68
+ await this.handle.kill();
69
+ }
70
+ async wait(timeout) {
71
+ if (!this.waitPromise)
72
+ this.waitPromise = this.waitOnce();
73
+ return waitFor(this.waitPromise, timeout);
74
+ }
75
+ async waitOnce() {
76
+ try {
77
+ this.output.replace(await this.handle.wait());
78
+ }
79
+ catch (error) {
80
+ if (error instanceof CommandExitError) {
81
+ this.output.replace(error);
82
+ }
83
+ else {
84
+ throw error;
85
+ }
86
+ }
87
+ await this.onExit?.(this.output.exitCode ?? 0);
88
+ return this.output;
89
+ }
90
+ async sendStdin(data) {
91
+ await this.handle.sendStdin(data);
92
+ }
93
+ }
94
+ function waitFor(promise, timeoutMs) {
95
+ if (timeoutMs === undefined || timeoutMs <= 0)
96
+ return promise;
97
+ return new Promise((resolve, reject) => {
98
+ const timer = setTimeout(() => reject(new TimeoutError()), timeoutMs);
99
+ promise.then(resolve, reject).finally(() => clearTimeout(timer));
100
+ });
101
+ }
102
+ /** Manager for starting and interacting with sandbox processes. */
103
+ export class ProcessManager {
104
+ commands;
105
+ constructor(commands) {
106
+ this.commands = commands;
107
+ }
108
+ async start(cmdOrOpts) {
109
+ const opts = processOpts(cmdOrOpts);
110
+ const { cmd, onStdout, onStderr, onExit, ...commandOpts } = opts;
111
+ const output = new ProcessOutput();
112
+ const handle = await this.commands.start(cmd, {
113
+ ...commandOpts,
114
+ onStdout: async (data) => {
115
+ const message = processMessage(data, false);
116
+ output.addStdout(message);
117
+ await onStdout?.(message);
118
+ },
119
+ onStderr: async (data) => {
120
+ const message = processMessage(data, true);
121
+ output.addStderr(message);
122
+ await onStderr?.(message);
123
+ },
124
+ });
125
+ return new Process(String(handle.pid), handle, output, onExit);
126
+ }
127
+ async startAndWait(cmdOrOpts) {
128
+ const process = await this.start(cmdOrOpts);
129
+ return process.wait(typeof cmdOrOpts === 'string' ? undefined : cmdOrOpts.timeout);
130
+ }
131
+ }
132
+ function processOpts(cmdOrOpts) {
133
+ return typeof cmdOrOpts === 'string' ? { cmd: cmdOrOpts } : cmdOrOpts;
134
+ }
135
+ function processMessage(line, error) {
136
+ return new ProcessMessage(line, Date.now() * 1_000_000, error);
137
+ }
@@ -14,6 +14,7 @@ export declare class ProcessSocket implements AsyncIterable<ProcessFrame> {
14
14
  connect(): Promise<this>;
15
15
  sendJson(payload: ProcessFrame): void;
16
16
  sendStdin(data: string | Uint8Array): void;
17
+ closeStdin(): void;
17
18
  close(): void;
18
19
  [Symbol.asyncIterator](): AsyncIterator<ProcessFrame>;
19
20
  private next;
@@ -55,6 +55,9 @@ export class ProcessSocket {
55
55
  const raw = typeof data === 'string' ? new TextEncoder().encode(data) : data;
56
56
  this.sendJson({ type: 'stdin', data: base64Encode(raw) });
57
57
  }
58
+ closeStdin() {
59
+ this.sendJson({ type: 'close_stdin' });
60
+ }
58
61
  close() {
59
62
  this.closed = true;
60
63
  if (this.keepalive)
package/dist/pty.d.ts CHANGED
@@ -5,7 +5,11 @@ export interface PtySize {
5
5
  cols: number;
6
6
  rows: number;
7
7
  }
8
- export interface PtyCreateOpts extends Omit<CommandStartOpts, 'background' | 'onStdout' | 'onStderr'>, PtySize {
8
+ export interface PtyCreateOpts extends Omit<CommandStartOpts, 'background' | 'onStdout' | 'onStderr'> {
9
+ cols?: number;
10
+ rows?: number;
11
+ size?: PtySize;
12
+ cmd?: string;
9
13
  onData?: (data: Uint8Array) => void | Promise<void>;
10
14
  }
11
15
  export interface PtyConnectOpts {
package/dist/pty.js CHANGED
@@ -12,18 +12,20 @@ export class Pty {
12
12
  /** Create an interactive shell PTY and return its live command handle. */
13
13
  async create(opts) {
14
14
  const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, '/runtime/v1/process', opts.requestTimeoutMs ?? this.config.requestTimeoutMs).connect();
15
- const envs = { TERM: 'xterm-256color', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8', ...(opts.envs ?? {}) };
15
+ const envs = { TERM: 'xterm-256color', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8', ...(opts.envVars ?? opts.envs ?? {}) };
16
+ const size = opts.size ?? { cols: opts.cols ?? 80, rows: opts.rows ?? 24 };
17
+ const args = opts.cmd === undefined ? ['-i', '-l'] : ['-l', '-c', opts.cmd];
16
18
  socket.sendJson({
17
19
  type: 'start',
18
20
  cmd: '/bin/bash',
19
- args: ['-i', '-l'],
20
- cwd: opts.cwd,
21
+ args,
22
+ cwd: opts.cwd ?? opts.rootDir,
21
23
  user: opts.user,
22
24
  environment: envs,
23
25
  envs,
24
26
  stdin: true,
25
- pty: { cols: opts.cols, rows: opts.rows },
26
- timeout_ms: opts.timeoutMs ?? 60_000,
27
+ pty: { cols: size.cols, rows: size.rows },
28
+ timeout_ms: opts.timeoutMs ?? opts.timeout ?? 60_000,
27
29
  });
28
30
  const first = await nextStarted(socket);
29
31
  const pid = framePid(first);
@@ -45,7 +47,7 @@ export class Pty {
45
47
  await handle.sendStdin(data);
46
48
  }
47
49
  finally {
48
- handle.disconnect();
50
+ await handle.disconnect();
49
51
  }
50
52
  }
51
53
  /** Alias for `sendStdin`. */
@@ -59,7 +61,7 @@ export class Pty {
59
61
  await handle.resize(size);
60
62
  }
61
63
  finally {
62
- handle.disconnect();
64
+ await handle.disconnect();
63
65
  }
64
66
  }
65
67
  /** Kill a running PTY. */