modal 0.7.1 → 0.7.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.
package/README.md CHANGED
@@ -40,6 +40,7 @@ We also provide a number of examples:
40
40
  - [Create a Sandbox with GPU](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-gpu.ts)
41
41
  - [Create a Sandbox using a private image from AWS ECR](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-private-image.ts)
42
42
  - [Take a snapshot of the filesystem of a Sandbox](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-filesystem-snapshot.ts)
43
+ - [Snapshot a directory, and mount it in a running Sandbox](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-directory-snapshot.ts)
43
44
  - [Execute Sandbox commands](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-exec.ts)
44
45
  - [Running a coding agent in a Sandbox](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-agent.ts)
45
46
  - [Check the status and exit code of a Sandbox](https://github.com/modal-labs/libmodal/blob/main/modal-js/examples/sandbox-poll.ts)
package/dist/index.cjs CHANGED
@@ -48940,24 +48940,33 @@ var Sandbox2 = class _Sandbox {
48940
48940
  throw new ClientClosedError();
48941
48941
  }
48942
48942
  }
48943
+ static #maxGetTaskIdAttempts = 600;
48944
+ // 5 minutes at 500ms intervals
48943
48945
  async #getTaskId() {
48944
- if (this.#taskId === void 0) {
48946
+ if (this.#taskId !== void 0) {
48947
+ return this.#taskId;
48948
+ }
48949
+ for (let i = 0; i < _Sandbox.#maxGetTaskIdAttempts; i++) {
48945
48950
  const resp = await this.#client.cpClient.sandboxGetTaskId({
48946
48951
  sandboxId: this.sandboxId
48947
48952
  });
48948
- if (!resp.taskId) {
48949
- throw new Error(
48950
- `Sandbox ${this.sandboxId} does not have a task ID. It may not be running.`
48951
- );
48952
- }
48953
48953
  if (resp.taskResult) {
48954
+ if (resp.taskResult.status === 1 /* GENERIC_STATUS_SUCCESS */ || !resp.taskResult.exception) {
48955
+ throw new Error(`Sandbox ${this.sandboxId} has already completed`);
48956
+ }
48954
48957
  throw new Error(
48955
- `Sandbox ${this.sandboxId} has already completed with result: ${resp.taskResult}`
48958
+ `Sandbox ${this.sandboxId} has already completed with result: exception:"${resp.taskResult.exception}"`
48956
48959
  );
48957
48960
  }
48958
- this.#taskId = resp.taskId;
48961
+ if (resp.taskId) {
48962
+ this.#taskId = resp.taskId;
48963
+ return this.#taskId;
48964
+ }
48965
+ await (0, import_promises2.setTimeout)(500);
48959
48966
  }
48960
- return this.#taskId;
48967
+ throw new Error(
48968
+ `Timed out waiting for task ID for Sandbox ${this.sandboxId}`
48969
+ );
48961
48970
  }
48962
48971
  async #getOrCreateCommandRouterClient(taskId) {
48963
48972
  if (this.#commandRouterClient !== void 0) {
@@ -49270,6 +49279,9 @@ async function* outputStreamSb(cpClient, sandboxId, fileDescriptor, signal) {
49270
49279
  completed = true;
49271
49280
  break;
49272
49281
  }
49282
+ if (signal?.aborted) {
49283
+ return;
49284
+ }
49273
49285
  }
49274
49286
  } catch (err) {
49275
49287
  if (signal?.aborted) {
@@ -49480,28 +49492,47 @@ var AuthTokenManager = class {
49480
49492
  logger;
49481
49493
  currentToken = "";
49482
49494
  tokenExpiry = 0;
49483
- timeoutId = null;
49484
- running = false;
49485
- fetchPromise = null;
49495
+ refreshPromise = null;
49486
49496
  constructor(client2, logger) {
49487
49497
  this.client = client2;
49488
49498
  this.logger = logger;
49489
49499
  }
49490
49500
  /**
49491
49501
  * Returns a valid auth token.
49492
- * If the current token is expired and the manager is running, triggers an on-demand refresh.
49493
49502
  */
49494
49503
  async getToken() {
49495
- if (this.currentToken && !this.isExpired()) {
49496
- return this.currentToken;
49504
+ if (!this.currentToken || this.isExpired()) {
49505
+ return this.lockedRefreshToken();
49497
49506
  }
49498
- if (this.running) {
49499
- await this.runFetch();
49500
- if (this.currentToken && !this.isExpired()) {
49501
- return this.currentToken;
49507
+ if (this.needsRefresh() && !this.refreshPromise) {
49508
+ try {
49509
+ await this.lockedRefreshToken();
49510
+ } catch (error) {
49511
+ this.logger.error("refreshing auth token", "error", error);
49502
49512
  }
49503
49513
  }
49504
- throw new Error("No valid auth token available");
49514
+ return this.currentToken;
49515
+ }
49516
+ /**
49517
+ * Ensures only one fetch is in progress at a time. Concurrent callers
49518
+ * await the same promise. Includes a double-check so that if another
49519
+ * caller already refreshed, we skip the RPC.
49520
+ */
49521
+ async lockedRefreshToken() {
49522
+ if (!this.refreshPromise) {
49523
+ this.refreshPromise = (async () => {
49524
+ try {
49525
+ if (this.currentToken && !this.needsRefresh()) {
49526
+ return;
49527
+ }
49528
+ await this.fetchToken();
49529
+ } finally {
49530
+ this.refreshPromise = null;
49531
+ }
49532
+ })();
49533
+ }
49534
+ await this.refreshPromise;
49535
+ return this.currentToken;
49505
49536
  }
49506
49537
  /**
49507
49538
  * Fetches a new auth token from the server and stores it.
@@ -49533,56 +49564,6 @@ var AuthTokenManager = class {
49533
49564
  `${refreshIn}s`
49534
49565
  );
49535
49566
  }
49536
- /**
49537
- * Background loop that refreshes tokens REFRESH_WINDOW seconds before they expire.
49538
- */
49539
- async backgroundRefresh() {
49540
- while (this.running) {
49541
- const now = Math.floor(Date.now() / 1e3);
49542
- const refreshTime = this.tokenExpiry - REFRESH_WINDOW;
49543
- const delay = Math.max(0, refreshTime - now) * 1e3;
49544
- await new Promise((resolve) => {
49545
- this.timeoutId = setTimeout(resolve, delay);
49546
- this.timeoutId.unref();
49547
- });
49548
- if (!this.running) {
49549
- return;
49550
- }
49551
- try {
49552
- await this.runFetch();
49553
- } catch (error) {
49554
- this.logger.error("Failed to refresh auth token", "error", error);
49555
- await new Promise((resolve) => setTimeout(resolve, 5e3));
49556
- }
49557
- }
49558
- }
49559
- /**
49560
- * Fetches the initial token and starts the refresh loop.
49561
- * Throws an error if the initial token fetch fails.
49562
- */
49563
- async start() {
49564
- if (this.running) {
49565
- return;
49566
- }
49567
- this.running = true;
49568
- try {
49569
- await this.runFetch();
49570
- } catch (error) {
49571
- this.running = false;
49572
- throw error;
49573
- }
49574
- this.backgroundRefresh();
49575
- }
49576
- /**
49577
- * Stops the background refresh.
49578
- */
49579
- stop() {
49580
- this.running = false;
49581
- if (this.timeoutId) {
49582
- clearTimeout(this.timeoutId);
49583
- this.timeoutId = null;
49584
- }
49585
- }
49586
49567
  /**
49587
49568
  * Extracts the exp claim from a JWT token.
49588
49569
  */
@@ -49607,17 +49588,9 @@ var AuthTokenManager = class {
49607
49588
  const now = Math.floor(Date.now() / 1e3);
49608
49589
  return now >= this.tokenExpiry;
49609
49590
  }
49610
- runFetch() {
49611
- if (!this.fetchPromise) {
49612
- this.fetchPromise = (async () => {
49613
- try {
49614
- await this.fetchToken();
49615
- } finally {
49616
- this.fetchPromise = null;
49617
- }
49618
- })();
49619
- }
49620
- return this.fetchPromise;
49591
+ needsRefresh() {
49592
+ const now = Math.floor(Date.now() / 1e3);
49593
+ return now >= this.tokenExpiry - REFRESH_WINDOW;
49621
49594
  }
49622
49595
  getCurrentToken() {
49623
49596
  return this.currentToken;
@@ -49630,7 +49603,7 @@ var AuthTokenManager = class {
49630
49603
 
49631
49604
  // src/version.ts
49632
49605
  function getSDKVersion() {
49633
- return true ? "0.7.1" : "0.0.0";
49606
+ return true ? "0.7.2" : "0.0.0";
49634
49607
  }
49635
49608
 
49636
49609
  // src/logger.ts
@@ -49813,10 +49786,7 @@ var ModalClient2 = class {
49813
49786
  }
49814
49787
  close() {
49815
49788
  this.logger.debug("Closing Modal client");
49816
- if (this.authTokenManager) {
49817
- this.authTokenManager.stop();
49818
- this.authTokenManager = null;
49819
- }
49789
+ this.authTokenManager = null;
49820
49790
  this.logger.debug("Modal client closed");
49821
49791
  }
49822
49792
  version() {
@@ -49926,7 +49896,6 @@ var ModalClient2 = class {
49926
49896
  this.cpClient,
49927
49897
  this.logger
49928
49898
  );
49929
- this.authTokenManager.start();
49930
49899
  }
49931
49900
  return this.authTokenManager;
49932
49901
  };
package/dist/index.js CHANGED
@@ -48878,24 +48878,33 @@ var Sandbox2 = class _Sandbox {
48878
48878
  throw new ClientClosedError();
48879
48879
  }
48880
48880
  }
48881
+ static #maxGetTaskIdAttempts = 600;
48882
+ // 5 minutes at 500ms intervals
48881
48883
  async #getTaskId() {
48882
- if (this.#taskId === void 0) {
48884
+ if (this.#taskId !== void 0) {
48885
+ return this.#taskId;
48886
+ }
48887
+ for (let i = 0; i < _Sandbox.#maxGetTaskIdAttempts; i++) {
48883
48888
  const resp = await this.#client.cpClient.sandboxGetTaskId({
48884
48889
  sandboxId: this.sandboxId
48885
48890
  });
48886
- if (!resp.taskId) {
48887
- throw new Error(
48888
- `Sandbox ${this.sandboxId} does not have a task ID. It may not be running.`
48889
- );
48890
- }
48891
48891
  if (resp.taskResult) {
48892
+ if (resp.taskResult.status === 1 /* GENERIC_STATUS_SUCCESS */ || !resp.taskResult.exception) {
48893
+ throw new Error(`Sandbox ${this.sandboxId} has already completed`);
48894
+ }
48892
48895
  throw new Error(
48893
- `Sandbox ${this.sandboxId} has already completed with result: ${resp.taskResult}`
48896
+ `Sandbox ${this.sandboxId} has already completed with result: exception:"${resp.taskResult.exception}"`
48894
48897
  );
48895
48898
  }
48896
- this.#taskId = resp.taskId;
48899
+ if (resp.taskId) {
48900
+ this.#taskId = resp.taskId;
48901
+ return this.#taskId;
48902
+ }
48903
+ await setTimeout3(500);
48897
48904
  }
48898
- return this.#taskId;
48905
+ throw new Error(
48906
+ `Timed out waiting for task ID for Sandbox ${this.sandboxId}`
48907
+ );
48899
48908
  }
48900
48909
  async #getOrCreateCommandRouterClient(taskId) {
48901
48910
  if (this.#commandRouterClient !== void 0) {
@@ -49208,6 +49217,9 @@ async function* outputStreamSb(cpClient, sandboxId, fileDescriptor, signal) {
49208
49217
  completed = true;
49209
49218
  break;
49210
49219
  }
49220
+ if (signal?.aborted) {
49221
+ return;
49222
+ }
49211
49223
  }
49212
49224
  } catch (err) {
49213
49225
  if (signal?.aborted) {
@@ -49418,28 +49430,47 @@ var AuthTokenManager = class {
49418
49430
  logger;
49419
49431
  currentToken = "";
49420
49432
  tokenExpiry = 0;
49421
- timeoutId = null;
49422
- running = false;
49423
- fetchPromise = null;
49433
+ refreshPromise = null;
49424
49434
  constructor(client2, logger) {
49425
49435
  this.client = client2;
49426
49436
  this.logger = logger;
49427
49437
  }
49428
49438
  /**
49429
49439
  * Returns a valid auth token.
49430
- * If the current token is expired and the manager is running, triggers an on-demand refresh.
49431
49440
  */
49432
49441
  async getToken() {
49433
- if (this.currentToken && !this.isExpired()) {
49434
- return this.currentToken;
49442
+ if (!this.currentToken || this.isExpired()) {
49443
+ return this.lockedRefreshToken();
49435
49444
  }
49436
- if (this.running) {
49437
- await this.runFetch();
49438
- if (this.currentToken && !this.isExpired()) {
49439
- return this.currentToken;
49445
+ if (this.needsRefresh() && !this.refreshPromise) {
49446
+ try {
49447
+ await this.lockedRefreshToken();
49448
+ } catch (error) {
49449
+ this.logger.error("refreshing auth token", "error", error);
49440
49450
  }
49441
49451
  }
49442
- throw new Error("No valid auth token available");
49452
+ return this.currentToken;
49453
+ }
49454
+ /**
49455
+ * Ensures only one fetch is in progress at a time. Concurrent callers
49456
+ * await the same promise. Includes a double-check so that if another
49457
+ * caller already refreshed, we skip the RPC.
49458
+ */
49459
+ async lockedRefreshToken() {
49460
+ if (!this.refreshPromise) {
49461
+ this.refreshPromise = (async () => {
49462
+ try {
49463
+ if (this.currentToken && !this.needsRefresh()) {
49464
+ return;
49465
+ }
49466
+ await this.fetchToken();
49467
+ } finally {
49468
+ this.refreshPromise = null;
49469
+ }
49470
+ })();
49471
+ }
49472
+ await this.refreshPromise;
49473
+ return this.currentToken;
49443
49474
  }
49444
49475
  /**
49445
49476
  * Fetches a new auth token from the server and stores it.
@@ -49471,56 +49502,6 @@ var AuthTokenManager = class {
49471
49502
  `${refreshIn}s`
49472
49503
  );
49473
49504
  }
49474
- /**
49475
- * Background loop that refreshes tokens REFRESH_WINDOW seconds before they expire.
49476
- */
49477
- async backgroundRefresh() {
49478
- while (this.running) {
49479
- const now = Math.floor(Date.now() / 1e3);
49480
- const refreshTime = this.tokenExpiry - REFRESH_WINDOW;
49481
- const delay = Math.max(0, refreshTime - now) * 1e3;
49482
- await new Promise((resolve) => {
49483
- this.timeoutId = setTimeout(resolve, delay);
49484
- this.timeoutId.unref();
49485
- });
49486
- if (!this.running) {
49487
- return;
49488
- }
49489
- try {
49490
- await this.runFetch();
49491
- } catch (error) {
49492
- this.logger.error("Failed to refresh auth token", "error", error);
49493
- await new Promise((resolve) => setTimeout(resolve, 5e3));
49494
- }
49495
- }
49496
- }
49497
- /**
49498
- * Fetches the initial token and starts the refresh loop.
49499
- * Throws an error if the initial token fetch fails.
49500
- */
49501
- async start() {
49502
- if (this.running) {
49503
- return;
49504
- }
49505
- this.running = true;
49506
- try {
49507
- await this.runFetch();
49508
- } catch (error) {
49509
- this.running = false;
49510
- throw error;
49511
- }
49512
- this.backgroundRefresh();
49513
- }
49514
- /**
49515
- * Stops the background refresh.
49516
- */
49517
- stop() {
49518
- this.running = false;
49519
- if (this.timeoutId) {
49520
- clearTimeout(this.timeoutId);
49521
- this.timeoutId = null;
49522
- }
49523
- }
49524
49505
  /**
49525
49506
  * Extracts the exp claim from a JWT token.
49526
49507
  */
@@ -49545,17 +49526,9 @@ var AuthTokenManager = class {
49545
49526
  const now = Math.floor(Date.now() / 1e3);
49546
49527
  return now >= this.tokenExpiry;
49547
49528
  }
49548
- runFetch() {
49549
- if (!this.fetchPromise) {
49550
- this.fetchPromise = (async () => {
49551
- try {
49552
- await this.fetchToken();
49553
- } finally {
49554
- this.fetchPromise = null;
49555
- }
49556
- })();
49557
- }
49558
- return this.fetchPromise;
49529
+ needsRefresh() {
49530
+ const now = Math.floor(Date.now() / 1e3);
49531
+ return now >= this.tokenExpiry - REFRESH_WINDOW;
49559
49532
  }
49560
49533
  getCurrentToken() {
49561
49534
  return this.currentToken;
@@ -49568,7 +49541,7 @@ var AuthTokenManager = class {
49568
49541
 
49569
49542
  // src/version.ts
49570
49543
  function getSDKVersion() {
49571
- return true ? "0.7.1" : "0.0.0";
49544
+ return true ? "0.7.2" : "0.0.0";
49572
49545
  }
49573
49546
 
49574
49547
  // src/logger.ts
@@ -49751,10 +49724,7 @@ var ModalClient2 = class {
49751
49724
  }
49752
49725
  close() {
49753
49726
  this.logger.debug("Closing Modal client");
49754
- if (this.authTokenManager) {
49755
- this.authTokenManager.stop();
49756
- this.authTokenManager = null;
49757
- }
49727
+ this.authTokenManager = null;
49758
49728
  this.logger.debug("Modal client closed");
49759
49729
  }
49760
49730
  version() {
@@ -49864,7 +49834,6 @@ var ModalClient2 = class {
49864
49834
  this.cpClient,
49865
49835
  this.logger
49866
49836
  );
49867
- this.authTokenManager.start();
49868
49837
  }
49869
49838
  return this.authTokenManager;
49870
49839
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modal",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Modal SDK for JavaScript/TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://modal.com/docs/guide/sdk-javascript-go",