@hyperbrowser/sdk 0.92.7 → 1.0.0

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.
Files changed (68) hide show
  1. package/README.md +190 -0
  2. package/dist/client.d.ts +4 -20
  3. package/dist/client.js +7 -17
  4. package/dist/error.d.ts +27 -0
  5. package/dist/error.js +32 -0
  6. package/dist/image-builds.d.ts +6 -0
  7. package/dist/image-builds.js +12 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +12 -1
  10. package/dist/retry.d.ts +14 -0
  11. package/dist/retry.js +49 -0
  12. package/dist/sandbox/base.d.ts +27 -0
  13. package/dist/sandbox/base.js +298 -99
  14. package/dist/sandbox/files.d.ts +24 -25
  15. package/dist/sandbox/files.js +146 -37
  16. package/dist/sandbox/image-build/artifacts.d.ts +16 -0
  17. package/dist/sandbox/image-build/artifacts.js +16 -0
  18. package/dist/sandbox/image-build/blake2b.d.ts +2 -0
  19. package/dist/sandbox/image-build/blake2b.js +89 -0
  20. package/dist/sandbox/image-build/common.d.ts +19 -0
  21. package/dist/sandbox/image-build/common.js +109 -0
  22. package/dist/sandbox/image-build/context.d.ts +43 -0
  23. package/dist/sandbox/image-build/context.js +599 -0
  24. package/dist/sandbox/image-build/docker-image.d.ts +34 -0
  25. package/dist/sandbox/image-build/docker-image.js +472 -0
  26. package/dist/sandbox/image-build/dockerfile-analysis.d.ts +17 -0
  27. package/dist/sandbox/image-build/dockerfile-analysis.js +409 -0
  28. package/dist/sandbox/image-build/dockerignore.d.ts +18 -0
  29. package/dist/sandbox/image-build/dockerignore.js +275 -0
  30. package/dist/sandbox/image-build/gzip.d.ts +11 -0
  31. package/dist/sandbox/image-build/gzip.js +91 -0
  32. package/dist/sandbox/image-build/image-init.d.ts +5 -0
  33. package/dist/sandbox/image-build/image-init.js +97 -0
  34. package/dist/sandbox/image-build/index.d.ts +13 -0
  35. package/dist/sandbox/image-build/index.js +36 -0
  36. package/dist/sandbox/image-build/local-docker.d.ts +11 -0
  37. package/dist/sandbox/image-build/local-docker.js +72 -0
  38. package/dist/sandbox/image-build/resolution.d.ts +27 -0
  39. package/dist/sandbox/image-build/resolution.js +137 -0
  40. package/dist/sandbox/image-build/tar.d.ts +41 -0
  41. package/dist/sandbox/image-build/tar.js +378 -0
  42. package/dist/sandbox/image-build/upload.d.ts +10 -0
  43. package/dist/sandbox/image-build/upload.js +143 -0
  44. package/dist/sandbox/index.d.ts +2 -1
  45. package/dist/sandbox/index.js +4 -1
  46. package/dist/sandbox/process-output.d.ts +36 -0
  47. package/dist/sandbox/process-output.js +117 -0
  48. package/dist/sandbox/process.d.ts +25 -1
  49. package/dist/sandbox/process.js +197 -29
  50. package/dist/sandbox/terminal.js +9 -5
  51. package/dist/sandbox/ws.d.ts +4 -1
  52. package/dist/sandbox/ws.js +33 -7
  53. package/dist/services/agents/jev-computer-use.d.ts +30 -0
  54. package/dist/services/agents/jev-computer-use.js +103 -0
  55. package/dist/services/base.d.ts +2 -0
  56. package/dist/services/base.js +46 -30
  57. package/dist/services/normalize.d.ts +3 -0
  58. package/dist/services/normalize.js +21 -0
  59. package/dist/services/sandboxes.d.ts +40 -3
  60. package/dist/services/sandboxes.js +569 -53
  61. package/dist/services/volumes.js +29 -14
  62. package/dist/types/agents/jev-computer-use.d.ts +43 -0
  63. package/dist/types/agents/jev-computer-use.js +2 -0
  64. package/dist/types/constants.d.ts +5 -2
  65. package/dist/types/index.d.ts +4 -2
  66. package/dist/types/sandbox.d.ts +246 -30
  67. package/dist/types/volume.d.ts +7 -7
  68. package/package.json +24 -5
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ /** Collection and validation of the receiver's command event stream. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.validateOutputLimit = exports.ProcessOutput = exports.normalizeProcessResult = exports.DEFAULT_MAX_PROCESS_OUTPUT_BYTES = void 0;
5
+ const string_decoder_1 = require("string_decoder");
6
+ const error_1 = require("../error");
7
+ exports.DEFAULT_MAX_PROCESS_OUTPUT_BYTES = 64 * 1024 * 1024;
8
+ const BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
9
+ const normalizeProcessResult = (result) => ({
10
+ id: result.id,
11
+ status: result.status,
12
+ exitCode: result.exit_code !== undefined ? result.exit_code : result.exitCode,
13
+ stdout: result.stdout,
14
+ stderr: result.stderr,
15
+ startedAt: result.started_at ?? result.startedAt,
16
+ completedAt: result.completed_at !== undefined ? result.completed_at : result.completedAt,
17
+ error: result.error,
18
+ outputTruncated: result.output_truncated,
19
+ lastSeq: result.last_seq,
20
+ });
21
+ exports.normalizeProcessResult = normalizeProcessResult;
22
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23
+ class ProcessOutput {
24
+ constructor(processId, maxBytes) {
25
+ this.processId = processId;
26
+ this.maxBytes = maxBytes;
27
+ this.size = 0;
28
+ this.seq = 0;
29
+ this.events = [];
30
+ this.result = null;
31
+ this.error = null;
32
+ this.chunks = { stdout: [], stderr: [] };
33
+ this.decoders = {
34
+ stdout: new string_decoder_1.StringDecoder("utf8"),
35
+ stderr: new string_decoder_1.StringDecoder("utf8"),
36
+ system: new string_decoder_1.StringDecoder("utf8"),
37
+ };
38
+ }
39
+ failure(message, code = "incomplete_output") {
40
+ return new error_1.HyperbrowserError(message, {
41
+ code,
42
+ service: "runtime",
43
+ retryable: false,
44
+ details: { process_id: this.processId, last_seq: this.seq },
45
+ });
46
+ }
47
+ consume(event) {
48
+ if (event.event === "output") {
49
+ this.consumeOutput(event.data);
50
+ return;
51
+ }
52
+ if (event.event === "done") {
53
+ this.consumeDone(event.data);
54
+ return;
55
+ }
56
+ if (event.event === "error") {
57
+ const data = isRecord(event.data) ? event.data : {};
58
+ throw this.failure(String(data.error ?? "Command stream failed"), typeof data.code === "string" ? data.code : "incomplete_output");
59
+ }
60
+ }
61
+ decodePayload(data) {
62
+ if (data.encoding === "base64") {
63
+ const text = data.data;
64
+ if (text.length % 4 !== 0 || !BASE64_PATTERN.test(text)) {
65
+ throw this.failure("Command output contains invalid base64 data");
66
+ }
67
+ return Buffer.from(text, "base64");
68
+ }
69
+ return Buffer.from(data.data, "utf8");
70
+ }
71
+ consumeOutput(payload) {
72
+ if (!isRecord(payload)) {
73
+ throw this.failure("Command output event is malformed");
74
+ }
75
+ const data = payload;
76
+ if (data.seq !== this.seq + 1) {
77
+ throw this.failure("Command output contains a sequence gap");
78
+ }
79
+ const stream = data.stream;
80
+ if (!Object.prototype.hasOwnProperty.call(this.decoders, stream)) {
81
+ throw this.failure("Unknown command output stream");
82
+ }
83
+ const raw = this.decodePayload(data);
84
+ this.size += raw.length;
85
+ if (this.size > this.maxBytes) {
86
+ throw this.failure("Command output exceeds maxOutputBytes; increase the collection limit or disconnect a detached process", "output_limit_exceeded");
87
+ }
88
+ this.seq = data.seq;
89
+ const text = this.decoders[stream].write(raw);
90
+ this.chunks[stream === "stdout" ? "stdout" : "stderr"].push(text);
91
+ this.events.push({ type: stream, seq: this.seq, data: text, timestamp: data.timestamp });
92
+ }
93
+ consumeDone(payload) {
94
+ if (!isRecord(payload)) {
95
+ throw this.failure("Command completion event is malformed");
96
+ }
97
+ const data = payload;
98
+ if (data.last_seq !== this.seq || data.output_truncated) {
99
+ throw this.failure("Receiver reported incomplete command output");
100
+ }
101
+ for (const stream of Object.keys(this.decoders)) {
102
+ this.chunks[stream === "stdout" ? "stdout" : "stderr"].push(this.decoders[stream].end());
103
+ }
104
+ this.result = (0, exports.normalizeProcessResult)({
105
+ ...data,
106
+ stdout: this.chunks.stdout.join(""),
107
+ stderr: this.chunks.stderr.join(""),
108
+ });
109
+ }
110
+ }
111
+ exports.ProcessOutput = ProcessOutput;
112
+ const validateOutputLimit = (value) => {
113
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
114
+ throw new error_1.HyperbrowserError("maxOutputBytes must be a positive integer");
115
+ }
116
+ };
117
+ exports.validateOutputLimit = validateOutputLimit;
@@ -1,25 +1,49 @@
1
- import { RuntimeTransport } from "./base";
1
+ import { RuntimeSSEStream, RuntimeTransport } from "./base";
2
2
  import { SandboxExecParams, SandboxExecOptions, SandboxProcessListParams, SandboxProcessListResponse, SandboxProcessResult, SandboxProcessSignal, SandboxProcessStdinParams, SandboxProcessStreamEvent, SandboxProcessSummary, SandboxProcessWaitParams } from "../types/sandbox";
3
3
  export declare class SandboxProcessHandle {
4
4
  private readonly transport;
5
5
  private summary;
6
+ private output;
7
+ private collector;
8
+ private closeStream;
9
+ private readonly changed;
6
10
  constructor(transport: RuntimeTransport, summary: SandboxProcessSummary);
11
+ /** @internal Collect output from the start-stream that created this process. */
12
+ attachCollector(stream: RuntimeSSEStream, maxOutputBytes: number): void;
13
+ private collect;
14
+ private collectedResult;
15
+ /** Stop collecting output; the detached command continues running. */
16
+ disconnect(): void;
7
17
  get id(): string;
8
18
  get status(): SandboxProcessSummary["status"];
9
19
  toJSON(): SandboxProcessSummary;
10
20
  refresh(): Promise<SandboxProcessHandle>;
21
+ /**
22
+ * Wait for completion. Processes started by this client resolve from the
23
+ * collected stream; a local timeout rejects without stopping collection.
24
+ */
11
25
  wait(params?: SandboxProcessWaitParams): Promise<SandboxProcessResult>;
12
26
  signal(signal: SandboxProcessSignal): Promise<void>;
13
27
  kill(params?: SandboxProcessWaitParams): Promise<SandboxProcessResult>;
14
28
  writeStdin(input: string | Uint8Array | SandboxProcessStdinParams): Promise<void>;
29
+ /**
30
+ * Replay and follow output. Processes started by this client stream from the
31
+ * collected events; other handles attach to the receiver's stream endpoint.
32
+ */
15
33
  stream(fromSeq?: number): AsyncGenerator<SandboxProcessStreamEvent>;
16
34
  result(): Promise<SandboxProcessResult>;
17
35
  }
18
36
  export declare class SandboxProcessesApi {
19
37
  private readonly transport;
20
38
  constructor(transport: RuntimeTransport);
39
+ /** Run a command to completion, collecting its full output from process start. */
21
40
  exec(command: string, options?: SandboxExecOptions): Promise<SandboxProcessResult>;
22
41
  exec(input: SandboxExecParams): Promise<SandboxProcessResult>;
42
+ /**
43
+ * Start a process and collect its output in the background from the same
44
+ * streamed request, so output is complete even beyond the receiver's replay
45
+ * limit. Call `disconnect()` to stop collecting without killing the process.
46
+ */
23
47
  start(command: string, options?: SandboxExecOptions): Promise<SandboxProcessHandle>;
24
48
  start(input: SandboxExecParams): Promise<SandboxProcessHandle>;
25
49
  get(processId: string): Promise<SandboxProcessHandle>;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SandboxProcessesApi = exports.SandboxProcessHandle = void 0;
4
+ const error_1 = require("../error");
5
+ const process_output_1 = require("./process-output");
4
6
  const DEFAULT_PROCESS_KILL_WAIT_MS = 5000;
5
7
  const SHELL_SAFE_TOKEN_PATTERN = /^[A-Za-z0-9_@%+=:,./-]+$/;
6
8
  const normalizeProcessSummary = (process) => ({
@@ -10,19 +12,9 @@ const normalizeProcessSummary = (process) => ({
10
12
  args: process.args,
11
13
  cwd: process.cwd,
12
14
  pid: process.pid,
13
- exitCode: process.exit_code,
14
- startedAt: process.started_at,
15
- completedAt: process.completed_at,
16
- });
17
- const normalizeProcessResult = (result) => ({
18
- id: result.id,
19
- status: result.status,
20
- exitCode: result.exit_code,
21
- stdout: result.stdout,
22
- stderr: result.stderr,
23
- startedAt: result.started_at,
24
- completedAt: result.completed_at,
25
- error: result.error,
15
+ exitCode: process.exit_code !== undefined ? process.exit_code : process.exitCode,
16
+ startedAt: process.started_at ?? process.startedAt,
17
+ completedAt: process.completed_at !== undefined ? process.completed_at : process.completedAt,
26
18
  });
27
19
  const normalizeResultToSummary = (result) => ({
28
20
  id: result.id,
@@ -46,7 +38,7 @@ const normalizeStreamEvent = (event) => {
46
38
  if (event.event === "done") {
47
39
  return {
48
40
  type: "exit",
49
- result: normalizeProcessResult(event.data),
41
+ result: (0, process_output_1.normalizeProcessResult)(event.data),
50
42
  };
51
43
  }
52
44
  return null;
@@ -69,6 +61,44 @@ const normalizeLegacyProcessParams = (input) => ({
69
61
  args: undefined,
70
62
  useShell: undefined,
71
63
  });
64
+ class ChangeSignal {
65
+ constructor() {
66
+ this.waiters = [];
67
+ }
68
+ notify() {
69
+ const waiters = this.waiters;
70
+ this.waiters = [];
71
+ waiters.forEach((resolve) => resolve());
72
+ }
73
+ /** Resolve true on the next notification, or false once `timeoutMs` elapses. */
74
+ wait(timeoutMs) {
75
+ return new Promise((resolve) => {
76
+ let timer;
77
+ const onChange = () => {
78
+ if (timer) {
79
+ clearTimeout(timer);
80
+ }
81
+ resolve(true);
82
+ };
83
+ this.waiters.push(onChange);
84
+ if (timeoutMs !== undefined) {
85
+ timer = setTimeout(() => {
86
+ this.waiters = this.waiters.filter((waiter) => waiter !== onChange);
87
+ resolve(false);
88
+ }, timeoutMs);
89
+ }
90
+ });
91
+ }
92
+ }
93
+ const localWaitTimeoutMs = (params) => {
94
+ if (params.timeoutSec !== undefined && params.timeoutSec > 0) {
95
+ return params.timeoutSec * 1000;
96
+ }
97
+ if (params.timeoutMs !== undefined && params.timeoutMs > 0) {
98
+ return params.timeoutMs;
99
+ }
100
+ return undefined;
101
+ };
72
102
  const buildProcessPayload = (input) => ({
73
103
  command: input.command,
74
104
  cwd: input.cwd,
@@ -106,6 +136,79 @@ class SandboxProcessHandle {
106
136
  constructor(transport, summary) {
107
137
  this.transport = transport;
108
138
  this.summary = summary;
139
+ this.output = null;
140
+ this.collector = null;
141
+ this.closeStream = null;
142
+ this.changed = new ChangeSignal();
143
+ }
144
+ /** @internal Collect output from the start-stream that created this process. */
145
+ attachCollector(stream, maxOutputBytes) {
146
+ this.output = new process_output_1.ProcessOutput(this.id, maxOutputBytes);
147
+ this.closeStream = stream.close;
148
+ this.collector = this.collect(stream.events);
149
+ }
150
+ async collect(events) {
151
+ const output = this.output;
152
+ try {
153
+ for await (const event of events) {
154
+ if (output.error !== null) {
155
+ return;
156
+ }
157
+ output.consume(event);
158
+ this.changed.notify();
159
+ if (output.result !== null) {
160
+ return;
161
+ }
162
+ }
163
+ output.error = output.failure("Command stream ended before its completion event");
164
+ }
165
+ catch (error) {
166
+ if (output.error === null) {
167
+ output.error =
168
+ error instanceof error_1.HyperbrowserError
169
+ ? error
170
+ : output.failure(error instanceof Error ? error.message : String(error));
171
+ }
172
+ }
173
+ finally {
174
+ try {
175
+ await events.return(undefined);
176
+ }
177
+ catch (error) {
178
+ if (output.result === null && output.error === null) {
179
+ output.error = output.failure(error instanceof Error ? error.message : String(error));
180
+ }
181
+ }
182
+ this.closeStream?.();
183
+ this.changed.notify();
184
+ }
185
+ }
186
+ collectedResult() {
187
+ const output = this.output;
188
+ if (output.error !== null) {
189
+ throw output.error;
190
+ }
191
+ if (output.result === null) {
192
+ throw output.failure("Command stream ended before its completion event");
193
+ }
194
+ const result = output.result;
195
+ this.summary = {
196
+ ...this.summary,
197
+ status: result.status,
198
+ exitCode: result.exitCode,
199
+ completedAt: result.completedAt,
200
+ };
201
+ return result;
202
+ }
203
+ /** Stop collecting output; the detached command continues running. */
204
+ disconnect() {
205
+ if (this.output !== null) {
206
+ if (this.output.result === null && this.output.error === null) {
207
+ this.output.error = this.output.failure("Command output collection disconnected");
208
+ }
209
+ this.changed.notify();
210
+ this.closeStream?.();
211
+ }
109
212
  }
110
213
  get id() {
111
214
  return this.summary.id;
@@ -121,7 +224,28 @@ class SandboxProcessHandle {
121
224
  this.summary = normalizeProcessSummary(response.process);
122
225
  return this;
123
226
  }
227
+ /**
228
+ * Wait for completion. Processes started by this client resolve from the
229
+ * collected stream; a local timeout rejects without stopping collection.
230
+ */
124
231
  async wait(params = {}) {
232
+ if (this.output !== null && this.collector !== null) {
233
+ const output = this.output;
234
+ const timeoutMs = localWaitTimeoutMs(params);
235
+ const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
236
+ while (output.result === null && output.error === null) {
237
+ const remaining = deadline === undefined ? undefined : deadline - Date.now();
238
+ const ready = remaining !== undefined && remaining <= 0 ? false : await this.changed.wait(remaining);
239
+ if (!ready) {
240
+ throw new error_1.HyperbrowserError("Timed out waiting for command output", {
241
+ code: "wait_timeout",
242
+ service: "runtime",
243
+ retryable: false,
244
+ });
245
+ }
246
+ }
247
+ return this.collectedResult();
248
+ }
125
249
  const response = await this.transport.requestJSON(`/sandbox/processes/${this.id}/wait`, {
126
250
  method: "POST",
127
251
  body: JSON.stringify({
@@ -132,7 +256,10 @@ class SandboxProcessHandle {
132
256
  "content-type": "application/json",
133
257
  },
134
258
  });
135
- const result = normalizeProcessResult(response.result);
259
+ const result = (0, process_output_1.normalizeProcessResult)(response.result);
260
+ if (result.outputTruncated) {
261
+ throw new process_output_1.ProcessOutput(this.id, 0).failure("Retained process output is incomplete; collect output from process start");
262
+ }
136
263
  this.summary = {
137
264
  ...this.summary,
138
265
  ...normalizeResultToSummary(result),
@@ -186,7 +313,33 @@ class SandboxProcessHandle {
186
313
  },
187
314
  });
188
315
  }
316
+ /**
317
+ * Replay and follow output. Processes started by this client stream from the
318
+ * collected events; other handles attach to the receiver's stream endpoint.
319
+ */
189
320
  async *stream(fromSeq) {
321
+ if (this.output !== null) {
322
+ const output = this.output;
323
+ let index = 0;
324
+ for (;;) {
325
+ const events = output.events.slice(index);
326
+ index += events.length;
327
+ const done = output.result !== null || output.error !== null;
328
+ if (events.length === 0 && !done) {
329
+ await this.changed.wait();
330
+ continue;
331
+ }
332
+ for (const event of events) {
333
+ if (fromSeq === undefined || event.seq >= fromSeq) {
334
+ yield event;
335
+ }
336
+ }
337
+ if (done) {
338
+ yield { type: "exit", result: this.collectedResult() };
339
+ return;
340
+ }
341
+ }
342
+ }
190
343
  const params = fromSeq && fromSeq > 0
191
344
  ? {
192
345
  from_seq: fromSeq,
@@ -209,26 +362,41 @@ class SandboxProcessesApi {
209
362
  this.transport = transport;
210
363
  }
211
364
  async exec(input, options) {
212
- const params = normalizeExecParams(input, options);
213
- const response = await this.transport.requestJSON("/sandbox/exec", {
214
- method: "POST",
215
- body: JSON.stringify(buildProcessPayload(params)),
216
- headers: {
217
- "content-type": "application/json",
218
- },
219
- });
220
- return normalizeProcessResult(response.result);
365
+ const handle = typeof input === "string" ? await this.start(input, options) : await this.start(input);
366
+ try {
367
+ return await handle.wait();
368
+ }
369
+ finally {
370
+ handle.disconnect();
371
+ }
221
372
  }
222
373
  async start(input, options) {
223
374
  const params = normalizeExecParams(input, options);
224
- const response = await this.transport.requestJSON("/sandbox/processes", {
375
+ const maxOutputBytes = params.maxOutputBytes ?? process_output_1.DEFAULT_MAX_PROCESS_OUTPUT_BYTES;
376
+ (0, process_output_1.validateOutputLimit)(maxOutputBytes);
377
+ const stream = await this.transport.openSSE("/sandbox/processes", undefined, {
225
378
  method: "POST",
226
379
  body: JSON.stringify(buildProcessPayload(params)),
227
- headers: {
228
- "content-type": "application/json",
229
- },
380
+ signal: params.signal,
230
381
  });
231
- return new SandboxProcessHandle(this.transport, normalizeProcessSummary(response.process));
382
+ let handle;
383
+ try {
384
+ const started = await stream.events.next();
385
+ if (started.done || started.value.event !== "started") {
386
+ throw new error_1.HyperbrowserError("Expected process start event", {
387
+ service: "runtime",
388
+ retryable: false,
389
+ });
390
+ }
391
+ handle = new SandboxProcessHandle(this.transport, normalizeProcessSummary(started.value.data));
392
+ }
393
+ catch (error) {
394
+ stream.close();
395
+ await stream.events.return(undefined).catch(() => undefined);
396
+ throw error;
397
+ }
398
+ handle.attachCollector(stream, maxOutputBytes);
399
+ return handle;
232
400
  }
233
401
  async get(processId) {
234
402
  const response = await this.transport.requestJSON(`/sandbox/processes/${processId}`);
@@ -54,10 +54,12 @@ class SandboxTerminalConnection {
54
54
  });
55
55
  return;
56
56
  }
57
- this.eventsQueue.push({
58
- type: "exit",
59
- status: normalizeTerminalStatus(parsed.status),
60
- });
57
+ if (parsed.type === "exit") {
58
+ this.eventsQueue.push({
59
+ type: "exit",
60
+ status: normalizeTerminalStatus(parsed.status),
61
+ });
62
+ }
61
63
  }
62
64
  catch (error) {
63
65
  this.eventsQueue.fail(error);
@@ -195,7 +197,9 @@ class SandboxTerminalHandle {
195
197
  headers.Host = target.hostHeader;
196
198
  }
197
199
  const ws = await (0, ws_2.openRuntimeWebSocket)(target, headers);
198
- return new SandboxTerminalConnection(ws);
200
+ const connection = new SandboxTerminalConnection(ws);
201
+ ws.resume?.();
202
+ return connection;
199
203
  }
200
204
  }
201
205
  exports.SandboxTerminalHandle = SandboxTerminalHandle;
@@ -15,4 +15,7 @@ export interface RuntimeTransportTarget {
15
15
  }
16
16
  export declare const resolveRuntimeTransportTarget: (baseUrl: string, path: string, runtimeProxyOverride?: string) => RuntimeTransportTarget;
17
17
  export declare const toWebSocketUrl: (baseUrl: string, path: string, runtimeProxyOverride?: string) => RuntimeTransportTarget;
18
- export declare const openRuntimeWebSocket: (target: RuntimeTransportTarget, headers: Record<string, string>) => Promise<WebSocket>;
18
+ export declare const openRuntimeWebSocket: (target: RuntimeTransportTarget, headers: Record<string, string>, options?: {
19
+ signal?: AbortSignal;
20
+ timeoutMs?: number;
21
+ }) => Promise<WebSocket>;
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.openRuntimeWebSocket = exports.toWebSocketUrl = exports.resolveRuntimeTransportTarget = exports.AsyncEventQueue = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
- const client_1 = require("../client");
8
+ const error_1 = require("../error");
9
9
  const runtime_path_1 = require("./runtime-path");
10
10
  class AsyncEventQueue {
11
11
  constructor() {
@@ -157,11 +157,11 @@ const readIncomingMessageBody = (response) => new Promise((resolve, reject) => {
157
157
  response.on("error", reject);
158
158
  });
159
159
  const normalizeWebSocketError = (error) => {
160
- if (error instanceof client_1.HyperbrowserError) {
160
+ if (error instanceof error_1.HyperbrowserError) {
161
161
  return error;
162
162
  }
163
163
  const networkError = error;
164
- return new client_1.HyperbrowserError(error instanceof Error ? error.message : "Unknown runtime websocket error", {
164
+ return new error_1.HyperbrowserError(error instanceof Error ? error.message : "Unknown runtime websocket error", {
165
165
  retryable: Boolean(networkError?.code && RETRYABLE_NETWORK_CODES.has(networkError.code)),
166
166
  service: "runtime",
167
167
  cause: error,
@@ -184,7 +184,7 @@ const buildHandshakeError = async (response) => {
184
184
  message = rawText;
185
185
  }
186
186
  }
187
- return new client_1.HyperbrowserError(message, {
187
+ return new error_1.HyperbrowserError(message, {
188
188
  statusCode: response.statusCode,
189
189
  code,
190
190
  requestId: (typeof response.headers["x-request-id"] === "string"
@@ -198,25 +198,51 @@ const buildHandshakeError = async (response) => {
198
198
  details,
199
199
  });
200
200
  };
201
- const openRuntimeWebSocket = async (target, headers) => new Promise((resolve, reject) => {
201
+ const openRuntimeWebSocket = async (target, headers, options = {}) => new Promise((resolve, reject) => {
202
202
  let settled = false;
203
+ let response;
204
+ if (options.signal?.aborted) {
205
+ reject(new error_1.HyperbrowserError("Runtime websocket request canceled", {
206
+ code: "request_aborted", service: "runtime", retryable: false,
207
+ }));
208
+ return;
209
+ }
203
210
  const socket = new ws_1.default(target.url, { headers });
211
+ const cleanup = () => {
212
+ clearTimeout(timer);
213
+ options.signal?.removeEventListener("abort", abort);
214
+ };
204
215
  const rejectOnce = (error) => {
205
216
  if (settled) {
206
217
  return;
207
218
  }
208
219
  settled = true;
220
+ cleanup();
221
+ response?.destroy();
222
+ socket.terminate();
209
223
  reject(normalizeWebSocketError(error));
210
224
  };
225
+ const abort = () => rejectOnce(new error_1.HyperbrowserError("Runtime websocket request canceled", {
226
+ code: "request_aborted", service: "runtime", retryable: false,
227
+ }));
228
+ const timer = setTimeout(() => rejectOnce(new error_1.HyperbrowserError("Runtime websocket handshake timed out", {
229
+ code: "request_timeout", service: "runtime", retryable: true,
230
+ })), options.timeoutMs ?? 30000);
231
+ timer.unref?.();
232
+ options.signal?.addEventListener("abort", abort, { once: true });
211
233
  socket.once("open", () => {
212
234
  if (settled) {
213
235
  return;
214
236
  }
215
237
  settled = true;
238
+ cleanup();
239
+ // Frames can arrive with the upgrade response, before an awaiting caller resumes.
240
+ socket.pause();
216
241
  resolve(socket);
217
242
  });
218
- socket.once("unexpected-response", (_request, response) => {
219
- void buildHandshakeError(response).then(rejectOnce).catch(rejectOnce);
243
+ socket.once("unexpected-response", (_request, incoming) => {
244
+ response = incoming;
245
+ void buildHandshakeError(incoming).then(rejectOnce).catch(rejectOnce);
220
246
  });
221
247
  socket.once("error", rejectOnce);
222
248
  });
@@ -0,0 +1,30 @@
1
+ import { BasicResponse } from "../../types";
2
+ import { BaseService } from "../base";
3
+ import { JevComputerUseTaskResponse, JevComputerUseTaskStatusResponse, StartJevComputerUseTaskParams, StartJevComputerUseTaskResponse } from "../../types/agents/jev-computer-use";
4
+ export declare class JevComputerUseService extends BaseService {
5
+ /**
6
+ * Start a new Jev Computer Use task job
7
+ * @param params The parameters for the task job
8
+ */
9
+ start(params: StartJevComputerUseTaskParams): Promise<StartJevComputerUseTaskResponse>;
10
+ /**
11
+ * Get the status of a Jev Computer Use task job
12
+ * @param id The ID of the task job to get
13
+ */
14
+ getStatus(id: string): Promise<JevComputerUseTaskStatusResponse>;
15
+ /**
16
+ * Get the result of a Jev Computer Use task job
17
+ * @param id The ID of the task job to get
18
+ */
19
+ get(id: string): Promise<JevComputerUseTaskResponse>;
20
+ /**
21
+ * Stop a Jev Computer Use task job
22
+ * @param id The ID of the task job to stop
23
+ */
24
+ stop(id: string): Promise<BasicResponse>;
25
+ /**
26
+ * Start a Jev Computer Use task job and wait for it to complete
27
+ * @param params The parameters for the task job
28
+ */
29
+ startAndWait(params: StartJevComputerUseTaskParams): Promise<JevComputerUseTaskResponse>;
30
+ }