veryfront 0.1.14 → 0.1.16

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 (31) hide show
  1. package/esm/cli/commands/serve/command.d.ts.map +1 -1
  2. package/esm/cli/commands/serve/command.js +8 -0
  3. package/esm/deno.d.ts +3 -0
  4. package/esm/deno.js +4 -1
  5. package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
  6. package/esm/src/platform/adapters/runtime/deno/adapter.js +19 -2
  7. package/esm/src/platform/compat/http/deno-server.d.ts.map +1 -1
  8. package/esm/src/platform/compat/http/deno-server.js +25 -1
  9. package/esm/src/sandbox/index.d.ts +31 -0
  10. package/esm/src/sandbox/index.d.ts.map +1 -0
  11. package/esm/src/sandbox/index.js +30 -0
  12. package/esm/src/sandbox/sandbox.d.ts +48 -0
  13. package/esm/src/sandbox/sandbox.d.ts.map +1 -0
  14. package/esm/src/sandbox/sandbox.js +178 -0
  15. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.d.ts.map +1 -1
  16. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.js +8 -2
  17. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts +1 -0
  18. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts.map +1 -1
  19. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.js +1 -0
  20. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.d.ts.map +1 -1
  21. package/esm/src/transforms/pipeline/stages/ssr-vf-modules/transform.js +15 -1
  22. package/package.json +4 -1
  23. package/src/cli/commands/serve/command.ts +10 -0
  24. package/src/deno.js +4 -1
  25. package/src/src/platform/adapters/runtime/deno/adapter.ts +20 -2
  26. package/src/src/platform/compat/http/deno-server.ts +31 -1
  27. package/src/src/sandbox/index.ts +32 -0
  28. package/src/src/sandbox/sandbox.ts +236 -0
  29. package/src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts +9 -2
  30. package/src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +1 -0
  31. package/src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts +17 -0
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/command.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,YAAY,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAsFD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAcvE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/serve/command.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,YAAY,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;CAChB;AAgGD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAcvE"}
@@ -21,7 +21,15 @@ async function runProxy(options) {
21
21
  const { setEnv } = await import("../../../src/platform/index.js");
22
22
  setEnv("PORT", String(options.port));
23
23
  setEnv("HOST", options.bindAddress);
24
+ registerTerminationSignals((signal) => {
25
+ cliLogger.info(`Received ${signal}, shutting down proxy server...`);
26
+ exitProcess(0);
27
+ });
28
+ // DenoHttpServer.serve() blocks until the server stops,
29
+ // so this import keeps the process alive.
24
30
  await import("../../../src/proxy/main.js");
31
+ // Keep the process alive (Deno.serve returns immediately in compiled binaries)
32
+ await new Promise(() => { });
25
33
  }
26
34
  async function runProductionServer(options) {
27
35
  showLogo();
package/esm/deno.d.ts CHANGED
@@ -27,6 +27,7 @@ declare namespace _default {
27
27
  "./provider": string;
28
28
  "./fs": string;
29
29
  "./integrations": string;
30
+ "./sandbox": string;
30
31
  "./cli": string;
31
32
  };
32
33
  let imports: {
@@ -45,6 +46,7 @@ declare namespace _default {
45
46
  "veryfront/react/fonts": string;
46
47
  "veryfront/react/components/ai": string;
47
48
  "veryfront/components/ai": string;
49
+ "veryfront/sandbox": string;
48
50
  "veryfront/agent/react": string;
49
51
  "veryfront/agent/testing": string;
50
52
  "veryfront/agent/middleware": string;
@@ -127,6 +129,7 @@ declare namespace _default {
127
129
  "#veryfront/testing/bdd.ts": string;
128
130
  "#veryfront/testing/deno-compat": string;
129
131
  "#veryfront/testing/utils": string;
132
+ "#veryfront/sandbox": string;
130
133
  "#veryfront/tool": string;
131
134
  "#veryfront/tool/schema": string;
132
135
  "#veryfront/transforms": string;
package/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "nodeModulesDir": "auto",
5
5
  "exclude": [
6
6
  "npm/",
@@ -37,6 +37,7 @@ export default {
37
37
  "./provider": "./src/provider/index.ts",
38
38
  "./fs": "./src/fs/index.ts",
39
39
  "./integrations": "./src/integrations/index.ts",
40
+ "./sandbox": "./src/sandbox/index.ts",
40
41
  "./cli": "./cli/main.ts"
41
42
  },
42
43
  "imports": {
@@ -55,6 +56,7 @@ export default {
55
56
  "veryfront/react/fonts": "./src/react/fonts/index.ts",
56
57
  "veryfront/react/components/ai": "./src/react/components/ai/index.ts",
57
58
  "veryfront/components/ai": "./src/react/components/ai/index.ts",
59
+ "veryfront/sandbox": "./src/sandbox/index.ts",
58
60
  "veryfront/agent/react": "./src/agent/react/index.ts",
59
61
  "veryfront/agent/testing": "./src/agent/testing/index.ts",
60
62
  "veryfront/agent/middleware": "./src/agent/middleware/index.ts",
@@ -137,6 +139,7 @@ export default {
137
139
  "#veryfront/testing/bdd.ts": "./src/testing/bdd.ts",
138
140
  "#veryfront/testing/deno-compat": "./src/testing/deno-compat.ts",
139
141
  "#veryfront/testing/utils": "./src/testing/utils.ts",
142
+ "#veryfront/sandbox": "./src/sandbox/index.ts",
140
143
  "#veryfront/tool": "./src/tool/index.ts",
141
144
  "#veryfront/tool/schema": "./src/tool/schema/index.ts",
142
145
  "#veryfront/transforms": "./src/transforms/index.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/runtime/deno/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,8BAA8B,CAAC;AAGxD,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAElB,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAiHvB,cAAM,qBAAsB,YAAW,iBAAiB;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKvC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAKhD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC;IAY/C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAYrC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKlD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW;CAsErE;AAED,cAAM,sBAAuB,YAAW,kBAAkB;IACxD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOrC,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAInC;AAED,cAAM,iBAAkB,YAAW,aAAa;IAC9C,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,gBAAgB;CAO7D;AAED,cAAM,gBAAiB,YAAW,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE;IAejE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAanC;AAwBD,qBAAa,WAAY,YAAW,cAAc;IAChD,QAAQ,CAAC,EAAE,EAAG,MAAM,CAAU;IAC9B,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,EAAE,wBAA+B;IAC1C,QAAQ,CAAC,GAAG,yBAAgC;IAC5C,QAAQ,CAAC,MAAM,oBAA2B;IAC1C,QAAQ,CAAC,KAAK,mBAA0B;IAExC,QAAQ,CAAC,YAAY;;;;;;;;;;MAUnB;IAEF,OAAO,CAAC,YAAY,CAA2B;IAE/C,KAAK,CACH,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,EACnF,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC;IAmDZ,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAGhC;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/runtime/deno/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,8BAA8B,CAAC;AAGxD,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAElB,QAAQ,EACR,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,MAAM,EACN,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAiHvB,cAAM,qBAAsB,YAAW,iBAAiB;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKvC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAKhD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC;IAY/C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAYrC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKlD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW;CAsErE;AAED,cAAM,sBAAuB,YAAW,kBAAkB;IACxD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOrC,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAInC;AAED,cAAM,iBAAkB,YAAW,aAAa;IAC9C,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,gBAAgB;CAO7D;AAED,cAAM,gBAAiB,YAAW,YAAY;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE;IAejE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAanC;AAwBD,qBAAa,WAAY,YAAW,cAAc;IAChD,QAAQ,CAAC,EAAE,EAAG,MAAM,CAAU;IAC9B,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,EAAE,wBAA+B;IAC1C,QAAQ,CAAC,GAAG,yBAAgC;IAC5C,QAAQ,CAAC,MAAM,oBAA2B;IAC1C,QAAQ,CAAC,KAAK,mBAA0B;IAExC,QAAQ,CAAC,YAAY;;;;;;;;;;MAUnB;IAEF,OAAO,CAAC,YAAY,CAA2B;IAE/C,KAAK,CACH,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,EACnF,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,MAAM,CAAC;IAqEZ,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAGhC;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC"}
@@ -312,17 +312,34 @@ export class DenoAdapter {
312
312
  // dnt rewrites both `Deno.*` and `globalThis.*` to use @deno/shim-deno which lacks .serve.
313
313
  // `self` is not shimmed by dnt and equals `globalThis` in Deno.
314
314
  const nativeDeno = self["Deno"];
315
+ // Access native Response via `self` to bypass dnt shim transform.
316
+ // In npm packages, dnt replaces Response with undici's polyfill,
317
+ // but Deno.serve requires native Response instances.
318
+ const NativeResponse = self
319
+ .Response;
315
320
  const server = nativeDeno.serve({
316
321
  port,
317
322
  hostname,
318
323
  signal,
319
324
  handler: async (request) => {
320
325
  try {
321
- return await wrappedHandler(request);
326
+ const response = await wrappedHandler(request);
327
+ // If already native (compiled binary), return as-is
328
+ if (response instanceof NativeResponse)
329
+ return response;
330
+ // Re-wrap polyfilled Response as native Response.
331
+ // dnt replaces Response with undici's polyfill which fails
332
+ // Deno.serve's native instanceof check.
333
+ const r = response;
334
+ return new NativeResponse(r.body, {
335
+ status: r.status,
336
+ statusText: r.statusText,
337
+ headers: r.headers,
338
+ });
322
339
  }
323
340
  catch (error) {
324
341
  serverLogger.error("Request handler error:", error);
325
- return new dntShim.Response("Internal Server Error", { status: 500 });
342
+ return new NativeResponse("Internal Server Error", { status: 500 });
326
343
  }
327
344
  },
328
345
  onListen: (params) => {
@@ -1 +1 @@
1
- {"version":3,"file":"deno-server.d.ts","sourceRoot":"","sources":["../../../../../src/src/platform/compat/http/deno-server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGpE,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAkB;IAEpC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAIvB"}
1
+ {"version":3,"file":"deno-server.d.ts","sourceRoot":"","sources":["../../../../../src/src/platform/compat/http/deno-server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGpE,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAkB;IAEpC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAIvB"}
@@ -8,7 +8,31 @@ export class DenoHttpServer {
8
8
  onListen?.({ hostname, port });
9
9
  // Access native Deno.serve via `self` to bypass dnt shim transform.
10
10
  const nativeDeno = self["Deno"];
11
- await nativeDeno.serve({ port, hostname, signal: serveSignal }, handler);
11
+ // Access native Response via `self` to bypass dnt shim transform.
12
+ // In npm packages, dnt replaces Response with undici's polyfill,
13
+ // but Deno.serve requires native Response instances.
14
+ const NativeResponse = self
15
+ .Response;
16
+ const wrappedHandler = async (req) => {
17
+ const response = await handler(req);
18
+ // If already native (compiled binary or WebSocket upgrade), return as-is
19
+ if (response instanceof NativeResponse)
20
+ return response;
21
+ // Re-wrap polyfilled Response as native Response.
22
+ // At runtime, `response` may be an undici Response (from dnt shim) that
23
+ // fails Deno's native instanceof check. Cast to access its properties.
24
+ const r = response;
25
+ return new NativeResponse(r.body, {
26
+ status: r.status,
27
+ statusText: r.statusText,
28
+ headers: r.headers,
29
+ });
30
+ };
31
+ const httpServer = nativeDeno.serve({ port, hostname, signal: serveSignal }, wrappedHandler);
32
+ // Block until the server stops (e.g. via signal abort).
33
+ // Deno.serve() returns synchronously; without awaiting .finished
34
+ // the event loop drains and the process exits in compiled binaries.
35
+ await httpServer.finished;
12
36
  }
13
37
  close() {
14
38
  this.abortController?.abort();
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Sandbox module for ephemeral compute environments.
3
+ *
4
+ * Provides the `Sandbox` class for creating and interacting with
5
+ * isolated execution environments, and re-exports `createBashTool`
6
+ * for AI agent integration.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Sandbox } from "veryfront/sandbox";
11
+ *
12
+ * const sandbox = await Sandbox.create({ authToken: userJwt });
13
+ * const result = await sandbox.executeCommand("echo hello");
14
+ * console.log(result.stdout); // "hello\n"
15
+ * await sandbox.close();
16
+ * ```
17
+ *
18
+ * @example With bash-tool for AI agents:
19
+ * ```ts
20
+ * import { Sandbox, createBashTool } from "veryfront/sandbox";
21
+ *
22
+ * const sandbox = await Sandbox.create({ authToken });
23
+ * const { tools } = await createBashTool({ sandbox });
24
+ * // Pass tools to agent...
25
+ * ```
26
+ *
27
+ * @module
28
+ */
29
+ import "../../_dnt.polyfills.js";
30
+ export { type ExecResult, type ExecStreamEvent, Sandbox, type SandboxOptions } from "./sandbox.js";
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Sandbox module for ephemeral compute environments.
3
+ *
4
+ * Provides the `Sandbox` class for creating and interacting with
5
+ * isolated execution environments, and re-exports `createBashTool`
6
+ * for AI agent integration.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Sandbox } from "veryfront/sandbox";
11
+ *
12
+ * const sandbox = await Sandbox.create({ authToken: userJwt });
13
+ * const result = await sandbox.executeCommand("echo hello");
14
+ * console.log(result.stdout); // "hello\n"
15
+ * await sandbox.close();
16
+ * ```
17
+ *
18
+ * @example With bash-tool for AI agents:
19
+ * ```ts
20
+ * import { Sandbox, createBashTool } from "veryfront/sandbox";
21
+ *
22
+ * const sandbox = await Sandbox.create({ authToken });
23
+ * const { tools } = await createBashTool({ sandbox });
24
+ * // Pass tools to agent...
25
+ * ```
26
+ *
27
+ * @module
28
+ */
29
+ import "../../_dnt.polyfills.js";
30
+ export { Sandbox } from "./sandbox.js";
@@ -0,0 +1,48 @@
1
+ export interface SandboxOptions {
2
+ /** Base URL of the Veryfront API. Defaults to VERYFRONT_API_URL env. */
3
+ apiUrl?: string;
4
+ /** User's JWT for authentication. */
5
+ authToken: string;
6
+ }
7
+ export interface ExecResult {
8
+ stdout: string;
9
+ stderr: string;
10
+ exitCode: number;
11
+ }
12
+ export interface ExecStreamEvent {
13
+ type: "stdout" | "stderr" | "exit" | "error";
14
+ data?: string;
15
+ exitCode?: number;
16
+ }
17
+ export declare class Sandbox {
18
+ private endpoint;
19
+ private sessionId;
20
+ private authToken;
21
+ private apiUrl;
22
+ private constructor();
23
+ /** Create a new sandbox session. Claims a warm pod or creates a new one. */
24
+ static create(options: SandboxOptions): Promise<Sandbox>;
25
+ /** Reconnect to an existing sandbox session. */
26
+ static get(id: string, options: SandboxOptions): Promise<Sandbox>;
27
+ private static waitForReady;
28
+ /** Execute a bash command in the sandbox and return buffered result. */
29
+ executeCommand(command: string): Promise<ExecResult>;
30
+ /** Execute a bash command with streaming output (NDJSON). */
31
+ executeStream(command: string): AsyncGenerator<ExecStreamEvent>;
32
+ /** Read a file from the sandbox workspace. */
33
+ readFile(path: string): Promise<string>;
34
+ /** Write files to the sandbox workspace. */
35
+ writeFiles(files: Array<{
36
+ path: string;
37
+ content: string;
38
+ }>): Promise<void>;
39
+ /** Send a heartbeat to prevent idle timeout. */
40
+ heartbeat(): Promise<void>;
41
+ /** Close the sandbox session and mark for deletion. */
42
+ close(): Promise<void>;
43
+ /** Get the session ID. */
44
+ get id(): string;
45
+ /** Get the sandbox endpoint URL. */
46
+ get url(): string;
47
+ }
48
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/src/sandbox/sandbox.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,OAAO;IAEhB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,MAAM;IAJhB,OAAO;IAOP,4EAA4E;WAC/D,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IA6B9D,gDAAgD;WACnC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;mBAmBlD,YAAY;IA0BjC,wEAAwE;IAClE,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAsB1D,6DAA6D;IACtD,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC,eAAe,CAAC;IAsCtE,8CAA8C;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAe7C,4CAA4C;IACtC,UAAU,CACd,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,GAC9C,OAAO,CAAC,IAAI,CAAC;IAehB,gDAAgD;IAC1C,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAOhC,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,0BAA0B;IAC1B,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,oCAAoC;IACpC,IAAI,GAAG,IAAI,MAAM,CAEhB;CACF"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Sandbox client SDK for ephemeral compute environments.
3
+ *
4
+ * Implements the bash-tool Sandbox interface for seamless integration
5
+ * with AI agent tool loops.
6
+ *
7
+ * @module
8
+ */
9
+ import * as dntShim from "../../_dnt.shims.js";
10
+ export class Sandbox {
11
+ endpoint;
12
+ sessionId;
13
+ authToken;
14
+ apiUrl;
15
+ constructor(endpoint, sessionId, authToken, apiUrl) {
16
+ this.endpoint = endpoint;
17
+ this.sessionId = sessionId;
18
+ this.authToken = authToken;
19
+ this.apiUrl = apiUrl;
20
+ }
21
+ /** Create a new sandbox session. Claims a warm pod or creates a new one. */
22
+ static async create(options) {
23
+ const apiUrl = options.apiUrl ||
24
+ (typeof dntShim.Deno !== "undefined"
25
+ ? dntShim.Deno.env.get("VERYFRONT_API_URL")
26
+ : process.env.VERYFRONT_API_URL) ||
27
+ "https://api.veryfront.com";
28
+ const res = await dntShim.fetch(`${apiUrl}/sandbox-sessions`, {
29
+ method: "POST",
30
+ headers: {
31
+ Authorization: `Bearer ${options.authToken}`,
32
+ "Content-Type": "application/json",
33
+ },
34
+ });
35
+ if (!res.ok) {
36
+ throw new Error(`Failed to create sandbox: ${res.status} ${await res.text()}`);
37
+ }
38
+ const { id, endpoint, status } = await res.json();
39
+ // If not yet running, poll until ready
40
+ if (status !== "running") {
41
+ await Sandbox.waitForReady(apiUrl, id, options.authToken);
42
+ }
43
+ return new Sandbox(endpoint, id, options.authToken, apiUrl);
44
+ }
45
+ /** Reconnect to an existing sandbox session. */
46
+ static async get(id, options) {
47
+ const apiUrl = options.apiUrl ||
48
+ (typeof dntShim.Deno !== "undefined"
49
+ ? dntShim.Deno.env.get("VERYFRONT_API_URL")
50
+ : process.env.VERYFRONT_API_URL) ||
51
+ "https://api.veryfront.com";
52
+ const res = await dntShim.fetch(`${apiUrl}/sandbox-sessions/${id}`, {
53
+ headers: { Authorization: `Bearer ${options.authToken}` },
54
+ });
55
+ if (!res.ok) {
56
+ throw new Error(`Failed to get sandbox: ${res.status} ${await res.text()}`);
57
+ }
58
+ const { endpoint } = await res.json();
59
+ return new Sandbox(endpoint, id, options.authToken, apiUrl);
60
+ }
61
+ static async waitForReady(apiUrl, id, authToken, maxWaitMs = 60_000, pollIntervalMs = 2_000) {
62
+ const start = Date.now();
63
+ while (Date.now() - start < maxWaitMs) {
64
+ await new Promise((r) => dntShim.setTimeout(r, pollIntervalMs));
65
+ const res = await dntShim.fetch(`${apiUrl}/sandbox-sessions/${id}`, {
66
+ headers: { Authorization: `Bearer ${authToken}` },
67
+ });
68
+ if (res.ok) {
69
+ const data = await res.json();
70
+ if (data.status === "running")
71
+ return;
72
+ if (data.status === "error" || data.status === "deleting") {
73
+ throw new Error(`Sandbox failed to start: status=${data.status}`);
74
+ }
75
+ }
76
+ }
77
+ throw new Error("Sandbox did not become ready within timeout");
78
+ }
79
+ /** Execute a bash command in the sandbox and return buffered result. */
80
+ async executeCommand(command) {
81
+ let stdout = "";
82
+ let stderr = "";
83
+ let exitCode = 1;
84
+ for await (const event of this.executeStream(command)) {
85
+ switch (event.type) {
86
+ case "stdout":
87
+ stdout += event.data ?? "";
88
+ break;
89
+ case "stderr":
90
+ stderr += event.data ?? "";
91
+ break;
92
+ case "exit":
93
+ exitCode = event.exitCode ?? 1;
94
+ break;
95
+ }
96
+ }
97
+ return { stdout, stderr, exitCode };
98
+ }
99
+ /** Execute a bash command with streaming output (NDJSON). */
100
+ async *executeStream(command) {
101
+ const res = await dntShim.fetch(`${this.endpoint}/exec`, {
102
+ method: "POST",
103
+ headers: {
104
+ Authorization: `Bearer ${this.authToken}`,
105
+ "Content-Type": "application/json",
106
+ },
107
+ body: JSON.stringify({ command }),
108
+ });
109
+ if (!res.ok) {
110
+ throw new Error(`Exec failed: ${res.status} ${await res.text()}`);
111
+ }
112
+ const reader = res.body.getReader();
113
+ const decoder = new TextDecoder();
114
+ let buffer = "";
115
+ while (true) {
116
+ const { done, value } = await reader.read();
117
+ if (done)
118
+ break;
119
+ buffer += decoder.decode(value, { stream: true });
120
+ const lines = buffer.split("\n");
121
+ buffer = lines.pop();
122
+ for (const line of lines) {
123
+ if (line.trim()) {
124
+ yield JSON.parse(line);
125
+ }
126
+ }
127
+ }
128
+ if (buffer.trim()) {
129
+ yield JSON.parse(buffer);
130
+ }
131
+ }
132
+ /** Read a file from the sandbox workspace. */
133
+ async readFile(path) {
134
+ const res = await dntShim.fetch(`${this.endpoint}/file?path=${encodeURIComponent(path)}`, {
135
+ headers: { Authorization: `Bearer ${this.authToken}` },
136
+ });
137
+ if (!res.ok) {
138
+ throw new Error(`Read file failed: ${res.status} ${await res.text()}`);
139
+ }
140
+ return res.text();
141
+ }
142
+ /** Write files to the sandbox workspace. */
143
+ async writeFiles(files) {
144
+ const res = await dntShim.fetch(`${this.endpoint}/files`, {
145
+ method: "POST",
146
+ headers: {
147
+ Authorization: `Bearer ${this.authToken}`,
148
+ "Content-Type": "application/json",
149
+ },
150
+ body: JSON.stringify({ files }),
151
+ });
152
+ if (!res.ok) {
153
+ throw new Error(`Write files failed: ${res.status} ${await res.text()}`);
154
+ }
155
+ }
156
+ /** Send a heartbeat to prevent idle timeout. */
157
+ async heartbeat() {
158
+ await dntShim.fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}/heartbeat`, {
159
+ method: "POST",
160
+ headers: { Authorization: `Bearer ${this.authToken}` },
161
+ });
162
+ }
163
+ /** Close the sandbox session and mark for deletion. */
164
+ async close() {
165
+ await dntShim.fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}`, {
166
+ method: "DELETE",
167
+ headers: { Authorization: `Bearer ${this.authToken}` },
168
+ });
169
+ }
170
+ /** Get the session ID. */
171
+ get id() {
172
+ return this.sessionId;
173
+ }
174
+ /** Get the sandbox endpoint URL. */
175
+ get url() {
176
+ return this.endpoint;
177
+ }
178
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"import-finder.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAY1D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAU1D"}
1
+ {"version":3,"file":"import-finder.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAY1D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB1D"}
@@ -22,9 +22,15 @@ export function findVfModuleImports(code) {
22
22
  */
23
23
  export function findRelativeImports(code) {
24
24
  const imports = [];
25
- const pattern = /from\s*["'](\.\.?\/[^"']+)["']/g;
25
+ // Match: from "./foo" or from "../bar"
26
+ const fromPattern = /from\s*["'](\.\.?\/[^"']+)["']/g;
27
+ // Match side-effect imports: import "./foo" or import "../bar" (no `from`)
28
+ const sideEffectPattern = /import\s*["'](\.\.?\/[^"']+)["']/g;
26
29
  let match;
27
- while ((match = pattern.exec(code)) !== null) {
30
+ while ((match = fromPattern.exec(code)) !== null) {
31
+ imports.push(match[1]);
32
+ }
33
+ while ((match = sideEffectPattern.exec(code)) !== null) {
28
34
  imports.push(match[1]);
29
35
  }
30
36
  return [...new Set(imports)];
@@ -24,6 +24,7 @@ export declare const _testExports: {
24
24
  resolveVeryfrontSourcePath: typeof resolveVeryfrontSourcePath;
25
25
  resolveAndTransformVeryfrontImport: typeof resolveAndTransformVeryfrontImport;
26
26
  FRAMEWORK_ROOT: string;
27
+ EMBEDDED_SRC_DIR: string;
27
28
  EXTENSIONS: string[];
28
29
  };
29
30
  export declare const ssrVfModulesPlugin: TransformPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACL,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,kCAAkC,EAEnC,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACL,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,kCAAkC,EAClC,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,EACV,yBAAyB,EACzB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AAexB,eAAO,MAAM,YAAY;;;;;;;;;CASxB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,eA+EhC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACL,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,kCAAkC,EAEnC,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACL,oBAAoB,EACpB,8BAA8B,EAC9B,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,kCAAkC,EAClC,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,EACV,yBAAyB,EACzB,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AAexB,eAAO,MAAM,YAAY;;;;;;;;;;CAUxB,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,eA+EhC,CAAC"}
@@ -43,6 +43,7 @@ export const _testExports = {
43
43
  resolveVeryfrontSourcePath,
44
44
  resolveAndTransformVeryfrontImport,
45
45
  FRAMEWORK_ROOT,
46
+ EMBEDDED_SRC_DIR,
46
47
  EXTENSIONS,
47
48
  };
48
49
  export const ssrVfModulesPlugin = {
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAYrE,OAAO,EAML,KAAK,gBAAgB,EAGtB,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,gBAAgB,EACrB,oBAAoB,UAAQ,EAC5B,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC,CAoOjB;AAED;;;GAGG;AACH,wBAAsB,kCAAkC,CACtD,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4CxB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,CAEjB"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/pipeline/stages/ssr-vf-modules/transform.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAYrE,OAAO,EAOL,KAAK,gBAAgB,EAGtB,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,CA2BjB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,gBAAgB,EACrB,oBAAoB,UAAQ,EAC5B,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC,CAoPjB;AAED;;;GAGG;AACH,wBAAsB,kCAAkC,CACtD,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4CxB;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,MAAM,CAAC,CAEjB"}
@@ -15,7 +15,7 @@ import { VERSION } from "../../../../utils/version.js";
15
15
  import { buildReactUrl, getReactImportMap } from "../../../import-rewriter/url-builder.js";
16
16
  import { findRelativeImports } from "./import-finder.js";
17
17
  import { resolveRelativeFrameworkImport, resolveVeryfrontSourcePath } from "./path-resolver.js";
18
- import { FRAMEWORK_ROOT, frameworkFileCache, frameworkWriteFlight, LOG_PREFIX, MAX_RELATIVE_IMPORT_DEPTH, transformingFiles, veryfrontTransformCache, } from "./constants.js";
18
+ import { EMBEDDED_SRC_DIR, FRAMEWORK_ROOT, frameworkFileCache, frameworkWriteFlight, LOG_PREFIX, MAX_RELATIVE_IMPORT_DEPTH, transformingFiles, veryfrontTransformCache, } from "./constants.js";
19
19
  /**
20
20
  * Check if a transformed code string is a cycle placeholder.
21
21
  * Cycle placeholders are returned when transformFrameworkCode detects a cycle
@@ -159,6 +159,10 @@ export async function transformFrameworkCode(content, sourcePath, ctx, throwOnMi
159
159
  // Safety: MAX_RELATIVE_IMPORT_DEPTH limits recursion, transformingFiles detects
160
160
  // cycles, and frameworkFileCache deduplicates already-transformed files.
161
161
  const relativeReplacements = new Map();
162
+ // Prefixes for framework source directories - files outside these are
163
+ // already-compiled JS (e.g. dnt shims) that should not be recursively transformed.
164
+ const frameworkSrcDir = join(FRAMEWORK_ROOT, "src") + "/";
165
+ const embeddedSrcDirPrefix = EMBEDDED_SRC_DIR + "/";
162
166
  {
163
167
  const relativeImports = findRelativeImports(transformed);
164
168
  for (const specifier of relativeImports) {
@@ -174,6 +178,16 @@ export async function transformFrameworkCode(content, sourcePath, ctx, throwOnMi
174
178
  logger.warn(`${LOG_PREFIX} Could not resolve relative import "${specifier}" in ${sourcePath}`);
175
179
  continue;
176
180
  }
181
+ // Files outside framework source directories (e.g. _dnt.shims.js,
182
+ // _dnt.polyfills.js) are already-compiled JS with bare npm imports
183
+ // that Deno resolves natively. Skip recursive transformation and
184
+ // just point to the file directly.
185
+ const isFrameworkSource = resolvedPath.startsWith(frameworkSrcDir) ||
186
+ resolvedPath.startsWith(embeddedSrcDirPrefix);
187
+ if (!isFrameworkSource) {
188
+ relativeReplacements.set(specifier, `file://${resolvedPath}`);
189
+ continue;
190
+ }
177
191
  // Check if this dependency was already transformed (by absolute path)
178
192
  const existingFileUrl = frameworkFileCache.get(resolvedPath);
179
193
  if (existingFileUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -94,6 +94,9 @@
94
94
  "./integrations": {
95
95
  "import": "./esm/src/integrations/index.js"
96
96
  },
97
+ "./sandbox": {
98
+ "import": "./esm/src/sandbox/index.js"
99
+ },
97
100
  "./cli": {
98
101
  "import": "./esm/cli/main.js"
99
102
  },
@@ -37,7 +37,17 @@ async function runProxy(options: ServeOptions): Promise<void> {
37
37
  setEnv("PORT", String(options.port));
38
38
  setEnv("HOST", options.bindAddress);
39
39
 
40
+ registerTerminationSignals((signal) => {
41
+ cliLogger.info(`Received ${signal}, shutting down proxy server...`);
42
+ exitProcess(0);
43
+ });
44
+
45
+ // DenoHttpServer.serve() blocks until the server stops,
46
+ // so this import keeps the process alive.
40
47
  await import("../../../src/proxy/main.js");
48
+
49
+ // Keep the process alive (Deno.serve returns immediately in compiled binaries)
50
+ await new Promise(() => {});
41
51
  }
42
52
 
43
53
  async function runProductionServer(options: ServeOptions): Promise<void> {
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "nodeModulesDir": "auto",
5
5
  "exclude": [
6
6
  "npm/",
@@ -37,6 +37,7 @@ export default {
37
37
  "./provider": "./src/provider/index.ts",
38
38
  "./fs": "./src/fs/index.ts",
39
39
  "./integrations": "./src/integrations/index.ts",
40
+ "./sandbox": "./src/sandbox/index.ts",
40
41
  "./cli": "./cli/main.ts"
41
42
  },
42
43
  "imports": {
@@ -55,6 +56,7 @@ export default {
55
56
  "veryfront/react/fonts": "./src/react/fonts/index.ts",
56
57
  "veryfront/react/components/ai": "./src/react/components/ai/index.ts",
57
58
  "veryfront/components/ai": "./src/react/components/ai/index.ts",
59
+ "veryfront/sandbox": "./src/sandbox/index.ts",
58
60
  "veryfront/agent/react": "./src/agent/react/index.ts",
59
61
  "veryfront/agent/testing": "./src/agent/testing/index.ts",
60
62
  "veryfront/agent/middleware": "./src/agent/middleware/index.ts",
@@ -137,6 +139,7 @@ export default {
137
139
  "#veryfront/testing/bdd.ts": "./src/testing/bdd.ts",
138
140
  "#veryfront/testing/deno-compat": "./src/testing/deno-compat.ts",
139
141
  "#veryfront/testing/utils": "./src/testing/utils.ts",
142
+ "#veryfront/sandbox": "./src/sandbox/index.ts",
140
143
  "#veryfront/tool": "./src/tool/index.ts",
141
144
  "#veryfront/tool/schema": "./src/tool/schema/index.ts",
142
145
  "#veryfront/transforms": "./src/transforms/index.ts",
@@ -397,16 +397,34 @@ export class DenoAdapter implements RuntimeAdapter {
397
397
  // dnt rewrites both `Deno.*` and `globalThis.*` to use @deno/shim-deno which lacks .serve.
398
398
  // `self` is not shimmed by dnt and equals `globalThis` in Deno.
399
399
  const nativeDeno = (self as unknown as Record<string, typeof dntShim.Deno>)["Deno"]!;
400
+
401
+ // Access native Response via `self` to bypass dnt shim transform.
402
+ // In npm packages, dnt replaces Response with undici's polyfill,
403
+ // but Deno.serve requires native Response instances.
404
+ const NativeResponse = (self as unknown as { Response: typeof dntShim.Response })
405
+ .Response;
406
+
400
407
  const server = nativeDeno.serve({
401
408
  port,
402
409
  hostname,
403
410
  signal,
404
411
  handler: async (request) => {
405
412
  try {
406
- return await wrappedHandler(request);
413
+ const response: dntShim.Response = await wrappedHandler(request);
414
+ // If already native (compiled binary), return as-is
415
+ if (response instanceof NativeResponse) return response;
416
+ // Re-wrap polyfilled Response as native Response.
417
+ // dnt replaces Response with undici's polyfill which fails
418
+ // Deno.serve's native instanceof check.
419
+ const r = response as unknown as dntShim.Response;
420
+ return new NativeResponse(r.body, {
421
+ status: r.status,
422
+ statusText: r.statusText,
423
+ headers: r.headers,
424
+ });
407
425
  } catch (error) {
408
426
  serverLogger.error("Request handler error:", error);
409
- return new dntShim.Response("Internal Server Error", { status: 500 });
427
+ return new NativeResponse("Internal Server Error", { status: 500 });
410
428
  }
411
429
  },
412
430
  onListen: (params) => {
@@ -15,7 +15,37 @@ export class DenoHttpServer implements HttpServer {
15
15
 
16
16
  // Access native Deno.serve via `self` to bypass dnt shim transform.
17
17
  const nativeDeno = (self as unknown as Record<string, typeof dntShim.Deno>)["Deno"]!;
18
- await nativeDeno.serve({ port, hostname, signal: serveSignal }, handler);
18
+
19
+ // Access native Response via `self` to bypass dnt shim transform.
20
+ // In npm packages, dnt replaces Response with undici's polyfill,
21
+ // but Deno.serve requires native Response instances.
22
+ const NativeResponse = (self as unknown as { Response: typeof dntShim.Response })
23
+ .Response;
24
+
25
+ const wrappedHandler: Handler = async (req) => {
26
+ const response: dntShim.Response = await handler(req);
27
+ // If already native (compiled binary or WebSocket upgrade), return as-is
28
+ if (response instanceof NativeResponse) return response;
29
+ // Re-wrap polyfilled Response as native Response.
30
+ // At runtime, `response` may be an undici Response (from dnt shim) that
31
+ // fails Deno's native instanceof check. Cast to access its properties.
32
+ const r = response as unknown as dntShim.Response;
33
+ return new NativeResponse(r.body, {
34
+ status: r.status,
35
+ statusText: r.statusText,
36
+ headers: r.headers,
37
+ });
38
+ };
39
+
40
+ const httpServer = nativeDeno.serve(
41
+ { port, hostname, signal: serveSignal },
42
+ wrappedHandler,
43
+ );
44
+
45
+ // Block until the server stops (e.g. via signal abort).
46
+ // Deno.serve() returns synchronously; without awaiting .finished
47
+ // the event loop drains and the process exits in compiled binaries.
48
+ await httpServer.finished;
19
49
  }
20
50
 
21
51
  close(): Promise<void> {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Sandbox module for ephemeral compute environments.
3
+ *
4
+ * Provides the `Sandbox` class for creating and interacting with
5
+ * isolated execution environments, and re-exports `createBashTool`
6
+ * for AI agent integration.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Sandbox } from "veryfront/sandbox";
11
+ *
12
+ * const sandbox = await Sandbox.create({ authToken: userJwt });
13
+ * const result = await sandbox.executeCommand("echo hello");
14
+ * console.log(result.stdout); // "hello\n"
15
+ * await sandbox.close();
16
+ * ```
17
+ *
18
+ * @example With bash-tool for AI agents:
19
+ * ```ts
20
+ * import { Sandbox, createBashTool } from "veryfront/sandbox";
21
+ *
22
+ * const sandbox = await Sandbox.create({ authToken });
23
+ * const { tools } = await createBashTool({ sandbox });
24
+ * // Pass tools to agent...
25
+ * ```
26
+ *
27
+ * @module
28
+ */
29
+ import "../../_dnt.polyfills.js";
30
+
31
+
32
+ export { type ExecResult, type ExecStreamEvent, Sandbox, type SandboxOptions } from "./sandbox.js";
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Sandbox client SDK for ephemeral compute environments.
3
+ *
4
+ * Implements the bash-tool Sandbox interface for seamless integration
5
+ * with AI agent tool loops.
6
+ *
7
+ * @module
8
+ */
9
+ import * as dntShim from "../../_dnt.shims.js";
10
+
11
+
12
+ export interface SandboxOptions {
13
+ /** Base URL of the Veryfront API. Defaults to VERYFRONT_API_URL env. */
14
+ apiUrl?: string;
15
+ /** User's JWT for authentication. */
16
+ authToken: string;
17
+ }
18
+
19
+ export interface ExecResult {
20
+ stdout: string;
21
+ stderr: string;
22
+ exitCode: number;
23
+ }
24
+
25
+ export interface ExecStreamEvent {
26
+ type: "stdout" | "stderr" | "exit" | "error";
27
+ data?: string;
28
+ exitCode?: number;
29
+ }
30
+
31
+ export class Sandbox {
32
+ private constructor(
33
+ private endpoint: string,
34
+ private sessionId: string,
35
+ private authToken: string,
36
+ private apiUrl: string,
37
+ ) {}
38
+
39
+ /** Create a new sandbox session. Claims a warm pod or creates a new one. */
40
+ static async create(options: SandboxOptions): Promise<Sandbox> {
41
+ const apiUrl = options.apiUrl ||
42
+ (typeof dntShim.Deno !== "undefined"
43
+ ? dntShim.Deno.env.get("VERYFRONT_API_URL")
44
+ : process.env.VERYFRONT_API_URL) ||
45
+ "https://api.veryfront.com";
46
+
47
+ const res = await dntShim.fetch(`${apiUrl}/sandbox-sessions`, {
48
+ method: "POST",
49
+ headers: {
50
+ Authorization: `Bearer ${options.authToken}`,
51
+ "Content-Type": "application/json",
52
+ },
53
+ });
54
+
55
+ if (!res.ok) {
56
+ throw new Error(`Failed to create sandbox: ${res.status} ${await res.text()}`);
57
+ }
58
+
59
+ const { id, endpoint, status } = await res.json();
60
+
61
+ // If not yet running, poll until ready
62
+ if (status !== "running") {
63
+ await Sandbox.waitForReady(apiUrl, id, options.authToken);
64
+ }
65
+
66
+ return new Sandbox(endpoint, id, options.authToken, apiUrl);
67
+ }
68
+
69
+ /** Reconnect to an existing sandbox session. */
70
+ static async get(id: string, options: SandboxOptions): Promise<Sandbox> {
71
+ const apiUrl = options.apiUrl ||
72
+ (typeof dntShim.Deno !== "undefined"
73
+ ? dntShim.Deno.env.get("VERYFRONT_API_URL")
74
+ : process.env.VERYFRONT_API_URL) ||
75
+ "https://api.veryfront.com";
76
+
77
+ const res = await dntShim.fetch(`${apiUrl}/sandbox-sessions/${id}`, {
78
+ headers: { Authorization: `Bearer ${options.authToken}` },
79
+ });
80
+
81
+ if (!res.ok) {
82
+ throw new Error(`Failed to get sandbox: ${res.status} ${await res.text()}`);
83
+ }
84
+
85
+ const { endpoint } = await res.json();
86
+ return new Sandbox(endpoint, id, options.authToken, apiUrl);
87
+ }
88
+
89
+ private static async waitForReady(
90
+ apiUrl: string,
91
+ id: string,
92
+ authToken: string,
93
+ maxWaitMs = 60_000,
94
+ pollIntervalMs = 2_000,
95
+ ): Promise<void> {
96
+ const start = Date.now();
97
+ while (Date.now() - start < maxWaitMs) {
98
+ await new Promise((r) => dntShim.setTimeout(r, pollIntervalMs));
99
+
100
+ const res = await dntShim.fetch(`${apiUrl}/sandbox-sessions/${id}`, {
101
+ headers: { Authorization: `Bearer ${authToken}` },
102
+ });
103
+
104
+ if (res.ok) {
105
+ const data = await res.json();
106
+ if (data.status === "running") return;
107
+ if (data.status === "error" || data.status === "deleting") {
108
+ throw new Error(`Sandbox failed to start: status=${data.status}`);
109
+ }
110
+ }
111
+ }
112
+ throw new Error("Sandbox did not become ready within timeout");
113
+ }
114
+
115
+ /** Execute a bash command in the sandbox and return buffered result. */
116
+ async executeCommand(command: string): Promise<ExecResult> {
117
+ let stdout = "";
118
+ let stderr = "";
119
+ let exitCode = 1;
120
+
121
+ for await (const event of this.executeStream(command)) {
122
+ switch (event.type) {
123
+ case "stdout":
124
+ stdout += event.data ?? "";
125
+ break;
126
+ case "stderr":
127
+ stderr += event.data ?? "";
128
+ break;
129
+ case "exit":
130
+ exitCode = event.exitCode ?? 1;
131
+ break;
132
+ }
133
+ }
134
+
135
+ return { stdout, stderr, exitCode };
136
+ }
137
+
138
+ /** Execute a bash command with streaming output (NDJSON). */
139
+ async *executeStream(command: string): AsyncGenerator<ExecStreamEvent> {
140
+ const res = await dntShim.fetch(`${this.endpoint}/exec`, {
141
+ method: "POST",
142
+ headers: {
143
+ Authorization: `Bearer ${this.authToken}`,
144
+ "Content-Type": "application/json",
145
+ },
146
+ body: JSON.stringify({ command }),
147
+ });
148
+
149
+ if (!res.ok) {
150
+ throw new Error(`Exec failed: ${res.status} ${await res.text()}`);
151
+ }
152
+
153
+ const reader = res.body!.getReader();
154
+ const decoder = new TextDecoder();
155
+ let buffer = "";
156
+
157
+ while (true) {
158
+ const { done, value } = await reader.read();
159
+ if (done) break;
160
+
161
+ buffer += decoder.decode(value, { stream: true });
162
+ const lines = buffer.split("\n");
163
+ buffer = lines.pop()!;
164
+
165
+ for (const line of lines) {
166
+ if (line.trim()) {
167
+ yield JSON.parse(line) as ExecStreamEvent;
168
+ }
169
+ }
170
+ }
171
+
172
+ if (buffer.trim()) {
173
+ yield JSON.parse(buffer) as ExecStreamEvent;
174
+ }
175
+ }
176
+
177
+ /** Read a file from the sandbox workspace. */
178
+ async readFile(path: string): Promise<string> {
179
+ const res = await dntShim.fetch(
180
+ `${this.endpoint}/file?path=${encodeURIComponent(path)}`,
181
+ {
182
+ headers: { Authorization: `Bearer ${this.authToken}` },
183
+ },
184
+ );
185
+
186
+ if (!res.ok) {
187
+ throw new Error(`Read file failed: ${res.status} ${await res.text()}`);
188
+ }
189
+
190
+ return res.text();
191
+ }
192
+
193
+ /** Write files to the sandbox workspace. */
194
+ async writeFiles(
195
+ files: Array<{ path: string; content: string }>,
196
+ ): Promise<void> {
197
+ const res = await dntShim.fetch(`${this.endpoint}/files`, {
198
+ method: "POST",
199
+ headers: {
200
+ Authorization: `Bearer ${this.authToken}`,
201
+ "Content-Type": "application/json",
202
+ },
203
+ body: JSON.stringify({ files }),
204
+ });
205
+
206
+ if (!res.ok) {
207
+ throw new Error(`Write files failed: ${res.status} ${await res.text()}`);
208
+ }
209
+ }
210
+
211
+ /** Send a heartbeat to prevent idle timeout. */
212
+ async heartbeat(): Promise<void> {
213
+ await dntShim.fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}/heartbeat`, {
214
+ method: "POST",
215
+ headers: { Authorization: `Bearer ${this.authToken}` },
216
+ });
217
+ }
218
+
219
+ /** Close the sandbox session and mark for deletion. */
220
+ async close(): Promise<void> {
221
+ await dntShim.fetch(`${this.apiUrl}/sandbox-sessions/${this.sessionId}`, {
222
+ method: "DELETE",
223
+ headers: { Authorization: `Bearer ${this.authToken}` },
224
+ });
225
+ }
226
+
227
+ /** Get the session ID. */
228
+ get id(): string {
229
+ return this.sessionId;
230
+ }
231
+
232
+ /** Get the sandbox endpoint URL. */
233
+ get url(): string {
234
+ return this.endpoint;
235
+ }
236
+ }
@@ -26,10 +26,17 @@ export function findVfModuleImports(code: string): string[] {
26
26
  */
27
27
  export function findRelativeImports(code: string): string[] {
28
28
  const imports: string[] = [];
29
- const pattern = /from\s*["'](\.\.?\/[^"']+)["']/g;
29
+
30
+ // Match: from "./foo" or from "../bar"
31
+ const fromPattern = /from\s*["'](\.\.?\/[^"']+)["']/g;
32
+ // Match side-effect imports: import "./foo" or import "../bar" (no `from`)
33
+ const sideEffectPattern = /import\s*["'](\.\.?\/[^"']+)["']/g;
30
34
 
31
35
  let match: RegExpExecArray | null;
32
- while ((match = pattern.exec(code)) !== null) {
36
+ while ((match = fromPattern.exec(code)) !== null) {
37
+ imports.push(match[1]!);
38
+ }
39
+ while ((match = sideEffectPattern.exec(code)) !== null) {
33
40
  imports.push(match[1]!);
34
41
  }
35
42
 
@@ -85,6 +85,7 @@ export const _testExports = {
85
85
  resolveVeryfrontSourcePath,
86
86
  resolveAndTransformVeryfrontImport,
87
87
  FRAMEWORK_ROOT,
88
+ EMBEDDED_SRC_DIR,
88
89
  EXTENSIONS,
89
90
  };
90
91
 
@@ -18,6 +18,7 @@ import { buildReactUrl, getReactImportMap } from "../../../import-rewriter/url-b
18
18
  import { findRelativeImports } from "./import-finder.js";
19
19
  import { resolveRelativeFrameworkImport, resolveVeryfrontSourcePath } from "./path-resolver.js";
20
20
  import {
21
+ EMBEDDED_SRC_DIR,
21
22
  FRAMEWORK_ROOT,
22
23
  frameworkFileCache,
23
24
  frameworkWriteFlight,
@@ -193,6 +194,11 @@ export async function transformFrameworkCode(
193
194
  // cycles, and frameworkFileCache deduplicates already-transformed files.
194
195
  const relativeReplacements = new Map<string, string>();
195
196
 
197
+ // Prefixes for framework source directories - files outside these are
198
+ // already-compiled JS (e.g. dnt shims) that should not be recursively transformed.
199
+ const frameworkSrcDir = join(FRAMEWORK_ROOT, "src") + "/";
200
+ const embeddedSrcDirPrefix = EMBEDDED_SRC_DIR + "/";
201
+
196
202
  {
197
203
  const relativeImports = findRelativeImports(transformed);
198
204
 
@@ -215,6 +221,17 @@ export async function transformFrameworkCode(
215
221
  continue;
216
222
  }
217
223
 
224
+ // Files outside framework source directories (e.g. _dnt.shims.js,
225
+ // _dnt.polyfills.js) are already-compiled JS with bare npm imports
226
+ // that Deno resolves natively. Skip recursive transformation and
227
+ // just point to the file directly.
228
+ const isFrameworkSource = resolvedPath.startsWith(frameworkSrcDir) ||
229
+ resolvedPath.startsWith(embeddedSrcDirPrefix);
230
+ if (!isFrameworkSource) {
231
+ relativeReplacements.set(specifier, `file://${resolvedPath}`);
232
+ continue;
233
+ }
234
+
218
235
  // Check if this dependency was already transformed (by absolute path)
219
236
  const existingFileUrl = frameworkFileCache.get(resolvedPath);
220
237
  if (existingFileUrl) {