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.js CHANGED
@@ -538,6 +538,17 @@ var WebSocketManager = class {
538
538
  data: { terminal_id: terminalId, cols, rows }
539
539
  });
540
540
  }
541
+ /**
542
+ * Start a pending streaming command
543
+ * Used in two-phase streaming flow: HTTP request creates pending command,
544
+ * then this signal triggers execution after client has subscribed.
545
+ */
546
+ startCommand(cmdId) {
547
+ this.sendRaw({
548
+ type: "command:start",
549
+ data: { cmd_id: cmdId }
550
+ });
551
+ }
541
552
  on(event, handler) {
542
553
  if (!this.eventHandlers.has(event)) {
543
554
  this.eventHandlers.set(event, /* @__PURE__ */ new Set());
@@ -1011,6 +1022,7 @@ var TerminalInstance = class {
1011
1022
  throw new Error("Destroy handler not set");
1012
1023
  }
1013
1024
  await this._destroyHandler();
1025
+ this._status = "stopped";
1014
1026
  this.cleanup();
1015
1027
  }
1016
1028
  /**
@@ -1916,7 +1928,8 @@ var Sandbox = class {
1916
1928
  headers: config.headers || {},
1917
1929
  timeout: config.timeout || 3e4,
1918
1930
  protocol: config.protocol || "binary",
1919
- metadata: config.metadata
1931
+ metadata: config.metadata,
1932
+ destroyHandler: config.destroyHandler
1920
1933
  };
1921
1934
  this.WebSocketImpl = config.WebSocket || globalThis.WebSocket;
1922
1935
  if (!this.WebSocketImpl) {
@@ -2339,18 +2352,24 @@ API request failed (${response.status}): ${error}`
2339
2352
  * Get file metadata (without content)
2340
2353
  */
2341
2354
  async getFile(path) {
2342
- return this.request(`/files/${encodeURIComponent(path)}`);
2355
+ return this.request(`/files/${this.encodeFilePath(path)}`);
2356
+ }
2357
+ /**
2358
+ * Encode a file path for use in URLs
2359
+ * Strips leading slash and encodes each segment separately to preserve path structure
2360
+ */
2361
+ encodeFilePath(path) {
2362
+ const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2363
+ const segments = pathWithoutLeadingSlash.split("/");
2364
+ return segments.map((s) => encodeURIComponent(s)).join("/");
2343
2365
  }
2344
2366
  /**
2345
2367
  * Read file content
2346
2368
  */
2347
2369
  async readFile(path) {
2348
2370
  const params = new URLSearchParams({ content: "true" });
2349
- const pathWithoutLeadingSlash = path.startsWith("/") ? path.slice(1) : path;
2350
- const segments = pathWithoutLeadingSlash.split("/");
2351
- const encodedPath = segments.map((s) => encodeURIComponent(s)).join("/");
2352
2371
  const response = await this.request(
2353
- `/files/${encodedPath}?${params}`
2372
+ `/files/${this.encodeFilePath(path)}?${params}`
2354
2373
  );
2355
2374
  return response.data.content || "";
2356
2375
  }
@@ -2367,7 +2386,7 @@ API request failed (${response.status}): ${error}`
2367
2386
  * Delete a file or directory
2368
2387
  */
2369
2388
  async deleteFile(path) {
2370
- return this.request(`/files/${encodeURIComponent(path)}`, {
2389
+ return this.request(`/files/${this.encodeFilePath(path)}`, {
2371
2390
  method: "DELETE"
2372
2391
  });
2373
2392
  }
@@ -2386,7 +2405,7 @@ API request failed (${response.status}): ${error}`
2386
2405
  headers["Authorization"] = `Bearer ${this._token}`;
2387
2406
  }
2388
2407
  const response = await fetch(
2389
- `${this.config.sandboxUrl}/files/${encodeURIComponent(path)}`,
2408
+ `${this.config.sandboxUrl}/files/${this.encodeFilePath(path)}`,
2390
2409
  {
2391
2410
  method: "HEAD",
2392
2411
  headers,
@@ -2908,6 +2927,8 @@ API request failed (${response.status}): ${error}`
2908
2927
  * @param options.background - Run in background (server uses goroutines)
2909
2928
  * @param options.cwd - Working directory (server uses cmd.Dir)
2910
2929
  * @param options.env - Environment variables (server uses cmd.Env)
2930
+ * @param options.onStdout - Callback for streaming stdout data
2931
+ * @param options.onStderr - Callback for streaming stderr data
2911
2932
  * @returns Command execution result
2912
2933
  *
2913
2934
  * @example
@@ -2923,10 +2944,77 @@ API request failed (${response.status}): ${error}`
2923
2944
  * background: true,
2924
2945
  * env: { PORT: '3000' }
2925
2946
  * })
2947
+ *
2948
+ * // With streaming output
2949
+ * await sandbox.runCommand('npm install', {
2950
+ * onStdout: (data) => console.log(data),
2951
+ * onStderr: (data) => console.error(data),
2952
+ * })
2926
2953
  * ```
2927
2954
  */
2928
2955
  async runCommand(command, options) {
2929
- return this.run.command(command, options);
2956
+ const hasStreamingCallbacks = options?.onStdout || options?.onStderr;
2957
+ if (!hasStreamingCallbacks) {
2958
+ return this.run.command(command, options);
2959
+ }
2960
+ const ws = await this.ensureWebSocket();
2961
+ const result = await this.runCommandRequest({
2962
+ command,
2963
+ stream: true,
2964
+ cwd: options?.cwd,
2965
+ env: options?.env
2966
+ });
2967
+ const { cmd_id, channel } = result.data;
2968
+ if (!cmd_id || !channel) {
2969
+ throw new Error("Server did not return streaming channel info");
2970
+ }
2971
+ ws.subscribe(channel);
2972
+ let stdout = "";
2973
+ let stderr = "";
2974
+ let exitCode = 0;
2975
+ let resolvePromise = null;
2976
+ const cleanup = () => {
2977
+ ws.off("command:stdout", handleStdout);
2978
+ ws.off("command:stderr", handleStderr);
2979
+ ws.off("command:exit", handleExit);
2980
+ ws.unsubscribe(channel);
2981
+ };
2982
+ const handleStdout = (msg) => {
2983
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2984
+ stdout += msg.data.output;
2985
+ options?.onStdout?.(msg.data.output);
2986
+ }
2987
+ };
2988
+ const handleStderr = (msg) => {
2989
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2990
+ stderr += msg.data.output;
2991
+ options?.onStderr?.(msg.data.output);
2992
+ }
2993
+ };
2994
+ const handleExit = (msg) => {
2995
+ if (msg.channel === channel && msg.data.cmd_id === cmd_id) {
2996
+ exitCode = msg.data.exit_code;
2997
+ cleanup();
2998
+ if (resolvePromise) {
2999
+ resolvePromise({ stdout, stderr, exitCode, durationMs: 0 });
3000
+ }
3001
+ }
3002
+ };
3003
+ ws.on("command:stdout", handleStdout);
3004
+ ws.on("command:stderr", handleStderr);
3005
+ ws.on("command:exit", handleExit);
3006
+ ws.startCommand(cmd_id);
3007
+ if (options?.background) {
3008
+ return {
3009
+ stdout: "",
3010
+ stderr: "",
3011
+ exitCode: 0,
3012
+ durationMs: 0
3013
+ };
3014
+ }
3015
+ return new Promise((resolve) => {
3016
+ resolvePromise = resolve;
3017
+ });
2930
3018
  }
2931
3019
  /**
2932
3020
  * Get server information
@@ -2979,9 +3067,15 @@ API request failed (${response.status}): ${error}`
2979
3067
  }
2980
3068
  /**
2981
3069
  * Destroy the sandbox (Sandbox interface method)
3070
+ *
3071
+ * If a destroyHandler was provided (e.g., from gateway), calls it to destroy
3072
+ * the sandbox on the backend. Otherwise, only disconnects the WebSocket.
2982
3073
  */
2983
3074
  async destroy() {
2984
3075
  await this.disconnect();
3076
+ if (this.config.destroyHandler) {
3077
+ await this.config.destroyHandler();
3078
+ }
2985
3079
  }
2986
3080
  /**
2987
3081
  * Disconnect WebSocket
@@ -3420,9 +3514,17 @@ function buildConfigExample(provider, authOptions) {
3420
3514
  return options.join("\n\n");
3421
3515
  }
3422
3516
  function createConfigFromExplicit(config) {
3423
- if (!config.apiKey) {
3517
+ const computesdkApiKey = config.computesdkApiKey || config.apiKey;
3518
+ if (!computesdkApiKey) {
3424
3519
  throw new Error(
3425
- `Missing ComputeSDK API key. The 'apiKey' field is required.
3520
+ `Missing ComputeSDK API key. Set 'computesdkApiKey' in your config.
3521
+
3522
+ Example:
3523
+ compute.setConfig({
3524
+ provider: 'e2b',
3525
+ computesdkApiKey: process.env.COMPUTESDK_API_KEY,
3526
+ e2b: { apiKey: process.env.E2B_API_KEY }
3527
+ })
3426
3528
 
3427
3529
  Get your API key at: https://computesdk.com/dashboard`
3428
3530
  );
@@ -3430,7 +3532,7 @@ Get your API key at: https://computesdk.com/dashboard`
3430
3532
  validateProviderConfig(config);
3431
3533
  const providerHeaders = buildProviderHeaders2(config);
3432
3534
  return {
3433
- apiKey: config.apiKey,
3535
+ apiKey: computesdkApiKey,
3434
3536
  gatewayUrl: config.gatewayUrl || GATEWAY_URL,
3435
3537
  provider: config.provider,
3436
3538
  providerHeaders
@@ -3546,7 +3648,12 @@ var ComputeManager = class {
3546
3648
  ...name && { name },
3547
3649
  ...namespace && { namespace }
3548
3650
  },
3549
- WebSocket: globalThis.WebSocket
3651
+ WebSocket: globalThis.WebSocket,
3652
+ destroyHandler: async () => {
3653
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3654
+ method: "DELETE"
3655
+ });
3656
+ }
3550
3657
  });
3551
3658
  await waitForComputeReady(sandbox);
3552
3659
  return sandbox;
@@ -3567,7 +3674,12 @@ var ComputeManager = class {
3567
3674
  provider,
3568
3675
  token: token || config.apiKey,
3569
3676
  metadata,
3570
- WebSocket: globalThis.WebSocket
3677
+ WebSocket: globalThis.WebSocket,
3678
+ destroyHandler: async () => {
3679
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3680
+ method: "DELETE"
3681
+ });
3682
+ }
3571
3683
  });
3572
3684
  await waitForComputeReady(sandbox);
3573
3685
  return sandbox;
@@ -3617,7 +3729,12 @@ var ComputeManager = class {
3617
3729
  name: result.data.name,
3618
3730
  namespace: result.data.namespace
3619
3731
  },
3620
- WebSocket: globalThis.WebSocket
3732
+ WebSocket: globalThis.WebSocket,
3733
+ destroyHandler: async () => {
3734
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3735
+ method: "DELETE"
3736
+ });
3737
+ }
3621
3738
  });
3622
3739
  await waitForComputeReady(sandbox);
3623
3740
  return sandbox;
@@ -3648,7 +3765,12 @@ var ComputeManager = class {
3648
3765
  name,
3649
3766
  namespace
3650
3767
  },
3651
- WebSocket: globalThis.WebSocket
3768
+ WebSocket: globalThis.WebSocket,
3769
+ destroyHandler: async () => {
3770
+ await gatewayFetch(`${config.gatewayUrl}/v1/sandboxes/${sandboxId}`, config, {
3771
+ method: "DELETE"
3772
+ });
3773
+ }
3652
3774
  });
3653
3775
  await waitForComputeReady(sandbox);
3654
3776
  return sandbox;
@@ -3689,7 +3811,7 @@ var ComputeManager = class {
3689
3811
 
3690
3812
  Options:
3691
3813
  1. Zero-config: Set COMPUTESDK_API_KEY and provider credentials (e.g., E2B_API_KEY)
3692
- 2. Explicit: Call compute.setConfig({ provider: "e2b", apiKey: "...", e2b: { apiKey: "..." } })
3814
+ 2. Explicit: Call compute.setConfig({ provider: "e2b", computesdkApiKey: "...", e2b: { apiKey: "..." } })
3693
3815
  3. Use provider directly: import { e2b } from '@computesdk/e2b'
3694
3816
 
3695
3817
  Docs: https://computesdk.com/docs/quickstart`