@giselles-ai/sandkit 0.1.0 → 0.1.2

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.
Files changed (49) hide show
  1. package/README.md +51 -7
  2. package/dist/adapters/drizzle.d.ts +2 -2
  3. package/dist/adapters/memory.d.ts +2 -2
  4. package/dist/adapters/sqlite-bun.d.ts +2 -2
  5. package/dist/{chunk-FSDVHEEX.js → chunk-2M2AZUOC.js} +2 -2
  6. package/dist/chunk-5MXHFOJH.js +50 -0
  7. package/dist/chunk-5MXHFOJH.js.map +1 -0
  8. package/dist/{chunk-7DLK7LOM.js → chunk-6GHZO3Y5.js} +2 -2
  9. package/dist/{chunk-VISDS5T7.js → chunk-NKTNTBOY.js} +97 -4
  10. package/dist/chunk-NKTNTBOY.js.map +1 -0
  11. package/dist/chunk-T76VM5D2.js +23 -0
  12. package/dist/chunk-T76VM5D2.js.map +1 -0
  13. package/dist/{chunk-REGOUXVI.js → chunk-UEAKE56H.js} +2 -2
  14. package/dist/{chunk-HVYCAAZQ.js → chunk-XN6DGLRP.js} +1 -1
  15. package/dist/chunk-XN6DGLRP.js.map +1 -0
  16. package/dist/chunk-YGUNJGP7.js +23 -0
  17. package/dist/chunk-YGUNJGP7.js.map +1 -0
  18. package/dist/index.d.ts +12 -8
  19. package/dist/index.js +353 -127
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/mock.d.ts +3 -3
  22. package/dist/integrations/mock.js +21 -4
  23. package/dist/integrations/mock.js.map +1 -1
  24. package/dist/integrations/vercel.d.ts +3 -3
  25. package/dist/integrations/vercel.js +59 -17
  26. package/dist/integrations/vercel.js.map +1 -1
  27. package/dist/policies/ai-gateway.d.ts +1 -1
  28. package/dist/policies/ai-gateway.js +2 -2
  29. package/dist/policies/bun.d.ts +9 -0
  30. package/dist/policies/bun.js +10 -0
  31. package/dist/policies/bun.js.map +1 -0
  32. package/dist/policies/codex.d.ts +1 -1
  33. package/dist/policies/codex.js +2 -2
  34. package/dist/policies/gemini.d.ts +1 -1
  35. package/dist/policies/gemini.js +2 -2
  36. package/dist/policies/npm.d.ts +12 -0
  37. package/dist/policies/npm.js +10 -0
  38. package/dist/policies/npm.js.map +1 -0
  39. package/dist/{types-Cy36bS1j.d.ts → types-DNpj280o.d.ts} +2 -2
  40. package/dist/{types-BCgprbo8.d.ts → types-Dpr_BkF9.d.ts} +3 -0
  41. package/dist/{types-BEKQnjeb.d.ts → types-nu3vpBCZ.d.ts} +35 -5
  42. package/package.json +11 -1
  43. package/dist/chunk-HVYCAAZQ.js.map +0 -1
  44. package/dist/chunk-VISDS5T7.js.map +0 -1
  45. package/dist/chunk-XM4HGRXW.js +0 -37
  46. package/dist/chunk-XM4HGRXW.js.map +0 -1
  47. /package/dist/{chunk-FSDVHEEX.js.map → chunk-2M2AZUOC.js.map} +0 -0
  48. /package/dist/{chunk-7DLK7LOM.js.map → chunk-6GHZO3Y5.js.map} +0 -0
  49. /package/dist/{chunk-REGOUXVI.js.map → chunk-UEAKE56H.js.map} +0 -0
package/README.md CHANGED
@@ -11,6 +11,8 @@ It keeps two paths explicit:
11
11
 
12
12
  An active session is an exclusive workspace lease. While a live session is open, `runCommand()` is unavailable until you attach to that session or commit it.
13
13
 
14
+ Durable lock enforcement is currently in-process (`packages/sandkit` only). Concurrent durable commands for the same workspace are excluded while one is in flight in the same process.
15
+
14
16
  Provider-specific behavior still matters, but the public API stays centered on workspaces, policies, and durable state.
15
17
 
16
18
  ## Problem
@@ -95,18 +97,43 @@ const result = await workspace.sandbox.runCommand({
95
97
  console.log(result.stdout.trim());
96
98
  ```
97
99
 
100
+ `runCommand(...)` (without `detached`) resolves to `CommandResult` only after the full unit-of-work is complete: process exit, snapshot/commit, and persist.
101
+
102
+ For detached execution, pass `detached: true` and you get a `Command` object. Observe logs and then await durable completion:
103
+
104
+ ```ts
105
+ const command = await workspace.sandbox.runCommand({
106
+ command: "sh",
107
+ args: ["-lc", "echo 'start'; sleep 1; echo 'done'"],
108
+ detached: true,
109
+ });
110
+ for await (const chunk of command.logs?.() ?? []) {
111
+ console.log(`${chunk.stream}: ${chunk.chunk}`);
112
+ }
113
+
114
+ const commandResult = await command.wait();
115
+ ```
116
+
98
117
  Set `VERCEL_OIDC_TOKEN` for local runs or `VERCEL_ACCESS_TOKEN` in CI before creating a Vercel-backed sandbox.
99
118
 
100
119
  Declare `exposedPorts` on `createWorkspace({ sandbox: ... })` only when you need a live session URL. `defaultTimeout` is the provider-level lease default; override a specific live session with `openSession({ timeoutMs })`.
101
120
 
102
121
  ## Setup bootstrap
103
122
 
104
- Pass setup to `createSandkit({ setup })` to seed a shared durable state used by all workspaces on the same adapter.
105
- Each workspace starts from that shared bootstrap snapshot when no workspace-specific durable state exists.
106
- Sandkit persists one shared bootstrap state per adapter and bootstrap definition (command + args + explicit setup policy), runs setup once per unique bootstrap definition, and reuses the matching state for subsequent workspaces.
107
- If a shared bootstrap state is stale or unusable, Sandkit re-runs setup and persists a replacement.
108
- By default setup runs under the workspace policy; set `setup.policy` when bootstrap needs broader access than steady-state execution.
109
- Because setup becomes shared durable state, `setup.policy` must also be durable: explicit secret-bearing policies are rejected there.
123
+ `setup` is the shared bootstrap definition, not the materialized artifact.
124
+ It is optional.
125
+ When `setup` is provided, it is the shared bootstrap command, args, and required durable `policy`.
126
+ This produces adapter-scoped shared bootstrap state keyed by `adapter.id` + setup definition fingerprint.
127
+ Multiple Sandkit instances using the same adapter and setup definition can reuse the same shared bootstrap state.
128
+
129
+ `sandkit.bootstrap()` is an optional eager materialization step:
130
+
131
+ - it creates shared bootstrap state if missing,
132
+ - it leaves existing shared bootstrap state untouched,
133
+ - it does not run a restore path to prove an existing shared bootstrap state is still usable.
134
+
135
+ Without `bootstrap()`, shared setup is still materialized lazily on first workspace use (first `runCommand(...)` or `openSession(...)` that needs it).
136
+ Stale or unusable shared bootstrap artifacts are detected and rebuilt in those workspace flows, not by `bootstrap()` alone.
110
137
 
111
138
  `setup` durability is adapter-backed. With a persistent adapter such as Bun SQLite or Drizzle, the shared bootstrap survives process restarts. With the default in-memory adapter, it does not.
112
139
 
@@ -123,6 +150,9 @@ const sandkit = createSandkit({
123
150
  },
124
151
  });
125
152
 
153
+ await sandkit.bootstrap();
154
+
155
+ // Optional: omit bootstrap() and let setup materialize on first workspace use.
126
156
  const workspace = await sandkit.createWorkspace({
127
157
  name: "bootstrapped-workspace",
128
158
  });
@@ -135,12 +165,25 @@ If you do not pass `database`, Sandkit defaults to the in-memory adapter. That d
135
165
 
136
166
  ## Policies
137
167
 
168
+ - `npm()` allows the public npm registry host `registry.npmjs.org`
169
+ - `bun()` allows Bun install/distribution hosts `bun.sh` and `bun.com`
138
170
  - `codex()` reads `CODEX_API_KEY`
139
171
  - `gemini()` reads `GEMINI_API_KEY`
140
- - `github()` reads `GITHUB_TOKEN`
172
+ - `github()` reads `GITHUB_TOKEN` and maps it through Vercel Sandbox firewall transforms:
173
+ - `Authorization: Basic <base64(x-access-token:<token>)>` on requests to `github.com`, intended for Git-over-HTTPS operations
174
+ - `Authorization: Bearer <token>` on requests to `api.github.com`
175
+ - no Authorization header for `*.githubusercontent.com`
141
176
  - `aiGateway()` reads `AI_GATEWAY_API_KEY` from host env and allows the hostname (plus wildcard) from `AI_GATEWAY_BASE_URL`.
142
177
  `AI_GATEWAY_BASE_URL` ports are ignored for allow-listing; only host/domain matches are used.
143
178
 
179
+ For JavaScript package bootstrap, prefer explicit service presets over `allowAll()`:
180
+
181
+ ```ts
182
+ import { allowServices, bun, npm } from "@giselles-ai/sandkit";
183
+
184
+ const policy = allowServices([bun(), npm()]);
185
+ ```
186
+
144
187
  Durable default policy belongs to the workspace: use `createWorkspace({ policy: ... })` when you create it, or `workspace.setPolicy(...)` later. Pass `policy` to `runCommand(...)` for one-off overrides.
145
188
 
146
189
  ## Schema Generation
@@ -157,6 +200,7 @@ npx @giselles-ai/sandkit generate
157
200
 
158
201
  ## Examples
159
202
 
203
+ - `examples/workflow-hello-git`
160
204
  - `examples/sandbox-openclaw`
161
205
  - `smoke/drizzle-sample`
162
206
 
@@ -1,6 +1,6 @@
1
1
  import { SQLWrapper } from 'drizzle-orm';
2
- import { a as SandkitAdapter } from '../types-Cy36bS1j.js';
3
- import '../types-BCgprbo8.js';
2
+ import { a as SandkitAdapter } from '../types-DNpj280o.js';
3
+ import '../types-Dpr_BkF9.js';
4
4
 
5
5
  interface DrizzleWorkspaceTableShape {
6
6
  readonly id: SQLWrapper;
@@ -1,5 +1,5 @@
1
- import { a as SandkitAdapter } from '../types-Cy36bS1j.js';
2
- import '../types-BCgprbo8.js';
1
+ import { a as SandkitAdapter } from '../types-DNpj280o.js';
2
+ import '../types-Dpr_BkF9.js';
3
3
 
4
4
  declare const createMemoryAdapter: () => SandkitAdapter;
5
5
 
@@ -1,6 +1,6 @@
1
1
  import { Database } from 'bun:sqlite';
2
- import { a as SandkitAdapter } from '../types-Cy36bS1j.js';
3
- import '../types-BCgprbo8.js';
2
+ import { a as SandkitAdapter } from '../types-DNpj280o.js';
3
+ import '../types-Dpr_BkF9.js';
4
4
 
5
5
  declare function createBunSqliteAdapter(db: Database): SandkitAdapter;
6
6
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  allowService
3
- } from "./chunk-VISDS5T7.js";
3
+ } from "./chunk-NKTNTBOY.js";
4
4
 
5
5
  // src/policies/codex.ts
6
6
  var CODEX_DOMAINS = ["api.openai.com", "*.openai.com", "openrouter.ai", "*.openrouter.ai"];
@@ -42,4 +42,4 @@ export {
42
42
  codex,
43
43
  allowCodex
44
44
  };
45
- //# sourceMappingURL=chunk-FSDVHEEX.js.map
45
+ //# sourceMappingURL=chunk-2M2AZUOC.js.map
@@ -0,0 +1,50 @@
1
+ // src/policies/github.ts
2
+ var GITHUB_DOMAINS = ["github.com", "api.github.com", "*.githubusercontent.com"];
3
+ function resolveGithubCredential(options) {
4
+ if (options === void 0) {
5
+ return { kind: "default" };
6
+ }
7
+ if (typeof options.token !== "string" || options.token.trim().length === 0) {
8
+ throw new Error(
9
+ 'github(...) explicit override requires a non-empty "token". Omit the options object to use GITHUB_TOKEN.'
10
+ );
11
+ }
12
+ return { kind: "value", value: options.token };
13
+ }
14
+ function resolveGithubDefaultApiKey() {
15
+ return process.env.GITHUB_TOKEN;
16
+ }
17
+ function github(options) {
18
+ const credential = resolveGithubCredential(options);
19
+ return {
20
+ id: "github",
21
+ name: "GitHub",
22
+ description: "Allow requests to github.com and api.github.com through Vercel Sandbox firewall header transforms.",
23
+ domains: GITHUB_DOMAINS,
24
+ domainHeaders: {
25
+ "github.com": [
26
+ {
27
+ headerName: "authorization",
28
+ valuePrefix: "Basic ",
29
+ credentialPrefix: "x-access-token:",
30
+ valueEncoding: "base64",
31
+ credential
32
+ }
33
+ ],
34
+ "api.github.com": [
35
+ {
36
+ headerName: "authorization",
37
+ valuePrefix: "Bearer ",
38
+ credential
39
+ }
40
+ ],
41
+ "*.githubusercontent.com": []
42
+ }
43
+ };
44
+ }
45
+
46
+ export {
47
+ resolveGithubDefaultApiKey,
48
+ github
49
+ };
50
+ //# sourceMappingURL=chunk-5MXHFOJH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policies/github.ts"],"sourcesContent":["import type { PolicyServiceDescriptor } from \"./types.ts\";\n\nconst GITHUB_DOMAINS = [\"github.com\", \"api.github.com\", \"*.githubusercontent.com\"] as const;\n\nexport interface GithubOptions {\n readonly token?: string;\n}\n\nfunction resolveGithubCredential(options?: GithubOptions) {\n if (options === undefined) {\n return { kind: \"default\" } as const;\n }\n\n if (typeof options.token !== \"string\" || options.token.trim().length === 0) {\n throw new Error(\n 'github(...) explicit override requires a non-empty \"token\". Omit the options object to use GITHUB_TOKEN.',\n );\n }\n\n return { kind: \"value\", value: options.token } as const;\n}\n\nexport function resolveGithubDefaultApiKey(): string | undefined {\n return process.env.GITHUB_TOKEN;\n}\n\nexport function github(options?: GithubOptions): PolicyServiceDescriptor {\n const credential = resolveGithubCredential(options);\n\n return {\n id: \"github\",\n name: \"GitHub\",\n description:\n \"Allow requests to github.com and api.github.com through Vercel Sandbox firewall header transforms.\",\n domains: GITHUB_DOMAINS,\n domainHeaders: {\n \"github.com\": [\n {\n headerName: \"authorization\",\n valuePrefix: \"Basic \",\n credentialPrefix: \"x-access-token:\",\n valueEncoding: \"base64\",\n credential,\n },\n ],\n \"api.github.com\": [\n {\n headerName: \"authorization\",\n valuePrefix: \"Bearer \",\n credential,\n },\n ],\n \"*.githubusercontent.com\": [],\n },\n };\n}\n"],"mappings":";AAEA,IAAM,iBAAiB,CAAC,cAAc,kBAAkB,yBAAyB;AAMjF,SAAS,wBAAwB,SAAyB;AACxD,MAAI,YAAY,QAAW;AACzB,WAAO,EAAE,MAAM,UAAU;AAAA,EAC3B;AAEA,MAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,MAAM,KAAK,EAAE,WAAW,GAAG;AAC1E,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,SAAS,OAAO,QAAQ,MAAM;AAC/C;AAEO,SAAS,6BAAiD;AAC/D,SAAO,QAAQ,IAAI;AACrB;AAEO,SAAS,OAAO,SAAkD;AACvE,QAAM,aAAa,wBAAwB,OAAO;AAElD,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,IACF,SAAS;AAAA,IACT,eAAe;AAAA,MACb,cAAc;AAAA,QACZ;AAAA,UACE,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,kBAAkB;AAAA,UAClB,eAAe;AAAA,UACf;AAAA,QACF;AAAA,MACF;AAAA,MACA,kBAAkB;AAAA,QAChB;AAAA,UACE,YAAY;AAAA,UACZ,aAAa;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,MACA,2BAA2B,CAAC;AAAA,IAC9B;AAAA,EACF;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  allowService
3
- } from "./chunk-VISDS5T7.js";
3
+ } from "./chunk-NKTNTBOY.js";
4
4
 
5
5
  // src/policies/gemini.ts
6
6
  var GEMINI_DOMAINS = ["ai.google.dev", "*.ai.google.dev", "generativelanguage.googleapis.com"];
@@ -41,4 +41,4 @@ export {
41
41
  gemini,
42
42
  allowGemini
43
43
  };
44
- //# sourceMappingURL=chunk-7DLK7LOM.js.map
44
+ //# sourceMappingURL=chunk-6GHZO3Y5.js.map
@@ -22,12 +22,18 @@ function normalizeCredentialSource(source) {
22
22
  }
23
23
  function normalizeHeaderTransform(header) {
24
24
  const headerName = header.headerName.trim().toLowerCase();
25
+ const valueEncoding = header.valueEncoding ?? "plain";
25
26
  if (!headerName) {
26
27
  throw new Error("Policy header transform must include a header name.");
27
28
  }
29
+ if (valueEncoding !== "plain" && valueEncoding !== "base64") {
30
+ throw new Error(`Policy header transform for "${headerName}" has unsupported valueEncoding.`);
31
+ }
28
32
  return {
29
33
  headerName,
30
34
  valuePrefix: header.valuePrefix,
35
+ credentialPrefix: header.credentialPrefix,
36
+ valueEncoding,
31
37
  credential: normalizeCredentialSource(header.credential)
32
38
  };
33
39
  }
@@ -46,12 +52,26 @@ function normalizeService(service) {
46
52
  if (domains.length === 0) {
47
53
  throw new Error(`Policy service "${id}" must declare at least one domain.`);
48
54
  }
55
+ const normalizedDomainHeaders = service.domainHeaders === void 0 ? void 0 : Object.fromEntries(
56
+ Object.entries(service.domainHeaders).map(([domain, headers]) => [
57
+ domain.trim(),
58
+ headers.map((header) => normalizeHeaderTransform(header))
59
+ ]).filter(([domain]) => Boolean(domain))
60
+ );
61
+ for (const domain of Object.keys(normalizedDomainHeaders ?? {})) {
62
+ if (!domains.includes(domain)) {
63
+ throw new Error(
64
+ `Policy service "${id}" defines domainHeaders for "${domain}" but does not declare that domain.`
65
+ );
66
+ }
67
+ }
49
68
  return {
50
69
  id,
51
70
  name,
52
71
  description: service.description?.trim() || void 0,
53
72
  domains,
54
- headers: service.headers?.map((header) => normalizeHeaderTransform(header))
73
+ headers: service.headers?.map((header) => normalizeHeaderTransform(header)),
74
+ domainHeaders: normalizedDomainHeaders
55
75
  };
56
76
  }
57
77
  function normalizeServices(services) {
@@ -104,8 +124,22 @@ function redactWorkspacePolicy(policy) {
104
124
  headers: service.headers?.map((header) => ({
105
125
  headerName: header.headerName,
106
126
  valuePrefix: header.valuePrefix,
127
+ credentialPrefix: header.credentialPrefix,
128
+ valueEncoding: header.valueEncoding,
107
129
  credential: header.credential.kind === "value" ? { kind: "redacted" } : header.credential
108
- }))
130
+ })),
131
+ domainHeaders: service.domainHeaders === void 0 ? void 0 : Object.fromEntries(
132
+ Object.entries(service.domainHeaders).map(([domain, headers]) => [
133
+ domain,
134
+ headers.map((header) => ({
135
+ headerName: header.headerName,
136
+ valuePrefix: header.valuePrefix,
137
+ credentialPrefix: header.credentialPrefix,
138
+ valueEncoding: header.valueEncoding,
139
+ credential: header.credential.kind === "value" ? { kind: "redacted" } : header.credential
140
+ }))
141
+ ])
142
+ )
109
143
  }))
110
144
  };
111
145
  }
@@ -121,6 +155,15 @@ function assertWorkspacePolicyIsDurable(policy) {
121
155
  );
122
156
  }
123
157
  }
158
+ for (const headers of Object.values(service.domainHeaders ?? {})) {
159
+ for (const header of headers) {
160
+ if (header.credential.kind === "value") {
161
+ throw new Error(
162
+ `Workspace policy for service "${service.id}" contains an explicit secret and cannot be stored durably.`
163
+ );
164
+ }
165
+ }
166
+ }
124
167
  }
125
168
  }
126
169
  function parseWorkspacePolicy(value) {
@@ -158,6 +201,8 @@ function parseWorkspacePolicy(value) {
158
201
  return normalizeHeaderTransform({
159
202
  headerName: header.headerName,
160
203
  valuePrefix: typeof header.valuePrefix === "string" ? header.valuePrefix : void 0,
204
+ credentialPrefix: typeof header.credentialPrefix === "string" ? header.credentialPrefix : void 0,
205
+ valueEncoding: header.valueEncoding === "base64" || header.valueEncoding === "plain" ? header.valueEncoding : void 0,
161
206
  credential: {
162
207
  kind: "default"
163
208
  }
@@ -167,6 +212,8 @@ function parseWorkspacePolicy(value) {
167
212
  return normalizeHeaderTransform({
168
213
  headerName: header.headerName,
169
214
  valuePrefix: typeof header.valuePrefix === "string" ? header.valuePrefix : void 0,
215
+ credentialPrefix: typeof header.credentialPrefix === "string" ? header.credentialPrefix : void 0,
216
+ valueEncoding: header.valueEncoding === "base64" || header.valueEncoding === "plain" ? header.valueEncoding : void 0,
170
217
  credential: { kind: "redacted" }
171
218
  });
172
219
  }
@@ -175,12 +222,58 @@ function parseWorkspacePolicy(value) {
175
222
  );
176
223
  });
177
224
  })();
225
+ const domainHeaders = service.domainHeaders === void 0 ? void 0 : (() => {
226
+ if (!isRecord(service.domainHeaders)) {
227
+ throw new Error(`service at index ${index} has invalid domain headers`);
228
+ }
229
+ return Object.fromEntries(
230
+ Object.entries(service.domainHeaders).map(([domain, rawHeaders]) => {
231
+ if (!isRecordArray(rawHeaders)) {
232
+ throw new Error(
233
+ `service at index ${index} has invalid domain headers for ${domain}`
234
+ );
235
+ }
236
+ return [
237
+ domain,
238
+ rawHeaders.map((header, headerIndex) => {
239
+ if (typeof header.headerName !== "string" || !isRecord(header.credential) || typeof header.credential.kind !== "string") {
240
+ throw new Error(
241
+ `service at index ${index} has invalid domain header transform for ${domain} at ${headerIndex}`
242
+ );
243
+ }
244
+ if (header.credential.kind === "default") {
245
+ return normalizeHeaderTransform({
246
+ headerName: header.headerName,
247
+ valuePrefix: typeof header.valuePrefix === "string" ? header.valuePrefix : void 0,
248
+ credentialPrefix: typeof header.credentialPrefix === "string" ? header.credentialPrefix : void 0,
249
+ valueEncoding: header.valueEncoding === "base64" || header.valueEncoding === "plain" ? header.valueEncoding : void 0,
250
+ credential: { kind: "default" }
251
+ });
252
+ }
253
+ if (header.credential.kind === "redacted") {
254
+ return normalizeHeaderTransform({
255
+ headerName: header.headerName,
256
+ valuePrefix: typeof header.valuePrefix === "string" ? header.valuePrefix : void 0,
257
+ credentialPrefix: typeof header.credentialPrefix === "string" ? header.credentialPrefix : void 0,
258
+ valueEncoding: header.valueEncoding === "base64" || header.valueEncoding === "plain" ? header.valueEncoding : void 0,
259
+ credential: { kind: "redacted" }
260
+ });
261
+ }
262
+ throw new Error(
263
+ `service at index ${index} contains a non-durable credential source in domain headers`
264
+ );
265
+ })
266
+ ];
267
+ })
268
+ );
269
+ })();
178
270
  return normalizeService({
179
271
  id: service.id,
180
272
  name: service.name,
181
273
  description: typeof service.description === "string" ? service.description : void 0,
182
274
  domains: service.domains,
183
- headers
275
+ headers,
276
+ domainHeaders
184
277
  });
185
278
  });
186
279
  return allowServices(services);
@@ -199,4 +292,4 @@ export {
199
292
  assertWorkspacePolicyIsDurable,
200
293
  parseWorkspacePolicy
201
294
  };
202
- //# sourceMappingURL=chunk-VISDS5T7.js.map
295
+ //# sourceMappingURL=chunk-NKTNTBOY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policies/dsl.ts"],"sourcesContent":["import type { JsonValue } from \"../types.ts\";\nimport type {\n PolicyServiceCredentialSource,\n PolicyServiceDescriptor,\n PolicyServiceHeaderTransform,\n WorkspacePolicy,\n} from \"./types.ts\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\nfunction isRecordArray(value: unknown): value is Record<string, unknown>[] {\n return Array.isArray(value) && value.every((entry) => isRecord(entry));\n}\n\nfunction normalizeCredentialSource(\n source: PolicyServiceCredentialSource,\n): PolicyServiceCredentialSource {\n if (source.kind === \"default\") {\n return { kind: \"default\" };\n }\n\n if (source.kind === \"value\") {\n if (!source.value) {\n throw new Error(\"Policy credential value must not be empty.\");\n }\n return { kind: \"value\", value: source.value };\n }\n\n return { kind: \"redacted\" };\n}\n\nfunction normalizeHeaderTransform(\n header: PolicyServiceHeaderTransform,\n): PolicyServiceHeaderTransform {\n const headerName = header.headerName.trim().toLowerCase();\n const valueEncoding = header.valueEncoding ?? \"plain\";\n if (!headerName) {\n throw new Error(\"Policy header transform must include a header name.\");\n }\n if (valueEncoding !== \"plain\" && valueEncoding !== \"base64\") {\n throw new Error(`Policy header transform for \"${headerName}\" has unsupported valueEncoding.`);\n }\n\n return {\n headerName,\n valuePrefix: header.valuePrefix,\n credentialPrefix: header.credentialPrefix,\n valueEncoding,\n credential: normalizeCredentialSource(header.credential),\n };\n}\n\nfunction normalizeService(service: PolicyServiceDescriptor): PolicyServiceDescriptor {\n const id = service.id.trim();\n const name = service.name.trim();\n const domains = [\n ...new Set(service.domains.map((domain) => domain.trim()).filter(Boolean)),\n ].sort();\n\n if (!id) {\n throw new Error(\"Policy service id must not be empty.\");\n }\n\n if (!name) {\n throw new Error(`Policy service \"${id}\" must have a name.`);\n }\n\n if (domains.length === 0) {\n throw new Error(`Policy service \"${id}\" must declare at least one domain.`);\n }\n\n const normalizedDomainHeaders =\n service.domainHeaders === undefined\n ? undefined\n : Object.fromEntries(\n Object.entries(service.domainHeaders)\n .map(([domain, headers]) => [\n domain.trim(),\n headers.map((header) => normalizeHeaderTransform(header)),\n ])\n .filter(([domain]) => Boolean(domain)),\n );\n\n for (const domain of Object.keys(normalizedDomainHeaders ?? {})) {\n if (!domains.includes(domain)) {\n throw new Error(\n `Policy service \"${id}\" defines domainHeaders for \"${domain}\" but does not declare that domain.`,\n );\n }\n }\n\n return {\n id,\n name,\n description: service.description?.trim() || undefined,\n domains,\n headers: service.headers?.map((header) => normalizeHeaderTransform(header)),\n domainHeaders: normalizedDomainHeaders,\n };\n}\n\nfunction normalizeServices(\n services: readonly PolicyServiceDescriptor[],\n): readonly PolicyServiceDescriptor[] {\n if (services.length === 0) {\n throw new Error(\"allowServices(...) requires at least one service descriptor.\");\n }\n\n const deduped = new Map<string, PolicyServiceDescriptor>();\n for (const service of services) {\n const normalized = normalizeService(service);\n deduped.set(normalized.id, normalized);\n }\n\n return [...deduped.values()].sort((left, right) => left.id.localeCompare(right.id));\n}\n\nexport function allowAll(): WorkspacePolicy {\n return { mode: \"allow-all\" };\n}\n\nexport function denyAll(): WorkspacePolicy {\n return { mode: \"deny-all\" };\n}\n\nexport function allowService(service: PolicyServiceDescriptor): WorkspacePolicy {\n return allowServices([service]);\n}\n\nexport function allowServices(services: readonly PolicyServiceDescriptor[]): WorkspacePolicy {\n return {\n mode: \"allow-services\",\n services: normalizeServices(services),\n };\n}\n\nexport function describeWorkspacePolicy(policy: WorkspacePolicy): string {\n switch (policy.mode) {\n case \"allow-all\":\n return \"allow-all\";\n case \"deny-all\":\n return \"deny-all\";\n case \"allow-services\":\n return `allow-services:${policy.services.map((service) => service.id).join(\",\")}`;\n }\n}\n\nexport function serializeWorkspacePolicy(policy: WorkspacePolicy): JsonValue {\n return policy as unknown as JsonValue;\n}\n\nexport function redactWorkspacePolicy(policy: WorkspacePolicy): WorkspacePolicy {\n if (policy.mode !== \"allow-services\") {\n return policy;\n }\n\n return {\n mode: \"allow-services\",\n services: policy.services.map((service) => ({\n ...service,\n headers: service.headers?.map((header) => ({\n headerName: header.headerName,\n valuePrefix: header.valuePrefix,\n credentialPrefix: header.credentialPrefix,\n valueEncoding: header.valueEncoding,\n credential: header.credential.kind === \"value\" ? { kind: \"redacted\" } : header.credential,\n })),\n domainHeaders:\n service.domainHeaders === undefined\n ? undefined\n : Object.fromEntries(\n Object.entries(service.domainHeaders).map(([domain, headers]) => [\n domain,\n headers.map((header) => ({\n headerName: header.headerName,\n valuePrefix: header.valuePrefix,\n credentialPrefix: header.credentialPrefix,\n valueEncoding: header.valueEncoding,\n credential:\n header.credential.kind === \"value\" ? { kind: \"redacted\" } : header.credential,\n })),\n ]),\n ),\n })),\n };\n}\n\nexport function assertWorkspacePolicyIsDurable(policy: WorkspacePolicy): void {\n if (policy.mode !== \"allow-services\") {\n return;\n }\n\n for (const service of policy.services) {\n for (const header of service.headers ?? []) {\n if (header.credential.kind === \"value\") {\n throw new Error(\n `Workspace policy for service \"${service.id}\" contains an explicit secret and cannot be stored durably.`,\n );\n }\n }\n for (const headers of Object.values(service.domainHeaders ?? {})) {\n for (const header of headers) {\n if (header.credential.kind === \"value\") {\n throw new Error(\n `Workspace policy for service \"${service.id}\" contains an explicit secret and cannot be stored durably.`,\n );\n }\n }\n }\n }\n}\n\nexport function parseWorkspacePolicy(value: unknown): WorkspacePolicy {\n if (!isRecord(value) || typeof value.mode !== \"string\") {\n throw new Error(\"expected workspace policy object\");\n }\n\n if (value.mode === \"allow-all\") {\n return allowAll();\n }\n\n if (value.mode === \"deny-all\") {\n return denyAll();\n }\n\n if (value.mode === \"allow-services\") {\n if (!Array.isArray(value.services)) {\n throw new Error(\"allow-services policy must include a services array\");\n }\n\n const services = value.services.map((service, index) => {\n if (!isRecord(service)) {\n throw new Error(`service at index ${index} must be an object`);\n }\n\n if (\n typeof service.id !== \"string\" ||\n typeof service.name !== \"string\" ||\n !isStringArray(service.domains)\n ) {\n throw new Error(`service at index ${index} has an invalid shape`);\n }\n\n const headers =\n service.headers === undefined\n ? undefined\n : (() => {\n if (!isRecordArray(service.headers)) {\n throw new Error(`service at index ${index} has invalid headers`);\n }\n\n return service.headers.map((header, headerIndex) => {\n if (\n typeof header.headerName !== \"string\" ||\n !isRecord(header.credential) ||\n typeof header.credential.kind !== \"string\"\n ) {\n throw new Error(\n `service at index ${index} has invalid header transform at ${headerIndex}`,\n );\n }\n\n if (header.credential.kind === \"default\") {\n return normalizeHeaderTransform({\n headerName: header.headerName,\n valuePrefix:\n typeof header.valuePrefix === \"string\" ? header.valuePrefix : undefined,\n credentialPrefix:\n typeof header.credentialPrefix === \"string\"\n ? header.credentialPrefix\n : undefined,\n valueEncoding:\n header.valueEncoding === \"base64\" || header.valueEncoding === \"plain\"\n ? header.valueEncoding\n : undefined,\n credential: {\n kind: \"default\",\n },\n });\n }\n\n if (header.credential.kind === \"redacted\") {\n return normalizeHeaderTransform({\n headerName: header.headerName,\n valuePrefix:\n typeof header.valuePrefix === \"string\" ? header.valuePrefix : undefined,\n credentialPrefix:\n typeof header.credentialPrefix === \"string\"\n ? header.credentialPrefix\n : undefined,\n valueEncoding:\n header.valueEncoding === \"base64\" || header.valueEncoding === \"plain\"\n ? header.valueEncoding\n : undefined,\n credential: { kind: \"redacted\" },\n });\n }\n\n throw new Error(\n `service at index ${index} contains a non-durable credential source`,\n );\n });\n })();\n\n const domainHeaders =\n service.domainHeaders === undefined\n ? undefined\n : (() => {\n if (!isRecord(service.domainHeaders)) {\n throw new Error(`service at index ${index} has invalid domain headers`);\n }\n\n return Object.fromEntries(\n Object.entries(service.domainHeaders).map(([domain, rawHeaders]) => {\n if (!isRecordArray(rawHeaders)) {\n throw new Error(\n `service at index ${index} has invalid domain headers for ${domain}`,\n );\n }\n\n return [\n domain,\n rawHeaders.map((header, headerIndex) => {\n if (\n typeof header.headerName !== \"string\" ||\n !isRecord(header.credential) ||\n typeof header.credential.kind !== \"string\"\n ) {\n throw new Error(\n `service at index ${index} has invalid domain header transform for ${domain} at ${headerIndex}`,\n );\n }\n\n if (header.credential.kind === \"default\") {\n return normalizeHeaderTransform({\n headerName: header.headerName,\n valuePrefix:\n typeof header.valuePrefix === \"string\" ? header.valuePrefix : undefined,\n credentialPrefix:\n typeof header.credentialPrefix === \"string\"\n ? header.credentialPrefix\n : undefined,\n valueEncoding:\n header.valueEncoding === \"base64\" || header.valueEncoding === \"plain\"\n ? header.valueEncoding\n : undefined,\n credential: { kind: \"default\" },\n });\n }\n\n if (header.credential.kind === \"redacted\") {\n return normalizeHeaderTransform({\n headerName: header.headerName,\n valuePrefix:\n typeof header.valuePrefix === \"string\" ? header.valuePrefix : undefined,\n credentialPrefix:\n typeof header.credentialPrefix === \"string\"\n ? header.credentialPrefix\n : undefined,\n valueEncoding:\n header.valueEncoding === \"base64\" || header.valueEncoding === \"plain\"\n ? header.valueEncoding\n : undefined,\n credential: { kind: \"redacted\" },\n });\n }\n\n throw new Error(\n `service at index ${index} contains a non-durable credential source in domain headers`,\n );\n }),\n ];\n }),\n );\n })();\n\n return normalizeService({\n id: service.id,\n name: service.name,\n description: typeof service.description === \"string\" ? service.description : undefined,\n domains: service.domains,\n headers,\n domainHeaders,\n });\n });\n\n return allowServices(services);\n }\n\n throw new Error(`unsupported workspace policy mode \"${value.mode}\"`);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,23 @@
1
+ import {
2
+ allowService
3
+ } from "./chunk-NKTNTBOY.js";
4
+
5
+ // src/policies/npm.ts
6
+ var NPM_DOMAINS = ["registry.npmjs.org"];
7
+ function npm() {
8
+ return {
9
+ id: "npm",
10
+ name: "npm",
11
+ description: "Allow outbound access to the public npm package registry.",
12
+ domains: NPM_DOMAINS
13
+ };
14
+ }
15
+ function allowNpm() {
16
+ return allowService(npm());
17
+ }
18
+
19
+ export {
20
+ npm,
21
+ allowNpm
22
+ };
23
+ //# sourceMappingURL=chunk-T76VM5D2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policies/npm.ts"],"sourcesContent":["import { allowService } from \"./dsl.ts\";\nimport type { PolicyServiceDescriptor, WorkspacePolicy } from \"./types.ts\";\n\nconst NPM_DOMAINS = [\"registry.npmjs.org\"] as const;\n\n/**\n * Allow outbound access to the public npm package registry.\n *\n * Keep this preset scoped to the actual package source host instead of\n * broadening to unrelated npm web properties.\n */\nexport function npm(): PolicyServiceDescriptor {\n return {\n id: \"npm\",\n name: \"npm\",\n description: \"Allow outbound access to the public npm package registry.\",\n domains: NPM_DOMAINS,\n };\n}\n\nexport function allowNpm(): WorkspacePolicy {\n return allowService(npm());\n}\n"],"mappings":";;;;;AAGA,IAAM,cAAc,CAAC,oBAAoB;AAQlC,SAAS,MAA+B;AAC7C,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AACF;AAEO,SAAS,WAA4B;AAC1C,SAAO,aAAa,IAAI,CAAC;AAC3B;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  allowService
3
- } from "./chunk-VISDS5T7.js";
3
+ } from "./chunk-NKTNTBOY.js";
4
4
 
5
5
  // src/policies/ai-gateway.ts
6
6
  var DEFAULT_AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1";
@@ -55,4 +55,4 @@ export {
55
55
  aiGateway,
56
56
  allowAiGateway
57
57
  };
58
- //# sourceMappingURL=chunk-REGOUXVI.js.map
58
+ //# sourceMappingURL=chunk-UEAKE56H.js.map
@@ -22,4 +22,4 @@ export {
22
22
  createSandboxProvider,
23
23
  getSandboxDriverFactory
24
24
  };
25
- //# sourceMappingURL=chunk-HVYCAAZQ.js.map
25
+ //# sourceMappingURL=chunk-XN6DGLRP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { SandkitAdapter, WorkspaceRecord } from \"./adapters/types.ts\";\nimport type {\n PolicySnapshotAdapter,\n PolicySnapshotRecord,\n PolicySnapshotCreateInput,\n RunAdapter,\n RunCreateInput,\n RunFinishInput,\n RunRecord,\n RunStatus,\n SetupStateAdapter,\n SetupStateRecord,\n SetupStatePutInput,\n WorkspaceMetadata,\n SharedSetup,\n SharedSetupState,\n WorkspaceStatus,\n} from \"./adapters/types.ts\";\nimport type { WorkspacePolicy } from \"./policies/types.ts\";\n\nexport type JsonPrimitive = boolean | number | string | null;\n\nexport type JsonValue =\n | JsonPrimitive\n | JsonValue[]\n | {\n [key: string]: JsonValue;\n };\n\nexport type {\n SandkitAdapter,\n WorkspaceRecord,\n RunAdapter,\n RunCreateInput,\n RunFinishInput,\n RunRecord,\n PolicySnapshotAdapter,\n PolicySnapshotRecord,\n PolicySnapshotCreateInput,\n RunStatus,\n SetupStateAdapter,\n SetupStateRecord,\n SetupStatePutInput,\n SharedSetup,\n SharedSetupState,\n};\nexport type { WorkspacePolicy };\n\nexport interface WorkspaceCreateOptions {\n id?: string;\n name?: string;\n metadata?: WorkspaceMetadata;\n policy?: WorkspacePolicy;\n sandbox?: {\n /**\n * Durable workspace default for provider port publication. This affects\n * create/restore behavior for live session/public URL flows, not the\n * durable runCommand() unit-of-work model itself.\n */\n readonly exposedPorts?: readonly number[];\n };\n status?: WorkspaceStatus;\n sandboxId?: string;\n lastResumedAt?: string;\n}\n\nexport interface CommandResult {\n exitCode: number;\n stderr: string;\n stdout: string;\n}\n\nexport interface Command {\n /**\n * Wait for durable completion of the command unit-of-work: process exit,\n * snapshot/commit, and persist outcome.\n */\n readonly wait: () => Promise<CommandResult>;\n /**\n * Ephemeral log stream for detached execution. The stream is live-only and does\n * not participate in durable state replay.\n */\n readonly logs?: () => AsyncIterable<WorkspaceSessionLog>;\n}\n\nexport interface SandboxSessionLease {\n readonly sandboxId: string;\n readonly observedAt: string;\n readonly expiresAt: string;\n}\n\nexport interface WorkspaceSandboxLease {\n readonly sandboxId: string;\n readonly observedAt: string;\n readonly expiresAt: string;\n readonly remainingMs: number;\n}\n\nexport interface WorkspaceSessionProcess {\n readonly processId: string;\n wait(): Promise<CommandResult>;\n /**\n * Accesses the same normalized log stream used by onStdout/onStderr callbacks.\n * Sandkit keeps a single internal stream, so callbacks and logs() observe\n * the same sequence and buffered historical chunks are replayed to new readers.\n */\n logs?: () => AsyncIterable<WorkspaceSessionLog>;\n}\n\nexport interface WorkspaceSessionLog {\n readonly stream: \"stdout\" | \"stderr\";\n readonly chunk: string;\n}\n\nexport interface WorkspaceSessionProcessStartInput {\n readonly command: string;\n readonly args: readonly string[];\n /**\n * Optional per-call policy override for this session process.\n */\n readonly policy?: WorkspacePolicy;\n /**\n * Callbacks consume chunks from Sandkit's normalized process log stream.\n */\n readonly onStdout?: ((chunk: string) => void) | undefined;\n /**\n * Callbacks consume chunks from Sandkit's normalized process log stream.\n */\n readonly onStderr?: ((chunk: string) => void) | undefined;\n}\n\nexport interface WorkspaceSessionRunCommandOptions {\n readonly command: string;\n readonly args?: readonly string[];\n readonly policy?: WorkspacePolicy;\n}\n\ninterface WorkspaceRunCommandBaseOptions {\n readonly command: string;\n readonly args?: readonly string[];\n readonly policy?: WorkspacePolicy;\n /**\n * Ephemeral timeout override for this durable runCommand() invocation.\n * This affects sandbox create/restore lease timing for the run only and\n * must not mutate durable workspace defaults.\n */\n readonly timeoutMs?: number;\n}\n\nexport interface WorkspaceRunCommandOptions extends WorkspaceRunCommandBaseOptions {}\n\nexport interface WorkspaceRunCommandDetachedOptions extends WorkspaceRunCommandBaseOptions {\n readonly detached: true;\n}\n\nexport type SandboxRunCommandOptions =\n | WorkspaceRunCommandOptions\n | WorkspaceRunCommandDetachedOptions;\n\nexport interface PersistedSandboxState {\n readonly kind: string;\n readonly sessionId: string;\n readonly state?: JsonValue;\n}\n\nexport interface SandboxDriver {\n readonly id: string;\n readonly provider: string;\n applyPolicy(policy: WorkspacePolicy): Promise<void>;\n /**\n * Returns lease timing observed from the current sandbox instance.\n * Some providers expose this as an interpreted timeout value.\n * Sandkit persists lease updates only from explicit session open/extend paths,\n * so callers should not treat mere reads as lease refreshes.\n */\n getSessionLease(): Promise<SandboxSessionLease>;\n runCommand(\n command: string,\n args: string[],\n options?: { readonly detached?: boolean },\n ): Promise<Command>;\n startProcess?(input: WorkspaceSessionProcessStartInput): Promise<WorkspaceSessionProcess>;\n /** Persists and restores durability state through commit() and attach/restore APIs. */\n snapshot(): Promise<PersistedSandboxState>;\n url?(port: number): Promise<string>;\n extendTimeout?(durationMs: number): Promise<void>;\n}\n\nexport interface SandboxCreateOptions {\n readonly policy: WorkspacePolicy;\n readonly exposedPorts?: readonly number[];\n readonly timeoutMs?: number;\n}\n\nexport interface SandboxDriverFactory {\n createSandbox(workspace: WorkspaceRecord, options: SandboxCreateOptions): Promise<SandboxDriver>;\n resumeSandbox(\n workspace: WorkspaceRecord,\n snapshot: PersistedSandboxState,\n options: SandboxCreateOptions,\n ): Promise<SandboxDriver>;\n isSessionUnavailableError?(error: unknown): boolean;\n}\n\nexport interface VercelSandboxOptions {\n runtime?: string;\n /**\n * Provider default sandbox lease timeout in milliseconds. Sandkit uses this\n * when creating or restoring a sandbox unless openSession({ timeoutMs })\n * supplies a live-session override.\n */\n defaultTimeout?: number;\n /**\n * Legacy alias retained for compatibility with existing call sites.\n * Prefer defaultTimeout for new code.\n */\n timeout?: number;\n}\n\nconst sandboxProviderContract = Symbol(\"sandkit.sandbox.provider\");\n\ntype SandboxProviderRecord = {\n readonly provider: string;\n readonly driverFactory: SandboxDriverFactory;\n};\n\nexport interface SandkitSandboxProvider {\n readonly [sandboxProviderContract]: SandboxProviderRecord;\n}\n\nexport function createSandboxProvider(\n provider: string,\n driverFactory: SandboxDriverFactory,\n): SandkitSandboxProvider {\n return {\n [sandboxProviderContract]: {\n provider,\n driverFactory,\n },\n };\n}\n\nexport function getSandboxDriverFactory(\n sandboxProvider: SandkitSandboxProvider,\n): SandboxDriverFactory {\n const driverFactory = sandboxProvider[sandboxProviderContract]?.driverFactory;\n if (!driverFactory) {\n throw new Error(\n \"SandkitOptions.sandbox is invalid. Pass a provider created by Sandkit integrations such as vercelSandbox(...).\",\n );\n }\n\n return driverFactory;\n}\n\nexport interface SandkitOptions {\n readonly database?: SandkitAdapter | undefined;\n readonly setup?: SharedSetup | undefined;\n readonly network?: readonly unknown[] | undefined;\n readonly sandbox: SandkitSandboxProvider;\n}\n"],"mappings":";AA2NA,IAAM,0BAA0B,uBAAO,0BAA0B;AAW1D,SAAS,sBACd,UACA,eACwB;AACxB,SAAO;AAAA,IACL,CAAC,uBAAuB,GAAG;AAAA,MACzB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,wBACd,iBACsB;AACtB,QAAM,gBAAgB,gBAAgB,uBAAuB,GAAG;AAChE,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,23 @@
1
+ import {
2
+ allowService
3
+ } from "./chunk-NKTNTBOY.js";
4
+
5
+ // src/policies/bun.ts
6
+ var BUN_DOMAINS = ["bun.sh", "bun.com"];
7
+ function bun() {
8
+ return {
9
+ id: "bun",
10
+ name: "Bun",
11
+ description: "Allow outbound access to Bun install and distribution endpoints.",
12
+ domains: BUN_DOMAINS
13
+ };
14
+ }
15
+ function allowBun() {
16
+ return allowService(bun());
17
+ }
18
+
19
+ export {
20
+ bun,
21
+ allowBun
22
+ };
23
+ //# sourceMappingURL=chunk-YGUNJGP7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policies/bun.ts"],"sourcesContent":["import { allowService } from \"./dsl.ts\";\nimport type { PolicyServiceDescriptor, WorkspacePolicy } from \"./types.ts\";\n\nconst BUN_DOMAINS = [\"bun.sh\", \"bun.com\"] as const;\n\n/**\n * Allow outbound access to Bun distribution and install endpoints.\n */\nexport function bun(): PolicyServiceDescriptor {\n return {\n id: \"bun\",\n name: \"Bun\",\n description: \"Allow outbound access to Bun install and distribution endpoints.\",\n domains: BUN_DOMAINS,\n };\n}\n\nexport function allowBun(): WorkspacePolicy {\n return allowService(bun());\n}\n"],"mappings":";;;;;AAGA,IAAM,cAAc,CAAC,UAAU,SAAS;AAKjC,SAAS,MAA+B;AAC7C,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,EACX;AACF;AAEO,SAAS,WAA4B;AAC1C,SAAO,aAAa,IAAI,CAAC;AAC3B;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { S as SandkitOptions, a as SandboxDriverFactory, C as CommandResult, b as SandboxRunCommandOptions, W as WorkspaceSessionProcess, c as WorkspaceSessionProcessStartInput, d as WorkspaceSandboxLease, e as WorkspaceCreateOptions } from './types-BEKQnjeb.js';
2
- import { W as WorkspacePolicy, P as PolicyServiceDescriptor } from './types-BCgprbo8.js';
3
- export { a as PolicyServiceCredentialSource } from './types-BCgprbo8.js';
1
+ import { S as SandkitOptions, a as SandboxDriverFactory, C as CommandResult, W as WorkspaceRunCommandOptions, b as WorkspaceRunCommandDetachedOptions, c as Command, d as WorkspaceSessionRunCommandOptions, e as WorkspaceSessionProcess, f as WorkspaceSessionProcessStartInput, g as WorkspaceSandboxLease, h as WorkspaceCreateOptions } from './types-nu3vpBCZ.js';
2
+ import { W as WorkspacePolicy, P as PolicyServiceDescriptor } from './types-Dpr_BkF9.js';
3
+ export { a as PolicyServiceCredentialSource } from './types-Dpr_BkF9.js';
4
4
  export { aiGateway } from './policies/ai-gateway.js';
5
+ export { allowBun, bun } from './policies/bun.js';
5
6
  export { codex } from './policies/codex.js';
6
7
  export { gemini } from './policies/gemini.js';
7
- export { S as SharedSetup } from './types-Cy36bS1j.js';
8
+ export { allowNpm, npm } from './policies/npm.js';
9
+ export { S as SharedSetup } from './types-DNpj280o.js';
8
10
 
9
11
  interface SandkitContext {
10
12
  readonly adapter: NonNullable<SandkitOptions["database"]>;
@@ -14,7 +16,8 @@ interface SandkitContext {
14
16
 
15
17
  interface WorkspaceSandboxHandle {
16
18
  runCommand(command: string, args: string[]): Promise<CommandResult>;
17
- runCommand(input: SandboxRunCommandOptions): Promise<CommandResult>;
19
+ runCommand(input: WorkspaceRunCommandOptions): Promise<CommandResult>;
20
+ runCommand(input: WorkspaceRunCommandDetachedOptions): Promise<Command>;
18
21
  /**
19
22
  * Opens a live sandbox lease. timeoutMs overrides the provider's default
20
23
  * lease timeout for this session start; it does not change runCommand()
@@ -28,7 +31,7 @@ interface WorkspaceSandboxHandle {
28
31
  }
29
32
  interface WorkspaceSessionHandle {
30
33
  exec(command: string, args: string[]): Promise<CommandResult>;
31
- exec(input: SandboxRunCommandOptions): Promise<CommandResult>;
34
+ exec(input: WorkspaceSessionRunCommandOptions): Promise<CommandResult>;
32
35
  commit(): Promise<void>;
33
36
  /**
34
37
  * Sets a non-durable, session-scoped policy override for subsequent session
@@ -60,6 +63,7 @@ declare class Sandkit {
60
63
  #private;
61
64
  constructor(options: SandkitOptions);
62
65
  get context(): SandkitContext;
66
+ bootstrap(): Promise<void>;
63
67
  createWorkspace(input?: WorkspaceCreateOptions): Promise<PublicWorkspaceHandle>;
64
68
  getWorkspace(id: string): Promise<PublicWorkspaceHandle>;
65
69
  }
@@ -71,8 +75,8 @@ declare function allowService(service: PolicyServiceDescriptor): WorkspacePolicy
71
75
  declare function allowServices(services: readonly PolicyServiceDescriptor[]): WorkspacePolicy;
72
76
 
73
77
  interface GithubOptions {
74
- readonly apiKey?: string;
78
+ readonly token?: string;
75
79
  }
76
80
  declare function github(options?: GithubOptions): PolicyServiceDescriptor;
77
81
 
78
- export { PolicyServiceDescriptor, type PublicWorkspaceHandle, Sandkit, SandkitOptions, WorkspaceCreateOptions, type WorkspaceDescriptor, WorkspacePolicy, type WorkspaceSandboxHandle, type WorkspaceSessionHandle, type WorkspaceStatus, allowAll, allowService, allowServices, createSandkit, denyAll, github };
82
+ export { Command, CommandResult, PolicyServiceDescriptor, type PublicWorkspaceHandle, Sandkit, SandkitOptions, WorkspaceCreateOptions, type WorkspaceDescriptor, WorkspacePolicy, WorkspaceRunCommandDetachedOptions, WorkspaceRunCommandOptions, type WorkspaceSandboxHandle, type WorkspaceSessionHandle, WorkspaceSessionRunCommandOptions, type WorkspaceStatus, allowAll, allowService, allowServices, createSandkit, denyAll, github };