@sandboxrouter/sdk 0.3.5 → 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/dist/util.js CHANGED
@@ -28,7 +28,8 @@ export function wrapCommand(cmd, opts = {}) {
28
28
  }
29
29
  /** The one error every adapter throws when a command outlives ExecOptions.timeoutSeconds. */
30
30
  export function timeoutError(provider, seconds) {
31
- return new ProviderError(provider, `command timed out after ${seconds}s`);
31
+ // 504 for the HTTP API: the command ran out of time, not the server.
32
+ return new ProviderError(provider, `command timed out after ${seconds}s`, 504);
32
33
  }
33
34
  /**
34
35
  * True when an exit code is the one left behind by a timeout kill and the command really ran
@@ -0,0 +1,17 @@
1
+ import { type RouteOptions, type SandboxSpec, type Strategy } from "./types.js";
2
+ /** Every key a SandboxSpec can have. Anything else in a request is a mistake (e.g. "memory" for "memoryGb"). */
3
+ export declare const SPEC_KEYS: readonly ["vcpu", "memoryGb", "gpu", "env", "timeoutMinutes", "image", "metadata"];
4
+ /** Every key RouteOptions can have. */
5
+ export declare const ROUTE_KEYS: readonly ["provider", "strategy", "fallback", "require", "createTimeoutSeconds", "deadlineSeconds"];
6
+ export declare const STRATEGIES: readonly Strategy[];
7
+ /**
8
+ * Throws RouterError("bad_request") for a spec or route a provider couldn't make sense of: sizes and
9
+ * times that aren't positive numbers, an unknown strategy or fallback, maps that aren't string maps.
10
+ * Called by `plan()`, so every create, route and CLI command goes through it.
11
+ */
12
+ export declare function validate(spec?: SandboxSpec, route?: RouteOptions): void;
13
+ /** Split a flat request body into spec and route options, refusing keys that are neither. */
14
+ export declare function splitRequest(body: Record<string, unknown>): {
15
+ spec: SandboxSpec;
16
+ route: RouteOptions;
17
+ };
@@ -0,0 +1,66 @@
1
+ import { CATALOG } from "./catalog.js";
2
+ import { RouterError } from "./types.js";
3
+ /** Every key a SandboxSpec can have. Anything else in a request is a mistake (e.g. "memory" for "memoryGb"). */
4
+ export const SPEC_KEYS = ["vcpu", "memoryGb", "gpu", "env", "timeoutMinutes", "image", "metadata"];
5
+ /** Every key RouteOptions can have. */
6
+ export const ROUTE_KEYS = ["provider", "strategy", "fallback", "require", "createTimeoutSeconds", "deadlineSeconds"];
7
+ export const STRATEGIES = ["default", "balanced", "verified", "cheapest", "fastest"];
8
+ const bad = (msg) => new RouterError(msg, [], "bad_request");
9
+ const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
10
+ /** How a bad value is shown: numbers as-is (NaN, Infinity), anything else as JSON. */
11
+ const show = (v) => (typeof v === "number" ? String(v) : JSON.stringify(v));
12
+ const stringMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && Object.values(v).every((x) => typeof x === "string");
13
+ /**
14
+ * Throws RouterError("bad_request") for a spec or route a provider couldn't make sense of: sizes and
15
+ * times that aren't positive numbers, an unknown strategy or fallback, maps that aren't string maps.
16
+ * Called by `plan()`, so every create, route and CLI command goes through it.
17
+ */
18
+ export function validate(spec = {}, route = {}) {
19
+ for (const k of ["vcpu", "memoryGb", "timeoutMinutes"]) {
20
+ if (spec[k] !== undefined && !positive(spec[k]))
21
+ throw bad(`${k} must be a positive number, got ${show(spec[k])}`);
22
+ }
23
+ if (spec.gpu !== undefined && (typeof spec.gpu !== "string" || !spec.gpu))
24
+ throw bad(`gpu must be a GPU type such as "T4", got ${JSON.stringify(spec.gpu)}`);
25
+ for (const k of ["env", "image", "metadata"]) {
26
+ if (spec[k] !== undefined && !stringMap(spec[k]))
27
+ throw bad(`${k} must be an object of strings`);
28
+ }
29
+ if (route.strategy !== undefined && !STRATEGIES.includes(route.strategy)) {
30
+ throw bad(`unknown strategy ${JSON.stringify(route.strategy)}: use one of ${STRATEGIES.filter((s) => s !== "default").join(", ")}`);
31
+ }
32
+ if (route.fallback !== undefined && route.fallback !== true && route.fallback !== false && route.fallback !== "any") {
33
+ throw bad(`fallback must be true, false or "any", got ${JSON.stringify(route.fallback)}`);
34
+ }
35
+ for (const k of ["createTimeoutSeconds", "deadlineSeconds"]) {
36
+ if (route[k] !== undefined && !positive(route[k]))
37
+ throw bad(`${k} must be a positive number, got ${show(route[k])}`);
38
+ }
39
+ if (route.require !== undefined && (!Array.isArray(route.require) || !route.require.every((r) => typeof r === "string"))) {
40
+ throw bad("require must be a list of capabilities, e.g. [\"pause\"]");
41
+ }
42
+ if (route.provider !== undefined) {
43
+ const ids = [].concat(route.provider);
44
+ const unknown = ids.filter((id) => typeof id !== "string" || !CATALOG.some((c) => c.id === id));
45
+ if (unknown.length || ids.length === 0)
46
+ throw bad(`unknown provider: ${unknown.map((u) => JSON.stringify(u)).join(", ") || "(empty list)"}`);
47
+ }
48
+ }
49
+ /** Split a flat request body into spec and route options, refusing keys that are neither. */
50
+ export function splitRequest(body) {
51
+ const spec = {};
52
+ const route = {};
53
+ const unknown = [];
54
+ for (const [k, v] of Object.entries(body ?? {})) {
55
+ if (ROUTE_KEYS.includes(k))
56
+ route[k] = v;
57
+ else if (SPEC_KEYS.includes(k))
58
+ spec[k] = v;
59
+ else
60
+ unknown.push(k);
61
+ }
62
+ if (unknown.length)
63
+ throw bad(`unknown field${unknown.length > 1 ? "s" : ""} ${unknown.join(", ")}: a sandbox takes ${SPEC_KEYS.join(", ")}; routing takes ${ROUTE_KEYS.join(", ")}`);
64
+ validate(spec, route);
65
+ return { spec: spec, route: route };
66
+ }
@@ -1,2 +1,4 @@
1
1
  import type { ProviderId } from "./types.js";
2
2
  export declare const VERIFIED: Partial<Record<ProviderId, string>>;
3
+ /** Providers whose last saved real-account check failed a step. The default strategy tries them last. */
4
+ export declare const FAILING: Partial<Record<ProviderId, string>>;
package/dist/verified.js CHANGED
@@ -16,3 +16,5 @@ export const VERIFIED = {
16
16
  superserve: "2026-11-11",
17
17
  vercel: "2026-11-11",
18
18
  };
19
+ /** Providers whose last saved real-account check failed a step. The default strategy tries them last. */
20
+ export const FAILING = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandboxrouter/sdk",
3
- "version": "0.3.5",
3
+ "version": "0.5.0",
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",