@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
@@ -3,12 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SandboxFilesApi = exports.SandboxWatchDirHandle = void 0;
6
+ exports.SandboxFilesApi = exports.SandboxWatchDirHandle = exports.SandboxFileWatchHandle = void 0;
7
7
  const buffer_1 = require("buffer");
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const web_1 = require("node:stream/web");
10
10
  const ws_1 = __importDefault(require("ws"));
11
- const client_1 = require("../client");
11
+ const stream_1 = require("stream");
12
+ const error_1 = require("../error");
12
13
  const ws_2 = require("./ws");
13
14
  const normalizeFileType = (value) => {
14
15
  if (!value) {
@@ -123,12 +124,13 @@ const encodeWriteData = async (data, encoding) => {
123
124
  }
124
125
  throw new Error("Unsupported write data type");
125
126
  };
126
- class RuntimeFileWatchHandle {
127
+ class SandboxFileWatchHandle {
127
128
  constructor(transport, getConnectionInfo, status, runtimeProxyOverride) {
128
129
  this.transport = transport;
129
130
  this.getConnectionInfo = getConnectionInfo;
130
131
  this.status = status;
131
132
  this.runtimeProxyOverride = runtimeProxyOverride;
133
+ this.connections = new Set();
132
134
  }
133
135
  get id() {
134
136
  return this.status.id;
@@ -136,27 +138,66 @@ class RuntimeFileWatchHandle {
136
138
  get path() {
137
139
  return this.status.path;
138
140
  }
139
- async stop() {
140
- await this.transport.requestJSON(`/sandbox/files/watch/${this.status.id}`, {
141
- method: "DELETE",
142
- });
141
+ get current() {
142
+ return this.toJSON();
143
+ }
144
+ toJSON() {
145
+ return {
146
+ ...this.status,
147
+ ...(this.status.events ? { events: this.status.events.map((event) => ({ ...event })) } : {}),
148
+ };
149
+ }
150
+ async refresh(includeEvents = false) {
151
+ const response = await this.transport.requestJSON(`/sandbox/files/watch/${encodeURIComponent(this.id)}`, undefined, includeEvents ? { includeEvents: true } : undefined);
152
+ this.status = response.watch;
153
+ return this;
143
154
  }
144
- async *events(cursor) {
155
+ async stop() {
156
+ try {
157
+ await this.transport.requestJSON(`/sandbox/files/watch/${encodeURIComponent(this.id)}`, {
158
+ method: "DELETE",
159
+ });
160
+ }
161
+ catch (error) {
162
+ if (!(error instanceof error_1.HyperbrowserError) || ![404, 409].includes(error.statusCode ?? 0))
163
+ throw error;
164
+ }
165
+ this.status = { ...this.status, active: false, stoppedAt: this.status.stoppedAt || Date.now() };
166
+ for (const socket of this.connections)
167
+ socket.terminate();
168
+ }
169
+ async *events(options = {}) {
170
+ const { cursor, route = "ws", signal } = options;
171
+ if (route !== "ws" && route !== "stream")
172
+ throw new error_1.HyperbrowserError("Watch route must be ws or stream");
173
+ if (signal?.aborted)
174
+ throw new error_1.HyperbrowserError("Watch canceled", {
175
+ code: "request_aborted",
176
+ service: "runtime",
177
+ });
145
178
  const connectionInfo = await this.getConnectionInfo();
146
- const target = (0, ws_2.toWebSocketUrl)(connectionInfo.baseUrl, `/sandbox/files/watch/${this.status.id}/ws?sessionId=${encodeURIComponent(connectionInfo.sandboxId)}${cursor !== undefined ? `&cursor=${encodeURIComponent(String(cursor))}` : ""}`, this.runtimeProxyOverride);
179
+ const target = (0, ws_2.toWebSocketUrl)(connectionInfo.baseUrl, `/sandbox/files/watch/${encodeURIComponent(this.status.id)}/${route}?sessionId=${encodeURIComponent(connectionInfo.sandboxId)}${cursor !== undefined ? `&cursor=${encodeURIComponent(String(cursor))}` : ""}`, this.runtimeProxyOverride);
147
180
  const headers = {
148
181
  Authorization: `Bearer ${connectionInfo.token}`,
149
182
  };
150
183
  if (target.hostHeader) {
151
184
  headers.Host = target.hostHeader;
152
185
  }
153
- const ws = await (0, ws_2.openRuntimeWebSocket)(target, headers);
186
+ const ws = await (0, ws_2.openRuntimeWebSocket)(target, headers, { signal });
187
+ this.connections.add(ws);
154
188
  const queue = new ws_2.AsyncEventQueue();
189
+ const abort = () => {
190
+ queue.fail(new error_1.HyperbrowserError("Watch canceled", { code: "request_aborted", service: "runtime" }));
191
+ ws.terminate();
192
+ };
193
+ signal?.addEventListener("abort", abort, { once: true });
194
+ if (signal?.aborted)
195
+ abort();
155
196
  ws.on("message", (data) => {
156
197
  try {
157
198
  const parsed = JSON.parse(data.toString());
158
199
  if ("error" in parsed) {
159
- queue.fail(new client_1.HyperbrowserError(parsed.error, {
200
+ queue.fail(new error_1.HyperbrowserError(parsed.error, {
160
201
  statusCode: 410,
161
202
  code: parsed.code,
162
203
  retryable: false,
@@ -165,9 +206,17 @@ class RuntimeFileWatchHandle {
165
206
  return;
166
207
  }
167
208
  if (parsed.type === "event") {
168
- queue.push(parsed.event);
209
+ this.status = {
210
+ ...this.status,
211
+ // Receiver sequences start at 1; zero denotes an empty buffer.
212
+ oldestSeq: this.status.oldestSeq || parsed.event.seq,
213
+ lastSeq: Math.max(this.status.lastSeq ?? 0, parsed.event.seq),
214
+ };
215
+ queue.push(parsed);
169
216
  return;
170
217
  }
218
+ this.status = parsed.status;
219
+ queue.push({ type: "done", status: this.current });
171
220
  queue.close();
172
221
  }
173
222
  catch (error) {
@@ -176,34 +225,38 @@ class RuntimeFileWatchHandle {
176
225
  });
177
226
  ws.on("close", () => queue.close());
178
227
  ws.on("error", (error) => queue.fail(error));
228
+ ws.resume?.();
179
229
  try {
180
230
  for await (const event of queue) {
181
231
  yield event;
182
232
  }
183
233
  }
184
234
  finally {
185
- if (ws.readyState !== ws_1.default.CLOSING && ws.readyState !== ws_1.default.CLOSED) {
186
- await new Promise((resolve) => {
187
- ws.once("close", () => resolve());
188
- ws.close();
189
- });
190
- }
235
+ signal?.removeEventListener("abort", abort);
236
+ this.connections.delete(ws);
237
+ // terminate also releases peers which never acknowledge a close frame.
238
+ if (ws.readyState !== ws_1.default.CLOSED)
239
+ ws.terminate();
191
240
  }
192
241
  }
193
242
  }
243
+ exports.SandboxFileWatchHandle = SandboxFileWatchHandle;
194
244
  class SandboxWatchDirHandle {
195
245
  constructor(watch, onEvent, onExit, timeoutMs) {
196
246
  this.watch = watch;
197
247
  this.onExit = onExit;
198
248
  this.stopRequested = false;
199
249
  this.exitNotified = false;
250
+ this.invokingCallback = false;
200
251
  if (timeoutMs !== undefined && timeoutMs > 0) {
201
252
  this.timeout = setTimeout(() => {
202
- void this.stop();
253
+ void this.stop().catch(() => undefined);
203
254
  }, timeoutMs);
204
255
  this.timeout.unref?.();
205
256
  }
206
257
  this.runPromise = this.run(onEvent);
258
+ // Callback failures must not become unhandled background rejections.
259
+ this.runPromise.catch(() => undefined);
207
260
  }
208
261
  async stop() {
209
262
  if (this.stopRequested) {
@@ -215,20 +268,27 @@ class SandboxWatchDirHandle {
215
268
  this.timeout = undefined;
216
269
  }
217
270
  await this.watch.stop();
218
- await this.runPromise.catch(() => undefined);
271
+ if (!this.invokingCallback)
272
+ await this.runPromise.catch(() => undefined);
219
273
  }
220
274
  async run(onEvent) {
221
275
  let exitError;
222
276
  try {
223
- for await (const event of this.watch.events()) {
277
+ for await (const message of this.watch.events()) {
278
+ if (message.type === "done")
279
+ break;
280
+ const event = message.event;
224
281
  const type = normalizeEventType(event.op);
225
282
  if (!type) {
226
283
  continue;
227
284
  }
228
- await onEvent({
229
- type,
230
- name: relativeWatchName(this.watch.path, event.path),
231
- });
285
+ this.invokingCallback = true;
286
+ try {
287
+ await onEvent({ type, name: relativeWatchName(this.watch.path, event.path) });
288
+ }
289
+ finally {
290
+ this.invokingCallback = false;
291
+ }
232
292
  }
233
293
  }
234
294
  catch (error) {
@@ -241,7 +301,13 @@ class SandboxWatchDirHandle {
241
301
  }
242
302
  if (!this.exitNotified) {
243
303
  this.exitNotified = true;
244
- await this.onExit?.(exitError);
304
+ this.invokingCallback = true;
305
+ try {
306
+ await this.onExit?.(exitError);
307
+ }
308
+ finally {
309
+ this.invokingCallback = false;
310
+ }
245
311
  }
246
312
  }
247
313
  }
@@ -264,7 +330,7 @@ class SandboxFilesApi {
264
330
  return true;
265
331
  }
266
332
  catch (error) {
267
- if (error instanceof client_1.HyperbrowserError && error.statusCode === 404) {
333
+ if (error instanceof error_1.HyperbrowserError && error.statusCode === 404) {
268
334
  return false;
269
335
  }
270
336
  if (error instanceof Error && /not found|no such file|does not exist/i.test(error.message)) {
@@ -381,12 +447,13 @@ class SandboxFilesApi {
381
447
  });
382
448
  return Boolean(response.created);
383
449
  }
384
- async rename(oldPath, newPath) {
450
+ async rename(oldPath, newPath, options = {}) {
385
451
  const response = await this.transport.requestJSON("/sandbox/files/move", {
386
452
  method: "POST",
387
453
  body: this.withRunAsBody({
388
454
  from: oldPath,
389
455
  to: newPath,
456
+ overwrite: options.overwrite,
390
457
  }),
391
458
  headers: {
392
459
  "content-type": "application/json",
@@ -440,18 +507,60 @@ class SandboxFilesApi {
440
507
  });
441
508
  }
442
509
  async watchDir(path, onEvent, options = {}) {
510
+ const watch = await this.watch(path, options);
511
+ return new SandboxWatchDirHandle(watch, onEvent, options.onExit, options.timeoutMs);
512
+ }
513
+ async watch(path, options = {}) {
443
514
  const response = await this.transport.requestJSON("/sandbox/files/watch", {
444
515
  method: "POST",
445
- body: this.withRunAsBody({
446
- path,
447
- recursive: options.recursive,
448
- }),
449
- headers: {
450
- "content-type": "application/json",
451
- },
516
+ body: this.withRunAsBody({ path, recursive: options.recursive }),
517
+ headers: { "content-type": "application/json" },
452
518
  });
453
- const watch = new RuntimeFileWatchHandle(this.transport, this.getConnectionInfo, response.watch, this.runtimeProxyOverride);
454
- return new SandboxWatchDirHandle(watch, onEvent, options.onExit, options.timeoutMs);
519
+ return new SandboxFileWatchHandle(this.transport, this.getConnectionInfo, response.watch, this.runtimeProxyOverride);
520
+ }
521
+ async getWatch(id, includeEvents = false) {
522
+ const response = await this.transport.requestJSON(`/sandbox/files/watch/${encodeURIComponent(id)}`, undefined, includeEvents ? { includeEvents: true } : undefined);
523
+ return new SandboxFileWatchHandle(this.transport, this.getConnectionInfo, response.watch, this.runtimeProxyOverride);
524
+ }
525
+ stat(path) {
526
+ return this.getInfo(path);
527
+ }
528
+ mkdir(path, options = {}) {
529
+ return this.makeDir(path, options);
530
+ }
531
+ move(params) {
532
+ return this.rename(params.source, params.destination, params);
533
+ }
534
+ delete(path, options = {}) {
535
+ return this.remove(path, options);
536
+ }
537
+ /** Upload without buffering; the source is closed on completion, rejection or cancellation. */
538
+ async uploadStream(path, source, options = {}) {
539
+ if (options.contentLength !== undefined &&
540
+ (!Number.isSafeInteger(options.contentLength) || options.contentLength < 0)) {
541
+ throw new error_1.HyperbrowserError("contentLength must be a nonnegative safe integer");
542
+ }
543
+ const body = stream_1.Readable.from(source, { objectMode: false, highWaterMark: 64 * 1024 });
544
+ try {
545
+ return await this.transport.requestJSON("/sandbox/files/upload", {
546
+ method: "PUT",
547
+ body,
548
+ signal: options.signal,
549
+ headers: {
550
+ "content-type": "application/octet-stream",
551
+ ...(options.contentLength !== undefined
552
+ ? { "content-length": String(options.contentLength) }
553
+ : {}),
554
+ },
555
+ }, this.withRunAsQuery({ path }));
556
+ }
557
+ finally {
558
+ body.destroy();
559
+ }
560
+ }
561
+ /** Pull-based download; consume with for-await or Readable.from(). */
562
+ downloadStream(path, options = {}) {
563
+ return this.transport.streamBytes("/sandbox/files/download", { signal: options.signal }, this.withRunAsQuery({ path }));
455
564
  }
456
565
  async uploadUrl(path, options = {}) {
457
566
  return this.transport.requestJSON("/sandbox/files/presign-upload", {
@@ -0,0 +1,16 @@
1
+ import { SandboxImageBuildInputFormat, SandboxImageInit } from "../../types/sandbox";
2
+ export declare const IMAGE_BUILD_INPUT_FORMAT: SandboxImageBuildInputFormat;
3
+ export declare const CONTEXT_MANIFEST_INPUT_FORMAT: SandboxImageBuildInputFormat;
4
+ export declare const DOCKER_IMAGE_MANIFEST_INPUT_FORMAT: SandboxImageBuildInputFormat;
5
+ export declare const IMAGE_BUILD_SOURCE_PLATFORM = "linux/amd64";
6
+ export interface DockerImageBuildArtifact {
7
+ path: string;
8
+ sha256Hex: string;
9
+ sizeBytes: number;
10
+ inputFormat: SandboxImageBuildInputFormat;
11
+ sourcePlatform: string;
12
+ imageConfigUser: string;
13
+ imageInit?: SandboxImageInit;
14
+ }
15
+ export declare const removeArtifact: (artifact: DockerImageBuildArtifact) => void;
16
+ export declare const removeWorkspace: (workspace: string) => void;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeWorkspace = exports.removeArtifact = exports.IMAGE_BUILD_SOURCE_PLATFORM = exports.DOCKER_IMAGE_MANIFEST_INPUT_FORMAT = exports.CONTEXT_MANIFEST_INPUT_FORMAT = exports.IMAGE_BUILD_INPUT_FORMAT = void 0;
4
+ const fs_1 = require("fs");
5
+ exports.IMAGE_BUILD_INPUT_FORMAT = "rootfs_export_tar_gz";
6
+ exports.CONTEXT_MANIFEST_INPUT_FORMAT = "dockerfile_context_manifest_v1";
7
+ exports.DOCKER_IMAGE_MANIFEST_INPUT_FORMAT = "docker_image_manifest_v1";
8
+ exports.IMAGE_BUILD_SOURCE_PLATFORM = "linux/amd64";
9
+ const removeArtifact = (artifact) => {
10
+ (0, fs_1.rmSync)(artifact.path, { force: true });
11
+ };
12
+ exports.removeArtifact = removeArtifact;
13
+ const removeWorkspace = (workspace) => {
14
+ (0, fs_1.rmSync)(workspace, { recursive: true, force: true });
15
+ };
16
+ exports.removeWorkspace = removeWorkspace;
@@ -0,0 +1,2 @@
1
+ /** Minimal BLAKE2b (RFC 7693) supporting arbitrary digest lengths. */
2
+ export declare const blake2b: (data: Buffer, digestSize: number) => Buffer;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /** Minimal BLAKE2b (RFC 7693) supporting arbitrary digest lengths. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.blake2b = void 0;
5
+ const IV = [
6
+ 0x6a09e667f3bcc908n,
7
+ 0xbb67ae8584caa73bn,
8
+ 0x3c6ef372fe94f82bn,
9
+ 0xa54ff53a5f1d36f1n,
10
+ 0x510e527fade682d1n,
11
+ 0x9b05688c2b3e6c1fn,
12
+ 0x1f83d9abfb41bd6bn,
13
+ 0x5be0cd19137e2179n,
14
+ ];
15
+ const SIGMA = [
16
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
17
+ [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
18
+ [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
19
+ [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
20
+ [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
21
+ [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
22
+ [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
23
+ [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
24
+ [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
25
+ [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
26
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
27
+ [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
28
+ ];
29
+ const MASK = (1n << 64n) - 1n;
30
+ const rotr = (value, bits) => ((value >> bits) | (value << (64n - bits))) & MASK;
31
+ const compress = (h, block, counter, last) => {
32
+ const m = [];
33
+ for (let index = 0; index < 16; index += 1) {
34
+ m.push(block.readBigUInt64LE(index * 8));
35
+ }
36
+ const v = [...h, ...IV];
37
+ v[12] ^= counter & MASK;
38
+ v[13] ^= (counter >> 64n) & MASK;
39
+ if (last) {
40
+ v[14] ^= MASK;
41
+ }
42
+ const mix = (a, b, c, d, x, y) => {
43
+ v[a] = (v[a] + v[b] + x) & MASK;
44
+ v[d] = rotr(v[d] ^ v[a], 32n);
45
+ v[c] = (v[c] + v[d]) & MASK;
46
+ v[b] = rotr(v[b] ^ v[c], 24n);
47
+ v[a] = (v[a] + v[b] + y) & MASK;
48
+ v[d] = rotr(v[d] ^ v[a], 16n);
49
+ v[c] = (v[c] + v[d]) & MASK;
50
+ v[b] = rotr(v[b] ^ v[c], 63n);
51
+ };
52
+ for (let round = 0; round < 12; round += 1) {
53
+ const s = SIGMA[round];
54
+ mix(0, 4, 8, 12, m[s[0]], m[s[1]]);
55
+ mix(1, 5, 9, 13, m[s[2]], m[s[3]]);
56
+ mix(2, 6, 10, 14, m[s[4]], m[s[5]]);
57
+ mix(3, 7, 11, 15, m[s[6]], m[s[7]]);
58
+ mix(0, 5, 10, 15, m[s[8]], m[s[9]]);
59
+ mix(1, 6, 11, 12, m[s[10]], m[s[11]]);
60
+ mix(2, 7, 8, 13, m[s[12]], m[s[13]]);
61
+ mix(3, 4, 9, 14, m[s[14]], m[s[15]]);
62
+ }
63
+ for (let index = 0; index < 8; index += 1) {
64
+ h[index] = h[index] ^ v[index] ^ v[index + 8];
65
+ }
66
+ };
67
+ const blake2b = (data, digestSize) => {
68
+ if (!Number.isInteger(digestSize) || digestSize < 1 || digestSize > 64) {
69
+ throw new RangeError("digestSize must be between 1 and 64");
70
+ }
71
+ const h = [...IV];
72
+ h[0] ^= BigInt(0x01010000 ^ digestSize);
73
+ let offset = 0;
74
+ let counter = 0n;
75
+ while (data.length - offset > 128) {
76
+ counter += 128n;
77
+ compress(h, data.subarray(offset, offset + 128), counter, false);
78
+ offset += 128;
79
+ }
80
+ const remaining = data.subarray(offset);
81
+ const block = Buffer.alloc(128);
82
+ remaining.copy(block);
83
+ counter += BigInt(remaining.length);
84
+ compress(h, block, counter, true);
85
+ const out = Buffer.alloc(64);
86
+ h.forEach((word, index) => out.writeBigUInt64LE(word, index * 8));
87
+ return out.subarray(0, digestSize);
88
+ };
89
+ exports.blake2b = blake2b;
@@ -0,0 +1,19 @@
1
+ /** Python `posixpath.normpath` semantics (keeps exactly two leading slashes). */
2
+ export declare const posixNormpath: (value: string) => string;
3
+ export declare const posixDirname: (value: string) => string;
4
+ export declare const posixJoin: (...parts: string[]) => string;
5
+ /** Compare strings by Unicode code point, like Python's default `str` ordering. */
6
+ export declare const compareCodePoints: (a: string, b: string) => number;
7
+ /**
8
+ * Compact JSON matching Python `json.dumps(value, separators=(",", ":"))`.
9
+ * `sortKeys` mirrors `sort_keys=True`; `ensureAscii` mirrors the default
10
+ * `ensure_ascii=True` escaping of non-ASCII characters.
11
+ */
12
+ export declare const compactJson: (value: unknown, options?: {
13
+ sortKeys?: boolean;
14
+ ensureAscii?: boolean;
15
+ }) => string;
16
+ export declare const SHA256_HEX_PATTERN: RegExp;
17
+ export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
18
+ export declare const requireRecord: (value: unknown, label: string) => Record<string, unknown>;
19
+ export declare const sleep: (seconds: number) => Promise<void>;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sleep = exports.requireRecord = exports.isRecord = exports.SHA256_HEX_PATTERN = exports.compactJson = exports.compareCodePoints = exports.posixJoin = exports.posixDirname = exports.posixNormpath = void 0;
4
+ /** Python `posixpath.normpath` semantics (keeps exactly two leading slashes). */
5
+ const posixNormpath = (value) => {
6
+ if (value === "") {
7
+ return ".";
8
+ }
9
+ let initialSlashes = value.startsWith("/") ? 1 : 0;
10
+ if (initialSlashes && value.startsWith("//") && !value.startsWith("///")) {
11
+ initialSlashes = 2;
12
+ }
13
+ const components = [];
14
+ for (const component of value.split("/")) {
15
+ if (component === "" || component === ".") {
16
+ continue;
17
+ }
18
+ if (component !== ".." ||
19
+ (!initialSlashes && components.length === 0) ||
20
+ (components.length > 0 && components[components.length - 1] === "..")) {
21
+ components.push(component);
22
+ }
23
+ else if (components.length > 0) {
24
+ components.pop();
25
+ }
26
+ }
27
+ const joined = "/".repeat(initialSlashes) + components.join("/");
28
+ return joined || ".";
29
+ };
30
+ exports.posixNormpath = posixNormpath;
31
+ const posixDirname = (value) => {
32
+ const index = value.lastIndexOf("/") + 1;
33
+ let head = value.slice(0, index);
34
+ if (head && head !== "/".repeat(head.length)) {
35
+ head = head.replace(/\/+$/, "");
36
+ }
37
+ return head;
38
+ };
39
+ exports.posixDirname = posixDirname;
40
+ const posixJoin = (...parts) => {
41
+ let joined = "";
42
+ for (const part of parts) {
43
+ if (part.startsWith("/")) {
44
+ joined = part;
45
+ }
46
+ else if (!joined || joined.endsWith("/")) {
47
+ joined += part;
48
+ }
49
+ else {
50
+ joined += "/" + part;
51
+ }
52
+ }
53
+ return joined;
54
+ };
55
+ exports.posixJoin = posixJoin;
56
+ /** Compare strings by Unicode code point, like Python's default `str` ordering. */
57
+ const compareCodePoints = (a, b) => {
58
+ const left = Array.from(a);
59
+ const right = Array.from(b);
60
+ const length = Math.min(left.length, right.length);
61
+ for (let index = 0; index < length; index += 1) {
62
+ const difference = (left[index].codePointAt(0) ?? 0) - (right[index].codePointAt(0) ?? 0);
63
+ if (difference !== 0) {
64
+ return difference;
65
+ }
66
+ }
67
+ return left.length - right.length;
68
+ };
69
+ exports.compareCodePoints = compareCodePoints;
70
+ const escapeNonAscii = (json) => json.replace(/[\u007f-\uffff]/g, (character) => {
71
+ return "\\u" + character.charCodeAt(0).toString(16).padStart(4, "0");
72
+ });
73
+ // Serialize keys directly: assigning sorted keys to an object lets JavaScript
74
+ // reorder integer-like keys and treats __proto__ as a setter.
75
+ const sortedJson = (value) => {
76
+ if (Array.isArray(value))
77
+ return `[${value.map((item) => sortedJson(item) ?? "null").join(",")}]`;
78
+ if (value && typeof value === "object") {
79
+ const record = value;
80
+ const entries = Object.keys(record).sort(exports.compareCodePoints).flatMap((key) => {
81
+ const item = sortedJson(record[key]);
82
+ return item === undefined ? [] : [`${JSON.stringify(key)}:${item}`];
83
+ });
84
+ return `{${entries.join(",")}}`;
85
+ }
86
+ return JSON.stringify(value);
87
+ };
88
+ /**
89
+ * Compact JSON matching Python `json.dumps(value, separators=(",", ":"))`.
90
+ * `sortKeys` mirrors `sort_keys=True`; `ensureAscii` mirrors the default
91
+ * `ensure_ascii=True` escaping of non-ASCII characters.
92
+ */
93
+ const compactJson = (value, options = {}) => {
94
+ const json = options.sortKeys ? sortedJson(value) : JSON.stringify(value);
95
+ return options.ensureAscii === false ? json : escapeNonAscii(json);
96
+ };
97
+ exports.compactJson = compactJson;
98
+ exports.SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
99
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
100
+ exports.isRecord = isRecord;
101
+ const requireRecord = (value, label) => {
102
+ if (!(0, exports.isRecord)(value)) {
103
+ throw new Error(`${label} must be a JSON object`);
104
+ }
105
+ return value;
106
+ };
107
+ exports.requireRecord = requireRecord;
108
+ const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, Math.max(0, seconds) * 1000));
109
+ exports.sleep = sleep;
@@ -0,0 +1,43 @@
1
+ /** Remote Dockerfile build context selection, fingerprinting, and packaging. */
2
+ import { SandboxBuildContextManifest } from "../../types/sandbox";
3
+ import { DockerImageBuildArtifact } from "./artifacts";
4
+ import { DockerIgnoreMatcher } from "./dockerignore";
5
+ /** The packaged context no longer matches the caller's cache fingerprint. */
6
+ export declare class DockerBuildContextChangedError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ export interface PackagedDockerBuildContext {
10
+ artifact: DockerImageBuildArtifact;
11
+ manifest: SandboxBuildContextManifest;
12
+ bundles: Record<string, DockerImageBuildArtifact>;
13
+ workspace: string;
14
+ fingerprint: string;
15
+ cleanup(): void;
16
+ }
17
+ export interface DockerBuildContextOptions {
18
+ dockerfile?: string;
19
+ forceFullContext?: boolean;
20
+ }
21
+ export declare const loadDockerignore: (ignorePath: string) => DockerIgnoreMatcher | null;
22
+ export declare const collectContextEntries: (contextRoot: string, sources: string[], options: {
23
+ ignoreMatcher: DockerIgnoreMatcher | null;
24
+ required: boolean;
25
+ }) => Set<string>;
26
+ /**
27
+ * Fingerprint the effective remote context without compressing or staging it.
28
+ *
29
+ * Uses the same Dockerfile source selection, ignore rules, and normalized tar
30
+ * entries as remote packaging. Pass the result as `expectedContextFingerprint`
31
+ * when building to detect context changes. Build options outside the context
32
+ * (e.g. platform or imageInit) must also be included in the caller's cache
33
+ * key. Mutable base tags and network resources are not resolved.
34
+ */
35
+ export declare const dockerBuildContextFingerprint: (contextPath: string, options?: DockerBuildContextOptions) => Promise<string>;
36
+ /** Serialize a manifest exactly as the Python SDK does (field order, no nulls). */
37
+ export declare const canonicalManifestJson: (value: unknown) => Buffer;
38
+ export declare const writeManifestArtifact: (workspace: string, filename: string, data: Buffer, inputFormat: DockerImageBuildArtifact["inputFormat"], extras?: Pick<DockerImageBuildArtifact, "imageConfigUser" | "imageInit">) => DockerImageBuildArtifact;
39
+ export interface PackageDockerBuildContextOptions extends DockerBuildContextOptions {
40
+ tempDir?: string;
41
+ expectedContextFingerprint?: string;
42
+ }
43
+ export declare const packageDockerBuildContextManifest: (contextPath: string, options?: PackageDockerBuildContextOptions) => Promise<PackagedDockerBuildContext>;