@watasu/sdk 0.1.67 → 0.1.70
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/dist/codeInterpreter.d.ts +1 -1
- package/dist/codeInterpreter.js +1 -1
- package/dist/commands.d.ts +14 -3
- package/dist/commands.js +73 -10
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pty.d.ts +1 -0
- package/dist/pty.js +14 -5
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Sandbox as BaseSandbox } from './sandbox.js';
|
|
2
|
-
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
2
|
+
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, SandboxOverloadedError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
3
3
|
export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
|
|
4
4
|
export type { ConnectionOpts, Username } from './connectionConfig.js';
|
|
5
5
|
export { ControlClient as ApiClient } from './transport.js';
|
package/dist/codeInterpreter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InvalidArgumentError } from './errors.js';
|
|
2
2
|
import { Sandbox as BaseSandbox } from './sandbox.js';
|
|
3
|
-
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
3
|
+
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, SandboxOverloadedError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
4
4
|
export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
|
|
5
5
|
export { ControlClient as ApiClient } from './transport.js';
|
|
6
6
|
export { ALL_TRAFFIC, SandboxPaginator, SnapshotPaginator, getSignature, } from './sandbox.js';
|
package/dist/commands.d.ts
CHANGED
|
@@ -94,21 +94,28 @@ export interface CommandConnectOpts extends CommandRequestOpts {
|
|
|
94
94
|
export type Stdout = string;
|
|
95
95
|
export type Stderr = string;
|
|
96
96
|
export type PtyOutput = Uint8Array;
|
|
97
|
+
type ProcessReconnect = (cursor: number) => Promise<{
|
|
98
|
+
socket: ProcessSocket;
|
|
99
|
+
events: AsyncIterable<ProcessFrame>;
|
|
100
|
+
}>;
|
|
97
101
|
/** Live handle for one sandbox process stream. */
|
|
98
102
|
export declare class CommandHandle implements Partial<CommandResult> {
|
|
99
103
|
readonly pid: number | string;
|
|
100
|
-
private
|
|
104
|
+
private socket;
|
|
101
105
|
private readonly handleKill;
|
|
102
|
-
private
|
|
106
|
+
private events;
|
|
103
107
|
private readonly onStdout?;
|
|
104
108
|
private readonly onStderr?;
|
|
105
109
|
private readonly onPty?;
|
|
106
110
|
private readonly onExit?;
|
|
111
|
+
private readonly reconnect?;
|
|
107
112
|
private _stdout;
|
|
108
113
|
private _stderr;
|
|
109
114
|
private result?;
|
|
110
115
|
private readonly pending;
|
|
111
|
-
|
|
116
|
+
private nextCursor;
|
|
117
|
+
private disconnected;
|
|
118
|
+
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, reconnect?: ProcessReconnect | undefined);
|
|
112
119
|
get stdout(): string;
|
|
113
120
|
get stderr(): string;
|
|
114
121
|
get exitCode(): number | undefined;
|
|
@@ -129,6 +136,8 @@ export declare class CommandHandle implements Partial<CommandResult> {
|
|
|
129
136
|
/** Detach the local stream without killing the process. */
|
|
130
137
|
disconnect(): Promise<void>;
|
|
131
138
|
private handleEvents;
|
|
139
|
+
private advanceCursor;
|
|
140
|
+
private reconnectStream;
|
|
132
141
|
}
|
|
133
142
|
/** Command runner for a sandbox data-plane session. */
|
|
134
143
|
export declare class Commands {
|
|
@@ -174,4 +183,6 @@ export declare class Commands {
|
|
|
174
183
|
stopProcess(pid: number | string, opts?: StopProcessOptions): Promise<ProcessStatus>;
|
|
175
184
|
/** Start a command and return a live handle immediately. */
|
|
176
185
|
start(cmd: string, opts?: CommandStartOpts): Promise<CommandHandle>;
|
|
186
|
+
private openProcessStream;
|
|
177
187
|
}
|
|
188
|
+
export {};
|
package/dist/commands.js
CHANGED
|
@@ -14,6 +14,9 @@ export class CommandExitError extends SandboxError {
|
|
|
14
14
|
get stdout() { return this.result.stdout; }
|
|
15
15
|
get stderr() { return this.result.stderr; }
|
|
16
16
|
}
|
|
17
|
+
const STREAM_RECONNECT_ATTEMPTS = 12;
|
|
18
|
+
const STREAM_RECONNECT_BASE_DELAY_MS = 250;
|
|
19
|
+
const STREAM_RECONNECT_MAX_DELAY_MS = 2_000;
|
|
17
20
|
/** Live handle for one sandbox process stream. */
|
|
18
21
|
export class CommandHandle {
|
|
19
22
|
pid;
|
|
@@ -24,11 +27,14 @@ export class CommandHandle {
|
|
|
24
27
|
onStderr;
|
|
25
28
|
onPty;
|
|
26
29
|
onExit;
|
|
30
|
+
reconnect;
|
|
27
31
|
_stdout = '';
|
|
28
32
|
_stderr = '';
|
|
29
33
|
result;
|
|
30
34
|
pending;
|
|
31
|
-
|
|
35
|
+
nextCursor = 0;
|
|
36
|
+
disconnected = false;
|
|
37
|
+
constructor(pid, socket, handleKill, events, onStdout, onStderr, onPty, onExit, reconnect) {
|
|
32
38
|
this.pid = pid;
|
|
33
39
|
this.socket = socket;
|
|
34
40
|
this.handleKill = handleKill;
|
|
@@ -37,6 +43,7 @@ export class CommandHandle {
|
|
|
37
43
|
this.onStderr = onStderr;
|
|
38
44
|
this.onPty = onPty;
|
|
39
45
|
this.onExit = onExit;
|
|
46
|
+
this.reconnect = reconnect;
|
|
40
47
|
this.pending = this.handleEvents();
|
|
41
48
|
}
|
|
42
49
|
get stdout() { return this._stdout; }
|
|
@@ -70,11 +77,14 @@ export class CommandHandle {
|
|
|
70
77
|
}
|
|
71
78
|
/** Detach the local stream without killing the process. */
|
|
72
79
|
async disconnect() {
|
|
80
|
+
this.disconnected = true;
|
|
73
81
|
this.socket.close();
|
|
74
82
|
}
|
|
75
83
|
async handleEvents() {
|
|
76
|
-
|
|
84
|
+
while (!this.disconnected && !this.result) {
|
|
85
|
+
let streamError;
|
|
77
86
|
for await (const frame of this.events) {
|
|
87
|
+
this.advanceCursor(frame);
|
|
78
88
|
const type = frame.type;
|
|
79
89
|
if (type === 'started' || type === 'ready' || type === 'pong')
|
|
80
90
|
continue;
|
|
@@ -103,16 +113,51 @@ export class CommandHandle {
|
|
|
103
113
|
stderr: this._stderr,
|
|
104
114
|
};
|
|
105
115
|
await this.onExit?.(exitCode);
|
|
116
|
+
this.socket.close();
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
108
119
|
else if (type === 'error') {
|
|
109
|
-
|
|
120
|
+
streamError = new SandboxError(String(frame.message ?? frame.code ?? 'process error'));
|
|
121
|
+
if (!isReconnectableStreamError(streamError))
|
|
122
|
+
throw streamError;
|
|
123
|
+
break;
|
|
110
124
|
}
|
|
111
125
|
}
|
|
126
|
+
if (this.result || this.disconnected)
|
|
127
|
+
return;
|
|
128
|
+
if (!this.reconnect) {
|
|
129
|
+
this.socket.close();
|
|
130
|
+
if (streamError)
|
|
131
|
+
throw streamError;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
await this.reconnectStream();
|
|
112
135
|
}
|
|
113
|
-
|
|
136
|
+
}
|
|
137
|
+
advanceCursor(frame) {
|
|
138
|
+
const cursor = numberValue(frame.cursor);
|
|
139
|
+
if (cursor !== undefined)
|
|
140
|
+
this.nextCursor = Math.max(this.nextCursor, cursor + 1);
|
|
141
|
+
}
|
|
142
|
+
async reconnectStream() {
|
|
143
|
+
let lastError;
|
|
144
|
+
for (let attempt = 0; attempt < STREAM_RECONNECT_ATTEMPTS && !this.disconnected; attempt += 1) {
|
|
114
145
|
this.socket.close();
|
|
146
|
+
if (attempt > 0)
|
|
147
|
+
await sleep(reconnectDelayMs(attempt));
|
|
148
|
+
try {
|
|
149
|
+
const next = await this.reconnect(this.nextCursor);
|
|
150
|
+
this.socket = next.socket;
|
|
151
|
+
this.events = next.events;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
lastError = error;
|
|
156
|
+
}
|
|
115
157
|
}
|
|
158
|
+
if (lastError instanceof Error)
|
|
159
|
+
throw lastError;
|
|
160
|
+
throw new SandboxError('process websocket closed before exit and could not reconnect');
|
|
116
161
|
}
|
|
117
162
|
}
|
|
118
163
|
/** Command runner for a sandbox data-plane session. */
|
|
@@ -177,11 +222,9 @@ export class Commands {
|
|
|
177
222
|
}
|
|
178
223
|
/** Reconnect to a live process stream by pid starting at a cursor. */
|
|
179
224
|
async connectSince(pid, cursor = 0, opts = {}) {
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const actualPid = framePid(first) ?? pid;
|
|
184
|
-
return new CommandHandle(actualPid, socket, () => this.kill(actualPid), socket, opts.onStdout, opts.onStderr, opts.onPty);
|
|
225
|
+
const stream = await this.openProcessStream(pid, cursor, opts);
|
|
226
|
+
const reconnect = async (nextCursor) => this.openProcessStream(stream.actualPid, nextCursor, opts);
|
|
227
|
+
return new CommandHandle(stream.actualPid, stream.socket, () => this.kill(stream.actualPid), stream.events, opts.onStdout, opts.onStderr, opts.onPty, undefined, reconnect);
|
|
185
228
|
}
|
|
186
229
|
/** Look up process status without attaching a WebSocket. */
|
|
187
230
|
async process(pid, opts = {}) {
|
|
@@ -229,7 +272,18 @@ export class Commands {
|
|
|
229
272
|
const pid = framePid(first);
|
|
230
273
|
if (pid === undefined)
|
|
231
274
|
throw new SandboxError('process started frame did not include pid');
|
|
232
|
-
|
|
275
|
+
const reconnect = async (nextCursor) => this.openProcessStream(pid, nextCursor, opts);
|
|
276
|
+
return new CommandHandle(pid, socket, () => this.kill(pid), withFirst(first, socket), opts.onStdout, opts.onStderr, opts.onPty, opts.onExit, reconnect);
|
|
277
|
+
}
|
|
278
|
+
async openProcessStream(pid, cursor, opts = {}) {
|
|
279
|
+
const encodedPid = encodeURIComponent(String(pid));
|
|
280
|
+
const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, withQuery(`/runtime/v1/process/${encodedPid}/connect`, { since: cursor }), opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
|
|
281
|
+
const first = await nextStarted(socket);
|
|
282
|
+
return {
|
|
283
|
+
actualPid: framePid(first) ?? pid,
|
|
284
|
+
socket,
|
|
285
|
+
events: socket,
|
|
286
|
+
};
|
|
233
287
|
}
|
|
234
288
|
}
|
|
235
289
|
function processStartConfig(cmd, opts) {
|
|
@@ -246,6 +300,15 @@ function waitFor(promise, timeoutMs) {
|
|
|
246
300
|
promise.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
247
301
|
});
|
|
248
302
|
}
|
|
303
|
+
function sleep(ms) {
|
|
304
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
305
|
+
}
|
|
306
|
+
function reconnectDelayMs(attempt) {
|
|
307
|
+
return Math.min(STREAM_RECONNECT_MAX_DELAY_MS, STREAM_RECONNECT_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1));
|
|
308
|
+
}
|
|
309
|
+
function isReconnectableStreamError(error) {
|
|
310
|
+
return error instanceof Error && /websocket|closed/i.test(error.message);
|
|
311
|
+
}
|
|
249
312
|
async function nextStarted(events) {
|
|
250
313
|
for await (const frame of events) {
|
|
251
314
|
if (frame.type === 'started')
|
package/dist/errors.d.ts
CHANGED
|
@@ -51,4 +51,7 @@ export declare class ApiError extends SandboxError {
|
|
|
51
51
|
readonly code?: string | undefined;
|
|
52
52
|
constructor(message: string, status: number, code?: string | undefined);
|
|
53
53
|
}
|
|
54
|
+
export declare class SandboxOverloadedError extends ApiError {
|
|
55
|
+
constructor(message?: string);
|
|
56
|
+
}
|
|
54
57
|
export declare function errorFromResponse(status: number, payload: unknown): Error;
|
package/dist/errors.js
CHANGED
|
@@ -104,11 +104,21 @@ export class ApiError extends SandboxError {
|
|
|
104
104
|
this.name = 'ApiError';
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
+
export class SandboxOverloadedError extends ApiError {
|
|
108
|
+
constructor(message = 'Sandbox is overloaded') {
|
|
109
|
+
super(message, 503, 'sandbox_overloaded');
|
|
110
|
+
this.name = 'SandboxOverloadedError';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
107
113
|
export function errorFromResponse(status, payload) {
|
|
108
114
|
const body = asRecord(payload);
|
|
115
|
+
const reason = asRecord(body.reason);
|
|
109
116
|
const code = stringValue(body.error);
|
|
110
117
|
const message = stringValue(body.message) ||
|
|
111
118
|
listMessage(body.errors) ||
|
|
119
|
+
stringValue(reason.message) ||
|
|
120
|
+
listMessage(reason.errors) ||
|
|
121
|
+
stringValue(body.reason) ||
|
|
112
122
|
code ||
|
|
113
123
|
`Request failed with status ${status}`;
|
|
114
124
|
if (code === 'not_enough_space')
|
|
@@ -117,6 +127,8 @@ export function errorFromResponse(status, payload) {
|
|
|
117
127
|
return new FileNotFoundError(message);
|
|
118
128
|
if (code === 'sandbox_not_found')
|
|
119
129
|
return new SandboxNotFoundError(message);
|
|
130
|
+
if (code === 'sandbox_overloaded')
|
|
131
|
+
return new SandboxOverloadedError(message);
|
|
120
132
|
if (code === 'git_auth')
|
|
121
133
|
return new GitAuthError(message);
|
|
122
134
|
if (code === 'git_upstream')
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
1
|
+
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, SandboxOverloadedError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
2
2
|
export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
|
|
3
3
|
export type { ConnectionOpts, Username } from './connectionConfig.js';
|
|
4
4
|
export { ControlClient as ApiClient } from './transport.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
1
|
+
export { ApiError, AuthenticationError, BuildError, ConflictError, FileNotFoundError, FileUploadError, GitAuthError, GitUpstreamError, InvalidArgumentError, NotEnoughSpaceError, NotFoundError, RateLimitError, SandboxError, SandboxNotFoundError, SandboxOverloadedError, TemplateError, TimeoutError, VolumeError, } from './errors.js';
|
|
2
2
|
export { ConnectionConfig, KEEPALIVE_PING_INTERVAL_SEC } from './connectionConfig.js';
|
|
3
3
|
export { ControlClient as ApiClient } from './transport.js';
|
|
4
4
|
export { ALL_TRAFFIC, Sandbox, SandboxPaginator, SnapshotPaginator, getSignature, } from './sandbox.js';
|
package/dist/pty.d.ts
CHANGED
|
@@ -39,4 +39,5 @@ export declare class Pty {
|
|
|
39
39
|
resize(pid: number | string, size: PtySize, opts?: PtyConnectOpts): Promise<void>;
|
|
40
40
|
/** Kill a running PTY. */
|
|
41
41
|
kill(pid: number | string, opts?: Pick<ConnectionOpts, 'requestTimeoutMs' | 'signal'>): Promise<boolean>;
|
|
42
|
+
private openPtyStream;
|
|
42
43
|
}
|
package/dist/pty.js
CHANGED
|
@@ -32,14 +32,14 @@ export class Pty {
|
|
|
32
32
|
const pid = framePid(first);
|
|
33
33
|
if (pid === undefined)
|
|
34
34
|
throw new SandboxError('PTY started frame did not include pid');
|
|
35
|
-
|
|
35
|
+
const reconnect = async (cursor) => this.openPtyStream(pid, cursor, opts);
|
|
36
|
+
return new CommandHandle(pid, socket, () => this.kill(pid), withFirst(first, socket), undefined, undefined, opts.onData ?? opts.onPty, undefined, reconnect);
|
|
36
37
|
}
|
|
37
38
|
/** Connect to a running PTY by pid. */
|
|
38
39
|
async connect(pid, opts = {}) {
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
return new CommandHandle(actualPid, socket, () => this.kill(actualPid), withFirst(first, socket), undefined, undefined, opts.onData);
|
|
40
|
+
const stream = await this.openPtyStream(pid, 0, opts);
|
|
41
|
+
const reconnect = async (cursor) => this.openPtyStream(stream.actualPid, cursor, opts);
|
|
42
|
+
return new CommandHandle(stream.actualPid, stream.socket, () => this.kill(stream.actualPid), stream.events, undefined, undefined, opts.onData, undefined, reconnect);
|
|
43
43
|
}
|
|
44
44
|
/** Send input bytes or text to a PTY. */
|
|
45
45
|
async sendStdin(pid, data, opts = {}) {
|
|
@@ -76,6 +76,15 @@ export class Pty {
|
|
|
76
76
|
});
|
|
77
77
|
return true;
|
|
78
78
|
}
|
|
79
|
+
async openPtyStream(pid, cursor, opts = {}) {
|
|
80
|
+
const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, withQuery(`/runtime/v1/process/${encodeURIComponent(String(pid))}/connect`, { since: cursor }), opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
|
|
81
|
+
const first = await nextStarted(socket);
|
|
82
|
+
return {
|
|
83
|
+
actualPid: framePid(first) ?? pid,
|
|
84
|
+
socket,
|
|
85
|
+
events: withFirst(first, socket),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
async function nextStarted(events) {
|
|
81
90
|
for await (const frame of events) {
|