@agent-relay/sandbox 0.1.2 → 0.1.3
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 +226 -5
- package/dist/agent37/client.d.ts +115 -0
- package/dist/agent37/client.d.ts.map +1 -0
- package/dist/agent37/client.js +270 -0
- package/dist/agent37/client.js.map +1 -0
- package/dist/agent37/runtime.d.ts +415 -0
- package/dist/agent37/runtime.d.ts.map +1 -0
- package/dist/agent37/runtime.js +855 -0
- package/dist/agent37/runtime.js.map +1 -0
- package/dist/agentcore/bench.d.ts +93 -0
- package/dist/agentcore/bench.d.ts.map +1 -0
- package/dist/agentcore/bench.js +125 -0
- package/dist/agentcore/bench.js.map +1 -0
- package/dist/agentcore/capabilities.d.ts +169 -0
- package/dist/agentcore/capabilities.d.ts.map +1 -0
- package/dist/agentcore/capabilities.js +153 -0
- package/dist/agentcore/capabilities.js.map +1 -0
- package/dist/agentcore/config.d.ts +156 -0
- package/dist/agentcore/config.d.ts.map +1 -0
- package/dist/agentcore/config.js +12 -0
- package/dist/agentcore/config.js.map +1 -0
- package/dist/agentcore/internal/sdk.d.ts +181 -0
- package/dist/agentcore/internal/sdk.d.ts.map +1 -0
- package/dist/agentcore/internal/sdk.js +223 -0
- package/dist/agentcore/internal/sdk.js.map +1 -0
- package/dist/agentcore/runtime.d.ts +177 -0
- package/dist/agentcore/runtime.d.ts.map +1 -0
- package/dist/agentcore/runtime.js +706 -0
- package/dist/agentcore/runtime.js.map +1 -0
- package/dist/daytona/runtime.d.ts +23 -0
- package/dist/daytona/runtime.d.ts.map +1 -1
- package/dist/daytona/runtime.js +167 -5
- package/dist/daytona/runtime.js.map +1 -1
- package/dist/daytona/wire-supplement.d.ts +25 -0
- package/dist/daytona/wire-supplement.d.ts.map +1 -0
- package/dist/daytona/wire-supplement.js +16 -0
- package/dist/daytona/wire-supplement.js.map +1 -0
- package/dist/e2b/runtime.d.ts +107 -72
- package/dist/e2b/runtime.d.ts.map +1 -1
- package/dist/e2b/runtime.js +675 -190
- package/dist/e2b/runtime.js.map +1 -1
- package/dist/freestyle/capabilities.d.ts +28 -0
- package/dist/freestyle/capabilities.d.ts.map +1 -0
- package/dist/freestyle/capabilities.js +42 -0
- package/dist/freestyle/capabilities.js.map +1 -0
- package/dist/freestyle/config.d.ts +36 -0
- package/dist/freestyle/config.d.ts.map +1 -0
- package/dist/freestyle/config.js +2 -0
- package/dist/freestyle/config.js.map +1 -0
- package/dist/freestyle/internal/sdk.d.ts +69 -0
- package/dist/freestyle/internal/sdk.d.ts.map +1 -0
- package/dist/freestyle/internal/sdk.js +19 -0
- package/dist/freestyle/internal/sdk.js.map +1 -0
- package/dist/freestyle/runtime.d.ts +163 -0
- package/dist/freestyle/runtime.d.ts.map +1 -0
- package/dist/freestyle/runtime.js +798 -0
- package/dist/freestyle/runtime.js.map +1 -0
- package/dist/index.d.ts +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/microsandbox/runtime.d.ts +793 -0
- package/dist/microsandbox/runtime.d.ts.map +1 -0
- package/dist/microsandbox/runtime.js +2303 -0
- package/dist/microsandbox/runtime.js.map +1 -0
- package/dist/modal/bench.d.ts +199 -0
- package/dist/modal/bench.d.ts.map +1 -0
- package/dist/modal/bench.js +450 -0
- package/dist/modal/bench.js.map +1 -0
- package/dist/modal/capabilities.d.ts +180 -0
- package/dist/modal/capabilities.d.ts.map +1 -0
- package/dist/modal/capabilities.js +159 -0
- package/dist/modal/capabilities.js.map +1 -0
- package/dist/modal/config.d.ts +192 -0
- package/dist/modal/config.d.ts.map +1 -0
- package/dist/modal/config.js +169 -0
- package/dist/modal/config.js.map +1 -0
- package/dist/modal/internal/sdk.d.ts +141 -0
- package/dist/modal/internal/sdk.d.ts.map +1 -0
- package/dist/modal/internal/sdk.js +51 -0
- package/dist/modal/internal/sdk.js.map +1 -0
- package/dist/modal/runtime.d.ts +271 -0
- package/dist/modal/runtime.d.ts.map +1 -0
- package/dist/modal/runtime.js +871 -0
- package/dist/modal/runtime.js.map +1 -0
- package/dist/mount-script.d.ts +16 -1
- package/dist/mount-script.d.ts.map +1 -1
- package/dist/mount-script.js +76 -15
- package/dist/mount-script.js.map +1 -1
- package/dist/orchestrator.d.ts +15 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +13 -1
- package/dist/orchestrator.js.map +1 -1
- package/dist/port.d.ts +144 -6
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +27 -5
- package/dist/port.js.map +1 -1
- package/dist/types.d.ts +50 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vercel/bench.d.ts +169 -0
- package/dist/vercel/bench.d.ts.map +1 -0
- package/dist/vercel/bench.js +337 -0
- package/dist/vercel/bench.js.map +1 -0
- package/dist/vercel/capabilities.d.ts +106 -0
- package/dist/vercel/capabilities.d.ts.map +1 -0
- package/dist/vercel/capabilities.js +128 -0
- package/dist/vercel/capabilities.js.map +1 -0
- package/dist/vercel/config.d.ts +119 -0
- package/dist/vercel/config.d.ts.map +1 -0
- package/dist/vercel/config.js +8 -0
- package/dist/vercel/config.js.map +1 -0
- package/dist/vercel/internal/sdk.d.ts +173 -0
- package/dist/vercel/internal/sdk.d.ts.map +1 -0
- package/dist/vercel/internal/sdk.js +117 -0
- package/dist/vercel/internal/sdk.js.map +1 -0
- package/dist/vercel/runtime.d.ts +305 -0
- package/dist/vercel/runtime.d.ts.map +1 -0
- package/dist/vercel/runtime.js +1169 -0
- package/dist/vercel/runtime.js.map +1 -0
- package/docs/freestyle.md +110 -0
- package/package.json +35 -4
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { agentCoreObservedCapabilities, agentCoreCapabilityModes, agentCoreSandboxCapabilities, agentCoreWorkflowCapabilities, } from "./capabilities.js";
|
|
5
|
+
import { createOfficialAgentCoreApi, isAgentCoreNotFoundError, } from "./internal/sdk.js";
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
7
|
+
const DEFAULT_CREATE_INTERPRETER_TIMEOUT_MS = 60_000;
|
|
8
|
+
const DEFAULT_LAUNCH_TIMEOUT_MS = 60_000;
|
|
9
|
+
const DEFAULT_LOOKUP_TIMEOUT_MS = 15_000;
|
|
10
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 120_000;
|
|
11
|
+
const DEFAULT_FILE_TIMEOUT_MS = 120_000;
|
|
12
|
+
const DEFAULT_DESTROY_TIMEOUT_MS = 30_000;
|
|
13
|
+
/** AgentCore session default per `StartCodeInterpreterSession` docs. */
|
|
14
|
+
const DEFAULT_SESSION_TIMEOUT_SECONDS = 900;
|
|
15
|
+
/** AgentCore session hard ceiling. */
|
|
16
|
+
const MAX_SESSION_TIMEOUT_SECONDS = 28_800;
|
|
17
|
+
const SYSTEM_INTERPRETER_ID = "aws.codeinterpreter.v1";
|
|
18
|
+
/** Bound on pages walked while searching for an owned interpreter to adopt by name. */
|
|
19
|
+
const MAX_ADOPT_LIST_PAGES = 10;
|
|
20
|
+
export class AgentCoreOperationTimeoutError extends Error {
|
|
21
|
+
operation;
|
|
22
|
+
timeoutMs;
|
|
23
|
+
constructor(operation, timeoutMs) {
|
|
24
|
+
super(`AgentCore ${operation} did not complete within ${timeoutMs}ms`);
|
|
25
|
+
this.operation = operation;
|
|
26
|
+
this.timeoutMs = timeoutMs;
|
|
27
|
+
this.name = "AgentCoreOperationTimeoutError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class AgentCoreDestroyVerificationError extends Error {
|
|
31
|
+
sessionId;
|
|
32
|
+
timeoutMs;
|
|
33
|
+
constructor(sessionId, timeoutMs) {
|
|
34
|
+
super(`AgentCore session "${sessionId}" stop was accepted but termination was not `
|
|
35
|
+
+ `verified within ${timeoutMs}ms`);
|
|
36
|
+
this.sessionId = sessionId;
|
|
37
|
+
this.timeoutMs = timeoutMs;
|
|
38
|
+
this.name = "AgentCoreDestroyVerificationError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class AgentCoreUnregisteredHandleError extends Error {
|
|
42
|
+
sessionId;
|
|
43
|
+
constructor(sessionId) {
|
|
44
|
+
super(`AgentCore session "${sessionId}" is not registered on this runtime instance; `
|
|
45
|
+
+ "call getById() to attach it first");
|
|
46
|
+
this.sessionId = sessionId;
|
|
47
|
+
this.name = "AgentCoreUnregisteredHandleError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class AgentCoreNetworkConfigError extends Error {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(`AgentCore network configuration: ${message}`);
|
|
53
|
+
this.name = "AgentCoreNetworkConfigError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class AgentCoreInterpreterNotReadyError extends Error {
|
|
57
|
+
codeInterpreterId;
|
|
58
|
+
lastStatus;
|
|
59
|
+
timeoutMs;
|
|
60
|
+
constructor(codeInterpreterId, lastStatus, timeoutMs) {
|
|
61
|
+
super(`AgentCore code interpreter "${codeInterpreterId}" did not reach READY within `
|
|
62
|
+
+ `${timeoutMs}ms (last status: "${lastStatus}")`);
|
|
63
|
+
this.codeInterpreterId = codeInterpreterId;
|
|
64
|
+
this.lastStatus = lastStatus;
|
|
65
|
+
this.timeoutMs = timeoutMs;
|
|
66
|
+
this.name = "AgentCoreInterpreterNotReadyError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class AgentCoreCommandFailedError extends Error {
|
|
70
|
+
stderr;
|
|
71
|
+
constructor(stderr) {
|
|
72
|
+
super(`AgentCore command reported an error: ${stderr || "(no stderr)"}`);
|
|
73
|
+
this.stderr = stderr;
|
|
74
|
+
this.name = "AgentCoreCommandFailedError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** One budget for one logical operation, shared across every round trip it makes. */
|
|
78
|
+
class Deadline {
|
|
79
|
+
totalMs;
|
|
80
|
+
operation;
|
|
81
|
+
expiresAt;
|
|
82
|
+
constructor(totalMs, operation) {
|
|
83
|
+
this.totalMs = totalMs;
|
|
84
|
+
this.operation = operation;
|
|
85
|
+
this.expiresAt = Date.now() + totalMs;
|
|
86
|
+
}
|
|
87
|
+
remaining() {
|
|
88
|
+
return Math.max(0, this.expiresAt - Date.now());
|
|
89
|
+
}
|
|
90
|
+
require() {
|
|
91
|
+
const left = this.remaining();
|
|
92
|
+
if (left <= 0) {
|
|
93
|
+
throw new AgentCoreOperationTimeoutError(this.operation, this.totalMs);
|
|
94
|
+
}
|
|
95
|
+
return left;
|
|
96
|
+
}
|
|
97
|
+
signal() {
|
|
98
|
+
return AbortSignal.timeout(this.require());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* AWS Bedrock AgentCore Code Interpreter provider adapter.
|
|
103
|
+
*
|
|
104
|
+
* Identity note: `RuntimeHandle.id` carries the AgentCore **session id**, not
|
|
105
|
+
* the code interpreter id. A session is the ephemeral, billable unit
|
|
106
|
+
* (bounded lifetime, terminal stop) that maps onto this package's notion of
|
|
107
|
+
* a "sandbox"; the code interpreter resource underneath it is closer to a
|
|
108
|
+
* Vercel `image` or a Modal `App` — a template every session in this runtime
|
|
109
|
+
* shares, created once and reused, not torn down per handle.
|
|
110
|
+
*
|
|
111
|
+
* Ownership is tracked only in this process's `registrations` map, not by a
|
|
112
|
+
* name prefix: AgentCore session ids are opaque tokens with no addressable
|
|
113
|
+
* namespace to scope a prefix against (see `capabilities.ts` on `warmLease`).
|
|
114
|
+
*/
|
|
115
|
+
export class AgentCoreSandboxRuntime {
|
|
116
|
+
id = "agentcore";
|
|
117
|
+
capabilities = agentCoreWorkflowCapabilities;
|
|
118
|
+
declaredCapabilities = agentCoreSandboxCapabilities;
|
|
119
|
+
declaredCapabilityModes = agentCoreCapabilityModes;
|
|
120
|
+
observedCapabilities = agentCoreObservedCapabilities;
|
|
121
|
+
region;
|
|
122
|
+
credentials;
|
|
123
|
+
interpreter;
|
|
124
|
+
defaultHomeDir;
|
|
125
|
+
sessionTimeoutSeconds;
|
|
126
|
+
env;
|
|
127
|
+
requestTimeoutMs;
|
|
128
|
+
createInterpreterTimeoutMs;
|
|
129
|
+
launchTimeoutMs;
|
|
130
|
+
lookupTimeoutMs;
|
|
131
|
+
execTimeoutMs;
|
|
132
|
+
fileTimeoutMs;
|
|
133
|
+
destroyTimeoutMs;
|
|
134
|
+
injectedApiFactory;
|
|
135
|
+
registrations = new Map();
|
|
136
|
+
apiPromise;
|
|
137
|
+
/** Resolved once, lazily, and cached for the life of this instance. */
|
|
138
|
+
resolvedInterpreterId;
|
|
139
|
+
resolvingInterpreter;
|
|
140
|
+
constructor(options, dependencies = {}) {
|
|
141
|
+
this.region = required(options.region, "AgentCore region");
|
|
142
|
+
this.credentials = options.credentials;
|
|
143
|
+
this.interpreter = validateInterpreterSource(options.interpreter);
|
|
144
|
+
this.defaultHomeDir = required(options.defaultHomeDir, "AgentCore default home directory");
|
|
145
|
+
this.sessionTimeoutSeconds = validateSessionTimeoutSeconds(options.sessionTimeoutSeconds);
|
|
146
|
+
this.env = { ...(options.env ?? {}) };
|
|
147
|
+
this.requestTimeoutMs = positiveDuration(options.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
148
|
+
this.createInterpreterTimeoutMs = positiveDuration(options.createInterpreterTimeoutMs, DEFAULT_CREATE_INTERPRETER_TIMEOUT_MS);
|
|
149
|
+
this.launchTimeoutMs = positiveDuration(options.launchTimeoutMs, DEFAULT_LAUNCH_TIMEOUT_MS);
|
|
150
|
+
this.lookupTimeoutMs = positiveDuration(options.lookupTimeoutMs, DEFAULT_LOOKUP_TIMEOUT_MS);
|
|
151
|
+
this.execTimeoutMs = positiveDuration(options.execTimeoutMs, DEFAULT_EXEC_TIMEOUT_MS);
|
|
152
|
+
this.fileTimeoutMs = positiveDuration(options.fileTimeoutMs, DEFAULT_FILE_TIMEOUT_MS);
|
|
153
|
+
this.destroyTimeoutMs = positiveDuration(options.destroyTimeoutMs, DEFAULT_DESTROY_TIMEOUT_MS);
|
|
154
|
+
this.injectedApiFactory = dependencies.apiFactory;
|
|
155
|
+
}
|
|
156
|
+
// --- launch -----------------------------------------------------------
|
|
157
|
+
async launch(options = {}) {
|
|
158
|
+
const deadline = new Deadline(options.createTimeoutSeconds && options.createTimeoutSeconds > 0
|
|
159
|
+
? Math.ceil(options.createTimeoutSeconds * 1_000)
|
|
160
|
+
: this.launchTimeoutMs, "launch");
|
|
161
|
+
const codeInterpreterId = await this.ensureInterpreter(deadline);
|
|
162
|
+
const api = await this.api();
|
|
163
|
+
const started = await api.data.startSession({
|
|
164
|
+
codeInterpreterIdentifier: codeInterpreterId,
|
|
165
|
+
...(options.name?.trim() ? { name: options.name.trim() } : {}),
|
|
166
|
+
sessionTimeoutSeconds: this.sessionTimeoutSeconds,
|
|
167
|
+
clientToken: randomUUID(),
|
|
168
|
+
abortSignal: deadline.signal(),
|
|
169
|
+
});
|
|
170
|
+
this.registrations.set(started.sessionId, {
|
|
171
|
+
owned: true,
|
|
172
|
+
labels: { ...(options.labels ?? {}) },
|
|
173
|
+
codeInterpreterIdentifier: codeInterpreterId,
|
|
174
|
+
state: "READY",
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
id: started.sessionId,
|
|
178
|
+
state: "READY",
|
|
179
|
+
homeDir: this.defaultHomeDir,
|
|
180
|
+
...(options.workdir ? { workdir: options.workdir } : {}),
|
|
181
|
+
...(started.createdAt ? { createdAt: started.createdAt.toISOString() } : {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// --- lookup -------------------------------------------------------------
|
|
185
|
+
async findByLabels(labels, options = {}) {
|
|
186
|
+
const matches = await this.findAllByLabels(labels, { ...options, limit: 1 });
|
|
187
|
+
return matches[0] ?? null;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* In-process only. See `capabilities.ts`: AgentCore sessions carry no
|
|
191
|
+
* server-side tag/label field, so there is no way to discover a labeled
|
|
192
|
+
* session started by another process. This still returns real matches
|
|
193
|
+
* among sessions this runtime instance itself registered.
|
|
194
|
+
*/
|
|
195
|
+
async findAllByLabels(labels, options = {}) {
|
|
196
|
+
const wanted = { ...labels };
|
|
197
|
+
const excluded = new Set(options.excludeIds ?? []);
|
|
198
|
+
const handles = [];
|
|
199
|
+
for (const [sessionId, entry] of this.registrations) {
|
|
200
|
+
if (!entry.owned || excluded.has(sessionId)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!matchesAllLabels(entry.labels, wanted)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
handles.push({ id: sessionId, state: entry.state, homeDir: this.defaultHomeDir });
|
|
207
|
+
if (options.limit && handles.length >= options.limit) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return handles;
|
|
212
|
+
}
|
|
213
|
+
async countByLabels(labels, options = {}) {
|
|
214
|
+
const matches = await this.findAllByLabels(labels, {
|
|
215
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
216
|
+
...(options.maxCount !== undefined ? { limit: options.maxCount } : {}),
|
|
217
|
+
});
|
|
218
|
+
return matches.length;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Re-resolve a session by id via `GetCodeInterpreterSession`. Works across
|
|
222
|
+
* a process restart as long as the caller's runtime config resolves to the
|
|
223
|
+
* same code interpreter (fixed by `AgentCoreInterpreterSource`) and the
|
|
224
|
+
* session has not reached its `sessionTimeoutSeconds` deadline.
|
|
225
|
+
*/
|
|
226
|
+
async getById(id, options = {}) {
|
|
227
|
+
const deadline = new Deadline(this.lookupTimeoutMs, "get session");
|
|
228
|
+
const codeInterpreterId = await this.ensureInterpreter(deadline);
|
|
229
|
+
const api = await this.api();
|
|
230
|
+
let session;
|
|
231
|
+
try {
|
|
232
|
+
session = await api.data.getSession({
|
|
233
|
+
codeInterpreterIdentifier: codeInterpreterId,
|
|
234
|
+
sessionId: id,
|
|
235
|
+
abortSignal: deadline.signal(),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (isAgentCoreNotFoundError(error)) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
const states = options.states === undefined ? null : options.states;
|
|
245
|
+
if (states && !states.includes(session.status)) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const existing = this.registrations.get(id);
|
|
249
|
+
this.registrations.set(id, {
|
|
250
|
+
owned: options.owned ?? existing?.owned ?? false,
|
|
251
|
+
labels: existing?.labels ?? {},
|
|
252
|
+
codeInterpreterIdentifier: codeInterpreterId,
|
|
253
|
+
state: session.status,
|
|
254
|
+
});
|
|
255
|
+
return { id, state: session.status, homeDir: this.defaultHomeDir };
|
|
256
|
+
}
|
|
257
|
+
// --- exec -----------------------------------------------------------------
|
|
258
|
+
async runScript(handle, options) {
|
|
259
|
+
const entry = this.requireRegistered(handle);
|
|
260
|
+
const timeoutMs = options.timeoutMs && options.timeoutMs > 0
|
|
261
|
+
? Math.ceil(options.timeoutMs)
|
|
262
|
+
: this.execTimeoutMs;
|
|
263
|
+
const deadline = new Deadline(timeoutMs, "invoke executeCommand");
|
|
264
|
+
const api = await this.api();
|
|
265
|
+
const result = await api.data.invoke({
|
|
266
|
+
codeInterpreterIdentifier: entry.codeInterpreterIdentifier,
|
|
267
|
+
sessionId: handle.id,
|
|
268
|
+
name: "executeCommand",
|
|
269
|
+
arguments: { command: this.shellCommand(options) },
|
|
270
|
+
abortSignal: deadline.signal(),
|
|
271
|
+
});
|
|
272
|
+
const stdout = result.structuredContent?.stdout
|
|
273
|
+
?? textContent(result.content);
|
|
274
|
+
const stderr = result.structuredContent?.stderr ?? "";
|
|
275
|
+
const exitCode = result.structuredContent?.exitCode
|
|
276
|
+
?? (result.isError ? 1 : 0);
|
|
277
|
+
return {
|
|
278
|
+
output: combineOutput(stdout, stderr),
|
|
279
|
+
...(stdout ? { stdout } : {}),
|
|
280
|
+
...(stderr ? { stderr } : {}),
|
|
281
|
+
exitCode,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async exec(handle, command, options = {}) {
|
|
285
|
+
const result = await this.runScript(handle, { command, ...options });
|
|
286
|
+
return { output: result.output, exitCode: result.exitCode ?? 1 };
|
|
287
|
+
}
|
|
288
|
+
// AgentCore's `InvokeCodeInterpreter` is a single synchronous call per
|
|
289
|
+
// action — there is no submit-then-poll pair to build `startScript`/
|
|
290
|
+
// `getScriptStatus`/`getScriptLogs` on, so async exec is omitted rather
|
|
291
|
+
// than faked with a buffered result masquerading as a pending one.
|
|
292
|
+
// --- files ------------------------------------------------------------
|
|
293
|
+
async uploadFile(handle, source, destination) {
|
|
294
|
+
await this.uploadBundle(handle, { files: [{ source, destination }] });
|
|
295
|
+
}
|
|
296
|
+
async uploadBundle(handle, options) {
|
|
297
|
+
const entry = this.requireRegistered(handle);
|
|
298
|
+
const deadline = new Deadline(this.fileTimeoutMs, "upload bundle");
|
|
299
|
+
const api = await this.api();
|
|
300
|
+
const textFiles = [];
|
|
301
|
+
// `writeFiles` only documents a `text` field for content, not a binary
|
|
302
|
+
// one; buffers are shipped as a base64 sidecar file and decoded in-
|
|
303
|
+
// sandbox with Python (a guaranteed-present runtime), rather than
|
|
304
|
+
// assuming an unconfirmed binary content type on the vendor API.
|
|
305
|
+
const decodeCommands = [];
|
|
306
|
+
for (const file of options.files) {
|
|
307
|
+
const isBuffer = typeof file.source !== "string";
|
|
308
|
+
const content = isBuffer
|
|
309
|
+
? file.source
|
|
310
|
+
: await readFile(file.source);
|
|
311
|
+
const binary = isBuffer || content.subarray(0, 8_192).includes(0);
|
|
312
|
+
if (binary) {
|
|
313
|
+
const b64Path = `${file.destination}.__b64__`;
|
|
314
|
+
textFiles.push({ path: b64Path, text: content.toString("base64") });
|
|
315
|
+
decodeCommands.push(`python3 -c "import base64; open(${shellSingleQuote(file.destination)}, 'wb').write(base64.b64decode(open(${shellSingleQuote(b64Path)}).read()))" && rm -f ${shellSingleQuote(b64Path)}`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
textFiles.push({ path: file.destination, text: content.toString("utf8") });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (textFiles.length === 0) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
await api.data.invoke({
|
|
325
|
+
codeInterpreterIdentifier: entry.codeInterpreterIdentifier,
|
|
326
|
+
sessionId: handle.id,
|
|
327
|
+
name: "writeFiles",
|
|
328
|
+
arguments: { content: textFiles },
|
|
329
|
+
abortSignal: deadline.signal(),
|
|
330
|
+
});
|
|
331
|
+
if (decodeCommands.length > 0) {
|
|
332
|
+
const verified = await this.runScript(handle, {
|
|
333
|
+
command: decodeCommands.join(" && "),
|
|
334
|
+
timeoutMs: deadline.require(),
|
|
335
|
+
});
|
|
336
|
+
if (verified.exitCode !== 0) {
|
|
337
|
+
throw new Error("Failed to decode uploaded AgentCore bundle files");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async downloadFile(handle, source, destination) {
|
|
342
|
+
this.requireRegistered(handle);
|
|
343
|
+
// Downloaded through `executeCommand` + base64, not the `readFiles`
|
|
344
|
+
// action: the AWS docs do not confirm whether `readFiles` returns binary
|
|
345
|
+
// content safely, and shelling through base64 is correct for both text
|
|
346
|
+
// and binary files regardless of that answer.
|
|
347
|
+
const result = await this.runScript(handle, {
|
|
348
|
+
command: `base64 ${shellSingleQuote(source)}`,
|
|
349
|
+
timeoutMs: this.fileTimeoutMs,
|
|
350
|
+
});
|
|
351
|
+
if (result.exitCode !== 0) {
|
|
352
|
+
throw new Error(`AgentCore session "${handle.id}" has no file at "${source}"`);
|
|
353
|
+
}
|
|
354
|
+
const buffer = Buffer.from(result.output.trim(), "base64");
|
|
355
|
+
if (destination) {
|
|
356
|
+
await writeFile(destination, buffer);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
return buffer;
|
|
360
|
+
}
|
|
361
|
+
async getHomeDir(handle) {
|
|
362
|
+
this.requireRegistered(handle);
|
|
363
|
+
handle.homeDir = handle.homeDir ?? this.defaultHomeDir;
|
|
364
|
+
return handle.homeDir;
|
|
365
|
+
}
|
|
366
|
+
// --- lifecycle ----------------------------------------------------------
|
|
367
|
+
// No `start`/`stop`: `StopCodeInterpreterSession` is a terminal transition
|
|
368
|
+
// with no resume path (`GetCodeInterpreterSession.status` is only
|
|
369
|
+
// `READY | TERMINATED`), the same shape the Modal adapter's `lifecycle:
|
|
370
|
+
// false` documents for `Sandbox.terminate()`. Shipping a `stop` that
|
|
371
|
+
// cannot be undone under a name implying it can would be worse than
|
|
372
|
+
// omitting it.
|
|
373
|
+
/**
|
|
374
|
+
* Stop a session and verify it reached `TERMINATED`.
|
|
375
|
+
*
|
|
376
|
+
* The registration is retained when verification fails, so a caller can
|
|
377
|
+
* retry cleanup — dropping it would make a leaked session unreachable
|
|
378
|
+
* through this runtime and therefore invisible to any subsequent audit.
|
|
379
|
+
*/
|
|
380
|
+
async destroy(handle) {
|
|
381
|
+
const entry = this.registrations.get(handle.id);
|
|
382
|
+
if (!entry) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!entry.owned) {
|
|
386
|
+
this.registrations.delete(handle.id);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const deadline = new Deadline(this.destroyTimeoutMs, "stop session");
|
|
390
|
+
const api = await this.api();
|
|
391
|
+
try {
|
|
392
|
+
await api.data.stopSession({
|
|
393
|
+
codeInterpreterIdentifier: entry.codeInterpreterIdentifier,
|
|
394
|
+
sessionId: handle.id,
|
|
395
|
+
abortSignal: deadline.signal(),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
if (!isAgentCoreNotFoundError(error)) {
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
// Already gone. Fall through to verification, which will agree.
|
|
403
|
+
}
|
|
404
|
+
const verified = await this.waitUntilTerminated(entry.codeInterpreterIdentifier, handle.id, deadline);
|
|
405
|
+
if (!verified) {
|
|
406
|
+
throw new AgentCoreDestroyVerificationError(handle.id, deadline.totalMs);
|
|
407
|
+
}
|
|
408
|
+
this.registrations.delete(handle.id);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Delete the owned code interpreter resource itself.
|
|
412
|
+
*
|
|
413
|
+
* Not part of `SandboxRuntime`/`WorkflowRuntime`: the code interpreter is
|
|
414
|
+
* a shared template across every session this runtime starts (see the
|
|
415
|
+
* class doc), analogous to a Vercel `image` or a Modal `App`, and neither
|
|
416
|
+
* of those is destroyed by a per-handle `destroy()` either. Callers that
|
|
417
|
+
* own the whole runtime's lifecycle (tests, a process shutdown hook) call
|
|
418
|
+
* this explicitly once they are done with every session.
|
|
419
|
+
*/
|
|
420
|
+
async deleteOwnedCodeInterpreter() {
|
|
421
|
+
if (this.interpreter.type !== "owned" || !this.resolvedInterpreterId) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const deadline = new Deadline(this.destroyTimeoutMs, "delete code interpreter");
|
|
425
|
+
const api = await this.api();
|
|
426
|
+
await api.control.deleteCodeInterpreter({
|
|
427
|
+
codeInterpreterId: this.resolvedInterpreterId,
|
|
428
|
+
clientToken: randomUUID(),
|
|
429
|
+
abortSignal: deadline.signal(),
|
|
430
|
+
});
|
|
431
|
+
this.resolvedInterpreterId = undefined;
|
|
432
|
+
}
|
|
433
|
+
// --- internals ------------------------------------------------------------
|
|
434
|
+
async api() {
|
|
435
|
+
if (this.injectedApiFactory) {
|
|
436
|
+
return this.injectedApiFactory();
|
|
437
|
+
}
|
|
438
|
+
this.apiPromise ??= createOfficialAgentCoreApi(this.credentials, this.region);
|
|
439
|
+
return this.apiPromise;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Resolve the code interpreter this runtime's sessions run against,
|
|
443
|
+
* creating it on first use and caching the id for the life of the
|
|
444
|
+
* instance. Idempotent under concurrent callers via `resolvingInterpreter`.
|
|
445
|
+
*/
|
|
446
|
+
async ensureInterpreter(callerDeadline) {
|
|
447
|
+
if (this.interpreter.type === "system") {
|
|
448
|
+
return SYSTEM_INTERPRETER_ID;
|
|
449
|
+
}
|
|
450
|
+
if (this.resolvedInterpreterId) {
|
|
451
|
+
return this.resolvedInterpreterId;
|
|
452
|
+
}
|
|
453
|
+
if (!this.resolvingInterpreter) {
|
|
454
|
+
this.resolvingInterpreter = this.createOrAdoptInterpreter().finally(() => {
|
|
455
|
+
this.resolvingInterpreter = undefined;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
// The caller's own deadline still governs how long *it* is willing to
|
|
459
|
+
// wait, independent of how long interpreter resolution takes.
|
|
460
|
+
callerDeadline.require();
|
|
461
|
+
return this.resolvingInterpreter;
|
|
462
|
+
}
|
|
463
|
+
async createOrAdoptInterpreter() {
|
|
464
|
+
if (this.interpreter.type !== "owned") {
|
|
465
|
+
throw new Error("createOrAdoptInterpreter called without an owned interpreter source");
|
|
466
|
+
}
|
|
467
|
+
const source = this.interpreter;
|
|
468
|
+
const deadline = new Deadline(this.createInterpreterTimeoutMs, "create code interpreter");
|
|
469
|
+
const api = await this.api();
|
|
470
|
+
// Adopt first: the name is fixed and deterministic precisely so a fresh
|
|
471
|
+
// process instance (or a second runtime pointed at the same
|
|
472
|
+
// configuration) rediscovers and reuses the interpreter a prior one
|
|
473
|
+
// created, instead of every instance independently creating its own —
|
|
474
|
+
// which would leave each instance's sessions unreachable from every
|
|
475
|
+
// other instance's `getById`, since a session can only be looked up
|
|
476
|
+
// through the `codeInterpreterIdentifier` that created it.
|
|
477
|
+
const existing = await this.findExistingInterpreterByName(api, source.name, deadline);
|
|
478
|
+
if (existing) {
|
|
479
|
+
const ready = await this.waitUntilInterpreterReady(existing.codeInterpreterId, existing.status ?? "CREATING", deadline);
|
|
480
|
+
this.resolvedInterpreterId = ready;
|
|
481
|
+
return ready;
|
|
482
|
+
}
|
|
483
|
+
const network = toNetworkConfigParams(source.network);
|
|
484
|
+
const created = await api.control.createCodeInterpreter({
|
|
485
|
+
name: source.name,
|
|
486
|
+
...(source.description ? { description: source.description } : {}),
|
|
487
|
+
...(source.executionRoleArn ? { executionRoleArn: source.executionRoleArn } : {}),
|
|
488
|
+
networkConfiguration: network,
|
|
489
|
+
...(source.tags ? { tags: { ...source.tags } } : {}),
|
|
490
|
+
clientToken: randomUUID(),
|
|
491
|
+
abortSignal: deadline.signal(),
|
|
492
|
+
});
|
|
493
|
+
const ready = await this.waitUntilInterpreterReady(created.codeInterpreterId, created.status, deadline);
|
|
494
|
+
this.resolvedInterpreterId = ready;
|
|
495
|
+
return ready;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Search for an already-created interpreter with this exact name.
|
|
499
|
+
*
|
|
500
|
+
* Bounded at `MAX_ADOPT_LIST_PAGES` pages so a large or pathologically
|
|
501
|
+
* paginated account cannot turn adoption into an unbounded crawl. Falling
|
|
502
|
+
* through to `createCodeInterpreter` when the search is inconclusive is
|
|
503
|
+
* safe rather than silent: a genuine duplicate name beyond the search
|
|
504
|
+
* window surfaces as a loud `ConflictException` from the create call
|
|
505
|
+
* (names are unique per account), not a silently-adopted wrong resource.
|
|
506
|
+
*/
|
|
507
|
+
async findExistingInterpreterByName(api, name, deadline) {
|
|
508
|
+
let nextToken;
|
|
509
|
+
for (let page = 0; page < MAX_ADOPT_LIST_PAGES; page += 1) {
|
|
510
|
+
const result = await api.control.listCodeInterpreters({
|
|
511
|
+
maxResults: 100,
|
|
512
|
+
...(nextToken ? { nextToken } : {}),
|
|
513
|
+
abortSignal: deadline.signal(),
|
|
514
|
+
});
|
|
515
|
+
const match = result.items.find((item) => item.name === name);
|
|
516
|
+
if (match) {
|
|
517
|
+
return { codeInterpreterId: match.codeInterpreterId, ...(match.status ? { status: match.status } : {}) };
|
|
518
|
+
}
|
|
519
|
+
if (!result.nextToken) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
nextToken = result.nextToken;
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
async waitUntilInterpreterReady(codeInterpreterId, initialStatus, deadline) {
|
|
527
|
+
let status = initialStatus;
|
|
528
|
+
const api = await this.api();
|
|
529
|
+
while (status !== "READY") {
|
|
530
|
+
if (status === "CREATE_FAILED") {
|
|
531
|
+
throw new AgentCoreInterpreterNotReadyError(codeInterpreterId, status, deadline.totalMs);
|
|
532
|
+
}
|
|
533
|
+
deadline.require();
|
|
534
|
+
await sleep(Math.min(1_000, deadline.remaining()));
|
|
535
|
+
const current = await api.control.getCodeInterpreter({
|
|
536
|
+
codeInterpreterId,
|
|
537
|
+
abortSignal: deadline.signal(),
|
|
538
|
+
});
|
|
539
|
+
status = current.status;
|
|
540
|
+
}
|
|
541
|
+
return codeInterpreterId;
|
|
542
|
+
}
|
|
543
|
+
async waitUntilTerminated(codeInterpreterId, sessionId, deadline) {
|
|
544
|
+
const api = await this.api();
|
|
545
|
+
while (deadline.remaining() > 0) {
|
|
546
|
+
try {
|
|
547
|
+
const current = await api.data.getSession({
|
|
548
|
+
codeInterpreterIdentifier: codeInterpreterId,
|
|
549
|
+
sessionId,
|
|
550
|
+
abortSignal: deadline.signal(),
|
|
551
|
+
});
|
|
552
|
+
if (current.status === "TERMINATED") {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
if (isAgentCoreNotFoundError(error)) {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
if (deadline.remaining() <= 0) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
await sleep(Math.min(750, deadline.remaining()));
|
|
566
|
+
}
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
requireRegistered(handle) {
|
|
570
|
+
const entry = this.registrations.get(handle.id);
|
|
571
|
+
if (!entry) {
|
|
572
|
+
throw new AgentCoreUnregisteredHandleError(handle.id);
|
|
573
|
+
}
|
|
574
|
+
return entry;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* AgentCore's `executeCommand` action takes a bare `command` string with
|
|
578
|
+
* no `cwd`/`env` fields of its own, so both are folded into the command
|
|
579
|
+
* text: `cd` for the working directory, `export` for each variable, each
|
|
580
|
+
* value single-quoted so nothing a caller passes is ever interpreted as
|
|
581
|
+
* shell syntax.
|
|
582
|
+
*/
|
|
583
|
+
shellCommand(options) {
|
|
584
|
+
const env = { ...this.env, ...(options.env ?? {}) };
|
|
585
|
+
const prefix = [];
|
|
586
|
+
if (options.cwd) {
|
|
587
|
+
prefix.push(`cd ${shellSingleQuote(options.cwd)}`);
|
|
588
|
+
}
|
|
589
|
+
for (const [key, value] of Object.entries(env)) {
|
|
590
|
+
prefix.push(`export ${key}=${shellSingleQuote(value)}`);
|
|
591
|
+
}
|
|
592
|
+
return prefix.length > 0
|
|
593
|
+
? `${prefix.join(" && ")} && ${options.command}`
|
|
594
|
+
: options.command;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// --- validation helpers -----------------------------------------------------
|
|
598
|
+
function required(value, label) {
|
|
599
|
+
const trimmed = value?.trim();
|
|
600
|
+
if (!trimmed) {
|
|
601
|
+
throw new Error(`${label} is required`);
|
|
602
|
+
}
|
|
603
|
+
return trimmed;
|
|
604
|
+
}
|
|
605
|
+
function positiveDuration(value, fallback) {
|
|
606
|
+
if (value === undefined) {
|
|
607
|
+
return fallback;
|
|
608
|
+
}
|
|
609
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
610
|
+
throw new Error(`Expected a positive duration, got ${value}`);
|
|
611
|
+
}
|
|
612
|
+
return value;
|
|
613
|
+
}
|
|
614
|
+
function validateSessionTimeoutSeconds(value) {
|
|
615
|
+
if (value === undefined) {
|
|
616
|
+
return DEFAULT_SESSION_TIMEOUT_SECONDS;
|
|
617
|
+
}
|
|
618
|
+
if (!Number.isFinite(value) || value < 1 || value > MAX_SESSION_TIMEOUT_SECONDS) {
|
|
619
|
+
throw new Error(`AgentCore sessionTimeoutSeconds must be between 1 and ${MAX_SESSION_TIMEOUT_SECONDS}, `
|
|
620
|
+
+ `got ${value}`);
|
|
621
|
+
}
|
|
622
|
+
return Math.floor(value);
|
|
623
|
+
}
|
|
624
|
+
function validateInterpreterSource(source) {
|
|
625
|
+
if (source.type === "system") {
|
|
626
|
+
return source;
|
|
627
|
+
}
|
|
628
|
+
if (source.type !== "owned") {
|
|
629
|
+
throw new Error(`Unknown AgentCore interpreter source type: ${source.type}`);
|
|
630
|
+
}
|
|
631
|
+
required(source.name, "AgentCore owned interpreter name");
|
|
632
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]{0,47}$/.test(source.name)) {
|
|
633
|
+
throw new Error(`AgentCore code interpreter name "${source.name}" must match [a-zA-Z][a-zA-Z0-9_]{0,47}`);
|
|
634
|
+
}
|
|
635
|
+
toNetworkConfigParams(source.network);
|
|
636
|
+
return source;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Translate the SDK-free `AgentCoreNetworkConfig` into the vendor API's
|
|
640
|
+
* `networkConfiguration` shape, enforcing the VPC-default / explicit-opt-in
|
|
641
|
+
* rule documented on `AgentCoreNetworkConfig` itself.
|
|
642
|
+
*/
|
|
643
|
+
function toNetworkConfigParams(network) {
|
|
644
|
+
const mode = network.mode ?? "VPC";
|
|
645
|
+
if (mode === "SANDBOX") {
|
|
646
|
+
// eslint-disable-next-line no-console
|
|
647
|
+
console.warn("[agentcore] network mode SANDBOX was explicitly requested. Public research "
|
|
648
|
+
+ "disclosed in March 2026 (BeyondTrust Phantom Labs, corroborated by Unit 42 "
|
|
649
|
+
+ "and the Cloud Security Alliance) showed SANDBOX mode still permits outbound "
|
|
650
|
+
+ "DNS A/AAAA queries, which is sufficient to build a covert command-and-"
|
|
651
|
+
+ "control and data-exfiltration channel out of the sandbox. AWS's mitigation "
|
|
652
|
+
+ "is VPC mode plus a Route 53 Resolver DNS Firewall, not a SANDBOX-mode fix. "
|
|
653
|
+
+ "Prefer network: { vpc: {...} } unless this session has no secrets to leak.");
|
|
654
|
+
return { networkMode: "SANDBOX" };
|
|
655
|
+
}
|
|
656
|
+
if (mode === "PUBLIC") {
|
|
657
|
+
return { networkMode: "PUBLIC" };
|
|
658
|
+
}
|
|
659
|
+
if (!("vpc" in network) || !network.vpc) {
|
|
660
|
+
throw new AgentCoreNetworkConfigError("mode is VPC (the default) but no `vpc` config was supplied; provide "
|
|
661
|
+
+ "{ vpc: { subnetIds, securityGroupIds } } or explicitly opt into "
|
|
662
|
+
+ '{ mode: "SANDBOX" } / { mode: "PUBLIC" }');
|
|
663
|
+
}
|
|
664
|
+
const { subnetIds, securityGroupIds, requireServiceS3Endpoint } = network.vpc;
|
|
665
|
+
if (subnetIds.length === 0) {
|
|
666
|
+
throw new AgentCoreNetworkConfigError("vpc.subnetIds must contain at least one subnet");
|
|
667
|
+
}
|
|
668
|
+
if (securityGroupIds.length === 0) {
|
|
669
|
+
throw new AgentCoreNetworkConfigError("vpc.securityGroupIds must contain at least one security group");
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
networkMode: "VPC",
|
|
673
|
+
vpcConfig: {
|
|
674
|
+
subnets: [...subnetIds],
|
|
675
|
+
securityGroups: [...securityGroupIds],
|
|
676
|
+
...(requireServiceS3Endpoint !== undefined ? { requireServiceS3Endpoint } : {}),
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function matchesAllLabels(actual, wanted) {
|
|
681
|
+
for (const [key, value] of Object.entries(wanted)) {
|
|
682
|
+
if (actual[key] !== value) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
function textContent(content) {
|
|
689
|
+
return content
|
|
690
|
+
.filter((item) => typeof item.text === "string")
|
|
691
|
+
.map((item) => item.text)
|
|
692
|
+
.join("");
|
|
693
|
+
}
|
|
694
|
+
function combineOutput(stdout, stderr) {
|
|
695
|
+
if (stdout && stderr) {
|
|
696
|
+
return `${stdout}\n${stderr}`;
|
|
697
|
+
}
|
|
698
|
+
return stdout || stderr;
|
|
699
|
+
}
|
|
700
|
+
function shellSingleQuote(value) {
|
|
701
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
702
|
+
}
|
|
703
|
+
function sleep(ms) {
|
|
704
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=runtime.js.map
|