@tangle-network/agent-provider-tangle 0.2.0 → 0.3.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.
- package/README.md +22 -0
- package/dist/exact-process.d.ts +10 -0
- package/dist/exact-process.js +323 -0
- package/dist/index.d.ts +62 -1
- package/dist/index.js +20 -3
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -10,3 +10,25 @@ const provider = createTangleProvider({
|
|
|
10
10
|
client: new Sandbox({ apiKey: process.env.TANGLE_API_KEY }),
|
|
11
11
|
})
|
|
12
12
|
```
|
|
13
|
+
|
|
14
|
+
Pass `exactProcess: {}` only when the Sandbox deployment supports `agent: false` creates and reports `metadata.runtimeMode: "control"`.
|
|
15
|
+
The optional capability creates an ephemeral sandbox with an authenticated control service but no managed agent workload or agent credentials, explicit resources, exact blocked/domain egress, bounded binary file reads, shell-free launch, and recoverable process output plus terminal reason.
|
|
16
|
+
Set `teamId` inside `exactProcess` to scope create, lookup, and recovery to one team.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const provider = createTangleProvider({
|
|
20
|
+
client: new Sandbox({ apiKey: process.env.TANGLE_API_KEY }),
|
|
21
|
+
exactProcess: {},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const environment = await provider.exactProcess!.create({
|
|
25
|
+
image: 'ghcr.io/acme/agent@sha256:<64-hex-manifest-digest>',
|
|
26
|
+
egress: { mode: 'blocked' },
|
|
27
|
+
maxLifetimeMs: 120_000,
|
|
28
|
+
resources: { cpu: 1, memoryMb: 1024, diskMb: 1024 },
|
|
29
|
+
metadata: { executionId: 'run-1' },
|
|
30
|
+
idempotencyKey: 'run-1',
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The adapter rejects ordinary sandboxes during create, recovery, and list operations.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentExactProcessProvider } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
import type { SandboxClientLike } from "./index.js";
|
|
3
|
+
export interface TangleExactProcessOptions {
|
|
4
|
+
teamId?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function createTangleExactProcessProvider(input: {
|
|
7
|
+
client: SandboxClientLike;
|
|
8
|
+
options: TangleExactProcessOptions;
|
|
9
|
+
providerName: string;
|
|
10
|
+
}): AgentExactProcessProvider;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
const IMMUTABLE_TANGLE_IMAGE = /^(?:sha256:[a-f0-9]{64}|\S+@sha256:[a-f0-9]{64})$/i;
|
|
4
|
+
const EXACT_PROCESS_METADATA_KEY = "tangle.exactProcess";
|
|
5
|
+
const LIST_PAGE_SIZE = 1_000;
|
|
6
|
+
const MAX_LIST_OFFSET = 1_000;
|
|
7
|
+
export function createTangleExactProcessProvider(input) {
|
|
8
|
+
const { client, options, providerName } = input;
|
|
9
|
+
const get = client.get;
|
|
10
|
+
const list = client.list;
|
|
11
|
+
if (!get || !list) {
|
|
12
|
+
throw new Error("Tangle exact process provider requires get() and list()");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
async create(createInput) {
|
|
16
|
+
assertSupportedProviderOptions(createInput.providerOptions);
|
|
17
|
+
assertUnreservedMetadata(createInput.metadata);
|
|
18
|
+
const box = await client.create(exactSandboxOptions(createInput, options), {
|
|
19
|
+
...(createInput.signal ? { signal: createInput.signal } : {}),
|
|
20
|
+
...(createInput.provisionTimeoutMs === undefined
|
|
21
|
+
? {}
|
|
22
|
+
: { timeoutMs: createInput.provisionTimeoutMs }),
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
assertExactProcessSandbox(box);
|
|
26
|
+
return sandboxInstanceAsExactProcessEnvironment(box, providerName);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!box.delete)
|
|
30
|
+
throw error;
|
|
31
|
+
try {
|
|
32
|
+
await box.delete();
|
|
33
|
+
}
|
|
34
|
+
catch (cleanupError) {
|
|
35
|
+
throw new AggregateError([error, cleanupError], "Tangle exact process validation and cleanup both failed");
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async get(id) {
|
|
41
|
+
const box = await get.call(client, id);
|
|
42
|
+
if (!box || !isExactProcessSandbox(box))
|
|
43
|
+
return null;
|
|
44
|
+
return sandboxInstanceAsExactProcessEnvironment(box, providerName);
|
|
45
|
+
},
|
|
46
|
+
async list(query) {
|
|
47
|
+
assertSupportedProviderOptions(query?.providerOptions);
|
|
48
|
+
const matches = [];
|
|
49
|
+
for (let offset = 0; offset <= MAX_LIST_OFFSET; offset += LIST_PAGE_SIZE) {
|
|
50
|
+
const page = await list.call(client, {
|
|
51
|
+
...(options.teamId
|
|
52
|
+
? { scope: `team:${options.teamId}` }
|
|
53
|
+
: { scope: "personal" }),
|
|
54
|
+
limit: LIST_PAGE_SIZE,
|
|
55
|
+
offset,
|
|
56
|
+
});
|
|
57
|
+
for (const box of page) {
|
|
58
|
+
if (isExactProcessSandbox(box) &&
|
|
59
|
+
metadataMatches(box.metadata, query?.metadata)) {
|
|
60
|
+
matches.push(sandboxInstanceAsExactProcessEnvironment(box, providerName));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (page.length < LIST_PAGE_SIZE)
|
|
64
|
+
return matches;
|
|
65
|
+
}
|
|
66
|
+
throw new Error("Tangle exact process lookup exceeds the Sandbox list pagination limit");
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function exactSandboxOptions(input, defaults) {
|
|
71
|
+
if (!input.image.trim())
|
|
72
|
+
throw new Error("exact process image is required");
|
|
73
|
+
if (!IMMUTABLE_TANGLE_IMAGE.test(input.image)) {
|
|
74
|
+
throw new Error("Tangle exact process image must include a sha256 manifest digest");
|
|
75
|
+
}
|
|
76
|
+
if (!input.idempotencyKey.trim()) {
|
|
77
|
+
throw new Error("exact process idempotencyKey is required");
|
|
78
|
+
}
|
|
79
|
+
if (!Number.isSafeInteger(input.maxLifetimeMs) ||
|
|
80
|
+
input.maxLifetimeMs < 1 ||
|
|
81
|
+
input.maxLifetimeMs % 1_000 !== 0) {
|
|
82
|
+
throw new Error("Tangle exact process maxLifetimeMs must be a positive whole number of seconds");
|
|
83
|
+
}
|
|
84
|
+
if (input.provisionTimeoutMs !== undefined &&
|
|
85
|
+
(!Number.isSafeInteger(input.provisionTimeoutMs) ||
|
|
86
|
+
input.provisionTimeoutMs < 1)) {
|
|
87
|
+
throw new Error("exact process provisionTimeoutMs must be a positive integer");
|
|
88
|
+
}
|
|
89
|
+
if (input.egress.mode === "strict" &&
|
|
90
|
+
input.egress.allowDomains.length === 0) {
|
|
91
|
+
throw new Error("strict exact process egress requires at least one domain");
|
|
92
|
+
}
|
|
93
|
+
const resources = sandboxResourcesFromRequest(input.resources);
|
|
94
|
+
return {
|
|
95
|
+
image: input.image,
|
|
96
|
+
agent: false,
|
|
97
|
+
driver: { type: "host-agent", runtimeBackend: "docker" },
|
|
98
|
+
publicEdge: false,
|
|
99
|
+
ephemeral: true,
|
|
100
|
+
sshEnabled: false,
|
|
101
|
+
webTerminalEnabled: false,
|
|
102
|
+
secrets: [],
|
|
103
|
+
capabilities: [],
|
|
104
|
+
egressPolicy: input.egress.mode === "blocked"
|
|
105
|
+
? { mode: "blocked" }
|
|
106
|
+
: {
|
|
107
|
+
mode: "strict",
|
|
108
|
+
allowDomains: [...input.egress.allowDomains],
|
|
109
|
+
includeImplicitDomains: false,
|
|
110
|
+
},
|
|
111
|
+
maxLifetimeSeconds: input.maxLifetimeMs / 1_000,
|
|
112
|
+
idempotencyKey: input.idempotencyKey,
|
|
113
|
+
metadata: {
|
|
114
|
+
...input.metadata,
|
|
115
|
+
[EXACT_PROCESS_METADATA_KEY]: true,
|
|
116
|
+
},
|
|
117
|
+
...(defaults.teamId ? { teamId: defaults.teamId } : {}),
|
|
118
|
+
resources,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function sandboxResourcesFromRequest(requested) {
|
|
122
|
+
if (!Number.isFinite(requested.cpu) || requested.cpu <= 0) {
|
|
123
|
+
throw new Error("Tangle exact process CPU must be positive and finite");
|
|
124
|
+
}
|
|
125
|
+
if (!Number.isSafeInteger(requested.memoryMb) || requested.memoryMb < 1) {
|
|
126
|
+
throw new Error("Tangle exact process memoryMb must be a positive integer");
|
|
127
|
+
}
|
|
128
|
+
if (!Number.isSafeInteger(requested.diskMb) ||
|
|
129
|
+
requested.diskMb < 1 ||
|
|
130
|
+
requested.diskMb % 1_024 !== 0) {
|
|
131
|
+
throw new Error("Tangle exact process diskMb must be a positive whole number of gibibytes");
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
cpuCores: requested.cpu,
|
|
135
|
+
memoryMB: requested.memoryMb,
|
|
136
|
+
diskGB: requested.diskMb / 1_024,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function sandboxInstanceAsExactProcessEnvironment(box, providerName) {
|
|
140
|
+
if (!box.fs ||
|
|
141
|
+
box.fs.supportsWriteMode !== true ||
|
|
142
|
+
!box.process ||
|
|
143
|
+
!box.delete) {
|
|
144
|
+
throw new Error("Tangle sandbox does not expose exact files, processes, and deletion");
|
|
145
|
+
}
|
|
146
|
+
const process = box.process;
|
|
147
|
+
const fs = box.fs;
|
|
148
|
+
const destroy = box.delete.bind(box);
|
|
149
|
+
return {
|
|
150
|
+
id: String(box.id),
|
|
151
|
+
provider: providerName,
|
|
152
|
+
...(box.metadata ? { metadata: box.metadata } : {}),
|
|
153
|
+
process: {
|
|
154
|
+
async list() {
|
|
155
|
+
return (await process.list()).map(exactProcessStatusFromSandbox);
|
|
156
|
+
},
|
|
157
|
+
async get(pid) {
|
|
158
|
+
const handle = await process.get(pid);
|
|
159
|
+
return handle ? sandboxProcessAsExactProcess(handle) : null;
|
|
160
|
+
},
|
|
161
|
+
async spawn(launch, operation = {}) {
|
|
162
|
+
operation.signal?.throwIfAborted();
|
|
163
|
+
validateExactProcessLaunch(launch);
|
|
164
|
+
const handle = await process.spawnExact(launch.executable, launch.args, {
|
|
165
|
+
cwd: launch.cwd,
|
|
166
|
+
env: { ...launch.env },
|
|
167
|
+
inheritEnv: false,
|
|
168
|
+
...(launch.stdin === undefined ? {} : { stdin: launch.stdin }),
|
|
169
|
+
timeoutMs: launch.timeoutMs,
|
|
170
|
+
...(operation.signal ? { signal: operation.signal } : {}),
|
|
171
|
+
});
|
|
172
|
+
operation.signal?.throwIfAborted();
|
|
173
|
+
return sandboxProcessAsExactProcess(handle);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
async writeFile(path, bytes, options) {
|
|
177
|
+
options.signal?.throwIfAborted();
|
|
178
|
+
assertAbsoluteFilePath(path);
|
|
179
|
+
if (!Number.isSafeInteger(options.mode) ||
|
|
180
|
+
options.mode < 0 ||
|
|
181
|
+
options.mode > 0o7777) {
|
|
182
|
+
throw new Error("Tangle exact process file mode must be between 0 and 07777");
|
|
183
|
+
}
|
|
184
|
+
await fs.write(path, Buffer.from(bytes).toString("base64"), {
|
|
185
|
+
encoding: "base64",
|
|
186
|
+
mode: options.mode,
|
|
187
|
+
});
|
|
188
|
+
options.signal?.throwIfAborted();
|
|
189
|
+
},
|
|
190
|
+
async readFile(path, options) {
|
|
191
|
+
options.signal?.throwIfAborted();
|
|
192
|
+
assertAbsoluteFilePath(path);
|
|
193
|
+
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1) {
|
|
194
|
+
throw new Error("Tangle exact process maxBytes must be a positive integer");
|
|
195
|
+
}
|
|
196
|
+
const stat = await fs.stat(path);
|
|
197
|
+
options.signal?.throwIfAborted();
|
|
198
|
+
if (!stat.isFile) {
|
|
199
|
+
throw new Error("Tangle exact process path is not a regular file");
|
|
200
|
+
}
|
|
201
|
+
if (stat.size > options.maxBytes) {
|
|
202
|
+
throw new Error("Tangle exact process file exceeds maxBytes");
|
|
203
|
+
}
|
|
204
|
+
const result = await fs.readBatch([path], { encoding: "base64" });
|
|
205
|
+
options.signal?.throwIfAborted();
|
|
206
|
+
const file = result.files[0];
|
|
207
|
+
if (result.errors.length !== 0 ||
|
|
208
|
+
result.files.length !== 1 ||
|
|
209
|
+
!file ||
|
|
210
|
+
file.path !== path ||
|
|
211
|
+
file.encoding !== "base64") {
|
|
212
|
+
throw new Error(result.errors[0]?.error ??
|
|
213
|
+
"Tangle exact process file read returned an invalid result");
|
|
214
|
+
}
|
|
215
|
+
const bytes = Uint8Array.from(Buffer.from(file.content, "base64"));
|
|
216
|
+
if (bytes.byteLength !== file.size ||
|
|
217
|
+
bytes.byteLength !== stat.size ||
|
|
218
|
+
bytes.byteLength > options.maxBytes) {
|
|
219
|
+
throw new Error("Tangle exact process file read violated its byte bound");
|
|
220
|
+
}
|
|
221
|
+
return bytes;
|
|
222
|
+
},
|
|
223
|
+
async destroy() {
|
|
224
|
+
await destroy();
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function validateExactProcessLaunch(input) {
|
|
229
|
+
if (!input.executable ||
|
|
230
|
+
(!isAbsolute(input.executable) && !input.env.PATH?.trim())) {
|
|
231
|
+
throw new Error("Tangle exact process executable must be absolute unless env.PATH is supplied");
|
|
232
|
+
}
|
|
233
|
+
if (!input.cwd)
|
|
234
|
+
throw new Error("Tangle exact process cwd is required");
|
|
235
|
+
if (!Number.isSafeInteger(input.timeoutMs) || input.timeoutMs < 0) {
|
|
236
|
+
throw new Error("Tangle exact process timeoutMs must be a non-negative integer");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function sandboxProcessAsExactProcess(process) {
|
|
240
|
+
return {
|
|
241
|
+
pid: process.pid,
|
|
242
|
+
async status() {
|
|
243
|
+
return exactProcessStatusFromSandbox(await process.status());
|
|
244
|
+
},
|
|
245
|
+
async wait() {
|
|
246
|
+
await process.wait();
|
|
247
|
+
const status = exactProcessStatusFromSandbox(await process.status());
|
|
248
|
+
if (!status.termination) {
|
|
249
|
+
throw new Error("Tangle exact process remained running after wait()");
|
|
250
|
+
}
|
|
251
|
+
return status.termination;
|
|
252
|
+
},
|
|
253
|
+
async kill() {
|
|
254
|
+
await process.kill("SIGKILL", { tree: true });
|
|
255
|
+
},
|
|
256
|
+
async *stdout() {
|
|
257
|
+
yield* process.stdout();
|
|
258
|
+
},
|
|
259
|
+
async *stderr() {
|
|
260
|
+
yield* process.stderr();
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function exactProcessStatusFromSandbox(status) {
|
|
265
|
+
if (status.running && status.exitSignal) {
|
|
266
|
+
throw new Error("Tangle exact process reported an exit signal while running");
|
|
267
|
+
}
|
|
268
|
+
const termination = processTermination(status);
|
|
269
|
+
return {
|
|
270
|
+
pid: status.pid,
|
|
271
|
+
running: status.running,
|
|
272
|
+
exitCode: status.exitCode,
|
|
273
|
+
...(status.exitSignal ? { exitSignal: status.exitSignal } : {}),
|
|
274
|
+
...(termination ? { termination } : {}),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function processTermination(status) {
|
|
278
|
+
if (status.running)
|
|
279
|
+
return undefined;
|
|
280
|
+
return status.exitSignal
|
|
281
|
+
? { kind: "signal", signal: status.exitSignal }
|
|
282
|
+
: { kind: "exit", exitCode: status.exitCode };
|
|
283
|
+
}
|
|
284
|
+
function assertExactProcessSandbox(box) {
|
|
285
|
+
if (!isExactProcessSandbox(box)) {
|
|
286
|
+
throw new Error("Tangle Sandbox did not create the requested process-only runtime");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function isExactProcessSandbox(box) {
|
|
290
|
+
return (box.metadata?.runtimeMode === "control" &&
|
|
291
|
+
box.metadata[EXACT_PROCESS_METADATA_KEY] === true);
|
|
292
|
+
}
|
|
293
|
+
function assertUnreservedMetadata(metadata) {
|
|
294
|
+
const reserved = [
|
|
295
|
+
"capabilities",
|
|
296
|
+
"customer_id",
|
|
297
|
+
"exactProcess",
|
|
298
|
+
"integrationLaunch",
|
|
299
|
+
"runtimeMode",
|
|
300
|
+
"teamId",
|
|
301
|
+
EXACT_PROCESS_METADATA_KEY,
|
|
302
|
+
];
|
|
303
|
+
if (reserved.some((name) => Object.hasOwn(metadata, name))) {
|
|
304
|
+
throw new Error("exact process ownership metadata is reserved by Tangle");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function assertSupportedProviderOptions(providerOptions) {
|
|
308
|
+
if (providerOptions && Object.keys(providerOptions).length > 0) {
|
|
309
|
+
throw new Error("Tangle exact process providerOptions are not supported");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function assertAbsoluteFilePath(path) {
|
|
313
|
+
if (!isAbsolute(path)) {
|
|
314
|
+
throw new Error("Tangle exact process file path must be absolute");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function metadataMatches(actual, expected) {
|
|
318
|
+
if (!expected)
|
|
319
|
+
return true;
|
|
320
|
+
if (!actual)
|
|
321
|
+
return false;
|
|
322
|
+
return Object.entries(expected).every(([key, value]) => isDeepStrictEqual(actual[key], value));
|
|
323
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
import type { BackendType, CreateSandboxOptions, ExecResult as SandboxExecResult, PromptOptions, PromptResult, SandboxEvent } from "@tangle-network/sandbox";
|
|
2
2
|
import type { AgentEnvironmentCapabilities, AgentEnvironmentProvider, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
|
|
3
3
|
import type { InputPart } from "@tangle-network/agent-interface";
|
|
4
|
+
import { type TangleExactProcessOptions } from "./exact-process.js";
|
|
5
|
+
export type { TangleExactProcessOptions } from "./exact-process.js";
|
|
4
6
|
export interface SandboxClientLike {
|
|
5
|
-
create(options?: CreateSandboxOptions
|
|
7
|
+
create(options?: CreateSandboxOptions, requestOptions?: {
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}): Promise<SandboxInstanceLike>;
|
|
6
11
|
get?(id: string): Promise<SandboxInstanceLike | null>;
|
|
7
12
|
list?(options?: unknown): Promise<SandboxInstanceLike[]>;
|
|
8
13
|
describePlacement?(box: SandboxInstanceLike): unknown;
|
|
9
14
|
}
|
|
15
|
+
export interface SandboxProcessStatusLike {
|
|
16
|
+
pid: number;
|
|
17
|
+
running: boolean;
|
|
18
|
+
exitCode: number;
|
|
19
|
+
exitSignal?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SandboxProcessLike {
|
|
22
|
+
readonly pid: number;
|
|
23
|
+
status(): Promise<SandboxProcessStatusLike>;
|
|
24
|
+
wait(): Promise<number>;
|
|
25
|
+
kill(signal?: "SIGKILL", options?: {
|
|
26
|
+
tree?: boolean;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
stdout(): AsyncIterable<string>;
|
|
29
|
+
stderr(): AsyncIterable<string>;
|
|
30
|
+
}
|
|
31
|
+
export interface SandboxProcessManagerLike {
|
|
32
|
+
list(): Promise<SandboxProcessStatusLike[]>;
|
|
33
|
+
get(pid: number): Promise<SandboxProcessLike | null>;
|
|
34
|
+
spawnExact(executable: string, args: readonly string[], options?: {
|
|
35
|
+
cwd?: string;
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
inheritEnv?: boolean;
|
|
38
|
+
stdin?: string;
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}): Promise<SandboxProcessLike>;
|
|
42
|
+
}
|
|
10
43
|
export interface SandboxInstanceLike {
|
|
11
44
|
id: string;
|
|
12
45
|
name?: string;
|
|
@@ -23,6 +56,33 @@ export interface SandboxInstanceLike {
|
|
|
23
56
|
sessionId?: string;
|
|
24
57
|
}): Promise<void>;
|
|
25
58
|
exec?(command: string, options?: unknown): Promise<SandboxExecResult>;
|
|
59
|
+
fs?: {
|
|
60
|
+
supportsWriteMode?: true;
|
|
61
|
+
stat(path: string): Promise<{
|
|
62
|
+
size: number;
|
|
63
|
+
isFile: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
readBatch(paths: string[], options?: {
|
|
66
|
+
encoding?: "utf8" | "base64";
|
|
67
|
+
}): Promise<{
|
|
68
|
+
files: Array<{
|
|
69
|
+
path: string;
|
|
70
|
+
content: string;
|
|
71
|
+
encoding: "utf8" | "base64";
|
|
72
|
+
size: number;
|
|
73
|
+
}>;
|
|
74
|
+
errors: Array<{
|
|
75
|
+
path: string;
|
|
76
|
+
error: string;
|
|
77
|
+
code?: string;
|
|
78
|
+
}>;
|
|
79
|
+
}>;
|
|
80
|
+
write(path: string, content: string, options: {
|
|
81
|
+
encoding: "base64";
|
|
82
|
+
mode: number;
|
|
83
|
+
}): Promise<unknown>;
|
|
84
|
+
};
|
|
85
|
+
process?: SandboxProcessManagerLike;
|
|
26
86
|
checkpoint?(options?: unknown): Promise<unknown>;
|
|
27
87
|
fork?(checkpointId: string, options?: unknown): Promise<SandboxInstanceLike>;
|
|
28
88
|
refresh?(): Promise<void>;
|
|
@@ -46,6 +106,7 @@ export interface TangleProviderOptions {
|
|
|
46
106
|
capabilities?: AgentEnvironmentCapabilities | (() => AgentEnvironmentCapabilities | Promise<AgentEnvironmentCapabilities>);
|
|
47
107
|
validateProfile?: AgentEnvironmentProvider["validateProfile"];
|
|
48
108
|
mapCreateInput?: (input: CreateAgentEnvironmentInput) => CreateSandboxOptions;
|
|
109
|
+
exactProcess?: TangleExactProcessOptions;
|
|
49
110
|
}
|
|
50
111
|
export declare function createTangleProvider(options: TangleProviderOptions): AgentEnvironmentProvider;
|
|
51
112
|
export declare function defaultTangleSandboxCapabilities(): AgentEnvironmentCapabilities;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
+
import { createTangleExactProcessProvider, } from "./exact-process.js";
|
|
1
2
|
export function createTangleProvider(options) {
|
|
2
3
|
const providerName = options.name ?? "tangle-sandbox";
|
|
4
|
+
const exactProcess = options.exactProcess
|
|
5
|
+
? createTangleExactProcessProvider({
|
|
6
|
+
client: options.client,
|
|
7
|
+
options: options.exactProcess,
|
|
8
|
+
providerName,
|
|
9
|
+
})
|
|
10
|
+
: undefined;
|
|
3
11
|
return {
|
|
4
12
|
name: providerName,
|
|
13
|
+
...(exactProcess ? { exactProcess } : {}),
|
|
5
14
|
capabilities: async () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
const capabilities = options.capabilities
|
|
16
|
+
? typeof options.capabilities === "function"
|
|
17
|
+
? await options.capabilities()
|
|
18
|
+
: options.capabilities
|
|
19
|
+
: defaultTangleSandboxCapabilities();
|
|
20
|
+
if (!exactProcess && capabilities.exactProcess) {
|
|
21
|
+
throw new Error("Tangle capabilities cannot advertise exactProcess without exactProcess configuration");
|
|
22
|
+
}
|
|
23
|
+
return exactProcess
|
|
24
|
+
? { ...capabilities, exactProcess: { egress: ["blocked", "strict"] } }
|
|
25
|
+
: capabilities;
|
|
9
26
|
},
|
|
10
27
|
...(options.validateProfile ? { validateProfile: options.validateProfile } : {}),
|
|
11
28
|
async create(input) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-tangle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,14 +25,16 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/index.d.ts",
|
|
27
27
|
"dist/index.js",
|
|
28
|
+
"dist/exact-process.d.ts",
|
|
29
|
+
"dist/exact-process.js",
|
|
28
30
|
"README.md",
|
|
29
31
|
"LICENSE"
|
|
30
32
|
],
|
|
31
33
|
"dependencies": {
|
|
32
|
-
"@tangle-network/agent-interface": "0.
|
|
34
|
+
"@tangle-network/agent-interface": "0.32.0"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
|
-
"@tangle-network/sandbox": ">=0.
|
|
37
|
+
"@tangle-network/sandbox": ">=0.11.1 <1.0.0"
|
|
36
38
|
},
|
|
37
39
|
"peerDependenciesMeta": {
|
|
38
40
|
"@tangle-network/sandbox": {
|
|
@@ -40,16 +42,16 @@
|
|
|
40
42
|
}
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
|
-
"@tangle-network/sandbox": "^0.
|
|
45
|
+
"@tangle-network/sandbox": "^0.11.1",
|
|
44
46
|
"@types/node": "25.6.0",
|
|
45
47
|
"typescript": "^6.0.3",
|
|
46
48
|
"vitest": "^4.1.5",
|
|
47
|
-
"@tangle-network/agent-provider-testkit": "0.
|
|
49
|
+
"@tangle-network/agent-provider-testkit": "0.3.0"
|
|
48
50
|
},
|
|
49
51
|
"scripts": {
|
|
50
52
|
"build": "tsc -p tsconfig.json",
|
|
51
53
|
"check-types": "tsc --noEmit",
|
|
52
54
|
"clean": "rm -rf dist",
|
|
53
|
-
"test": "vitest run"
|
|
55
|
+
"test": "vitest run src"
|
|
54
56
|
}
|
|
55
57
|
}
|