@sandblocks/sdk 0.1.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 +21 -0
- package/dist/agents.js +209 -0
- package/dist/bin.js +59 -0
- package/dist/index.js +1018 -0
- package/dist/output.js +54 -0
- package/dist/providers/custom.js +49 -0
- package/dist/providers/docker.js +257 -0
- package/dist/providers/podman.js +257 -0
- package/dist/providers/remote.js +341 -0
- package/dist/providers/unsafe-host.js +126 -0
- package/dist/types/agents.d.ts +63 -0
- package/dist/types/agents.d.ts.map +1 -0
- package/dist/types/bin.d.ts +3 -0
- package/dist/types/bin.d.ts.map +1 -0
- package/dist/types/index.d.ts +262 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/init.d.ts +6 -0
- package/dist/types/init.d.ts.map +1 -0
- package/dist/types/orchestration.d.ts +94 -0
- package/dist/types/orchestration.d.ts.map +1 -0
- package/dist/types/output.d.ts +12 -0
- package/dist/types/output.d.ts.map +1 -0
- package/dist/types/process.d.ts +6 -0
- package/dist/types/process.d.ts.map +1 -0
- package/dist/types/providers/custom.d.ts +15 -0
- package/dist/types/providers/custom.d.ts.map +1 -0
- package/dist/types/providers/docker.d.ts +7 -0
- package/dist/types/providers/docker.d.ts.map +1 -0
- package/dist/types/providers/oci.d.ts +37 -0
- package/dist/types/providers/oci.d.ts.map +1 -0
- package/dist/types/providers/podman.d.ts +7 -0
- package/dist/types/providers/podman.d.ts.map +1 -0
- package/dist/types/providers/remote.d.ts +31 -0
- package/dist/types/providers/remote.d.ts.map +1 -0
- package/dist/types/providers/unsafe-host.d.ts +14 -0
- package/dist/types/providers/unsafe-host.d.ts.map +1 -0
- package/dist/types/types.d.ts +196 -0
- package/dist/types/types.d.ts.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// src/providers/remote.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
// src/process.ts
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
async function executeProcess(command, options = {}) {
|
|
10
|
+
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
11
|
+
throw new Error("command must contain safe non-empty arguments");
|
|
12
|
+
}
|
|
13
|
+
const startedAt = new Date().toISOString();
|
|
14
|
+
const [executable, ...args] = command;
|
|
15
|
+
if (!executable)
|
|
16
|
+
throw new Error("command executable is required");
|
|
17
|
+
const child = spawn(executable, args, {
|
|
18
|
+
cwd: options.cwd ?? options.hostCwd,
|
|
19
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
20
|
+
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
21
|
+
});
|
|
22
|
+
if (options.stdin !== undefined)
|
|
23
|
+
child.stdin?.end(options.stdin);
|
|
24
|
+
let timedOut = false;
|
|
25
|
+
let stdout = "";
|
|
26
|
+
let stderr = "";
|
|
27
|
+
let callbackQueue = Promise.resolve();
|
|
28
|
+
child.stdout?.on("data", (chunk) => {
|
|
29
|
+
const text = chunk.toString();
|
|
30
|
+
stdout = `${stdout}${text}`.slice(-4 * 1024 * 1024);
|
|
31
|
+
callbackQueue = callbackQueue.then(() => options.onStdout?.(text)).then(() => {
|
|
32
|
+
return;
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
child.stderr?.on("data", (chunk) => {
|
|
36
|
+
const text = chunk.toString();
|
|
37
|
+
stderr = `${stderr}${text}`.slice(-4 * 1024 * 1024);
|
|
38
|
+
callbackQueue = callbackQueue.then(() => options.onStderr?.(text)).then(() => {
|
|
39
|
+
return;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
const stop = () => child.kill("SIGTERM");
|
|
43
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
44
|
+
const timer = options.timeoutMs ? setTimeout(() => {
|
|
45
|
+
timedOut = true;
|
|
46
|
+
child.kill("SIGKILL");
|
|
47
|
+
}, options.timeoutMs) : undefined;
|
|
48
|
+
try {
|
|
49
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
50
|
+
child.once("error", reject);
|
|
51
|
+
child.once("close", (code, signal) => resolve(code ?? (signal ? 128 : 1)));
|
|
52
|
+
});
|
|
53
|
+
await callbackQueue;
|
|
54
|
+
if (options.signal?.aborted)
|
|
55
|
+
throw options.signal.reason ?? new Error("operation aborted");
|
|
56
|
+
return {
|
|
57
|
+
exitCode: timedOut ? 124 : exitCode,
|
|
58
|
+
stdout,
|
|
59
|
+
stderr,
|
|
60
|
+
timedOut,
|
|
61
|
+
startedAt,
|
|
62
|
+
finishedAt: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
} finally {
|
|
65
|
+
if (timer)
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
options.signal?.removeEventListener("abort", stop);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function expandHome(path) {
|
|
71
|
+
if (path === "~")
|
|
72
|
+
return process.env.HOME ?? path;
|
|
73
|
+
if (path.startsWith("~/"))
|
|
74
|
+
return `${process.env.HOME ?? "~"}/${path.slice(2)}`;
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/providers/remote.ts
|
|
79
|
+
class RemoteSandblocksProvider {
|
|
80
|
+
options;
|
|
81
|
+
kind = "remote";
|
|
82
|
+
capabilities = {
|
|
83
|
+
bindMounts: false,
|
|
84
|
+
isolatedFilesystem: true,
|
|
85
|
+
persistent: true,
|
|
86
|
+
networks: true,
|
|
87
|
+
devices: false,
|
|
88
|
+
snapshots: false,
|
|
89
|
+
reconnect: true,
|
|
90
|
+
remote: true
|
|
91
|
+
};
|
|
92
|
+
baseUrl;
|
|
93
|
+
fetchImpl;
|
|
94
|
+
constructor(options) {
|
|
95
|
+
this.options = options;
|
|
96
|
+
if (!options.apiKey)
|
|
97
|
+
throw new Error("remote provider API key is required");
|
|
98
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
99
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
100
|
+
}
|
|
101
|
+
async create(input) {
|
|
102
|
+
if (!/^[a-f0-9-]{36}$/.test(input.id))
|
|
103
|
+
throw new Error("remote sandbox id must be a UUID");
|
|
104
|
+
if (input.mounts?.length || input.devices?.length || input.groups?.length) {
|
|
105
|
+
throw new Error("remote provider accepts policy-controlled resources, not host mounts, groups, or devices");
|
|
106
|
+
}
|
|
107
|
+
if (Object.keys(input.env ?? {}).length) {
|
|
108
|
+
throw new Error("remote provider environment must use Sandblocks managed deployment secrets");
|
|
109
|
+
}
|
|
110
|
+
const workspaceId = `${input.id.slice(0, 8)}-sdk`;
|
|
111
|
+
await this.request(`/v1/projects/${encodeURIComponent(this.options.projectId)}/sandboxes`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
id: input.id,
|
|
115
|
+
goal: input.metadata?.goal ?? "SDK sandbox",
|
|
116
|
+
status: "active",
|
|
117
|
+
previewUrls: [],
|
|
118
|
+
repositories: [input.metadata?.repository ?? "sdk"],
|
|
119
|
+
executor: "sandblocks"
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
const bundle = await sourceBundle(input.cwd);
|
|
123
|
+
const response = await this.fetchImpl(`${this.baseUrl}/v1/projects/${encodeURIComponent(this.options.projectId)}/workspaces/import`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"x-sandblocks-api-key": this.options.apiKey,
|
|
127
|
+
"content-type": "application/x-tar",
|
|
128
|
+
"content-length": String(bundle.byteLength),
|
|
129
|
+
"x-sandblocks-workspace-id": workspaceId,
|
|
130
|
+
"x-sandblocks-sandbox-id": input.id,
|
|
131
|
+
"x-sandblocks-worker-pool": this.options.pool ?? "sandbox-development",
|
|
132
|
+
...this.options.workspaceImage ? { "x-sandblocks-workspace-image": this.options.workspaceImage } : {},
|
|
133
|
+
"idempotency-key": input.idempotencyKey ?? `sdk:${input.id}:import`
|
|
134
|
+
},
|
|
135
|
+
body: new Blob([bundle.slice().buffer]),
|
|
136
|
+
signal: input.signal
|
|
137
|
+
});
|
|
138
|
+
const body = await response.json();
|
|
139
|
+
if (!response.ok || !body.operation)
|
|
140
|
+
throw new Error(body.error ?? "remote workspace import failed");
|
|
141
|
+
await this.wait(body.operation.id, input.signal);
|
|
142
|
+
return this.runtime(input.id, workspaceId, input.cwd);
|
|
143
|
+
}
|
|
144
|
+
async reconnect(input) {
|
|
145
|
+
const workspaceId = `${input.id.slice(0, 8)}-sdk`;
|
|
146
|
+
await this.request(`/v1/projects/${encodeURIComponent(this.options.projectId)}/sandboxes/${input.id}`);
|
|
147
|
+
return this.runtime(input.id, workspaceId, input.cwd ?? process.cwd());
|
|
148
|
+
}
|
|
149
|
+
runtime(sandboxId, workspaceId, cwd) {
|
|
150
|
+
const runtime = {
|
|
151
|
+
id: sandboxId,
|
|
152
|
+
provider: this.kind,
|
|
153
|
+
cwd,
|
|
154
|
+
exec: async (command, options = {}) => {
|
|
155
|
+
const id = randomUUID();
|
|
156
|
+
const submitted = await this.request(`/v1/projects/${encodeURIComponent(this.options.projectId)}/operations`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "idempotency-key": `sdk:${sandboxId}:exec:${id}` },
|
|
159
|
+
body: JSON.stringify({
|
|
160
|
+
kind: "workspace.step.run",
|
|
161
|
+
stack: this.options.stack ?? "sdk",
|
|
162
|
+
environmentId: this.options.environmentId,
|
|
163
|
+
payload: {
|
|
164
|
+
workspaceId,
|
|
165
|
+
sandboxId,
|
|
166
|
+
runtimeType: "sdk",
|
|
167
|
+
steps: [
|
|
168
|
+
{
|
|
169
|
+
id: `sdk-${id.slice(0, 8)}`,
|
|
170
|
+
command,
|
|
171
|
+
workingDirectory: options.cwd ?? ".",
|
|
172
|
+
timeoutSeconds: Math.ceil((options.timeoutMs ?? 900000) / 1000),
|
|
173
|
+
required: true
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
placement: this.options.hostId ? { hostId: this.options.hostId } : { pool: this.options.pool ?? "sandbox-development" }
|
|
177
|
+
},
|
|
178
|
+
maxAttempts: 1
|
|
179
|
+
}),
|
|
180
|
+
signal: options.signal
|
|
181
|
+
});
|
|
182
|
+
return this.waitExec(submitted.operation.id, options);
|
|
183
|
+
},
|
|
184
|
+
stop: async () => {
|
|
185
|
+
await this.request(`/v1/projects/${encodeURIComponent(this.options.projectId)}/sandboxes/${sandboxId}/operations/cancel`, { method: "POST" });
|
|
186
|
+
},
|
|
187
|
+
destroy: async () => {
|
|
188
|
+
const destroyed = await this.request(`/v1/projects/${encodeURIComponent(this.options.projectId)}/workspaces/${workspaceId}`, {
|
|
189
|
+
method: "DELETE",
|
|
190
|
+
headers: { "idempotency-key": `sdk:${sandboxId}:destroy` }
|
|
191
|
+
}).catch(() => ({}));
|
|
192
|
+
if (destroyed.operation?.id)
|
|
193
|
+
await this.wait(destroyed.operation.id).catch(() => {
|
|
194
|
+
return;
|
|
195
|
+
});
|
|
196
|
+
await this.request(`/v1/projects/${encodeURIComponent(this.options.projectId)}/sandboxes`, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
id: sandboxId,
|
|
200
|
+
goal: "SDK sandbox",
|
|
201
|
+
status: "destroyed",
|
|
202
|
+
previewUrls: [],
|
|
203
|
+
repositories: [],
|
|
204
|
+
executor: "sandblocks"
|
|
205
|
+
})
|
|
206
|
+
}).catch(() => {
|
|
207
|
+
return;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
runtime.syncToHost = async () => {
|
|
212
|
+
const commits = await runtime.exec([
|
|
213
|
+
"git",
|
|
214
|
+
"format-patch",
|
|
215
|
+
"--binary",
|
|
216
|
+
"--stdout",
|
|
217
|
+
"refs/sandblocks/base..HEAD"
|
|
218
|
+
]);
|
|
219
|
+
if (commits.exitCode !== 0)
|
|
220
|
+
throw new Error(commits.stderr || "remote commit export failed");
|
|
221
|
+
if (commits.stdout.trim()) {
|
|
222
|
+
const applied = await executeProcess(["git", "-C", cwd, "am"], { stdin: commits.stdout });
|
|
223
|
+
if (applied.exitCode !== 0) {
|
|
224
|
+
await executeProcess(["git", "-C", cwd, "am", "--abort"]);
|
|
225
|
+
throw new Error(applied.stderr || "remote commits could not be applied to the host worktree");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const changes = await runtime.exec(["git", "diff", "--binary", "HEAD"]);
|
|
229
|
+
if (changes.exitCode !== 0)
|
|
230
|
+
throw new Error(changes.stderr || "remote change export failed");
|
|
231
|
+
if (changes.stdout.trim()) {
|
|
232
|
+
const applied = await executeProcess(["git", "-C", cwd, "apply"], { stdin: changes.stdout });
|
|
233
|
+
if (applied.exitCode !== 0)
|
|
234
|
+
throw new Error(applied.stderr || "remote changes could not be applied");
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
return runtime;
|
|
238
|
+
}
|
|
239
|
+
async waitExec(operationId, options) {
|
|
240
|
+
let sequence = 0;
|
|
241
|
+
let stdout = "";
|
|
242
|
+
let stderr = "";
|
|
243
|
+
const startedAt = new Date().toISOString();
|
|
244
|
+
while (true) {
|
|
245
|
+
if (options.signal?.aborted) {
|
|
246
|
+
await this.request(`/v1/operations/${operationId}/cancel`, { method: "POST" }).catch(() => {
|
|
247
|
+
return;
|
|
248
|
+
});
|
|
249
|
+
throw options.signal.reason ?? new Error("operation aborted");
|
|
250
|
+
}
|
|
251
|
+
const events = await this.request(`/v1/operations/${operationId}/events?after=${sequence}`);
|
|
252
|
+
for (const event of events.events) {
|
|
253
|
+
sequence = Math.max(sequence, event.sequence);
|
|
254
|
+
if (event.kind !== "operation.log")
|
|
255
|
+
continue;
|
|
256
|
+
const message = String(event.payload.message ?? "");
|
|
257
|
+
if (event.payload.stream === "stderr") {
|
|
258
|
+
stderr += message;
|
|
259
|
+
await options.onStderr?.(message);
|
|
260
|
+
} else {
|
|
261
|
+
stdout += message;
|
|
262
|
+
await options.onStdout?.(message);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const { operation } = await this.request(`/v1/operations/${operationId}`);
|
|
266
|
+
if (operation.state === "succeeded")
|
|
267
|
+
return {
|
|
268
|
+
exitCode: 0,
|
|
269
|
+
stdout,
|
|
270
|
+
stderr,
|
|
271
|
+
timedOut: false,
|
|
272
|
+
startedAt,
|
|
273
|
+
finishedAt: new Date().toISOString()
|
|
274
|
+
};
|
|
275
|
+
if (operation.state === "failed" || operation.state === "cancelled")
|
|
276
|
+
return {
|
|
277
|
+
exitCode: 1,
|
|
278
|
+
stdout,
|
|
279
|
+
stderr: stderr || operation.failureReason || operation.state,
|
|
280
|
+
timedOut: false,
|
|
281
|
+
startedAt,
|
|
282
|
+
finishedAt: new Date().toISOString()
|
|
283
|
+
};
|
|
284
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async wait(operationId, signal) {
|
|
288
|
+
const result = await this.waitExec(operationId, { signal });
|
|
289
|
+
if (result.exitCode !== 0)
|
|
290
|
+
throw new Error(result.stderr || "remote operation failed");
|
|
291
|
+
}
|
|
292
|
+
async request(path, init = {}) {
|
|
293
|
+
const headers = new Headers(init.headers);
|
|
294
|
+
headers.set("x-sandblocks-api-key", this.options.apiKey);
|
|
295
|
+
if (init.body)
|
|
296
|
+
headers.set("content-type", "application/json");
|
|
297
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, { ...init, headers });
|
|
298
|
+
const body = await response.json().catch(() => ({}));
|
|
299
|
+
if (!response.ok)
|
|
300
|
+
throw new Error(body.error ?? `Sandblocks request failed (${response.status})`);
|
|
301
|
+
return body;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function remoteSandblocks(options) {
|
|
305
|
+
return new RemoteSandblocksProvider(options);
|
|
306
|
+
}
|
|
307
|
+
async function sourceBundle(cwd) {
|
|
308
|
+
const temporary = await mkdtemp(join(tmpdir(), "sandblocks-sdk-source-"));
|
|
309
|
+
const file = join(temporary, "source.tar");
|
|
310
|
+
const list = join(temporary, "files.list");
|
|
311
|
+
try {
|
|
312
|
+
const listed = await executeProcess([
|
|
313
|
+
"git",
|
|
314
|
+
"-C",
|
|
315
|
+
cwd,
|
|
316
|
+
"ls-files",
|
|
317
|
+
"--cached",
|
|
318
|
+
"--others",
|
|
319
|
+
"--exclude-standard",
|
|
320
|
+
"-z"
|
|
321
|
+
]);
|
|
322
|
+
if (listed.exitCode !== 0)
|
|
323
|
+
throw new Error("remote source directory must be a Git worktree");
|
|
324
|
+
const files = listed.stdout.split("\x00").filter((path) => path && path !== ".sandblocks" && !path.startsWith(".sandblocks/") && path !== ".git");
|
|
325
|
+
if (!files.length || files.length > 50000)
|
|
326
|
+
throw new Error("remote source bundle file count is invalid");
|
|
327
|
+
await writeFile(list, `${files.join("\x00")}\x00`);
|
|
328
|
+
const result = await executeProcess(["tar", "-C", cwd, "--null", "-T", list, "-cf", file]);
|
|
329
|
+
if (result.exitCode !== 0)
|
|
330
|
+
throw new Error(`source archive failed: ${result.stderr}`);
|
|
331
|
+
if ((await stat(file)).size > 512 * 1024 * 1024)
|
|
332
|
+
throw new Error("remote source bundle exceeds 512 MiB");
|
|
333
|
+
return new Uint8Array(await readFile(file));
|
|
334
|
+
} finally {
|
|
335
|
+
await rm(temporary, { recursive: true, force: true });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export {
|
|
339
|
+
remoteSandblocks,
|
|
340
|
+
RemoteSandblocksProvider
|
|
341
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/providers/unsafe-host.ts
|
|
2
|
+
import { cp, realpath } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/process.ts
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
async function executeProcess(command, options = {}) {
|
|
8
|
+
if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
|
|
9
|
+
throw new Error("command must contain safe non-empty arguments");
|
|
10
|
+
}
|
|
11
|
+
const startedAt = new Date().toISOString();
|
|
12
|
+
const [executable, ...args] = command;
|
|
13
|
+
if (!executable)
|
|
14
|
+
throw new Error("command executable is required");
|
|
15
|
+
const child = spawn(executable, args, {
|
|
16
|
+
cwd: options.cwd ?? options.hostCwd,
|
|
17
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
18
|
+
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
19
|
+
});
|
|
20
|
+
if (options.stdin !== undefined)
|
|
21
|
+
child.stdin?.end(options.stdin);
|
|
22
|
+
let timedOut = false;
|
|
23
|
+
let stdout = "";
|
|
24
|
+
let stderr = "";
|
|
25
|
+
let callbackQueue = Promise.resolve();
|
|
26
|
+
child.stdout?.on("data", (chunk) => {
|
|
27
|
+
const text = chunk.toString();
|
|
28
|
+
stdout = `${stdout}${text}`.slice(-4 * 1024 * 1024);
|
|
29
|
+
callbackQueue = callbackQueue.then(() => options.onStdout?.(text)).then(() => {
|
|
30
|
+
return;
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
child.stderr?.on("data", (chunk) => {
|
|
34
|
+
const text = chunk.toString();
|
|
35
|
+
stderr = `${stderr}${text}`.slice(-4 * 1024 * 1024);
|
|
36
|
+
callbackQueue = callbackQueue.then(() => options.onStderr?.(text)).then(() => {
|
|
37
|
+
return;
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
const stop = () => child.kill("SIGTERM");
|
|
41
|
+
options.signal?.addEventListener("abort", stop, { once: true });
|
|
42
|
+
const timer = options.timeoutMs ? setTimeout(() => {
|
|
43
|
+
timedOut = true;
|
|
44
|
+
child.kill("SIGKILL");
|
|
45
|
+
}, options.timeoutMs) : undefined;
|
|
46
|
+
try {
|
|
47
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
48
|
+
child.once("error", reject);
|
|
49
|
+
child.once("close", (code, signal) => resolve(code ?? (signal ? 128 : 1)));
|
|
50
|
+
});
|
|
51
|
+
await callbackQueue;
|
|
52
|
+
if (options.signal?.aborted)
|
|
53
|
+
throw options.signal.reason ?? new Error("operation aborted");
|
|
54
|
+
return {
|
|
55
|
+
exitCode: timedOut ? 124 : exitCode,
|
|
56
|
+
stdout,
|
|
57
|
+
stderr,
|
|
58
|
+
timedOut,
|
|
59
|
+
startedAt,
|
|
60
|
+
finishedAt: new Date().toISOString()
|
|
61
|
+
};
|
|
62
|
+
} finally {
|
|
63
|
+
if (timer)
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
options.signal?.removeEventListener("abort", stop);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function expandHome(path) {
|
|
69
|
+
if (path === "~")
|
|
70
|
+
return process.env.HOME ?? path;
|
|
71
|
+
if (path.startsWith("~/"))
|
|
72
|
+
return `${process.env.HOME ?? "~"}/${path.slice(2)}`;
|
|
73
|
+
return path;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/providers/unsafe-host.ts
|
|
77
|
+
class UnsafeHostProvider {
|
|
78
|
+
options;
|
|
79
|
+
kind = "unsafe-host";
|
|
80
|
+
capabilities = {
|
|
81
|
+
bindMounts: false,
|
|
82
|
+
isolatedFilesystem: false,
|
|
83
|
+
persistent: true,
|
|
84
|
+
networks: false,
|
|
85
|
+
devices: false,
|
|
86
|
+
snapshots: false,
|
|
87
|
+
reconnect: false,
|
|
88
|
+
remote: false
|
|
89
|
+
};
|
|
90
|
+
constructor(options) {
|
|
91
|
+
this.options = options;
|
|
92
|
+
if (options.acknowledgeHostExecutionRisk !== true)
|
|
93
|
+
throw new Error("host execution risk must be acknowledged");
|
|
94
|
+
}
|
|
95
|
+
async create(input) {
|
|
96
|
+
if (input.mounts?.length || input.devices?.length || input.network) {
|
|
97
|
+
throw new Error("unsafe host provider does not accept sandbox isolation options");
|
|
98
|
+
}
|
|
99
|
+
const cwd = await realpath(input.cwd);
|
|
100
|
+
return {
|
|
101
|
+
id: input.id,
|
|
102
|
+
provider: this.kind,
|
|
103
|
+
cwd,
|
|
104
|
+
exec: (command, options = {}) => executeProcess(command, {
|
|
105
|
+
...options,
|
|
106
|
+
cwd: options.cwd ? resolve(cwd, options.cwd) : cwd,
|
|
107
|
+
env: { ...this.options.env, ...input.env, ...options.env }
|
|
108
|
+
}),
|
|
109
|
+
upload: async (source, destination) => {
|
|
110
|
+
await cp(resolve(cwd, source), resolve(cwd, destination), { recursive: true });
|
|
111
|
+
},
|
|
112
|
+
download: async (source, destination) => {
|
|
113
|
+
await cp(resolve(cwd, source), resolve(cwd, destination), { recursive: true });
|
|
114
|
+
},
|
|
115
|
+
stop: async () => {},
|
|
116
|
+
destroy: async () => {}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function unsafeHost(options) {
|
|
121
|
+
return new UnsafeHostProvider(options);
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
unsafeHost,
|
|
125
|
+
UnsafeHostProvider
|
|
126
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AgentContext, AgentIterationResult, AgentProvider, AgentStreamEvent } from "./types.js";
|
|
2
|
+
export interface CommandAgentOptions {
|
|
3
|
+
id?: string;
|
|
4
|
+
command: string[] | ((context: AgentContext) => string[]);
|
|
5
|
+
env?: Record<string, string>;
|
|
6
|
+
prompt?: "stdin" | "argument" | "none";
|
|
7
|
+
parseEvent?: (line: string, context: {
|
|
8
|
+
iteration: number;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
}) => AgentStreamEvent | AgentStreamEvent[] | undefined;
|
|
11
|
+
sessionId?: (stdout: string, stderr: string) => string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare class CommandAgent implements AgentProvider {
|
|
14
|
+
protected readonly options: CommandAgentOptions;
|
|
15
|
+
readonly id: string;
|
|
16
|
+
constructor(options: CommandAgentOptions);
|
|
17
|
+
run(context: AgentContext): Promise<AgentIterationResult>;
|
|
18
|
+
private result;
|
|
19
|
+
private emitLine;
|
|
20
|
+
}
|
|
21
|
+
export interface AgentCredentialReference {
|
|
22
|
+
/** Environment variable name. Remote providers resolve it from Sandblocks managed secrets. */
|
|
23
|
+
environment: string;
|
|
24
|
+
/** Optional variable name exposed to the agent process. */
|
|
25
|
+
target?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ClaudeCodeAgentOptions {
|
|
28
|
+
model: string;
|
|
29
|
+
credential?: AgentCredentialReference;
|
|
30
|
+
effort?: "low" | "medium" | "high" | "max";
|
|
31
|
+
binary?: string;
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
extraArgs?: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare class ClaudeCodeAgent extends CommandAgent {
|
|
36
|
+
constructor(options: ClaudeCodeAgentOptions);
|
|
37
|
+
}
|
|
38
|
+
export interface CodexAgentOptions {
|
|
39
|
+
model: string;
|
|
40
|
+
credential?: AgentCredentialReference;
|
|
41
|
+
reasoningEffort?: "low" | "medium" | "high" | "xhigh";
|
|
42
|
+
binary?: string;
|
|
43
|
+
env?: Record<string, string>;
|
|
44
|
+
extraArgs?: string[];
|
|
45
|
+
}
|
|
46
|
+
export declare class CodexAgent extends CommandAgent {
|
|
47
|
+
constructor(options: CodexAgentOptions);
|
|
48
|
+
}
|
|
49
|
+
export interface PiAgentOptions {
|
|
50
|
+
model: string;
|
|
51
|
+
credential?: AgentCredentialReference;
|
|
52
|
+
binary?: string;
|
|
53
|
+
env?: Record<string, string>;
|
|
54
|
+
skills?: string[];
|
|
55
|
+
extraArgs?: string[];
|
|
56
|
+
}
|
|
57
|
+
export declare class PiAgent extends CommandAgent {
|
|
58
|
+
constructor(options: PiAgentOptions);
|
|
59
|
+
}
|
|
60
|
+
export declare const claudeCode: (model: string, options?: Omit<ClaudeCodeAgentOptions, "model">) => ClaudeCodeAgent;
|
|
61
|
+
export declare const codex: (model: string, options?: Omit<CodexAgentOptions, "model">) => CodexAgent;
|
|
62
|
+
export declare const pi: (model: string, options?: Omit<PiAgentOptions, "model">) => PiAgent;
|
|
63
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/bin.ts"],"names":[],"mappings":""}
|