computesdk 1.10.3 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -485,6 +485,17 @@ var WebSocketManager = class {
485
485
  data: { terminal_id: terminalId, cols, rows }
486
486
  });
487
487
  }
488
+ /**
489
+ * Start a pending streaming command
490
+ * Used in two-phase streaming flow: HTTP request creates pending command,
491
+ * then this signal triggers execution after client has subscribed.
492
+ */
493
+ startCommand(cmdId) {
494
+ this.sendRaw({
495
+ type: "command:start",
496
+ data: { cmd_id: cmdId }
497
+ });
498
+ }
488
499
  on(event, handler) {
489
500
  if (!this.eventHandlers.has(event)) {
490
501
  this.eventHandlers.set(event, /* @__PURE__ */ new Set());
@@ -958,6 +969,7 @@ var TerminalInstance = class {
958
969
  throw new Error("Destroy handler not set");
959
970
  }
960
971
  await this._destroyHandler();
972
+ this._status = "stopped";
961
973
  this.cleanup();
962
974
  }
963
975
  /**
@@ -1863,7 +1875,8 @@ var Sandbox = class {
1863
1875
  headers: config.headers || {},
1864
1876
  timeout: config.timeout || 3e4,
1865
1877
  protocol: config.protocol || "binary",
1866
- metadata: config.metadata
1878
+ metadata: config.metadata,
1879
+ destroyHandler: config.destroyHandler
1867
1880
  };
1868
1881
  this.WebSocketImpl = config.WebSocket || globalThis.WebSocket;
1869
1882
  if (!this.WebSocketImpl) {
@@ -2286,18 +2299,24 @@ API request failed (${response.status}): ${error}`
2286
2299
  * Get file metadata (without content)
2287
2300
  */
2288
2301
  async getFile(path) {
2289
- return this.request(`/files/${encodeURIComponent(path)}`);
2302
+ return this.request(`/files/${this.encodeFilePath(path)}`);
2303
+ }
2304
+ /**
2305
+ * Encode a file path for use in URLs
2306
+ * Strips leading slash and encodes each segment separately to preserve path structure
2307
+ */
2308
+ encodeFilePath(path) {
2309
+ const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2310
+ const segments = pathWithoutLeadingSlash.split("/");
2311
+ return segments.map((s) => encodeURIComponent(s)).join("/");
2290
2312
  }
2291
2313
  /**
2292
2314
  * Read file content
2293
2315
  */
2294
2316
  async readFile(path) {
2295
2317
  const params = new URLSearchParams({ content: "true" });
2296
- const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2297
- const segments = pathWithoutLeadingSlash.split("/");
2298
- const encodedPath = segments.map((s) => encodeURIComponent(s)).join("/");
2299
2318
  const response = await this.request(
2300
- `/files/${encodedPath}?${params}`
2319
+ `/files/${this.encodeFilePath(path)}?${params}`
2301
2320
  );
2302
2321
  return response.data.content || "";
2303
2322
  }
@@ -2314,7 +2333,7 @@ API request failed (${response.status}): ${error}`
2314
2333
  * Delete a file or directory
2315
2334
  */
2316
2335
  async deleteFile(path) {
2317
- return this.request(`/files/${encodeURIComponent(path)}`, {
2336
+ return this.request(`/files/${this.encodeFilePath(path)}`, {
2318
2337
  method: "DELETE"
2319
2338
  });
2320
2339
  }
@@ -2333,7 +2352,7 @@ API request failed (${response.status}): ${error}`
2333
2352
  headers["Authorization"] = `Bearer ${this._token}`;
2334
2353
  }
2335
2354
  const response = await fetch(
2336
- `${this.config.sandboxUrl}/files/${encodeURIComponent(path)}`,
2355
+ `${this.config.sandboxUrl}/files/${this.encodeFilePath(path)}`,
2337
2356
  {
2338
2357
  method: "HEAD",
2339
2358
  headers,
@@ -2855,6 +2874,8 @@ API request failed (${response.status}): ${error}`
2855
2874
  * @param options.background - Run in background (server uses goroutines)
2856
2875
  * @param options.cwd - Working directory (server uses cmd.Dir)
2857
2876
  * @param options.env - Environment variables (server uses cmd.Env)
2877
+ * @param options.onStdout - Callback for streaming stdout data
2878
+ * @param options.onStderr - Callback for streaming stderr data
2858
2879
  * @returns Command execution result
2859
2880
  *
2860
2881
  * @example
@@ -2870,10 +2891,77 @@ API request failed (${response.status}): ${error}`
2870
2891
  * background: true,
2871
2892
  * env: { PORT: '3000' }
2872
2893
  * })
2894
+ *
2895
+ * // With streaming output
2896
+ * await sandbox.runCommand('npm install', {
2897
+ * onStdout: (data) => console.log(data),
2898
+ * onStderr: (data) => console.error(data),
2899
+ * })
2873
2900
  * ```
2874
2901
  */
2875
2902
  async runCommand(command, options) {
2876
- return this.run.command(command, options);
2903
+ const hasStreamingCallbacks = options?.onStdout || options?.onStderr;
2904
+ if (!hasStreamingCallbacks) {
2905
+ return this.run.command(command, options);
2906
+ }
2907
+ const ws = await this.ensureWebSocket();
2908
+ const result = await this.runCommandRequest({
2909
+ command,
2910
+ stream: true,
2911
+ cwd: options?.cwd,
2912
+ env: options?.env
2913
+ });
2914
+ const { cmd_id, channel } = result.data;
2915
+ if (!cmd_id || !channel) {
2916
+ throw new Error("Server did not return streaming channel info");
2917
+ }
2918
+ ws.subscribe(channel);
2919
+ let stdout = "";
2920
+ let stderr = "";
2921
+ let exitCode = 0;
2922
+ let resolvePromise = null;
2923
+ const cleanup = () => {
2924
+ ws.off("command:stdout", handleStdout);
2925
+ ws.off("command:stderr", handleStderr);
2926
+ ws.off("command:exit", handleExit);
2927
+ ws.unsubscribe(channel);
2928
+ };
2929
+ const handleStdout = (msg) => {
2930
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2931
+ stdout += msg.data.output;
2932
+ options?.onStdout?.(msg.data.output);
2933
+ }
2934
+ };
2935
+ const handleStderr = (msg) => {
2936
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2937
+ stderr += msg.data.output;
2938
+ options?.onStderr?.(msg.data.output);
2939
+ }
2940
+ };
2941
+ const handleExit = (msg) => {
2942
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2943
+ exitCode = msg.data.exit_code;
2944
+ cleanup();
2945
+ if (resolvePromise) {
2946
+ resolvePromise({ stdout, stderr, exitCode, durationMs: 0 });
2947
+ }
2948
+ }
2949
+ };
2950
+ ws.on("command:stdout", handleStdout);
2951
+ ws.on("command:stderr", handleStderr);
2952
+ ws.on("command:exit", handleExit);
2953
+ ws.startCommand(cmd_id);
2954
+ if (options?.background) {
2955
+ return {
2956
+ stdout: "",
2957
+ stderr: "",
2958
+ exitCode: 0,
2959
+ durationMs: 0
2960
+ };
2961
+ }
2962
+ return new Promise((resolve) => {
2963
+ resolvePromise = resolve;
2964
+ });
2877
2965
  }
2878
2966
  /**
2879
2967
  * Get server information
@@ -2926,9 +3014,15 @@ API request failed (${response.status}): ${error}`
2926
3014
  }
2927
3015
  /**
2928
3016
  * Destroy the sandbox (Sandbox interface method)
3017
+ *
3018
+ * If a destroyHandler was provided (e.g., from gateway), calls it to destroy
3019
+ * the sandbox on the backend. Otherwise, only disconnects the WebSocket.
2929
3020
  */
2930
3021
  async destroy() {
2931
3022
  await this.disconnect();
3023
+ if (this.config.destroyHandler) {
3024
+ await this.config.destroyHandler();
3025
+ }
2932
3026
  }
2933
3027
  /**
2934
3028
  * Disconnect WebSocket
@@ -3367,9 +3461,17 @@ function buildConfigExample(provider, authOptions) {
3367
3461
  return options.join("\n\n");
3368
3462
  }
3369
3463
  function createConfigFromExplicit(config) {
3370
- if (!config.apiKey) {
3464
+ const computesdkApiKey = config.computesdkApiKey || config.apiKey;
3465
+ if (!computesdkApiKey) {
3371
3466
  throw new Error(
3372
- `Missing ComputeSDK API key. The 'apiKey' field is required.
3467
+ `Missing ComputeSDK API key. Set 'computesdkApiKey' in your config.
3468
+
3469
+ Example:
3470
+ compute.setConfig({
3471
+ provider: 'e2b',
3472
+ computesdkApiKey: process.env.COMPUTESDK_API_KEY,
3473
+ e2b: { apiKey: process.env.E2B_API_KEY }
3474
+ })
3373
3475
 
3374
3476
  Get your API key at: https://computesdk.com/dashboard`
3375
3477
  );
@@ -3377,7 +3479,7 @@ Get your API key at: https://computesdk.com/dashboard`
3377
3479
  validateProviderConfig(config);
3378
3480
  const providerHeaders = buildProviderHeaders2(config);
3379
3481
  return {
3380
- apiKey: config.apiKey,
3482
+ apiKey: computesdkApiKey,
3381
3483
  gatewayUrl: config.gatewayUrl || GATEWAY_URL,
3382
3484
  provider: config.provider,
3383
3485
  providerHeaders
@@ -3493,7 +3595,12 @@ var ComputeManager = class {
3493
3595
  ...name && { name },
3494
3596
  ...namespace && { namespace }
3495
3597
  },
3496
- WebSocket: globalThis.WebSocket
3598
+ WebSocket: globalThis.WebSocket,
3599
+ destroyHandler: async () => {
3600
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3601
+ method: "DELETE"
3602
+ });
3603
+ }
3497
3604
  });
3498
3605
  await waitForComputeReady(sandbox);
3499
3606
  return sandbox;
@@ -3514,7 +3621,12 @@ var ComputeManager = class {
3514
3621
  provider,
3515
3622
  token: token || config.apiKey,
3516
3623
  metadata,
3517
- WebSocket: globalThis.WebSocket
3624
+ WebSocket: globalThis.WebSocket,
3625
+ destroyHandler: async () => {
3626
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3627
+ method: "DELETE"
3628
+ });
3629
+ }
3518
3630
  });
3519
3631
  await waitForComputeReady(sandbox);
3520
3632
  return sandbox;
@@ -3564,7 +3676,12 @@ var ComputeManager = class {
3564
3676
  name: result.data.name,
3565
3677
  namespace: result.data.namespace
3566
3678
  },
3567
- WebSocket: globalThis.WebSocket
3679
+ WebSocket: globalThis.WebSocket,
3680
+ destroyHandler: async () => {
3681
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3682
+ method: "DELETE"
3683
+ });
3684
+ }
3568
3685
  });
3569
3686
  await waitForComputeReady(sandbox);
3570
3687
  return sandbox;
@@ -3595,7 +3712,12 @@ var ComputeManager = class {
3595
3712
  name,
3596
3713
  namespace
3597
3714
  },
3598
- WebSocket: globalThis.WebSocket
3715
+ WebSocket: globalThis.WebSocket,
3716
+ destroyHandler: async () => {
3717
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3718
+ method: "DELETE"
3719
+ });
3720
+ }
3599
3721
  });
3600
3722
  await waitForComputeReady(sandbox);
3601
3723
  return sandbox;
@@ -3636,7 +3758,7 @@ var ComputeManager = class {
3636
3758
 
3637
3759
  Options:
3638
3760
  1. Zero-config: Set COMPUTESDK_API_KEY and provider credentials (e.g., E2B_API_KEY)
3639
- 2. Explicit: Call compute.setConfig({ provider: "e2b", apiKey: "...", e2b: { apiKey: "..." } })
3761
+ 2. Explicit: Call compute.setConfig({ provider: "e2b", computesdkApiKey: "...", e2b: { apiKey: "..." } })
3640
3762
  3. Use provider directly: import { e2b } from '@computesdk/e2b'
3641
3763
 
3642
3764
  Docs: https://computesdk.com/docs/quickstart`