@sandboxrouter/sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -48
- package/dist/bench.js +4 -4
- package/dist/catalog.d.ts +159 -0
- package/dist/catalog.js +448 -15
- package/dist/check.d.ts +28 -0
- package/dist/check.js +185 -0
- package/dist/cli.js +148 -11
- package/dist/computers.d.ts +55 -13
- package/dist/computers.js +132 -6
- package/dist/conformance.d.ts +46 -0
- package/dist/conformance.js +65 -0
- package/dist/cost.d.ts +67 -0
- package/dist/cost.js +153 -0
- package/dist/gc.d.ts +29 -0
- package/dist/gc.js +54 -0
- package/dist/index.d.ts +28 -2
- package/dist/index.js +28 -2
- package/dist/leaderboard.d.ts +6 -0
- package/dist/leaderboard.js +5 -4
- package/dist/ledger.d.ts +38 -0
- package/dist/ledger.js +89 -0
- package/dist/providers/agent37.d.ts +40 -0
- package/dist/providers/agent37.js +224 -0
- package/dist/providers/agentcore.d.ts +71 -0
- package/dist/providers/agentcore.js +446 -0
- package/dist/providers/alibaba.d.ts +18 -0
- package/dist/providers/alibaba.js +106 -0
- package/dist/providers/azure.d.ts +43 -0
- package/dist/providers/azure.js +330 -0
- package/dist/providers/beam.d.ts +11 -0
- package/dist/providers/beam.js +179 -0
- package/dist/providers/blaxel.d.ts +3 -1
- package/dist/providers/blaxel.js +32 -22
- package/dist/providers/boat.js +8 -3
- package/dist/providers/cloudflare.js +8 -3
- package/dist/providers/codesandbox.d.ts +59 -0
- package/dist/providers/codesandbox.js +287 -0
- package/dist/providers/daytona.d.ts +25 -4
- package/dist/providers/daytona.js +449 -28
- package/dist/providers/dedalus.d.ts +12 -0
- package/dist/providers/dedalus.js +244 -0
- package/dist/providers/deno.d.ts +50 -0
- package/dist/providers/deno.js +170 -0
- package/dist/providers/docker.js +11 -5
- package/dist/providers/e2b.d.ts +70 -3
- package/dist/providers/e2b.js +376 -8
- package/dist/providers/exedev.js +17 -5
- package/dist/providers/freestyle.d.ts +49 -0
- package/dist/providers/freestyle.js +261 -0
- package/dist/providers/hopx.js +27 -18
- package/dist/providers/instabox.d.ts +76 -0
- package/dist/providers/instabox.js +360 -0
- package/dist/providers/koyeb.d.ts +71 -0
- package/dist/providers/koyeb.js +379 -0
- package/dist/providers/lambda.d.ts +51 -0
- package/dist/providers/lambda.js +467 -0
- package/dist/providers/langsmith.d.ts +16 -0
- package/dist/providers/langsmith.js +149 -0
- package/dist/providers/limrun.d.ts +77 -0
- package/dist/providers/limrun.js +626 -0
- package/dist/providers/maritime.d.ts +5 -1
- package/dist/providers/maritime.js +39 -8
- package/dist/providers/mobilerun.d.ts +44 -0
- package/dist/providers/mobilerun.js +417 -0
- package/dist/providers/modal.js +12 -4
- package/dist/providers/morph.js +12 -4
- package/dist/providers/northflank.js +11 -13
- package/dist/providers/novita.d.ts +12 -0
- package/dist/providers/novita.js +81 -0
- package/dist/providers/openscale.d.ts +17 -0
- package/dist/providers/openscale.js +87 -0
- package/dist/providers/orgo.d.ts +45 -0
- package/dist/providers/orgo.js +414 -0
- package/dist/providers/sail.d.ts +32 -0
- package/dist/providers/sail.js +372 -0
- package/dist/providers/superserve.d.ts +25 -0
- package/dist/providers/superserve.js +337 -0
- package/dist/providers/tensorlake.js +17 -14
- package/dist/providers/upstash.d.ts +30 -0
- package/dist/providers/upstash.js +258 -0
- package/dist/providers/vercel.js +14 -3
- package/dist/providers/workspaces.d.ts +60 -0
- package/dist/providers/workspaces.js +662 -0
- package/dist/router.d.ts +36 -5
- package/dist/router.js +149 -19
- package/dist/server.d.ts +1 -1
- package/dist/server.js +22 -3
- package/dist/types.d.ts +13 -5
- package/dist/util.d.ts +28 -1
- package/dist/util.js +56 -7
- package/package.json +80 -5
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { DEFAULT_MEMORY_GB, DEFAULT_VCPU, UPSTASH_SIZES, upstashSize } from "../catalog.js";
|
|
3
|
+
export { UPSTASH_SIZES, upstashSize };
|
|
4
|
+
import { ProviderError } from "../types.js";
|
|
5
|
+
import { dirname, shq, toBytes, wrapCommand } from "../util.js";
|
|
6
|
+
/**
|
|
7
|
+
* Upstash Box over plain HTTPS (no SDK). Each sandbox is an *ephemeral* box: exec + files only,
|
|
8
|
+
* ready on the create response (no polling), deleted automatically after `ttl` seconds.
|
|
9
|
+
*
|
|
10
|
+
* Upstash documents the Box API through its SDKs (`@upstash/box`, `upstash-box`). The ephemeral
|
|
11
|
+
* create body is documented; the other routes and field names below are the ones the official
|
|
12
|
+
* TypeScript SDK (@upstash/box 0.7.10, MIT) sends, so they are not invented but are not in the
|
|
13
|
+
* public docs either.
|
|
14
|
+
* base https://us-east-1.box.upstash.com (UPSTASH_BOX_BASE_URL), header X-Box-Api-Key
|
|
15
|
+
* POST /v2/box {ephemeral:true, ttl, size, runtime?, env_vars?} (documented)
|
|
16
|
+
* GET /v2/box/{id} box data ({id, status, size, expires_at, ...})
|
|
17
|
+
* POST /v2/box/{id}/exec {command:["sh","-c",cmd]} -> {output, error, exit_code}
|
|
18
|
+
* POST /v2/box/{id}/exec-stream same body; raw output, then "event: exit\ndata: {exit_code}"
|
|
19
|
+
* (or "event: error\ndata: {error}")
|
|
20
|
+
* GET /v2/box/{id}/files/read?path=P&encoding=base64 -> {content}
|
|
21
|
+
* POST /v2/box/{id}/files/write {path, content, encoding:"base64"}
|
|
22
|
+
* DELETE /v2/box/{id}
|
|
23
|
+
* errors: JSON {error: "..."} with the HTTP status
|
|
24
|
+
* Sizes are fixed: small 2 vCPU / 4 GB, medium 4 / 8, large 8 / 16; the smallest that fits the
|
|
25
|
+
* spec is used. TTL max 259 200 s (3 days). image.upstash = runtime: node, python, golang, ruby,
|
|
26
|
+
* rust or their -alpine variants (Debian by default); custom images are "coming soon".
|
|
27
|
+
* No pause: ephemeral boxes don't expose it (an idle one is auto-paused by Upstash and resumed by
|
|
28
|
+
* the next exec or file call). Commands get no per-request timeout from the API, so they run
|
|
29
|
+
* under `timeout`, as the docs recommend.
|
|
30
|
+
* exec-stream does not say whether stderr is included or how, so streaming commands send stderr to
|
|
31
|
+
* a temp file and deliver it (onStderr) once the command ends; stdout streams live.
|
|
32
|
+
* Pricing: active CPU only (memory free), see https://upstash.com/pricing/box.
|
|
33
|
+
*
|
|
34
|
+
* Docs: https://upstash.com/docs/box/overall/ephemeral-box.md
|
|
35
|
+
* https://upstash.com/docs/box/overall/how-it-works.md (sizes, lifecycle, runtimes)
|
|
36
|
+
* https://upstash.com/docs/box/overall/shell.md
|
|
37
|
+
* https://upstash.com/docs/box/overall/files.md
|
|
38
|
+
* https://upstash.com/docs/box/overall/quickstart.md (UPSTASH_BOX_API_KEY)
|
|
39
|
+
* https://upstash.com/pricing/box.md
|
|
40
|
+
* https://github.com/upstash/box (SDK source the routes above come from)
|
|
41
|
+
*/
|
|
42
|
+
const ID = "upstash";
|
|
43
|
+
const MAX_TTL_SECONDS = 259_200;
|
|
44
|
+
const RUNTIME = /^(node|python|golang|ruby|rust)(-alpine)?$/;
|
|
45
|
+
export class UpstashProvider {
|
|
46
|
+
apiKey;
|
|
47
|
+
baseUrl;
|
|
48
|
+
id = ID;
|
|
49
|
+
constructor(apiKey = process.env.UPSTASH_BOX_API_KEY, baseUrl = (process.env.UPSTASH_BOX_BASE_URL ?? "https://us-east-1.box.upstash.com").replace(/\/$/, "")) {
|
|
50
|
+
this.apiKey = apiKey;
|
|
51
|
+
this.baseUrl = baseUrl;
|
|
52
|
+
}
|
|
53
|
+
configured() {
|
|
54
|
+
return !!this.apiKey;
|
|
55
|
+
}
|
|
56
|
+
/** @internal One API call; JSON in and out. Non-2xx becomes a ProviderError with the status. */
|
|
57
|
+
async request(method, path, body, timeoutMs = 600_000, raw = false) {
|
|
58
|
+
if (!this.apiKey)
|
|
59
|
+
throw new ProviderError(ID, "UPSTASH_BOX_API_KEY is not set");
|
|
60
|
+
const headers = { "X-Box-Api-Key": this.apiKey };
|
|
61
|
+
if (body !== undefined)
|
|
62
|
+
headers["Content-Type"] = "application/json";
|
|
63
|
+
let res;
|
|
64
|
+
try {
|
|
65
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
66
|
+
method,
|
|
67
|
+
headers,
|
|
68
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
69
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const what = err?.name === "TimeoutError" ? `timed out after ${timeoutMs / 1000}s` : (err?.message ?? String(err));
|
|
74
|
+
throw new ProviderError(ID, `${method} ${path.split("?")[0]}: ${what}`);
|
|
75
|
+
}
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
const text = await res.text().catch(() => "");
|
|
78
|
+
let detail = text.slice(0, 300) || res.statusText;
|
|
79
|
+
try {
|
|
80
|
+
const j = JSON.parse(text);
|
|
81
|
+
if (typeof j?.error === "string")
|
|
82
|
+
detail = j.error;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* not JSON */
|
|
86
|
+
}
|
|
87
|
+
throw new ProviderError(ID, `${method} ${path.split("?")[0]} -> ${res.status} ${detail}`, res.status);
|
|
88
|
+
}
|
|
89
|
+
if (raw)
|
|
90
|
+
return res;
|
|
91
|
+
const text = await res.text();
|
|
92
|
+
return text ? JSON.parse(text) : {};
|
|
93
|
+
}
|
|
94
|
+
async create(spec) {
|
|
95
|
+
const { size } = upstashSize(spec.vcpu ?? DEFAULT_VCPU, spec.memoryGb ?? DEFAULT_MEMORY_GB);
|
|
96
|
+
const ttl = Math.min(MAX_TTL_SECONDS, Math.max(60, Math.round((spec.timeoutMinutes ?? 30) * 60)));
|
|
97
|
+
const body = { ephemeral: true, ttl, size };
|
|
98
|
+
const runtime = spec.image?.upstash;
|
|
99
|
+
if (runtime) {
|
|
100
|
+
if (!RUNTIME.test(runtime))
|
|
101
|
+
throw new ProviderError(ID, `unknown runtime "${runtime}" (node, python, golang, ruby, rust, optionally -alpine)`);
|
|
102
|
+
body.runtime = runtime;
|
|
103
|
+
}
|
|
104
|
+
if (spec.env && Object.keys(spec.env).length)
|
|
105
|
+
body.env_vars = spec.env;
|
|
106
|
+
const data = (await this.request("POST", "/v2/box", body));
|
|
107
|
+
if (!data?.id)
|
|
108
|
+
throw new ProviderError(ID, "create returned no box id");
|
|
109
|
+
if (data.status === "error")
|
|
110
|
+
throw new ProviderError(ID, `box ${data.id} failed to start`);
|
|
111
|
+
return new UpstashBox(this, data.id);
|
|
112
|
+
}
|
|
113
|
+
async connect(nativeId) {
|
|
114
|
+
const data = (await this.request("GET", `/v2/box/${encodeURIComponent(nativeId)}`));
|
|
115
|
+
if (data.status === "deleted" || data.status === "error")
|
|
116
|
+
throw new ProviderError(ID, `box ${nativeId} is ${data.status}`, 404);
|
|
117
|
+
return new UpstashBox(this, data.id ?? nativeId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
class UpstashBox {
|
|
121
|
+
p;
|
|
122
|
+
nativeId;
|
|
123
|
+
constructor(p, nativeId) {
|
|
124
|
+
this.p = p;
|
|
125
|
+
this.nativeId = nativeId;
|
|
126
|
+
}
|
|
127
|
+
get base() {
|
|
128
|
+
return `/v2/box/${encodeURIComponent(this.nativeId)}`;
|
|
129
|
+
}
|
|
130
|
+
async exec(cmd, opts = {}) {
|
|
131
|
+
const t0 = Date.now();
|
|
132
|
+
const secs = Math.max(1, Math.round(opts.timeoutSeconds ?? 60));
|
|
133
|
+
const script = shq(wrapCommand(cmd, { cwd: opts.cwd, env: opts.env }));
|
|
134
|
+
const guarded = `if command -v timeout >/dev/null 2>&1; then timeout ${secs} sh -c ${script}; else sh -c ${script}; fi`;
|
|
135
|
+
const requestMs = (secs + 30) * 1000;
|
|
136
|
+
let r;
|
|
137
|
+
if (opts.onStdout || opts.onStderr) {
|
|
138
|
+
r = await this.stream(guarded, requestMs, opts, t0);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const j = await this.p.request("POST", `${this.base}/exec`, { command: ["sh", "-c", guarded] }, requestMs);
|
|
142
|
+
r = { exitCode: Number(j.exit_code ?? 0), stdout: j.output ?? "", stderr: j.error ?? "", durationMs: Date.now() - t0 };
|
|
143
|
+
}
|
|
144
|
+
if (r.exitCode === 124 && r.durationMs >= secs * 1000)
|
|
145
|
+
throw new ProviderError(ID, `command timed out after ${secs}s`);
|
|
146
|
+
return r;
|
|
147
|
+
}
|
|
148
|
+
/** exec-stream with stderr parked in a temp file, so the stream carries stdout only. */
|
|
149
|
+
async stream(guarded, requestMs, opts, t0) {
|
|
150
|
+
const errFile = `/tmp/.sbr-err-${randomBytes(6).toString("hex")}`;
|
|
151
|
+
const res = await this.p.request("POST", `${this.base}/exec-stream`, { command: ["sh", "-c", `{ ${guarded} ; } 2>${errFile}`] }, requestMs, true);
|
|
152
|
+
let stdout = "";
|
|
153
|
+
let exitCode;
|
|
154
|
+
for await (const ev of parseExecStream(res)) {
|
|
155
|
+
if (ev.type === "output") {
|
|
156
|
+
stdout += ev.data;
|
|
157
|
+
opts.onStdout?.(ev.data);
|
|
158
|
+
}
|
|
159
|
+
else if (ev.type === "error") {
|
|
160
|
+
throw new ProviderError(ID, `exec-stream: ${ev.message}`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
exitCode = ev.exitCode;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (exitCode === undefined)
|
|
167
|
+
throw new ProviderError(ID, "exec-stream ended without an exit code");
|
|
168
|
+
const e = await this.p.request("POST", `${this.base}/exec`, { command: ["sh", "-c", `cat ${errFile} 2>/dev/null; rm -f ${errFile}`] });
|
|
169
|
+
const stderr = e.output ?? "";
|
|
170
|
+
if (stderr)
|
|
171
|
+
opts.onStderr?.(stderr);
|
|
172
|
+
return { exitCode, stdout, stderr, durationMs: Date.now() - t0 };
|
|
173
|
+
}
|
|
174
|
+
async writeFile(path, content) {
|
|
175
|
+
const parent = dirname(path);
|
|
176
|
+
if (parent !== "/") {
|
|
177
|
+
const m = await this.p.request("POST", `${this.base}/exec`, { command: ["mkdir", "-p", parent] });
|
|
178
|
+
if (Number(m.exit_code ?? 0) !== 0)
|
|
179
|
+
throw new ProviderError(ID, `writeFile ${path}: ${m.error || m.output || `mkdir exit ${m.exit_code}`}`);
|
|
180
|
+
}
|
|
181
|
+
await this.p.request("POST", `${this.base}/files/write`, {
|
|
182
|
+
path,
|
|
183
|
+
content: Buffer.from(toBytes(content)).toString("base64"),
|
|
184
|
+
encoding: "base64",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async readFile(path) {
|
|
188
|
+
const j = await this.p.request("GET", `${this.base}/files/read?path=${encodeURIComponent(path)}&encoding=base64`);
|
|
189
|
+
if (typeof j?.content !== "string")
|
|
190
|
+
throw new ProviderError(ID, `readFile ${path}: no content in response`);
|
|
191
|
+
return new Uint8Array(Buffer.from(j.content, "base64"));
|
|
192
|
+
}
|
|
193
|
+
async kill() {
|
|
194
|
+
await this.p.request("DELETE", this.base);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const EXIT = "event: exit\n";
|
|
198
|
+
const ERROR = "event: error\n";
|
|
199
|
+
/** Longest tail of `buf` that could be the start of an event marker split across reads. */
|
|
200
|
+
function heldBack(buf) {
|
|
201
|
+
// Hold back up to one character short of the longer marker, so a read ending inside either one waits.
|
|
202
|
+
for (let n = Math.min(buf.length, Math.max(EXIT.length, ERROR.length) - 1); n > 0; n--) {
|
|
203
|
+
const tail = buf.slice(-n);
|
|
204
|
+
if (EXIT.startsWith(tail) || ERROR.startsWith(tail))
|
|
205
|
+
return n;
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The exec-stream body as the official SDK reads it: raw output text, then a trailing SSE event
|
|
211
|
+
* `event: exit\ndata: {"exit_code":N,...}` (or `event: error\ndata: {"error":"..."}`).
|
|
212
|
+
* @internal exported for tests
|
|
213
|
+
*/
|
|
214
|
+
export async function* parseExecStream(res) {
|
|
215
|
+
if (!res.body)
|
|
216
|
+
throw new ProviderError(ID, "exec-stream response has no body");
|
|
217
|
+
const reader = res.body.getReader();
|
|
218
|
+
const decoder = new TextDecoder();
|
|
219
|
+
let buf = "";
|
|
220
|
+
let done = false;
|
|
221
|
+
while (!done) {
|
|
222
|
+
const chunk = await reader.read();
|
|
223
|
+
done = chunk.done;
|
|
224
|
+
buf += done ? decoder.decode() : decoder.decode(chunk.value, { stream: true });
|
|
225
|
+
for (const marker of [ERROR, EXIT]) {
|
|
226
|
+
const i = buf.indexOf(marker);
|
|
227
|
+
if (i === -1)
|
|
228
|
+
continue;
|
|
229
|
+
if (i > 0)
|
|
230
|
+
yield { type: "output", data: buf.slice(0, i) };
|
|
231
|
+
buf = buf.slice(i);
|
|
232
|
+
const m = buf.slice(marker.length).match(/^data:\s*(.+?)\r?(\n|$)/);
|
|
233
|
+
// Wait for the whole data line (it can arrive in a later read).
|
|
234
|
+
if (!m || (!m[2] && !done))
|
|
235
|
+
break;
|
|
236
|
+
let payload = {};
|
|
237
|
+
try {
|
|
238
|
+
payload = JSON.parse(m[1]);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
/* keep defaults */
|
|
242
|
+
}
|
|
243
|
+
await reader.cancel().catch(() => { });
|
|
244
|
+
if (marker === ERROR)
|
|
245
|
+
yield { type: "error", message: typeof payload.error === "string" ? payload.error : "stream error" };
|
|
246
|
+
else
|
|
247
|
+
yield { type: "exit", exitCode: Number(payload.exit_code ?? 0) };
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (buf.includes(EXIT) || buf.includes(ERROR))
|
|
251
|
+
continue;
|
|
252
|
+
const keep = done ? 0 : heldBack(buf);
|
|
253
|
+
if (buf.length > keep) {
|
|
254
|
+
yield { type: "output", data: buf.slice(0, buf.length - keep) };
|
|
255
|
+
buf = buf.slice(buf.length - keep);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
package/dist/providers/vercel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_MEMORY_GB, DEFAULT_VCPU } from "../catalog.js";
|
|
2
2
|
import { ProviderError } from "../types.js";
|
|
3
|
-
import { optionalImport, toBytes, wrapCommand } from "../util.js";
|
|
3
|
+
import { DEFAULT_EXEC_TIMEOUT_S, killedByTimeout, optionalImport, timeoutError, toBytes, wrapCommand } from "../util.js";
|
|
4
4
|
/**
|
|
5
5
|
* Vercel Sandbox via `@vercel/sandbox`. Auth: VERCEL_OIDC_TOKEN (from `vercel env pull`), or
|
|
6
6
|
* VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID. Memory is fixed at 2 GB per vCPU.
|
|
@@ -44,9 +44,12 @@ class VercelBox {
|
|
|
44
44
|
}
|
|
45
45
|
async exec(cmd, opts = {}) {
|
|
46
46
|
const t0 = Date.now();
|
|
47
|
-
const
|
|
47
|
+
const seconds = opts.timeoutSeconds ?? DEFAULT_EXEC_TIMEOUT_S;
|
|
48
|
+
// `timeoutMs` makes the sandbox SIGKILL the process at the deadline. The abort signal only
|
|
49
|
+
// guards this client's wait, so it runs a little longer and never ends a healthy command.
|
|
50
|
+
const signal = AbortSignal.timeout((seconds + 15) * 1000);
|
|
48
51
|
try {
|
|
49
|
-
const params = { cmd: "sh", args: ["-c", wrapCommand(cmd)], cwd: opts.cwd, env: opts.env, signal };
|
|
52
|
+
const params = { cmd: "sh", args: ["-c", wrapCommand(cmd)], cwd: opts.cwd, env: opts.env, signal, timeoutMs: seconds * 1000 };
|
|
50
53
|
if (opts.onStdout || opts.onStderr) {
|
|
51
54
|
const running = await this.sbx.runCommand({ ...params, detached: true });
|
|
52
55
|
let stdout = "";
|
|
@@ -62,13 +65,21 @@ class VercelBox {
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
const done = await running.wait({ signal });
|
|
68
|
+
if (killedByTimeout(done.exitCode, Date.now() - t0, seconds))
|
|
69
|
+
throw timeoutError("vercel", seconds);
|
|
65
70
|
return { exitCode: done.exitCode, stdout, stderr, durationMs: Date.now() - t0 };
|
|
66
71
|
}
|
|
67
72
|
const r = await this.sbx.runCommand(params);
|
|
73
|
+
if (killedByTimeout(r.exitCode, Date.now() - t0, seconds))
|
|
74
|
+
throw timeoutError("vercel", seconds);
|
|
68
75
|
const [stdout, stderr] = await Promise.all([r.stdout(), r.stderr()]);
|
|
69
76
|
return { exitCode: r.exitCode, stdout, stderr, durationMs: Date.now() - t0 };
|
|
70
77
|
}
|
|
71
78
|
catch (err) {
|
|
79
|
+
if (err instanceof ProviderError)
|
|
80
|
+
throw err;
|
|
81
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError")
|
|
82
|
+
throw timeoutError("vercel", seconds);
|
|
72
83
|
throw new ProviderError("vercel", err?.message ?? String(err));
|
|
73
84
|
}
|
|
74
85
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ComputerOs, ComputerProvider, ComputerSpec, ProviderComputer } from "../computers.js";
|
|
2
|
+
export interface AwsCredentials {
|
|
3
|
+
accessKeyId: string;
|
|
4
|
+
secretAccessKey: string;
|
|
5
|
+
sessionToken?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WorkspacesAgentOptions {
|
|
8
|
+
region?: string;
|
|
9
|
+
stack?: string;
|
|
10
|
+
fleet?: string;
|
|
11
|
+
os?: string;
|
|
12
|
+
/** Default AppStream user id when a spec has no externalUserId. Omitted: the server uses your IAM identity. */
|
|
13
|
+
userId?: string;
|
|
14
|
+
/** Override the MCP endpoint (tests). Default https://agentaccess-mcp.{region}.api.aws/mcp. */
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
/** Static credentials or a provider; default is the standard AWS chain. */
|
|
17
|
+
credentials?: AwsCredentials | (() => Promise<AwsCredentials>);
|
|
18
|
+
/** Per HTTP exchange. The first tools/list can wait for a desktop to boot or a profile to load. */
|
|
19
|
+
httpTimeoutMs?: number;
|
|
20
|
+
/** How long createComputer waits for the desktop tools to appear. */
|
|
21
|
+
readyTimeoutMs?: number;
|
|
22
|
+
pollMs?: number;
|
|
23
|
+
}
|
|
24
|
+
interface Signer {
|
|
25
|
+
sign(req: unknown): Promise<{
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export declare class WorkspacesAgentProvider implements ComputerProvider {
|
|
30
|
+
readonly id = "aws-workspaces";
|
|
31
|
+
readonly computerOs: ComputerOs[];
|
|
32
|
+
readonly region?: string;
|
|
33
|
+
readonly stack?: string;
|
|
34
|
+
readonly fleet?: string;
|
|
35
|
+
readonly os: "windows" | "linux";
|
|
36
|
+
/** @internal */ readonly opts: WorkspacesAgentOptions;
|
|
37
|
+
private signer?;
|
|
38
|
+
constructor(opts?: WorkspacesAgentOptions);
|
|
39
|
+
configured(): boolean;
|
|
40
|
+
/** @internal */
|
|
41
|
+
get endpoint(): string;
|
|
42
|
+
/** @internal */
|
|
43
|
+
requireConfig(): {
|
|
44
|
+
region: string;
|
|
45
|
+
stack: string;
|
|
46
|
+
fleet: string;
|
|
47
|
+
};
|
|
48
|
+
/** @internal SigV4 signer for service agentaccess-mcp, built on first use. */
|
|
49
|
+
getSigner(): Promise<Signer>;
|
|
50
|
+
/** The user id for a spec: externalUserId as-is when AppStream accepts it, else a stable hash of it. */
|
|
51
|
+
private userIdFor;
|
|
52
|
+
createComputer(spec: ComputerSpec): Promise<ProviderComputer>;
|
|
53
|
+
connectComputer(nativeId: string): Promise<ProviderComputer>;
|
|
54
|
+
/** @internal Uses @aws-sdk/client-appstream CreateStreamingURL. */
|
|
55
|
+
createStreamingUrl(userId: string, validitySeconds: number): Promise<{
|
|
56
|
+
url: string;
|
|
57
|
+
expiresAt: string | null;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
export {};
|