@sandboxrouter/sdk 0.3.2 → 0.3.4
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 +55 -434
- package/dist/catalog.d.ts +13 -0
- package/dist/catalog.js +14 -0
- package/dist/cli.js +14 -6
- package/dist/client.d.ts +58 -0
- package/dist/client.js +140 -0
- package/dist/cost.d.ts +7 -0
- package/dist/cost.js +13 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/leaderboard.js +3 -2
- package/dist/results.d.ts +12 -0
- package/dist/results.js +49 -3
- package/dist/router.d.ts +8 -0
- package/dist/router.js +18 -6
- package/dist/server.d.ts +29 -2
- package/dist/server.js +32 -6
- package/dist/types.d.ts +10 -6
- package/dist/verified.d.ts +2 -0
- package/dist/verified.js +7 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -31,11 +31,11 @@ import { ProviderError, RouterError } from "./types.js";
|
|
|
31
31
|
*/
|
|
32
32
|
export function createApp(router = new Router(), token = process.env.SANDBOXROUTER_TOKEN,
|
|
33
33
|
/** Browser origins allowed to call this server (e.g. the website's Playground). Off by default. */
|
|
34
|
-
corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
34
|
+
corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.trim()).filter(Boolean), hooks = {}) {
|
|
35
35
|
// With CORS on, any page open in a browser on this machine can call the server (Chrome even
|
|
36
36
|
// lets a public page reach localhost, which the Private-Network header below allows), so it
|
|
37
37
|
// would start sandboxes on these provider keys. A token is required for that.
|
|
38
|
-
if (corsOrigins.length && !token) {
|
|
38
|
+
if (corsOrigins.length && !token && !hooks.authorize) {
|
|
39
39
|
throw new Error("CORS is on but SANDBOXROUTER_TOKEN is not set: browser pages could start sandboxes on your keys. Set a token, or turn CORS off.");
|
|
40
40
|
}
|
|
41
41
|
const expected = token ? Buffer.from(`Bearer ${token}`) : undefined;
|
|
@@ -71,11 +71,23 @@ corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.tri
|
|
|
71
71
|
}
|
|
72
72
|
if (url.pathname === "/health")
|
|
73
73
|
return json(res, 200, { ok: true });
|
|
74
|
-
if (
|
|
74
|
+
if (hooks.extra && (await hooks.extra(req, res, url)))
|
|
75
|
+
return;
|
|
76
|
+
let caller = null;
|
|
77
|
+
if (hooks.authorize) {
|
|
78
|
+
caller = await hooks.authorize(req);
|
|
79
|
+
if (caller === null)
|
|
80
|
+
return json(res, 401, { error: "unauthorized" });
|
|
81
|
+
}
|
|
82
|
+
else if (!authorized(req))
|
|
75
83
|
return json(res, 401, { error: "unauthorized" });
|
|
76
84
|
if (parts[0] !== "v1")
|
|
77
85
|
return json(res, 404, { error: "not found" });
|
|
78
|
-
const box = async (id) =>
|
|
86
|
+
const box = async (id) => {
|
|
87
|
+
if (hooks.owns && !(await hooks.owns(caller, id)))
|
|
88
|
+
throw new HttpError(404, "no such sandbox");
|
|
89
|
+
return live.get(id) ?? router.get(id);
|
|
90
|
+
};
|
|
79
91
|
// GET /v1/providers
|
|
80
92
|
if (req.method === "GET" && parts[1] === "providers" && parts.length === 2) {
|
|
81
93
|
return json(res, 200, {
|
|
@@ -91,14 +103,25 @@ corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.tri
|
|
|
91
103
|
const { spec, route } = splitBody(await readJson(req));
|
|
92
104
|
return json(res, 200, { candidates: router.plan(spec, route) });
|
|
93
105
|
}
|
|
94
|
-
if (parts[1] === "computers")
|
|
106
|
+
if (parts[1] === "computers") {
|
|
107
|
+
if (hooks.computers === false)
|
|
108
|
+
return json(res, 404, { error: "computers aren't available on this server" });
|
|
95
109
|
return computers(req, res, url, parts);
|
|
110
|
+
}
|
|
96
111
|
if (parts[1] !== "sandboxes")
|
|
97
112
|
return json(res, 404, { error: "not found" });
|
|
98
113
|
// POST /v1/sandboxes
|
|
99
114
|
if (req.method === "POST" && parts.length === 2) {
|
|
100
115
|
const { spec, route } = splitBody(await readJson(req));
|
|
116
|
+
await hooks.beforeCreate?.(caller, spec, route);
|
|
101
117
|
const sbx = await router.create(spec, route);
|
|
118
|
+
try {
|
|
119
|
+
await hooks.afterCreate?.(caller, sbx, spec);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
await sbx.kill().catch(() => { });
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
102
125
|
live.set(sbx.id, sbx);
|
|
103
126
|
return json(res, 201, {
|
|
104
127
|
id: sbx.id,
|
|
@@ -132,6 +155,8 @@ corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.tri
|
|
|
132
155
|
}
|
|
133
156
|
// POST /v1/sandboxes/:id/pause
|
|
134
157
|
if (req.method === "POST" && parts[3] === "pause" && parts.length === 4) {
|
|
158
|
+
if (hooks.pause === false)
|
|
159
|
+
return json(res, 400, { error: "pause isn't available on this server" });
|
|
135
160
|
await (await box(id)).pause();
|
|
136
161
|
// Drop the cached handle so the next call reattaches through router.get(), which resumes.
|
|
137
162
|
live.delete(id);
|
|
@@ -156,6 +181,7 @@ corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.tri
|
|
|
156
181
|
if (req.method === "DELETE" && parts.length === 3) {
|
|
157
182
|
await (await box(id)).kill();
|
|
158
183
|
live.delete(id);
|
|
184
|
+
await hooks.afterKill?.(caller, id);
|
|
159
185
|
return json(res, 200, { id, killed: true });
|
|
160
186
|
}
|
|
161
187
|
return json(res, 404, { error: "not found" });
|
|
@@ -227,7 +253,7 @@ corsOrigins = (process.env.SANDBOXROUTER_CORS ?? "").split(",").map((s) => s.tri
|
|
|
227
253
|
});
|
|
228
254
|
});
|
|
229
255
|
}
|
|
230
|
-
class HttpError extends Error {
|
|
256
|
+
export class HttpError extends Error {
|
|
231
257
|
status;
|
|
232
258
|
constructor(status, message) {
|
|
233
259
|
super(message);
|
package/dist/types.d.ts
CHANGED
|
@@ -22,20 +22,24 @@ export interface SandboxSpec {
|
|
|
22
22
|
metadata?: Record<string, string>;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* "
|
|
25
|
+
* "verified" (and "default", the same thing): providers that passed the real-account check first
|
|
26
|
+
* (as of this release; `VERIFIED` in src/verified.ts), then the rest; cheapest first within each,
|
|
27
|
+
* ties alphabetical.
|
|
28
|
+
* "cheapest": lowest list price for the spec, tested or not, ties alphabetical.
|
|
26
29
|
* "fastest": lowest median cold start you measured with `bench`, then cheapest.
|
|
27
30
|
*/
|
|
28
|
-
export type Strategy = "default" | "cheapest" | "fastest";
|
|
31
|
+
export type Strategy = "default" | "verified" | "cheapest" | "fastest";
|
|
29
32
|
export interface RouteOptions {
|
|
30
33
|
/** Pin to one provider, or give an ordered list to try. Omit to let the router choose. */
|
|
31
34
|
provider?: ProviderId | ProviderId[];
|
|
32
|
-
/** How to order candidates when no explicit list is given. Default: cheapest. */
|
|
35
|
+
/** How to order candidates when no explicit list is given. Default: "verified" (tested providers first, then cheapest). */
|
|
33
36
|
strategy?: Strategy;
|
|
34
37
|
/**
|
|
35
38
|
* What to do when a create fails. `true` (default): try the next candidate when the provider
|
|
36
|
-
* looks unavailable (timeout, network error, 429, 5xx, or an error without a status)
|
|
37
|
-
* a setup error (400, 401,
|
|
38
|
-
* a mistake isn't hidden by quietly moving the work to another, maybe
|
|
39
|
+
* looks unavailable (timeout, network error, 429, 5xx, or an error without a status) or can't bill
|
|
40
|
+
* (402: no balance, plan needed), but stop on a setup error (400, 401, 403, 404, 422: a bad key, a
|
|
41
|
+
* bad image or request), so a mistake isn't hidden by quietly moving the work to another, maybe
|
|
42
|
+
* pricier, provider. Every failed attempt is on `sandbox.attempts`.
|
|
39
43
|
* `"any"`: try the next candidate on every error. `false`: only the first candidate.
|
|
40
44
|
*/
|
|
41
45
|
fallback?: boolean | "any";
|
package/dist/verified.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sandboxrouter/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "One API for every sandbox provider: route AI agents' code by price, speed or capability, with automatic fallback. Free and open source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|