@agen-ai/sandbox-docker 0.0.0-stage → 0.4.4

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -2
  3. package/dist/datasets/configuration.d.ts +10 -0
  4. package/dist/datasets/configuration.js +20 -0
  5. package/dist/datasets/mounts.d.ts +21 -0
  6. package/dist/datasets/mounts.js +50 -0
  7. package/dist/datasets/types.d.ts +6 -0
  8. package/dist/datasets/types.js +0 -0
  9. package/dist/datasets/warnings.d.ts +2 -0
  10. package/dist/datasets/warnings.js +7 -0
  11. package/dist/engine/client.d.ts +14 -0
  12. package/dist/engine/client.js +164 -0
  13. package/dist/engine/contracts.d.ts +40 -0
  14. package/dist/engine/contracts.js +22 -0
  15. package/dist/engine/response.d.ts +18 -0
  16. package/dist/engine/response.js +109 -0
  17. package/dist/engine/stream.d.ts +3 -0
  18. package/dist/engine/stream.js +134 -0
  19. package/dist/engine/upgrade.d.ts +9 -0
  20. package/dist/engine/upgrade.js +164 -0
  21. package/dist/engine/upload.d.ts +4 -0
  22. package/dist/engine/upload.js +48 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.js +30 -0
  25. package/dist/policy/config.d.ts +22 -0
  26. package/dist/policy/config.js +63 -0
  27. package/dist/policy/container.d.ts +70 -0
  28. package/dist/policy/container.js +81 -0
  29. package/dist/policy/preflight.d.ts +9 -0
  30. package/dist/policy/preflight.js +89 -0
  31. package/dist/policy/profiles.d.ts +32 -0
  32. package/dist/policy/profiles.js +44 -0
  33. package/dist/process/attachment.d.ts +19 -0
  34. package/dist/process/attachment.js +109 -0
  35. package/dist/process/framing.d.ts +10 -0
  36. package/dist/process/framing.js +43 -0
  37. package/dist/process/input.d.ts +16 -0
  38. package/dist/process/input.js +62 -0
  39. package/dist/process/output.d.ts +13 -0
  40. package/dist/process/output.js +41 -0
  41. package/dist/process/owner.d.ts +30 -0
  42. package/dist/process/owner.js +129 -0
  43. package/dist/recovery/identity.d.ts +11 -0
  44. package/dist/recovery/identity.js +78 -0
  45. package/dist/recovery/reconcile.d.ts +5 -0
  46. package/dist/recovery/reconcile.js +44 -0
  47. package/dist/resources/api.d.ts +23 -0
  48. package/dist/resources/api.js +70 -0
  49. package/dist/resources/fingerprint.d.ts +3 -0
  50. package/dist/resources/fingerprint.js +94 -0
  51. package/dist/resources/inspect.d.ts +49 -0
  52. package/dist/resources/inspect.js +103 -0
  53. package/dist/resources/owner.d.ts +46 -0
  54. package/dist/resources/owner.js +313 -0
  55. package/dist/workspace/exports.d.ts +5 -0
  56. package/dist/workspace/exports.js +52 -0
  57. package/dist/workspace/imports.d.ts +8 -0
  58. package/dist/workspace/imports.js +49 -0
  59. package/dist/workspace/paths.d.ts +8 -0
  60. package/dist/workspace/paths.js +39 -0
  61. package/dist/workspace/selected.d.ts +5 -0
  62. package/dist/workspace/selected.js +34 -0
  63. package/dist/workspace/stat.d.ts +18 -0
  64. package/dist/workspace/stat.js +63 -0
  65. package/dist/workspace/tar/contracts.d.ts +28 -0
  66. package/dist/workspace/tar/contracts.js +0 -0
  67. package/dist/workspace/tar/decode.d.ts +3 -0
  68. package/dist/workspace/tar/decode.js +62 -0
  69. package/dist/workspace/tar/encode.d.ts +3 -0
  70. package/dist/workspace/tar/encode.js +103 -0
  71. package/dist/workspace/tar/entry.d.ts +6 -0
  72. package/dist/workspace/tar/entry.js +80 -0
  73. package/dist/workspace/tar/header.d.ts +11 -0
  74. package/dist/workspace/tar/header.js +84 -0
  75. package/dist/workspace/tar/pax.d.ts +3 -0
  76. package/dist/workspace/tar/pax.js +40 -0
  77. package/dist/workspace/tar/reader.d.ts +16 -0
  78. package/dist/workspace/tar/reader.js +69 -0
  79. package/package.json +59 -3
@@ -0,0 +1,109 @@
1
+ import { DockerEngineTransportError } from "./contracts.js";
2
+ function invalid(message) {
3
+ return new DockerEngineTransportError("invalid_response", message);
4
+ }
5
+ async function waitReadable(response) {
6
+ await new Promise((resolve, reject) => {
7
+ const cleanup = () => {
8
+ response.removeListener("readable", ready);
9
+ response.removeListener("end", ready);
10
+ response.removeListener("error", failed);
11
+ response.removeListener("close", closed);
12
+ };
13
+ const ready = () => {
14
+ cleanup();
15
+ resolve();
16
+ };
17
+ const failed = (error) => {
18
+ cleanup();
19
+ reject(error);
20
+ };
21
+ const closed = () => {
22
+ cleanup();
23
+ response.readableEnded ? resolve() : reject(invalid("Engine response closed before EOF."));
24
+ };
25
+ response.once("readable", ready);
26
+ response.once("end", ready);
27
+ response.once("error", failed);
28
+ response.once("close", closed);
29
+ if (response.readableEnded || response.readableLength) ready();
30
+ else if (response.destroyed) closed();
31
+ });
32
+ }
33
+ class DockerResponseBody {
34
+ constructor(response, chunkBytes, signal, interrupt) {
35
+ this.response = response;
36
+ this.chunkBytes = chunkBytes;
37
+ this.signal = signal;
38
+ this.interrupt = interrupt;
39
+ }
40
+ active = true;
41
+ entered = false;
42
+ drained = false;
43
+ pending;
44
+ failure;
45
+ bytes = 0n;
46
+ body = { [Symbol.asyncIterator]: () => {
47
+ if (!this.active || this.entered) throw invalid("Engine response body is single-use and callback-scoped.");
48
+ this.entered = true;
49
+ return { next: () => this.next(), return: async () => {
50
+ this.active = false;
51
+ if (!this.drained) this.interrupt(invalid("Engine response body was abandoned."));
52
+ await this.pending?.catch(() => {
53
+ });
54
+ return { done: true, value: void 0 };
55
+ } };
56
+ } };
57
+ next() {
58
+ if (!this.active || this.pending) {
59
+ const error = invalid(this.pending ? "Concurrent Engine response pulls are forbidden." : "Engine response body is retired.");
60
+ this.failure ??= { error };
61
+ const rejected = Promise.reject(error);
62
+ void rejected.catch(() => {
63
+ });
64
+ return rejected;
65
+ }
66
+ const actual = Promise.resolve().then(async () => {
67
+ try {
68
+ for (; ; ) {
69
+ this.signal.throwIfAborted();
70
+ const available = this.response.readableLength;
71
+ const chunk = this.response.read(available ? Math.min(this.chunkBytes, available) : this.chunkBytes);
72
+ if (!this.active) throw invalid("Engine callback returned with an active body pull.");
73
+ if (chunk !== null) {
74
+ if (!(chunk instanceof Uint8Array) || chunk.length > this.chunkBytes) throw invalid("Engine response chunks exceed the selected byte buffer.");
75
+ this.bytes += BigInt(chunk.length);
76
+ return { done: false, value: chunk };
77
+ }
78
+ if (this.response.readableEnded) {
79
+ if (!this.response.complete) throw invalid("Engine response HTTP framing is incomplete.");
80
+ this.drained = true;
81
+ return { done: true, value: void 0 };
82
+ }
83
+ await waitReadable(this.response);
84
+ }
85
+ } catch (error) {
86
+ this.failure ??= { error };
87
+ throw error;
88
+ } finally {
89
+ this.pending = void 0;
90
+ }
91
+ });
92
+ this.pending = actual;
93
+ void actual.catch(() => {
94
+ });
95
+ return actual;
96
+ }
97
+ async retire() {
98
+ const entered = this.pending;
99
+ this.active = false;
100
+ if (!this.drained || entered) this.interrupt(this.failure ? this.failure.error : invalid("Engine response callback must drain and join its body."));
101
+ await entered?.catch(() => {
102
+ });
103
+ if (this.failure) throw this.failure.error;
104
+ if (!this.drained || entered) throw invalid("Engine response callback did not drain and join its body.");
105
+ }
106
+ }
107
+ export {
108
+ DockerResponseBody
109
+ };
@@ -0,0 +1,3 @@
1
+ import type { DockerEngineStreamOptions, DockerEngineStreamResponse, DockerEngineStreamResult } from './contracts.js';
2
+ export declare function streamEngine<Value>(socketPath: string, method: 'GET' | 'HEAD' | 'PUT', path: string, options: DockerEngineStreamOptions, consume: (response: DockerEngineStreamResponse) => Promise<Value>): Promise<DockerEngineStreamResult<Value>>;
3
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1,134 @@
1
+ import { request as httpRequest } from "node:http";
2
+ import { DockerEngineTransportError, validateEngineRequest } from "./contracts.js";
3
+ import { DockerResponseBody } from "./response.js";
4
+ import { upload } from "./upload.js";
5
+ async function streamEngine(socketPath, method, path, options, consume) {
6
+ const connectTimeoutMs = options.connectTimeoutMs ?? 3e4;
7
+ const headerTimeoutMs = options.headerTimeoutMs ?? 3e4;
8
+ validateEngineRequest(path, { timeoutMs: connectTimeoutMs, maxResponseBytes: 0 });
9
+ validateEngineRequest(path, { timeoutMs: headerTimeoutMs, maxResponseBytes: 0 });
10
+ if (!Number.isSafeInteger(options.chunkBytes) || options.chunkBytes < 512 || options.chunkBytes > 65536) throw new TypeError("Engine chunkBytes must be within 512..65536.");
11
+ if (method !== "PUT" && options.body !== void 0) throw new TypeError("Only PUT transfers accept a request body.");
12
+ if (options.signal?.aborted) throw new DockerEngineTransportError("aborted", "Engine transfer aborted before dispatch.", { cause: options.signal.reason });
13
+ const controller = new AbortController();
14
+ const ready = Promise.withResolvers();
15
+ const closed = Promise.withResolvers();
16
+ const socketClosed = Promise.withResolvers();
17
+ let response;
18
+ let responseBody;
19
+ let socket;
20
+ let failure;
21
+ let headerTimer;
22
+ let result;
23
+ let retirementError;
24
+ let uploadedBytes = 0n;
25
+ const request = httpRequest({
26
+ socketPath,
27
+ method,
28
+ path,
29
+ agent: false,
30
+ maxHeaderSize: 16384,
31
+ headers: { connection: "close", ...options.contentType ? { "content-type": options.contentType } : {} }
32
+ });
33
+ const fail = (error, origin) => {
34
+ if (!failure || failure.origin === "retirement" && origin === "callback") failure = { error, origin };
35
+ controller.abort(error);
36
+ ready.reject(error);
37
+ response?.destroy();
38
+ request.destroy();
39
+ };
40
+ const onAbort = () => fail(new DockerEngineTransportError("aborted", "Engine transfer aborted.", { cause: options.signal?.reason }), "request");
41
+ request.on("error", (cause) => {
42
+ const malformed = "code" in cause && typeof cause.code === "string" && cause.code.startsWith("HPE_");
43
+ fail(new DockerEngineTransportError(malformed ? "invalid_response" : "connection", "Engine transfer connection failed.", { cause }), "request");
44
+ });
45
+ request.once("socket", (acquired) => {
46
+ socket = acquired;
47
+ acquired.once("close", () => socketClosed.resolve());
48
+ if (acquired.readableHighWaterMark > 65536 || acquired.writableHighWaterMark > 65536) {
49
+ fail(new DockerEngineTransportError("invalid_response", "Engine socket exceeds the bounded native buffer profile."), "request");
50
+ }
51
+ if (acquired.connecting) acquired.once("connect", () => clearTimeout(connectTimer));
52
+ else clearTimeout(connectTimer);
53
+ });
54
+ request.once("finish", () => {
55
+ if (!response && !failure) headerTimer = setTimeout(() => fail(new DockerEngineTransportError("deadline", "Engine response-header deadline exceeded."), "request"), headerTimeoutMs);
56
+ });
57
+ request.once("response", (incoming) => {
58
+ response = incoming;
59
+ clearTimeout(headerTimer);
60
+ incoming.on("error", (cause) => {
61
+ if (!controller.signal.aborted) fail(new DockerEngineTransportError("invalid_response", "Engine response stream failed.", { cause }), "request");
62
+ });
63
+ incoming.once("aborted", () => {
64
+ if (!controller.signal.aborted) fail(new DockerEngineTransportError("invalid_response", "Engine response was truncated."), "request");
65
+ });
66
+ if (incoming.readableHighWaterMark > 65536) fail(new DockerEngineTransportError("invalid_response", "Engine response exceeds the bounded native buffer profile."), "request");
67
+ ready.resolve(incoming);
68
+ });
69
+ request.once("upgrade", (_incoming, upgraded) => {
70
+ upgraded.destroy();
71
+ fail(new DockerEngineTransportError("invalid_response", "Unexpected Engine HTTP upgrade."), "request");
72
+ });
73
+ request.once("close", () => {
74
+ if (!failure && (!response || !request.writableFinished)) fail(new DockerEngineTransportError("connection", "Engine connection closed before transfer dispatch finished."), "request");
75
+ closed.resolve();
76
+ });
77
+ options.signal?.addEventListener("abort", onAbort, { once: true });
78
+ const connectTimer = setTimeout(() => fail(new DockerEngineTransportError("deadline", "Engine connection deadline exceeded."), "request"), connectTimeoutMs);
79
+ if (options.signal?.aborted) onAbort();
80
+ const writer = upload(request, options.body, controller.signal, options.chunkBytes).then((bytes) => {
81
+ uploadedBytes = bytes;
82
+ }, (error) => {
83
+ fail(error, "writer");
84
+ throw error;
85
+ });
86
+ void writer.catch(() => {
87
+ });
88
+ try {
89
+ const incoming = await ready.promise;
90
+ responseBody = new DockerResponseBody(incoming, options.chunkBytes, controller.signal, (error) => {
91
+ retirementError ??= error;
92
+ fail(error, "retirement");
93
+ });
94
+ if (incoming.statusCode === void 0) throw new DockerEngineTransportError("invalid_response", "Engine response omitted its status.");
95
+ try {
96
+ result = { value: await consume({
97
+ statusCode: incoming.statusCode,
98
+ headers: incoming.headers,
99
+ rawHeaders: incoming.rawHeaders,
100
+ body: responseBody.body,
101
+ signal: controller.signal
102
+ }) };
103
+ } catch (error) {
104
+ fail(error, "callback");
105
+ }
106
+ try {
107
+ await responseBody.retire();
108
+ } catch (error) {
109
+ if (!failure) fail(error, "retirement");
110
+ }
111
+ await writer;
112
+ } catch (error) {
113
+ if (!failure) fail(error, "request");
114
+ } finally {
115
+ const writerOutcome = await Promise.allSettled([writer]);
116
+ response?.destroy();
117
+ request.destroy();
118
+ await closed.promise;
119
+ if (socket) await socketClosed.promise;
120
+ clearTimeout(connectTimer);
121
+ clearTimeout(headerTimer);
122
+ options.signal?.removeEventListener("abort", onAbort);
123
+ const outcome = writerOutcome[0];
124
+ if (outcome.status === "rejected" && failure && outcome.reason !== failure.error && outcome.reason !== retirementError) {
125
+ failure = { error: new AggregateError([failure.error, outcome.reason], "Engine transfer and request producer cleanup both failed."), origin: failure.origin };
126
+ }
127
+ }
128
+ if (failure) throw failure.error;
129
+ if (!result) throw new DockerEngineTransportError("invalid_response", "Engine transfer did not settle its response.");
130
+ return { value: result.value, uploadedBytes, downloadedBytes: responseBody?.bytes ?? 0n };
131
+ }
132
+ export {
133
+ streamEngine
134
+ };
@@ -0,0 +1,9 @@
1
+ import type { Duplex } from 'node:stream';
2
+ import { type DockerEngineRequestOptions } from './contracts.js';
3
+ export interface DockerEngineAttachment {
4
+ readonly stream: Duplex;
5
+ readonly head: Uint8Array;
6
+ readonly closed: Promise<void>;
7
+ }
8
+ export declare function upgradeEngineConnection(socketPath: string, path: string, options: DockerEngineRequestOptions): Promise<DockerEngineAttachment>;
9
+ //# sourceMappingURL=upgrade.d.ts.map
@@ -0,0 +1,164 @@
1
+ import { request as httpRequest } from "node:http";
2
+ import { performance } from "node:perf_hooks";
3
+ import { DockerEngineTransportError, validateEngineRequest } from "./contracts.js";
4
+ async function upgradeEngineConnection(socketPath, path, options) {
5
+ validateEngineRequest(path, options);
6
+ const { timeoutMs, maxResponseBytes, signal } = options;
7
+ if (options.body === void 0 || options.contentType !== "application/json") {
8
+ throw new TypeError("Engine upgrade requires an explicit JSON request body");
9
+ }
10
+ if (signal?.aborted) throw new DockerEngineTransportError("aborted", "Engine upgrade aborted");
11
+ const deadline = performance.now() + timeoutMs;
12
+ const body = Buffer.from(options.body);
13
+ if (performance.now() >= deadline) throw new DockerEngineTransportError("deadline", "Engine upgrade deadline exceeded");
14
+ return new Promise((resolve, reject) => {
15
+ let response;
16
+ let socket;
17
+ let requestClosed = false;
18
+ let responseClosed = false;
19
+ let socketClosed = false;
20
+ let outcome;
21
+ let settled = false;
22
+ let receivedBytes = 0;
23
+ const closed = Promise.withResolvers();
24
+ void closed.promise.catch(() => {
25
+ });
26
+ let socketError;
27
+ const request = httpRequest({
28
+ socketPath,
29
+ method: "POST",
30
+ path,
31
+ agent: false,
32
+ maxHeaderSize: 16384,
33
+ headers: {
34
+ connection: "Upgrade",
35
+ upgrade: "tcp",
36
+ "content-type": "application/json",
37
+ "content-length": body.byteLength
38
+ }
39
+ });
40
+ const clearHandshake = () => {
41
+ clearTimeout(timer);
42
+ signal?.removeEventListener("abort", onAbort);
43
+ };
44
+ const settleFailureIfClosed = () => {
45
+ if (settled || outcome?.kind !== "failure" || !requestClosed || response !== void 0 && !responseClosed || socket !== void 0 && !socketClosed) return;
46
+ settled = true;
47
+ clearHandshake();
48
+ reject(outcome.error);
49
+ };
50
+ const fail = (error) => {
51
+ if (outcome !== void 0) return;
52
+ outcome = { kind: "failure", error };
53
+ response?.destroy();
54
+ socket?.destroy();
55
+ request.destroy();
56
+ settleFailureIfClosed();
57
+ };
58
+ const deadlineExpired = () => {
59
+ if (performance.now() < deadline) return false;
60
+ fail(new DockerEngineTransportError("deadline", "Engine upgrade deadline exceeded"));
61
+ return true;
62
+ };
63
+ const onAbort = () => fail(new DockerEngineTransportError("aborted", "Engine upgrade aborted"));
64
+ const timer = setTimeout(() => {
65
+ fail(new DockerEngineTransportError("deadline", "Engine upgrade deadline exceeded"));
66
+ }, Math.max(1, Math.ceil(deadline - performance.now())));
67
+ request.once("socket", (connection) => {
68
+ socket = connection;
69
+ connection.on("error", (cause) => {
70
+ socketError ??= new DockerEngineTransportError("connection", "Engine upgraded connection failed", { cause });
71
+ fail(socketError);
72
+ });
73
+ connection.once("close", () => {
74
+ socketClosed = true;
75
+ if (socketError === void 0) closed.resolve();
76
+ else closed.reject(socketError);
77
+ if (outcome === void 0) fail(new DockerEngineTransportError("connection", "Engine connection closed before upgrade"));
78
+ settleFailureIfClosed();
79
+ });
80
+ if (outcome?.kind === "failure") connection.destroy();
81
+ });
82
+ request.on("error", (cause) => {
83
+ const malformedResponse = "code" in cause && typeof cause.code === "string" && cause.code.startsWith("HPE_");
84
+ fail(new DockerEngineTransportError(
85
+ malformedResponse ? "invalid_response" : "connection",
86
+ malformedResponse ? "Engine upgrade has invalid HTTP framing" : "Engine upgrade connection failed",
87
+ { cause }
88
+ ));
89
+ });
90
+ request.once("close", () => {
91
+ requestClosed = true;
92
+ if (outcome === void 0 && response === void 0) {
93
+ fail(new DockerEngineTransportError("connection", "Engine connection closed before upgrade"));
94
+ }
95
+ settleFailureIfClosed();
96
+ });
97
+ request.once("upgrade", (message, connection, head) => {
98
+ if (outcome !== void 0) {
99
+ connection.destroy();
100
+ return;
101
+ }
102
+ if (deadlineExpired()) return;
103
+ const connectionTokens = message.headers.connection?.split(",").map((token) => token.trim().toLowerCase());
104
+ if (message.statusCode !== 101 || !connectionTokens?.includes("upgrade") || message.headers.upgrade?.toLowerCase() !== "tcp") {
105
+ fail(new DockerEngineTransportError("invalid_response", "Engine upgrade requires HTTP 101 with the tcp protocol"));
106
+ return;
107
+ }
108
+ if (connection.destroyed) {
109
+ fail(new DockerEngineTransportError("connection", "Engine upgraded connection is already closed"));
110
+ return;
111
+ }
112
+ if (head.byteLength > 65536) {
113
+ fail(new DockerEngineTransportError("response_limit", "Engine upgrade head exceeds its byte limit"));
114
+ return;
115
+ }
116
+ connection.pause();
117
+ outcome = { kind: "handed_off" };
118
+ settled = true;
119
+ clearHandshake();
120
+ resolve({ stream: connection, head: Buffer.from(head), closed: closed.promise });
121
+ });
122
+ request.once("response", (message) => {
123
+ response = message;
124
+ message.on("error", (cause) => {
125
+ fail(new DockerEngineTransportError("invalid_response", "Engine upgrade response stream failed", { cause }));
126
+ });
127
+ message.once("aborted", () => {
128
+ fail(new DockerEngineTransportError("invalid_response", "Engine upgrade response was truncated"));
129
+ });
130
+ message.once("close", () => {
131
+ responseClosed = true;
132
+ if (outcome === void 0) fail(new DockerEngineTransportError("invalid_response", "Engine upgrade response closed before completion"));
133
+ settleFailureIfClosed();
134
+ });
135
+ message.on("data", (chunk) => {
136
+ if (outcome !== void 0 || deadlineExpired()) return;
137
+ if (chunk.byteLength > maxResponseBytes - receivedBytes) {
138
+ fail(new DockerEngineTransportError("response_limit", "Engine upgrade response exceeds its byte limit"));
139
+ return;
140
+ }
141
+ receivedBytes += chunk.byteLength;
142
+ });
143
+ message.once("end", () => {
144
+ if (outcome !== void 0 || deadlineExpired()) return;
145
+ fail(new DockerEngineTransportError("invalid_response", `Engine upgrade requires HTTP 101, received ${message.statusCode}`));
146
+ });
147
+ if (outcome !== void 0) {
148
+ message.destroy();
149
+ return;
150
+ }
151
+ if (deadlineExpired()) return;
152
+ const contentLength = message.headers["content-length"];
153
+ if (contentLength !== void 0 && Number(contentLength) > maxResponseBytes) {
154
+ fail(new DockerEngineTransportError("response_limit", "Engine upgrade response exceeds its byte limit"));
155
+ }
156
+ });
157
+ signal?.addEventListener("abort", onAbort, { once: true });
158
+ if (signal?.aborted) onAbort();
159
+ if (outcome === void 0) request.end(body);
160
+ });
161
+ }
162
+ export {
163
+ upgradeEngineConnection
164
+ };
@@ -0,0 +1,4 @@
1
+ import type { ClientRequest } from 'node:http';
2
+ import type { DockerEngineStreamOptions } from './contracts.js';
3
+ export declare function upload(request: ClientRequest, input: DockerEngineStreamOptions['body'], signal: AbortSignal, chunkBytes: number): Promise<bigint>;
4
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { DockerEngineTransportError } from "./contracts.js";
2
+ async function upload(request, input, signal, chunkBytes) {
3
+ let bytes = 0n;
4
+ if (input !== void 0) {
5
+ const source = typeof input === "function" ? input(signal) : input;
6
+ const iterator = Symbol.asyncIterator in source ? source[Symbol.asyncIterator]() : source[Symbol.iterator]();
7
+ let done = false;
8
+ let primary;
9
+ try {
10
+ for (; ; ) {
11
+ signal.throwIfAborted();
12
+ const item = await iterator.next();
13
+ signal.throwIfAborted();
14
+ if (item.done) {
15
+ done = true;
16
+ break;
17
+ }
18
+ const chunk = item.value;
19
+ if (!(chunk instanceof Uint8Array) || chunk.length > chunkBytes) throw new DockerEngineTransportError("invalid_response", "Engine upload chunks exceed the selected byte buffer.");
20
+ if (chunk.length === 0) continue;
21
+ await new Promise((resolve, reject) => {
22
+ request.write(chunk, (error) => error ? reject(error) : resolve());
23
+ });
24
+ bytes += BigInt(chunk.length);
25
+ }
26
+ } catch (error) {
27
+ primary = { error };
28
+ } finally {
29
+ if (!done) {
30
+ try {
31
+ await iterator.return?.();
32
+ } catch (error) {
33
+ if (primary) throw new AggregateError([primary.error, error], "Engine upload and producer cleanup both failed.");
34
+ throw error;
35
+ }
36
+ }
37
+ }
38
+ if (primary) throw primary.error;
39
+ }
40
+ signal.throwIfAborted();
41
+ await new Promise((resolve, reject) => {
42
+ request.end((error) => error ? reject(error) : resolve());
43
+ });
44
+ return bytes;
45
+ }
46
+ export {
47
+ upload
48
+ };
@@ -0,0 +1,7 @@
1
+ import type { SandboxBackend } from '@agen-ai/sandbox';
2
+ import type { DockerSandboxBackendOptions } from './policy/config.js';
3
+ export type { DockerSandboxBackendOptions } from './policy/config.js';
4
+ export type { DockerSandboxProfile } from './policy/profiles.js';
5
+ export type { DockerSandboxDataset } from './datasets/types.js';
6
+ export declare function createDockerSandboxBackend(options: DockerSandboxBackendOptions): SandboxBackend;
7
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ import { createSandboxBackend } from "@agen-ai/sandbox/backend";
2
+ import { DockerEngine } from "./engine/client.js";
3
+ import { parseDockerConfig, DOCKER_BACKEND_ID } from "./policy/config.js";
4
+ import { dockerBackendRevision } from "./policy/profiles.js";
5
+ import { DockerApi } from "./resources/api.js";
6
+ import { DockerResourceOwner } from "./resources/owner.js";
7
+ import { reconcileDocker } from "./recovery/reconcile.js";
8
+ function createDockerSandboxBackend(options) {
9
+ const config = parseDockerConfig(options);
10
+ const api = new DockerApi(new DockerEngine(config.endpoint.slice(7)), 3e4);
11
+ return createSandboxBackend({
12
+ support: {
13
+ backendId: DOCKER_BACKEND_ID,
14
+ backendRevision: dockerBackendRevision(config.profile),
15
+ networkModes: ["isolated", "outbound"],
16
+ workspaceDiskQuota: false,
17
+ networkAllowlist: false,
18
+ independentProcessTermination: false
19
+ },
20
+ reserve(request) {
21
+ return new DockerResourceOwner(api, config, request);
22
+ },
23
+ reconcile(record, operation) {
24
+ return reconcileDocker(api, config, record, operation);
25
+ }
26
+ });
27
+ }
28
+ export {
29
+ createDockerSandboxBackend
30
+ };
@@ -0,0 +1,22 @@
1
+ import type { DockerSandboxProfile } from './profiles.js';
2
+ import type { DockerSandboxDataset } from '../datasets/types.js';
3
+ export interface DockerSandboxBackendOptions {
4
+ readonly profile?: DockerSandboxProfile;
5
+ readonly endpoint: string;
6
+ readonly namespace: string;
7
+ readonly imageDigest: string;
8
+ readonly spillDirectory: string;
9
+ readonly user: Readonly<{
10
+ uid: number;
11
+ gid: number;
12
+ }>;
13
+ readonly datasets?: readonly DockerSandboxDataset[];
14
+ }
15
+ export interface DockerSandboxConfig extends DockerSandboxBackendOptions {
16
+ readonly profile: DockerSandboxProfile;
17
+ readonly datasets: readonly DockerSandboxDataset[];
18
+ }
19
+ export declare function parseDockerConfig(input: unknown): DockerSandboxConfig;
20
+ export declare const DOCKER_BACKEND_ID = "agenai-docker";
21
+ export declare const DOCKER_API = "/v1.52";
22
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { z } from "zod/v4";
2
+ import { normalizeZodValidationError } from "@agen-ai/validation/zod";
3
+ import { SandboxValidationError } from "@agen-ai/sandbox";
4
+ import { parseSandboxSpillDirectory } from "@agen-ai/sandbox/node";
5
+ import { DOCKER_SANDBOX_PROFILES } from "./profiles.js";
6
+ import { datasetsSchema, MAX_DATASET_ATTACHMENTS, normalizeDatasets } from "../datasets/configuration.js";
7
+ const userSchema = z.strictObject({ uid: z.number().int().min(1).max(262143), gid: z.number().int().min(1).max(262143) });
8
+ const configSchema = z.strictObject({
9
+ profile: z.enum(DOCKER_SANDBOX_PROFILES).default("linux-x64"),
10
+ endpoint: z.string().min(9).max(512).regex(/^unix:\/\/\/[a-zA-Z0-9_.\-/]+$/u).refine((value) => !value.slice(7).split("/").some((part) => part === "." || part === "..") && !value.includes("//", 7), "Expected a canonical absolute Unix socket endpoint"),
11
+ namespace: z.string().regex(/^[a-z][a-z0-9-]{0,62}$/u),
12
+ imageDigest: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
13
+ spillDirectory: z.string(),
14
+ user: userSchema,
15
+ datasets: datasetsSchema.default([])
16
+ });
17
+ function plainData(value, depth = 0) {
18
+ if (value === null || typeof value !== "object") return value;
19
+ if (Array.isArray(value)) {
20
+ if (depth > 1 || Object.getPrototypeOf(value) !== Array.prototype || value.length > MAX_DATASET_ATTACHMENTS || Reflect.ownKeys(value).length !== value.length + 1) {
21
+ throw new SandboxValidationError([{ code: "invalid_input", path: [], message: "Docker datasets require a bounded dense data array" }]);
22
+ }
23
+ const result2 = [];
24
+ for (let index = 0; index < value.length; index += 1) {
25
+ const entry = Object.getOwnPropertyDescriptor(value, String(index));
26
+ if (!entry || !("value" in entry) || !entry.enumerable) {
27
+ throw new SandboxValidationError([{ code: "invalid_input", path: [], message: "Docker dataset entries must be enumerable data properties" }]);
28
+ }
29
+ result2.push(plainData(entry.value, depth + 1));
30
+ }
31
+ return result2;
32
+ }
33
+ if (depth > 2 || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
34
+ throw new SandboxValidationError([{ code: "invalid_input", path: [], message: "Docker configuration requires plain data objects" }]);
35
+ }
36
+ const result = /* @__PURE__ */ Object.create(null);
37
+ for (const key of Reflect.ownKeys(value)) {
38
+ const property = Object.getOwnPropertyDescriptor(value, key);
39
+ if (typeof key !== "string" || key === "__proto__" || property === void 0 || !("value" in property) || !property.enumerable) {
40
+ throw new SandboxValidationError([{ code: "invalid_input", path: [], message: "Docker configuration cannot contain accessors, symbols or hidden fields" }]);
41
+ }
42
+ result[key] = plainData(property.value, depth + 1);
43
+ }
44
+ return result;
45
+ }
46
+ function parseDockerConfig(input) {
47
+ const captured = plainData(input);
48
+ const result = configSchema.safeParse(captured);
49
+ if (!result.success) throw new SandboxValidationError(normalizeZodValidationError(configSchema, result.error, captured));
50
+ return Object.freeze({
51
+ ...result.data,
52
+ spillDirectory: parseSandboxSpillDirectory(result.data.spillDirectory),
53
+ user: Object.freeze(result.data.user),
54
+ datasets: normalizeDatasets(result.data.datasets)
55
+ });
56
+ }
57
+ const DOCKER_BACKEND_ID = "agenai-docker";
58
+ const DOCKER_API = "/v1.52";
59
+ export {
60
+ DOCKER_API,
61
+ DOCKER_BACKEND_ID,
62
+ parseDockerConfig
63
+ };
@@ -0,0 +1,70 @@
1
+ import type { SandboxPolicy, SandboxRecoveryRecord } from '@agen-ai/sandbox';
2
+ import type { DockerSandboxConfig } from './config.js';
3
+ import type { ContainerInspection } from '../resources/inspect.js';
4
+ export declare const KEEPER: string[];
5
+ export declare const DOCKER_ENVIRONMENT: readonly string[];
6
+ export declare function tmpfs(policy: SandboxPolicy, config: DockerSandboxConfig): Readonly<Record<string, string>>;
7
+ export declare function containerBody(config: DockerSandboxConfig, record: SandboxRecoveryRecord, policy: SandboxPolicy, role: 'compute' | 'export', storageId: string, networkId: string | undefined): {
8
+ Image: string;
9
+ User: string;
10
+ WorkingDir: string;
11
+ Entrypoint: string[];
12
+ Cmd: string[];
13
+ Env: readonly string[];
14
+ Tty: boolean;
15
+ OpenStdin: boolean;
16
+ AttachStdin: boolean;
17
+ AttachStdout: boolean;
18
+ AttachStderr: boolean;
19
+ NetworkDisabled: boolean;
20
+ Healthcheck: {
21
+ Test: string[];
22
+ };
23
+ Labels: Readonly<Record<string, string>>;
24
+ HostConfig: {
25
+ ReadonlyRootfs: boolean;
26
+ CapDrop: string[];
27
+ CapAdd: never[];
28
+ SecurityOpt: string[];
29
+ Privileged: boolean;
30
+ PublishAllPorts: boolean;
31
+ PortBindings: {};
32
+ Devices: never[];
33
+ DeviceRequests: never[];
34
+ Binds: never[];
35
+ PidMode: string;
36
+ IpcMode: string;
37
+ CgroupnsMode: string;
38
+ UsernsMode: string;
39
+ NetworkMode: string | undefined;
40
+ NanoCpus: number;
41
+ Memory: number;
42
+ MemorySwap: number;
43
+ PidsLimit: number;
44
+ RestartPolicy: {
45
+ Name: string;
46
+ MaximumRetryCount: number;
47
+ };
48
+ AutoRemove: boolean;
49
+ Tmpfs: Readonly<Record<string, string>>;
50
+ Mounts: ({
51
+ ImageOptions?: {
52
+ Subpath: string;
53
+ } | undefined;
54
+ Type: "image";
55
+ Source: string;
56
+ Target: string;
57
+ ReadOnly: true;
58
+ } | {
59
+ Type: "volume";
60
+ Source: string;
61
+ Target: string;
62
+ ReadOnly: boolean;
63
+ VolumeOptions: {
64
+ NoCopy: true;
65
+ };
66
+ })[];
67
+ };
68
+ };
69
+ export declare function verifyContainer(container: ContainerInspection, config: DockerSandboxConfig, policy: SandboxPolicy, role: 'compute' | 'export', storageId: string, networkId: string | undefined, started: boolean): void;
70
+ //# sourceMappingURL=container.d.ts.map