@zocomputer/agent-sdk 0.5.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/LICENSE +21 -0
- package/README.md +673 -0
- package/dist/attachments.js +52 -0
- package/dist/gateway-fetch.js +67 -0
- package/dist/index.js +4169 -0
- package/dist/initiator-auth.js +49 -0
- package/dist/platform/agent-sandbox/index.js +691 -0
- package/dist/platform/cloud-tools/image.js +498 -0
- package/dist/platform/cloud-tools/index.js +507 -0
- package/dist/platform/cloud-tools/web-search.js +87 -0
- package/dist/platform/runtime-ai/gateway.js +87 -0
- package/dist/platform/runtime-ai/index.js +91 -0
- package/dist/platform/runtime-ai/register.js +88 -0
- package/dist/platform/runtime-ai/session-fetch.js +54 -0
- package/dist/platform/runtime-auth/index.js +141 -0
- package/dist/state-files.js +287 -0
- package/dist/state-sandbox.js +383 -0
- package/dist/state.js +29 -0
- package/dist/steer-inbox.js +84 -0
- package/dist/steer.js +83 -0
- package/package.json +143 -0
- package/platform/agent-sandbox/api-client.ts +196 -0
- package/platform/agent-sandbox/index.ts +27 -0
- package/platform/agent-sandbox/pure.ts +83 -0
- package/platform/agent-sandbox/sftp.ts +141 -0
- package/platform/agent-sandbox/ssh-connection.ts +165 -0
- package/platform/agent-sandbox/ssh-exec.ts +98 -0
- package/platform/agent-sandbox/ssh-session.ts +487 -0
- package/platform/agent-sandbox/zo-backend.ts +88 -0
- package/platform/agent-sandbox/zo-sandbox.ts +39 -0
- package/platform/cloud-tools/image-path.ts +44 -0
- package/platform/cloud-tools/image.ts +225 -0
- package/platform/cloud-tools/index.ts +22 -0
- package/platform/cloud-tools/state-files.ts +368 -0
- package/platform/cloud-tools/tool-meta.ts +32 -0
- package/platform/cloud-tools/web-search.ts +15 -0
- package/platform/runtime-ai/gateway.ts +76 -0
- package/platform/runtime-ai/index.ts +20 -0
- package/platform/runtime-ai/register.ts +26 -0
- package/platform/runtime-ai/session-fetch.ts +124 -0
- package/platform/runtime-auth/index.ts +331 -0
- package/src/async-tasks.ts +273 -0
- package/src/attachments.ts +109 -0
- package/src/backgroundable.ts +88 -0
- package/src/bounded-output.ts +159 -0
- package/src/dir-conventions.ts +238 -0
- package/src/extract/cache.ts +40 -0
- package/src/extract/docx.ts +18 -0
- package/src/extract/pdf.ts +54 -0
- package/src/extract/sheet.ts +56 -0
- package/src/file-kind.ts +258 -0
- package/src/file-view.ts +80 -0
- package/src/gateway-fetch.ts +115 -0
- package/src/glob-match.ts +13 -0
- package/src/hooks.ts +213 -0
- package/src/index.ts +419 -0
- package/src/initiator-auth.ts +81 -0
- package/src/instructions.ts +224 -0
- package/src/list-files.ts +41 -0
- package/src/mock-model.ts +572 -0
- package/src/park-delivery.ts +247 -0
- package/src/path-locks.ts +52 -0
- package/src/read-file-content.ts +142 -0
- package/src/read-text.ts +40 -0
- package/src/redeliver.ts +155 -0
- package/src/run.ts +159 -0
- package/src/sandbox-io.ts +414 -0
- package/src/state-files.ts +460 -0
- package/src/state-sandbox.ts +710 -0
- package/src/state.ts +96 -0
- package/src/steer-inbox.ts +105 -0
- package/src/steer-tool.ts +83 -0
- package/src/steer.ts +146 -0
- package/src/task.ts +320 -0
- package/src/tools/bash.ts +143 -0
- package/src/tools/edit.ts +58 -0
- package/src/tools/glob.ts +56 -0
- package/src/tools/grep.ts +188 -0
- package/src/tools/read.ts +319 -0
- package/src/tools/tasks.ts +241 -0
- package/src/tools/webfetch.ts +368 -0
- package/src/tools/write.ts +42 -0
- package/src/walk.ts +112 -0
- package/src/watch-output.ts +104 -0
- package/src/web-fetch.ts +324 -0
- package/src/web-page.ts +179 -0
- package/src/workspace-io.ts +225 -0
- package/src/workspace.ts +41 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/zo-sandbox.ts
|
|
2
|
+
import { defineSandbox } from "eve/sandbox";
|
|
3
|
+
|
|
4
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/runtime-auth/index.ts
|
|
5
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
6
|
+
var AGENT_TOKEN_HEADER = "x-zo-agent-token";
|
|
7
|
+
var EVE_SESSION_HEADER = "x-zo-eve-session";
|
|
8
|
+
var AGENT_TOKEN_ENV = "ZO_AGENT_TOKEN";
|
|
9
|
+
var ZO_PLATFORM_ORG = {
|
|
10
|
+
id: "org_zo",
|
|
11
|
+
name: "Zo",
|
|
12
|
+
slug: "zo"
|
|
13
|
+
};
|
|
14
|
+
var BUILDER_AGENT_IDENTITY = {
|
|
15
|
+
agentProjectId: "agt_builder",
|
|
16
|
+
ownerOrgId: ZO_PLATFORM_ORG.id
|
|
17
|
+
};
|
|
18
|
+
var LOCAL_AGENT_IDENTITY = {
|
|
19
|
+
agentProjectId: "agt_local",
|
|
20
|
+
ownerOrgId: ZO_PLATFORM_ORG.id
|
|
21
|
+
};
|
|
22
|
+
var RESERVED_AGENT_PROJECT_IDS = [
|
|
23
|
+
BUILDER_AGENT_IDENTITY.agentProjectId,
|
|
24
|
+
LOCAL_AGENT_IDENTITY.agentProjectId
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/api-client.ts
|
|
28
|
+
var SCRATCH_DECLARATION = "scratch";
|
|
29
|
+
var STATE_HANDLE_PATH = "/state/handles";
|
|
30
|
+
|
|
31
|
+
class SandboxBrokerError extends Error {
|
|
32
|
+
status;
|
|
33
|
+
code;
|
|
34
|
+
constructor(message, options) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "SandboxBrokerError";
|
|
37
|
+
this.status = options.status;
|
|
38
|
+
this.code = options.code ?? null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function parseSandboxAccess(value) {
|
|
42
|
+
if (typeof value !== "object" || value === null)
|
|
43
|
+
return null;
|
|
44
|
+
const v = value;
|
|
45
|
+
const { sandboxId, sshHost, sshUser, expiresAt } = v;
|
|
46
|
+
if (typeof sandboxId === "string" && typeof sshHost === "string" && typeof sshUser === "string" && typeof expiresAt === "string") {
|
|
47
|
+
return { sandboxId, sshHost, sshUser, expiresAt };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function parseSandboxHandleAccess(value) {
|
|
52
|
+
if (typeof value !== "object" || value === null)
|
|
53
|
+
return null;
|
|
54
|
+
const v = value;
|
|
55
|
+
if (v.engine !== "sandbox-daytona")
|
|
56
|
+
return null;
|
|
57
|
+
return parseSandboxAccess(v.sandbox);
|
|
58
|
+
}
|
|
59
|
+
async function requestScratchSandboxAccess(input) {
|
|
60
|
+
const doFetch = input.fetch ?? fetch;
|
|
61
|
+
const agentToken = (input.agentToken ?? process.env[AGENT_TOKEN_ENV])?.trim() || undefined;
|
|
62
|
+
const eveSessionKey = input.eveSessionKey.trim() || undefined;
|
|
63
|
+
const headers = { "content-type": "application/json" };
|
|
64
|
+
if (agentToken)
|
|
65
|
+
headers[AGENT_TOKEN_HEADER] = agentToken;
|
|
66
|
+
if (eveSessionKey)
|
|
67
|
+
headers[EVE_SESSION_HEADER] = eveSessionKey;
|
|
68
|
+
const res = await doFetch(`${input.apiBaseUrl}${STATE_HANDLE_PATH}`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers,
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
declarationName: SCRATCH_DECLARATION,
|
|
73
|
+
interface: "exec",
|
|
74
|
+
access: "rw",
|
|
75
|
+
suggestedDefaults: { engine: "sandbox-daytona", partition: "session" }
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
const json = await res.json().catch(() => null);
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const { code, message } = parseBrokerError(json);
|
|
81
|
+
throw new SandboxBrokerError(describeBrokerError(res.status, code, message), {
|
|
82
|
+
status: res.status,
|
|
83
|
+
code
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const access = parseSandboxHandleAccess(json);
|
|
87
|
+
if (access === null) {
|
|
88
|
+
throw new SandboxBrokerError("sandbox broker returned a non-sandbox or malformed state handle", { status: res.status, code: "malformed_handle" });
|
|
89
|
+
}
|
|
90
|
+
return access;
|
|
91
|
+
}
|
|
92
|
+
function parseBrokerError(value) {
|
|
93
|
+
if (typeof value !== "object" || value === null)
|
|
94
|
+
return { code: null, message: null };
|
|
95
|
+
const v = value;
|
|
96
|
+
return {
|
|
97
|
+
code: typeof v.error === "string" ? v.error : null,
|
|
98
|
+
message: typeof v.message === "string" ? v.message : null
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function describeBrokerError(status, code, message) {
|
|
102
|
+
if (code === "unsupported_actor") {
|
|
103
|
+
return "sandbox broker rejected the caller: POST /state/handles requires an agent token, not a human session (unsupported_actor). Ensure ZO_AGENT_TOKEN is set.";
|
|
104
|
+
}
|
|
105
|
+
if (code === "eve_session_required") {
|
|
106
|
+
return "sandbox broker requires an eve session: send the x-zo-eve-session header (eve_session_required).";
|
|
107
|
+
}
|
|
108
|
+
const detail = [code, message].filter((s) => s !== null && s.length > 0).join(" — ");
|
|
109
|
+
return `sandbox provisioning failed: ${status}${detail ? ` ${detail}` : ""}`.trim();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/ssh-session.ts
|
|
113
|
+
import { Client } from "ssh2";
|
|
114
|
+
import { extractLines } from "@ai-sdk/provider-utils";
|
|
115
|
+
|
|
116
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/pure.ts
|
|
117
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
118
|
+
import path from "node:path";
|
|
119
|
+
function resolveSandboxPath(workDir, p) {
|
|
120
|
+
if (path.posix.isAbsolute(p))
|
|
121
|
+
return p;
|
|
122
|
+
const resolved = path.posix.join(workDir, p);
|
|
123
|
+
const prefix = workDir.endsWith("/") ? workDir : `${workDir}/`;
|
|
124
|
+
if (resolved !== workDir && !resolved.startsWith(prefix)) {
|
|
125
|
+
throw new Error(`sandbox path escapes the work dir: ${p}`);
|
|
126
|
+
}
|
|
127
|
+
return resolved;
|
|
128
|
+
}
|
|
129
|
+
function shellSingleQuote(s) {
|
|
130
|
+
return `'${s.replaceAll("'", "'\\''")}'`;
|
|
131
|
+
}
|
|
132
|
+
function readSandboxId(metadata) {
|
|
133
|
+
const id = metadata?.daytonaSandboxId;
|
|
134
|
+
return typeof id === "string" && id !== "" ? id : null;
|
|
135
|
+
}
|
|
136
|
+
function resolveEncoding(encoding) {
|
|
137
|
+
if (encoding === undefined || /^utf-?8$/i.test(encoding))
|
|
138
|
+
return "utf8";
|
|
139
|
+
if (Buffer2.isEncoding(encoding))
|
|
140
|
+
return encoding;
|
|
141
|
+
throw new Error(`zo sandbox: unsupported text encoding ${JSON.stringify(encoding)}`);
|
|
142
|
+
}
|
|
143
|
+
function decodeText(bytes, encoding) {
|
|
144
|
+
const enc = resolveEncoding(encoding);
|
|
145
|
+
if (enc === "utf8")
|
|
146
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
147
|
+
return Buffer2.from(bytes).toString(enc);
|
|
148
|
+
}
|
|
149
|
+
function encodeText(text, encoding) {
|
|
150
|
+
const enc = resolveEncoding(encoding);
|
|
151
|
+
if (enc === "utf8")
|
|
152
|
+
return new TextEncoder().encode(text);
|
|
153
|
+
return new Uint8Array(Buffer2.from(text, enc));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/ssh-connection.ts
|
|
157
|
+
function isExpired(access, skewMs, now) {
|
|
158
|
+
const expiry = Date.parse(access.expiresAt);
|
|
159
|
+
if (Number.isNaN(expiry))
|
|
160
|
+
return true;
|
|
161
|
+
return expiry - skewMs <= now;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class SshConnectionManager {
|
|
165
|
+
conn = null;
|
|
166
|
+
access = null;
|
|
167
|
+
connecting = null;
|
|
168
|
+
disposed = false;
|
|
169
|
+
sandboxId = null;
|
|
170
|
+
opts;
|
|
171
|
+
now;
|
|
172
|
+
constructor(opts) {
|
|
173
|
+
this.opts = opts;
|
|
174
|
+
this.now = opts.now ?? Date.now;
|
|
175
|
+
}
|
|
176
|
+
currentSandboxId() {
|
|
177
|
+
return this.sandboxId;
|
|
178
|
+
}
|
|
179
|
+
expired(access) {
|
|
180
|
+
return isExpired(access, this.opts.expirySkewMs, this.now());
|
|
181
|
+
}
|
|
182
|
+
async ensure() {
|
|
183
|
+
if (this.disposed)
|
|
184
|
+
throw new Error("zo sandbox: session is disposed");
|
|
185
|
+
if (this.conn !== null && this.access !== null && !this.expired(this.access)) {
|
|
186
|
+
return this.conn;
|
|
187
|
+
}
|
|
188
|
+
if (this.connecting !== null)
|
|
189
|
+
return await this.connecting;
|
|
190
|
+
const held = this.access;
|
|
191
|
+
this.conn?.end();
|
|
192
|
+
this.conn = null;
|
|
193
|
+
this.connecting = this.openConnection(held);
|
|
194
|
+
try {
|
|
195
|
+
return await this.connecting;
|
|
196
|
+
} finally {
|
|
197
|
+
this.connecting = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async openConnection(held) {
|
|
201
|
+
const abortIfDisposed = () => {
|
|
202
|
+
if (this.disposed)
|
|
203
|
+
throw new Error("zo sandbox: session disposed during connect");
|
|
204
|
+
};
|
|
205
|
+
let used;
|
|
206
|
+
if (held !== null && !this.expired(held)) {
|
|
207
|
+
used = held;
|
|
208
|
+
} else {
|
|
209
|
+
abortIfDisposed();
|
|
210
|
+
used = await this.opts.acquireAccess();
|
|
211
|
+
}
|
|
212
|
+
abortIfDisposed();
|
|
213
|
+
let c;
|
|
214
|
+
try {
|
|
215
|
+
c = await this.opts.connect(used);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (used === held) {
|
|
218
|
+
abortIfDisposed();
|
|
219
|
+
used = await this.opts.acquireAccess();
|
|
220
|
+
abortIfDisposed();
|
|
221
|
+
c = await this.opts.connect(used);
|
|
222
|
+
} else {
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (this.disposed) {
|
|
227
|
+
c.end();
|
|
228
|
+
throw new Error("zo sandbox: session disposed during connect");
|
|
229
|
+
}
|
|
230
|
+
c.onClose(() => {
|
|
231
|
+
if (this.conn === c)
|
|
232
|
+
this.conn = null;
|
|
233
|
+
});
|
|
234
|
+
this.conn = c;
|
|
235
|
+
this.access = used;
|
|
236
|
+
this.sandboxId = used.sandboxId;
|
|
237
|
+
return c;
|
|
238
|
+
}
|
|
239
|
+
dispose() {
|
|
240
|
+
this.disposed = true;
|
|
241
|
+
this.conn?.end();
|
|
242
|
+
this.conn = null;
|
|
243
|
+
this.access = null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/ssh-exec.ts
|
|
248
|
+
var SIGNAL_EXIT_CODE = 137;
|
|
249
|
+
function abortError(signal) {
|
|
250
|
+
return signal.reason instanceof Error ? signal.reason : new Error(typeof signal.reason === "string" ? signal.reason : "aborted");
|
|
251
|
+
}
|
|
252
|
+
function awaitCommand(stream, abortSignal) {
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
let code = null;
|
|
255
|
+
let signal = null;
|
|
256
|
+
let sawExit = false;
|
|
257
|
+
let settled = false;
|
|
258
|
+
const onAbort = () => {
|
|
259
|
+
try {
|
|
260
|
+
stream.close();
|
|
261
|
+
} catch {}
|
|
262
|
+
settle();
|
|
263
|
+
};
|
|
264
|
+
const settle = (channelError) => {
|
|
265
|
+
if (settled)
|
|
266
|
+
return;
|
|
267
|
+
settled = true;
|
|
268
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
269
|
+
if (abortSignal?.aborted) {
|
|
270
|
+
reject(abortError(abortSignal));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (channelError !== undefined) {
|
|
274
|
+
reject(channelError);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const sig = signal;
|
|
278
|
+
const reconciled = sig != null ? SIGNAL_EXIT_CODE : code ?? 0;
|
|
279
|
+
resolve({ exitCode: reconciled, signal: sig });
|
|
280
|
+
};
|
|
281
|
+
if (abortSignal?.aborted) {
|
|
282
|
+
onAbort();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
286
|
+
stream.on("exit", (c, s) => {
|
|
287
|
+
sawExit = true;
|
|
288
|
+
code = c;
|
|
289
|
+
signal = s ?? null;
|
|
290
|
+
}).on("close", (closeCode, closeSignal) => {
|
|
291
|
+
if (!sawExit) {
|
|
292
|
+
code = closeCode ?? null;
|
|
293
|
+
signal = closeSignal ?? null;
|
|
294
|
+
}
|
|
295
|
+
settle();
|
|
296
|
+
}).on("error", (e) => settle(e));
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/sftp.ts
|
|
301
|
+
import path2 from "node:path";
|
|
302
|
+
var SFTP_NO_SUCH_FILE = 2;
|
|
303
|
+
function isNoSuchFile(error) {
|
|
304
|
+
return typeof error === "object" && error !== null && error.code === SFTP_NO_SUCH_FILE;
|
|
305
|
+
}
|
|
306
|
+
var sftpByClient = new WeakMap;
|
|
307
|
+
function getSftp(client) {
|
|
308
|
+
const existing = sftpByClient.get(client);
|
|
309
|
+
if (existing !== undefined)
|
|
310
|
+
return existing;
|
|
311
|
+
const opening = new Promise((resolve, reject) => {
|
|
312
|
+
client.sftp((err, sftp) => {
|
|
313
|
+
if (err) {
|
|
314
|
+
reject(err);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const evict = () => {
|
|
318
|
+
if (sftpByClient.get(client) === opening)
|
|
319
|
+
sftpByClient.delete(client);
|
|
320
|
+
};
|
|
321
|
+
sftp.on("close", evict).on("error", evict);
|
|
322
|
+
resolve(sftp);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
opening.catch(() => {
|
|
326
|
+
if (sftpByClient.get(client) === opening)
|
|
327
|
+
sftpByClient.delete(client);
|
|
328
|
+
});
|
|
329
|
+
sftpByClient.set(client, opening);
|
|
330
|
+
return opening;
|
|
331
|
+
}
|
|
332
|
+
function exec(client, command) {
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
client.exec(command, (err, stream) => {
|
|
335
|
+
if (err) {
|
|
336
|
+
reject(err);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
let stderr = "";
|
|
340
|
+
stream.on("data", () => {}).stderr.on("data", (d) => stderr += d.toString());
|
|
341
|
+
awaitCommand(stream).then(({ exitCode }) => resolve({ exitCode, stderr }), reject);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
async function ensureParentDir(client, filePath) {
|
|
346
|
+
const dir = path2.posix.dirname(filePath);
|
|
347
|
+
if (dir === "" || dir === "." || dir === "/")
|
|
348
|
+
return;
|
|
349
|
+
const r = await exec(client, `mkdir -p ${shellSingleQuote(dir)}`);
|
|
350
|
+
if (r.exitCode !== 0) {
|
|
351
|
+
throw new Error(`sandbox: mkdir -p ${dir} failed (exit ${r.exitCode}): ${r.stderr.trim()}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function sftpReadBytes(client, remotePath) {
|
|
355
|
+
const sftp = await getSftp(client);
|
|
356
|
+
return await new Promise((resolve, reject) => {
|
|
357
|
+
sftp.readFile(remotePath, (err, buf) => {
|
|
358
|
+
if (err) {
|
|
359
|
+
if (isNoSuchFile(err))
|
|
360
|
+
resolve(null);
|
|
361
|
+
else
|
|
362
|
+
reject(err);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
resolve(new Uint8Array(buf));
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async function sftpWriteBytes(client, remotePath, bytes) {
|
|
370
|
+
await ensureParentDir(client, remotePath);
|
|
371
|
+
const sftp = await getSftp(client);
|
|
372
|
+
await new Promise((resolve, reject) => {
|
|
373
|
+
sftp.writeFile(remotePath, Buffer.from(bytes), (err) => err ? reject(err) : resolve());
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
async function removePath(client, remotePath, opts = {}) {
|
|
377
|
+
const flags = `${opts.recursive ? "r" : ""}${opts.force ? "f" : ""}`;
|
|
378
|
+
const rm = flags === "" ? "rm" : `rm -${flags}`;
|
|
379
|
+
const r = await exec(client, `${rm} ${shellSingleQuote(remotePath)}`);
|
|
380
|
+
if (r.exitCode !== 0) {
|
|
381
|
+
throw new Error(`sandbox: ${rm} ${remotePath} failed (exit ${r.exitCode}): ${r.stderr.trim()}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/ssh-session.ts
|
|
386
|
+
var WORK_DIR = "/home/daytona";
|
|
387
|
+
var EXPIRY_SKEW_MS = 30000;
|
|
388
|
+
var SSH_PORT = 22;
|
|
389
|
+
function connectSsh(access) {
|
|
390
|
+
const conn = new Client;
|
|
391
|
+
return new Promise((resolve, reject) => {
|
|
392
|
+
conn.on("ready", () => resolve(conn)).on("error", reject).connect({
|
|
393
|
+
host: access.sshHost,
|
|
394
|
+
port: SSH_PORT,
|
|
395
|
+
username: access.sshUser,
|
|
396
|
+
tryKeyboard: false
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
function runOverSsh(conn, command, options = {}) {
|
|
401
|
+
return new Promise((resolve, reject) => {
|
|
402
|
+
conn.exec(command, (err, stream) => {
|
|
403
|
+
if (err) {
|
|
404
|
+
reject(err);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
let stdout = "";
|
|
408
|
+
let stderr = "";
|
|
409
|
+
stream.on("data", (d) => stdout += d.toString()).stderr.on("data", (d) => stderr += d.toString());
|
|
410
|
+
awaitCommand(stream, options.abortSignal).then(({ exitCode, signal }) => resolve({
|
|
411
|
+
exitCode,
|
|
412
|
+
stdout,
|
|
413
|
+
stderr: signal != null ? `${stderr}
|
|
414
|
+
[killed by signal ${signal}]` : stderr
|
|
415
|
+
}), reject);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function anchored(command, workingDirectory, env) {
|
|
420
|
+
const dir = shellSingleQuote(resolveSandboxPath(WORK_DIR, workingDirectory ?? "."));
|
|
421
|
+
const envPrefix = env === undefined || Object.keys(env).length === 0 ? "" : `${Object.entries(env).map(([k, v]) => {
|
|
422
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) {
|
|
423
|
+
throw new Error(`zo sandbox: invalid environment variable name ${JSON.stringify(k)}`);
|
|
424
|
+
}
|
|
425
|
+
return `${k}=${shellSingleQuote(v)}`;
|
|
426
|
+
}).join(" ")} `;
|
|
427
|
+
return `mkdir -p ${dir} && cd ${dir} && ${envPrefix}${command}`;
|
|
428
|
+
}
|
|
429
|
+
function throwIfAborted(signal) {
|
|
430
|
+
if (signal?.aborted)
|
|
431
|
+
throw signal.reason ?? new Error("operation aborted");
|
|
432
|
+
}
|
|
433
|
+
function bytesToStream(bytes) {
|
|
434
|
+
return new ReadableStream({
|
|
435
|
+
start(controller) {
|
|
436
|
+
controller.enqueue(bytes);
|
|
437
|
+
controller.close();
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async function streamToBytes(stream, abortSignal) {
|
|
442
|
+
const chunks = [];
|
|
443
|
+
let total = 0;
|
|
444
|
+
const reader = stream.getReader();
|
|
445
|
+
const abortReason = () => (abortSignal?.reason instanceof Error ? abortSignal.reason : null) ?? new Error("write aborted");
|
|
446
|
+
let onAbort;
|
|
447
|
+
const aborted = new Promise((_, reject) => {
|
|
448
|
+
if (abortSignal === undefined)
|
|
449
|
+
return;
|
|
450
|
+
if (abortSignal.aborted) {
|
|
451
|
+
reject(abortReason());
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
onAbort = () => reject(abortReason());
|
|
455
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
456
|
+
});
|
|
457
|
+
try {
|
|
458
|
+
for (;; ) {
|
|
459
|
+
const { done, value } = abortSignal === undefined ? await reader.read() : await Promise.race([reader.read(), aborted]);
|
|
460
|
+
if (done)
|
|
461
|
+
break;
|
|
462
|
+
chunks.push(value);
|
|
463
|
+
total += value.length;
|
|
464
|
+
}
|
|
465
|
+
} catch (e) {
|
|
466
|
+
await reader.cancel(e);
|
|
467
|
+
throw e;
|
|
468
|
+
} finally {
|
|
469
|
+
if (onAbort !== undefined)
|
|
470
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
471
|
+
aborted.catch(() => {});
|
|
472
|
+
}
|
|
473
|
+
const out = new Uint8Array(total);
|
|
474
|
+
let offset = 0;
|
|
475
|
+
for (const chunk of chunks) {
|
|
476
|
+
out.set(chunk, offset);
|
|
477
|
+
offset += chunk.length;
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
function nodeToWebStream(node) {
|
|
482
|
+
let done = false;
|
|
483
|
+
return new ReadableStream({
|
|
484
|
+
start(controller) {
|
|
485
|
+
node.on("data", (d) => {
|
|
486
|
+
if (done)
|
|
487
|
+
return;
|
|
488
|
+
try {
|
|
489
|
+
controller.enqueue(new Uint8Array(d));
|
|
490
|
+
} catch {
|
|
491
|
+
done = true;
|
|
492
|
+
node.destroy();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if ((controller.desiredSize ?? 1) <= 0)
|
|
496
|
+
node.pause();
|
|
497
|
+
});
|
|
498
|
+
node.on("end", () => {
|
|
499
|
+
if (done)
|
|
500
|
+
return;
|
|
501
|
+
done = true;
|
|
502
|
+
controller.close();
|
|
503
|
+
});
|
|
504
|
+
node.on("error", (e) => {
|
|
505
|
+
if (done)
|
|
506
|
+
return;
|
|
507
|
+
done = true;
|
|
508
|
+
controller.error(e);
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
pull() {
|
|
512
|
+
node.resume();
|
|
513
|
+
},
|
|
514
|
+
cancel() {
|
|
515
|
+
done = true;
|
|
516
|
+
node.destroy();
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
function spawnOverSsh(conn, command, options = {}) {
|
|
521
|
+
return new Promise((resolve, reject) => {
|
|
522
|
+
conn.exec(command, (err, stream) => {
|
|
523
|
+
if (err) {
|
|
524
|
+
reject(err);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
resolve(buildSpawnedProcess(stream, options));
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
function buildSpawnedProcess(stream, options = {}) {
|
|
532
|
+
let killed = false;
|
|
533
|
+
const kill = () => {
|
|
534
|
+
if (!killed) {
|
|
535
|
+
killed = true;
|
|
536
|
+
try {
|
|
537
|
+
stream.signal("KILL");
|
|
538
|
+
stream.close();
|
|
539
|
+
} catch {}
|
|
540
|
+
}
|
|
541
|
+
return Promise.resolve();
|
|
542
|
+
};
|
|
543
|
+
const exit = awaitCommand(stream, options.abortSignal);
|
|
544
|
+
exit.catch(() => {});
|
|
545
|
+
const wait = () => exit.then(({ exitCode }) => ({ exitCode }));
|
|
546
|
+
return {
|
|
547
|
+
stdout: nodeToWebStream(stream),
|
|
548
|
+
stderr: nodeToWebStream(stream.stderr),
|
|
549
|
+
wait,
|
|
550
|
+
kill
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function sshSandboxSession(id, acquireAccess) {
|
|
554
|
+
const resolvePath = (p) => resolveSandboxPath(WORK_DIR, p);
|
|
555
|
+
const manager = new SshConnectionManager({
|
|
556
|
+
acquireAccess,
|
|
557
|
+
expirySkewMs: EXPIRY_SKEW_MS,
|
|
558
|
+
connect: async (access) => {
|
|
559
|
+
const client = await connectSsh(access);
|
|
560
|
+
return {
|
|
561
|
+
client,
|
|
562
|
+
end: () => client.end(),
|
|
563
|
+
onClose: (cb) => void client.on("close", cb)
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
const session = {
|
|
568
|
+
id,
|
|
569
|
+
resolvePath,
|
|
570
|
+
async run({ command, workingDirectory, env, abortSignal }) {
|
|
571
|
+
const c = (await manager.ensure()).client;
|
|
572
|
+
return await runOverSsh(c, anchored(command, workingDirectory, env), { abortSignal });
|
|
573
|
+
},
|
|
574
|
+
async spawn({ command, workingDirectory, env, abortSignal }) {
|
|
575
|
+
const c = (await manager.ensure()).client;
|
|
576
|
+
return spawnOverSsh(c, anchored(command, workingDirectory, env), { abortSignal });
|
|
577
|
+
},
|
|
578
|
+
async readBinaryFile({ path: p, abortSignal }) {
|
|
579
|
+
throwIfAborted(abortSignal);
|
|
580
|
+
const c = (await manager.ensure()).client;
|
|
581
|
+
return await sftpReadBytes(c, resolvePath(p));
|
|
582
|
+
},
|
|
583
|
+
async readFile({ path: p, abortSignal }) {
|
|
584
|
+
const bytes = await this.readBinaryFile({
|
|
585
|
+
path: p,
|
|
586
|
+
...abortSignal !== undefined ? { abortSignal } : {}
|
|
587
|
+
});
|
|
588
|
+
return bytes === null ? null : bytesToStream(bytes);
|
|
589
|
+
},
|
|
590
|
+
async readTextFile({ path: p, encoding, startLine, endLine, abortSignal }) {
|
|
591
|
+
const bytes = await this.readBinaryFile({
|
|
592
|
+
path: p,
|
|
593
|
+
...abortSignal !== undefined ? { abortSignal } : {}
|
|
594
|
+
});
|
|
595
|
+
if (bytes === null)
|
|
596
|
+
return null;
|
|
597
|
+
const text = decodeText(bytes, encoding);
|
|
598
|
+
return startLine === undefined && endLine === undefined ? text : extractLines({
|
|
599
|
+
text,
|
|
600
|
+
...startLine !== undefined ? { startLine } : {},
|
|
601
|
+
...endLine !== undefined ? { endLine } : {}
|
|
602
|
+
});
|
|
603
|
+
},
|
|
604
|
+
async writeBinaryFile({ path: p, content, abortSignal }) {
|
|
605
|
+
throwIfAborted(abortSignal);
|
|
606
|
+
const c = (await manager.ensure()).client;
|
|
607
|
+
await sftpWriteBytes(c, resolvePath(p), content);
|
|
608
|
+
},
|
|
609
|
+
async writeFile({ path: p, content, abortSignal }) {
|
|
610
|
+
throwIfAborted(abortSignal);
|
|
611
|
+
const bytes = await streamToBytes(content, abortSignal);
|
|
612
|
+
await this.writeBinaryFile({
|
|
613
|
+
path: p,
|
|
614
|
+
content: bytes,
|
|
615
|
+
...abortSignal !== undefined ? { abortSignal } : {}
|
|
616
|
+
});
|
|
617
|
+
},
|
|
618
|
+
async writeTextFile({ path: p, content, encoding, abortSignal }) {
|
|
619
|
+
await this.writeBinaryFile({
|
|
620
|
+
path: p,
|
|
621
|
+
content: encodeText(content, encoding),
|
|
622
|
+
...abortSignal !== undefined ? { abortSignal } : {}
|
|
623
|
+
});
|
|
624
|
+
},
|
|
625
|
+
async removePath({ path: p, recursive, force, abortSignal }) {
|
|
626
|
+
throwIfAborted(abortSignal);
|
|
627
|
+
const c = (await manager.ensure()).client;
|
|
628
|
+
await removePath(c, resolvePath(p), { recursive, force });
|
|
629
|
+
},
|
|
630
|
+
setNetworkPolicy: () => {
|
|
631
|
+
throw new Error("zo sandbox: setNetworkPolicy() is not supported from the runtime — network policy is set when the control plane provisions the sandbox.");
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
return {
|
|
635
|
+
session,
|
|
636
|
+
currentSandboxId: () => manager.currentSandboxId(),
|
|
637
|
+
dispose: () => manager.dispose()
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/zo-backend.ts
|
|
642
|
+
var BACKEND_NAME = "zo";
|
|
643
|
+
function zoBackend(options) {
|
|
644
|
+
return {
|
|
645
|
+
name: BACKEND_NAME,
|
|
646
|
+
create(input) {
|
|
647
|
+
const rememberedId = readSandboxId(input.existingMetadata);
|
|
648
|
+
const acquireAccess = async () => await requestScratchSandboxAccess({
|
|
649
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
650
|
+
eveSessionKey: input.sessionKey
|
|
651
|
+
});
|
|
652
|
+
const ssh = sshSandboxSession(input.sessionKey, acquireAccess);
|
|
653
|
+
const useSessionFn = () => Promise.resolve(ssh.session);
|
|
654
|
+
return Promise.resolve({
|
|
655
|
+
session: ssh.session,
|
|
656
|
+
useSessionFn,
|
|
657
|
+
captureState: () => Promise.resolve({
|
|
658
|
+
backendName: BACKEND_NAME,
|
|
659
|
+
sessionKey: input.sessionKey,
|
|
660
|
+
metadata: {
|
|
661
|
+
daytonaSandboxId: ssh.currentSandboxId() ?? rememberedId ?? ""
|
|
662
|
+
}
|
|
663
|
+
}),
|
|
664
|
+
dispose: () => {
|
|
665
|
+
ssh.dispose();
|
|
666
|
+
return Promise.resolve();
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
prewarm(_input) {
|
|
671
|
+
return Promise.resolve({ reused: false });
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/platform/agent-sandbox/zo-sandbox.ts
|
|
677
|
+
var DEFAULT_API_URL = "http://api.zo.localhost:4000";
|
|
678
|
+
function zoSandbox(options = {}) {
|
|
679
|
+
return defineSandbox({
|
|
680
|
+
backend: () => zoBackend({
|
|
681
|
+
apiBaseUrl: options.apiBaseUrl ?? process.env.ZO_API_URL ?? DEFAULT_API_URL
|
|
682
|
+
})
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
export {
|
|
686
|
+
zoSandbox,
|
|
687
|
+
zoBackend,
|
|
688
|
+
sshSandboxSession,
|
|
689
|
+
requestScratchSandboxAccess,
|
|
690
|
+
SandboxBrokerError
|
|
691
|
+
};
|