@daytona/sdk 0.198.0 → 0.199.0-alpha.1

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/esm/Sandbox.js CHANGED
@@ -4,9 +4,8 @@
4
4
  */
5
5
  import { __esDecorate, __runInitializers } from "tslib";
6
6
  import { SandboxState } from '@daytona/api-client';
7
- import { TelemetryApi as AnalyticsTelemetryApi, Configuration as AnalyticsConfiguration, } from '@daytona/analytics-api-client';
8
7
  import { Daytona } from './Daytona.js';
9
- import { FileSystemApi, GitApi, ProcessApi, LspApi, InfoApi, SystemApi, ComputerUseApi, InterpreterApi, ServerApi, } from '@daytona/toolbox-api-client';
8
+ import { FileSystemApi, GitApi, ProcessApi, LspApi, InfoApi, ComputerUseApi, InterpreterApi, ServerApi, } from '@daytona/toolbox-api-client';
10
9
  import { FileSystem } from './FileSystem.js';
11
10
  import { Git } from './Git.js';
12
11
  import { Process } from './Process.js';
@@ -16,12 +15,35 @@ import { CODE_TOOLBOX_LANGUAGE_LABEL } from './Daytona.js';
16
15
  import { ComputerUse } from './ComputerUse.js';
17
16
  import { CodeInterpreter } from './CodeInterpreter.js';
18
17
  import { WithInstrumentation } from './utils/otel.decorator.js';
19
- function withEvents(target, _context) {
20
- return function (...args) {
21
- ;
22
- this.ensureSubscribed();
23
- return target.apply(this, args);
18
+ import { VolumeType } from '@daytona/api-client';
19
+ /**
20
+ * Builds the shell command that bootstraps the hotmount agent and mounts the volume inside a
21
+ * Sandbox. It exports the SEAWEED_* environment contract and runs the region's `init.sh`, using
22
+ * passwordless sudo when not already root (sudo strips env, so the vars are passed via `env`).
23
+ */
24
+ function buildHotmountMountCommand(mountToken, mountPath) {
25
+ const envVars = {
26
+ SEAWEED_TOKEN: mountToken.token,
27
+ SEAWEED_GATEWAY_GRPC: mountToken.gatewayGrpc,
28
+ SEAWEED_GATEWAY_HTTP: mountToken.gatewayHttp,
29
+ SEAWEED_BINARIES_URL: mountToken.binariesUrl,
30
+ SEAWEED_MOUNT_DIR: mountPath,
31
+ SEAWEED_VERSION: mountToken.version,
24
32
  };
33
+ const envAssignments = Object.entries(envVars)
34
+ .filter(([, value]) => value !== undefined && value !== '')
35
+ .map(([key, value]) => `${key}='${value}'`)
36
+ .join(' ');
37
+ // The bootstrap (and init.sh itself) requires curl. Fail loudly if it is missing or the
38
+ // download fails, rather than letting a broken `curl … | bash` pipe exit 0 and mount nothing.
39
+ const inner = [
40
+ 'set -e',
41
+ 'if ! command -v curl >/dev/null 2>&1; then echo "hotmount: curl is required to bootstrap the agent but was not found in the sandbox" >&2; exit 1; fi',
42
+ 'mkdir -p "$SEAWEED_MOUNT_DIR"',
43
+ 'init_script="$(curl -fsSL "$SEAWEED_BINARIES_URL/init.sh")"',
44
+ 'printf %s "$init_script" | bash',
45
+ ].join('; ');
46
+ return (`if [ "$(id -u)" != 0 ]; then SUDO="sudo -n"; else SUDO=""; fi; ` + `$SUDO env ${envAssignments} bash -c '${inner}'`);
25
47
  }
26
48
  /**
27
49
  * Represents a Daytona Sandbox.
@@ -52,7 +74,6 @@ function withEvents(target, _context) {
52
74
  * @property {string} [backupCreatedAt] - When the backup was created (not returned by list results;
53
75
  * call `refreshData()` on each item to populate)
54
76
  * @property {number} [autoStopInterval] - Auto-stop interval in minutes
55
- * @property {number} [autoPauseInterval] - Auto-pause interval in minutes
56
77
  * @property {number} [autoArchiveInterval] - Auto-archive interval in minutes
57
78
  * @property {number} [autoDeleteInterval] - Auto-delete interval in minutes
58
79
  * @property {Array<SandboxVolume>} [volumes] - Volumes attached to the Sandbox (not returned by
@@ -76,14 +97,13 @@ function withEvents(target, _context) {
76
97
  let Sandbox = (() => {
77
98
  let _instanceExtraInitializers = [];
78
99
  let _getUserHomeDir_decorators;
79
- let _getMetricsLatest_decorators;
80
- let _getMetrics_decorators;
81
100
  let _getUserRootDir_decorators;
82
101
  let _getWorkDir_decorators;
102
+ let _mountVolume_decorators;
103
+ let _pullVolumes_decorators;
83
104
  let _createLspServer_decorators;
84
105
  let _setLabels_decorators;
85
106
  let _start_decorators;
86
- let _recover_decorators;
87
107
  let _stop_decorators;
88
108
  let __experimental_fork_decorators;
89
109
  let __experimental_createSnapshot_decorators;
@@ -92,17 +112,13 @@ let Sandbox = (() => {
92
112
  let _waitUntilStarted_decorators;
93
113
  let _waitUntilStopped_decorators;
94
114
  let _refreshData_decorators;
95
- let _refreshActivity_decorators;
96
115
  let _setAutostopInterval_decorators;
97
- let _setAutoPauseInterval_decorators;
98
116
  let _setAutoArchiveInterval_decorators;
99
117
  let _setAutoDeleteInterval_decorators;
100
118
  let _updateNetworkSettings_decorators;
101
119
  let _updateSecrets_decorators;
102
120
  let _updateEnv_decorators;
103
121
  let _getPreviewLink_decorators;
104
- let _getSignedPreviewUrl_decorators;
105
- let _expireSignedPreviewUrl_decorators;
106
122
  let _archive_decorators;
107
123
  let _resize_decorators;
108
124
  let _waitForResizeComplete_decorators;
@@ -112,49 +128,43 @@ let Sandbox = (() => {
112
128
  return class Sandbox {
113
129
  static {
114
130
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
115
- _getUserHomeDir_decorators = [WithInstrumentation(), withEvents];
116
- _getMetricsLatest_decorators = [WithInstrumentation()];
117
- _getMetrics_decorators = [WithInstrumentation()];
118
- _getUserRootDir_decorators = [WithInstrumentation(), withEvents];
119
- _getWorkDir_decorators = [WithInstrumentation(), withEvents];
120
- _createLspServer_decorators = [WithInstrumentation(), withEvents];
121
- _setLabels_decorators = [WithInstrumentation(), withEvents];
122
- _start_decorators = [WithInstrumentation(), withEvents];
123
- _recover_decorators = [withEvents];
124
- _stop_decorators = [WithInstrumentation(), withEvents];
125
- __experimental_fork_decorators = [WithInstrumentation(), withEvents];
126
- __experimental_createSnapshot_decorators = [WithInstrumentation(), withEvents];
127
- _pause_decorators = [WithInstrumentation(), withEvents];
128
- _delete_decorators = [WithInstrumentation(), withEvents];
129
- _waitUntilStarted_decorators = [WithInstrumentation(), withEvents];
130
- _waitUntilStopped_decorators = [WithInstrumentation(), withEvents];
131
- _refreshData_decorators = [WithInstrumentation(), withEvents];
132
- _refreshActivity_decorators = [withEvents];
133
- _setAutostopInterval_decorators = [WithInstrumentation(), withEvents];
134
- _setAutoPauseInterval_decorators = [WithInstrumentation()];
135
- _setAutoArchiveInterval_decorators = [WithInstrumentation(), withEvents];
136
- _setAutoDeleteInterval_decorators = [WithInstrumentation(), withEvents];
131
+ _getUserHomeDir_decorators = [WithInstrumentation()];
132
+ _getUserRootDir_decorators = [WithInstrumentation()];
133
+ _getWorkDir_decorators = [WithInstrumentation()];
134
+ _mountVolume_decorators = [WithInstrumentation()];
135
+ _pullVolumes_decorators = [WithInstrumentation()];
136
+ _createLspServer_decorators = [WithInstrumentation()];
137
+ _setLabels_decorators = [WithInstrumentation()];
138
+ _start_decorators = [WithInstrumentation()];
139
+ _stop_decorators = [WithInstrumentation()];
140
+ __experimental_fork_decorators = [WithInstrumentation()];
141
+ __experimental_createSnapshot_decorators = [WithInstrumentation()];
142
+ _pause_decorators = [WithInstrumentation()];
143
+ _delete_decorators = [WithInstrumentation()];
144
+ _waitUntilStarted_decorators = [WithInstrumentation()];
145
+ _waitUntilStopped_decorators = [WithInstrumentation()];
146
+ _refreshData_decorators = [WithInstrumentation()];
147
+ _setAutostopInterval_decorators = [WithInstrumentation()];
148
+ _setAutoArchiveInterval_decorators = [WithInstrumentation()];
149
+ _setAutoDeleteInterval_decorators = [WithInstrumentation()];
137
150
  _updateNetworkSettings_decorators = [WithInstrumentation()];
138
151
  _updateSecrets_decorators = [WithInstrumentation()];
139
152
  _updateEnv_decorators = [WithInstrumentation()];
140
- _getPreviewLink_decorators = [WithInstrumentation(), withEvents];
141
- _getSignedPreviewUrl_decorators = [withEvents];
142
- _expireSignedPreviewUrl_decorators = [withEvents];
143
- _archive_decorators = [WithInstrumentation(), withEvents];
144
- _resize_decorators = [WithInstrumentation(), withEvents];
145
- _waitForResizeComplete_decorators = [WithInstrumentation(), withEvents];
146
- _createSshAccess_decorators = [WithInstrumentation(), withEvents];
147
- _revokeSshAccess_decorators = [WithInstrumentation(), withEvents];
148
- _validateSshAccess_decorators = [WithInstrumentation(), withEvents];
153
+ _getPreviewLink_decorators = [WithInstrumentation()];
154
+ _archive_decorators = [WithInstrumentation()];
155
+ _resize_decorators = [WithInstrumentation()];
156
+ _waitForResizeComplete_decorators = [WithInstrumentation()];
157
+ _createSshAccess_decorators = [WithInstrumentation()];
158
+ _revokeSshAccess_decorators = [WithInstrumentation()];
159
+ _validateSshAccess_decorators = [WithInstrumentation()];
149
160
  __esDecorate(this, null, _getUserHomeDir_decorators, { kind: "method", name: "getUserHomeDir", static: false, private: false, access: { has: obj => "getUserHomeDir" in obj, get: obj => obj.getUserHomeDir }, metadata: _metadata }, null, _instanceExtraInitializers);
150
- __esDecorate(this, null, _getMetricsLatest_decorators, { kind: "method", name: "getMetricsLatest", static: false, private: false, access: { has: obj => "getMetricsLatest" in obj, get: obj => obj.getMetricsLatest }, metadata: _metadata }, null, _instanceExtraInitializers);
151
- __esDecorate(this, null, _getMetrics_decorators, { kind: "method", name: "getMetrics", static: false, private: false, access: { has: obj => "getMetrics" in obj, get: obj => obj.getMetrics }, metadata: _metadata }, null, _instanceExtraInitializers);
152
161
  __esDecorate(this, null, _getUserRootDir_decorators, { kind: "method", name: "getUserRootDir", static: false, private: false, access: { has: obj => "getUserRootDir" in obj, get: obj => obj.getUserRootDir }, metadata: _metadata }, null, _instanceExtraInitializers);
153
162
  __esDecorate(this, null, _getWorkDir_decorators, { kind: "method", name: "getWorkDir", static: false, private: false, access: { has: obj => "getWorkDir" in obj, get: obj => obj.getWorkDir }, metadata: _metadata }, null, _instanceExtraInitializers);
163
+ __esDecorate(this, null, _mountVolume_decorators, { kind: "method", name: "mountVolume", static: false, private: false, access: { has: obj => "mountVolume" in obj, get: obj => obj.mountVolume }, metadata: _metadata }, null, _instanceExtraInitializers);
164
+ __esDecorate(this, null, _pullVolumes_decorators, { kind: "method", name: "pullVolumes", static: false, private: false, access: { has: obj => "pullVolumes" in obj, get: obj => obj.pullVolumes }, metadata: _metadata }, null, _instanceExtraInitializers);
154
165
  __esDecorate(this, null, _createLspServer_decorators, { kind: "method", name: "createLspServer", static: false, private: false, access: { has: obj => "createLspServer" in obj, get: obj => obj.createLspServer }, metadata: _metadata }, null, _instanceExtraInitializers);
155
166
  __esDecorate(this, null, _setLabels_decorators, { kind: "method", name: "setLabels", static: false, private: false, access: { has: obj => "setLabels" in obj, get: obj => obj.setLabels }, metadata: _metadata }, null, _instanceExtraInitializers);
156
167
  __esDecorate(this, null, _start_decorators, { kind: "method", name: "start", static: false, private: false, access: { has: obj => "start" in obj, get: obj => obj.start }, metadata: _metadata }, null, _instanceExtraInitializers);
157
- __esDecorate(this, null, _recover_decorators, { kind: "method", name: "recover", static: false, private: false, access: { has: obj => "recover" in obj, get: obj => obj.recover }, metadata: _metadata }, null, _instanceExtraInitializers);
158
168
  __esDecorate(this, null, _stop_decorators, { kind: "method", name: "stop", static: false, private: false, access: { has: obj => "stop" in obj, get: obj => obj.stop }, metadata: _metadata }, null, _instanceExtraInitializers);
159
169
  __esDecorate(this, null, __experimental_fork_decorators, { kind: "method", name: "_experimental_fork", static: false, private: false, access: { has: obj => "_experimental_fork" in obj, get: obj => obj._experimental_fork }, metadata: _metadata }, null, _instanceExtraInitializers);
160
170
  __esDecorate(this, null, __experimental_createSnapshot_decorators, { kind: "method", name: "_experimental_createSnapshot", static: false, private: false, access: { has: obj => "_experimental_createSnapshot" in obj, get: obj => obj._experimental_createSnapshot }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -163,17 +173,13 @@ let Sandbox = (() => {
163
173
  __esDecorate(this, null, _waitUntilStarted_decorators, { kind: "method", name: "waitUntilStarted", static: false, private: false, access: { has: obj => "waitUntilStarted" in obj, get: obj => obj.waitUntilStarted }, metadata: _metadata }, null, _instanceExtraInitializers);
164
174
  __esDecorate(this, null, _waitUntilStopped_decorators, { kind: "method", name: "waitUntilStopped", static: false, private: false, access: { has: obj => "waitUntilStopped" in obj, get: obj => obj.waitUntilStopped }, metadata: _metadata }, null, _instanceExtraInitializers);
165
175
  __esDecorate(this, null, _refreshData_decorators, { kind: "method", name: "refreshData", static: false, private: false, access: { has: obj => "refreshData" in obj, get: obj => obj.refreshData }, metadata: _metadata }, null, _instanceExtraInitializers);
166
- __esDecorate(this, null, _refreshActivity_decorators, { kind: "method", name: "refreshActivity", static: false, private: false, access: { has: obj => "refreshActivity" in obj, get: obj => obj.refreshActivity }, metadata: _metadata }, null, _instanceExtraInitializers);
167
176
  __esDecorate(this, null, _setAutostopInterval_decorators, { kind: "method", name: "setAutostopInterval", static: false, private: false, access: { has: obj => "setAutostopInterval" in obj, get: obj => obj.setAutostopInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
168
- __esDecorate(this, null, _setAutoPauseInterval_decorators, { kind: "method", name: "setAutoPauseInterval", static: false, private: false, access: { has: obj => "setAutoPauseInterval" in obj, get: obj => obj.setAutoPauseInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
169
177
  __esDecorate(this, null, _setAutoArchiveInterval_decorators, { kind: "method", name: "setAutoArchiveInterval", static: false, private: false, access: { has: obj => "setAutoArchiveInterval" in obj, get: obj => obj.setAutoArchiveInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
170
178
  __esDecorate(this, null, _setAutoDeleteInterval_decorators, { kind: "method", name: "setAutoDeleteInterval", static: false, private: false, access: { has: obj => "setAutoDeleteInterval" in obj, get: obj => obj.setAutoDeleteInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
171
179
  __esDecorate(this, null, _updateNetworkSettings_decorators, { kind: "method", name: "updateNetworkSettings", static: false, private: false, access: { has: obj => "updateNetworkSettings" in obj, get: obj => obj.updateNetworkSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
172
180
  __esDecorate(this, null, _updateSecrets_decorators, { kind: "method", name: "updateSecrets", static: false, private: false, access: { has: obj => "updateSecrets" in obj, get: obj => obj.updateSecrets }, metadata: _metadata }, null, _instanceExtraInitializers);
173
181
  __esDecorate(this, null, _updateEnv_decorators, { kind: "method", name: "updateEnv", static: false, private: false, access: { has: obj => "updateEnv" in obj, get: obj => obj.updateEnv }, metadata: _metadata }, null, _instanceExtraInitializers);
174
182
  __esDecorate(this, null, _getPreviewLink_decorators, { kind: "method", name: "getPreviewLink", static: false, private: false, access: { has: obj => "getPreviewLink" in obj, get: obj => obj.getPreviewLink }, metadata: _metadata }, null, _instanceExtraInitializers);
175
- __esDecorate(this, null, _getSignedPreviewUrl_decorators, { kind: "method", name: "getSignedPreviewUrl", static: false, private: false, access: { has: obj => "getSignedPreviewUrl" in obj, get: obj => obj.getSignedPreviewUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
176
- __esDecorate(this, null, _expireSignedPreviewUrl_decorators, { kind: "method", name: "expireSignedPreviewUrl", static: false, private: false, access: { has: obj => "expireSignedPreviewUrl" in obj, get: obj => obj.expireSignedPreviewUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
177
183
  __esDecorate(this, null, _archive_decorators, { kind: "method", name: "archive", static: false, private: false, access: { has: obj => "archive" in obj, get: obj => obj.archive }, metadata: _metadata }, null, _instanceExtraInitializers);
178
184
  __esDecorate(this, null, _resize_decorators, { kind: "method", name: "resize", static: false, private: false, access: { has: obj => "resize" in obj, get: obj => obj.resize }, metadata: _metadata }, null, _instanceExtraInitializers);
179
185
  __esDecorate(this, null, _waitForResizeComplete_decorators, { kind: "method", name: "waitForResizeComplete", static: false, private: false, access: { has: obj => "waitForResizeComplete" in obj, get: obj => obj.waitForResizeComplete }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -185,8 +191,7 @@ let Sandbox = (() => {
185
191
  clientConfig = __runInitializers(this, _instanceExtraInitializers);
186
192
  axiosInstance;
187
193
  sandboxApi;
188
- getAnalyticsApiUrl;
189
- subscriptionManager;
194
+ volumeService;
190
195
  fs;
191
196
  git;
192
197
  process;
@@ -211,7 +216,6 @@ let Sandbox = (() => {
211
216
  backupState;
212
217
  backupCreatedAt;
213
218
  autoStopInterval;
214
- autoPauseInterval;
215
219
  autoArchiveInterval;
216
220
  autoDeleteInterval;
217
221
  volumes;
@@ -226,28 +230,25 @@ let Sandbox = (() => {
226
230
  toolboxProxyUrl;
227
231
  infoApi;
228
232
  serverApi;
229
- systemApi;
230
- stateWaiters = [];
231
- stateWaiterErrorMessageFns = new WeakMap();
232
- subId;
233
233
  /**
234
- * Creates a new Sandbox instance.
235
- *
236
- * Internal: obtain sandboxes via {@link Daytona.create}, {@link Daytona.get}, or
237
- * {@link Daytona.list} rather than constructing directly.
234
+ * Creates a new Sandbox instance
238
235
  *
239
236
  * @param {SandboxDto} sandboxDto - The API Sandbox instance
240
- * @param {SandboxApi} sandboxApi - API client for Sandbox operations
241
- * @param {InfoApi} infoApi - API client for info operations
242
- * @param {EventSubscriptionManager} subscriptionManager - Event subscription manager for real-time updates
243
237
  */
244
- constructor(sandboxDto, clientConfig, axiosInstance, sandboxApi, getAnalyticsApiUrl, subscriptionManager) {
238
+ constructor(sandboxDto, clientConfig, axiosInstance, sandboxApi, volumeService) {
245
239
  this.clientConfig = clientConfig;
246
240
  this.axiosInstance = axiosInstance;
247
241
  this.sandboxApi = sandboxApi;
248
- this.getAnalyticsApiUrl = getAnalyticsApiUrl;
249
- this.subscriptionManager = subscriptionManager;
242
+ this.volumeService = volumeService;
250
243
  this.processSandboxDto(sandboxDto);
244
+ // Set the toolbox base URL
245
+ let baseUrl = this.toolboxProxyUrl;
246
+ if (!baseUrl.endsWith('/')) {
247
+ baseUrl += '/';
248
+ }
249
+ this.axiosInstance.defaults.baseURL = baseUrl + this.id;
250
+ this.clientConfig.basePath = this.axiosInstance.defaults.baseURL;
251
+ // Initialize Services
251
252
  const getPreviewToken = async () => (await this.getPreviewLink(1)).token;
252
253
  this.fs = new FileSystem(this.clientConfig, new FileSystemApi(this.clientConfig, '', this.axiosInstance));
253
254
  this.git = new Git(new GitApi(this.clientConfig, '', this.axiosInstance));
@@ -257,8 +258,6 @@ let Sandbox = (() => {
257
258
  this.computerUse = new ComputerUse(new ComputerUseApi(this.clientConfig, '', this.axiosInstance));
258
259
  this.infoApi = new InfoApi(this.clientConfig, '', this.axiosInstance);
259
260
  this.serverApi = new ServerApi(this.clientConfig, '', this.axiosInstance);
260
- this.systemApi = new SystemApi(this.clientConfig, '', this.axiosInstance);
261
- this.subscribeToEvents();
262
261
  }
263
262
  /**
264
263
  * Gets the user's home directory path for the logged in user inside the Sandbox.
@@ -273,53 +272,6 @@ let Sandbox = (() => {
273
272
  const response = await this.infoApi.getUserHomeDir();
274
273
  return response.data.dir;
275
274
  }
276
- /**
277
- * Gets the most recent resource usage sample directly from the sandbox daemon.
278
- *
279
- * Unlike {@link getMetrics}, which returns aggregated historical samples, this returns the
280
- * single current reading without going through the telemetry backend.
281
- *
282
- * @returns The current resource usage sample for the sandbox.
283
- *
284
- * @example
285
- * const m = await sandbox.getMetricsLatest()
286
- * console.log(`CPU: ${m.cpuUsedPct}%, mem: ${m.memUsed}/${m.memTotal}`)
287
- */
288
- async getMetricsLatest() {
289
- const response = await this.systemApi.getSystemMetrics();
290
- return sandboxMetricsFromSystemMetrics(response.data);
291
- }
292
- /**
293
- * Gets historical time-series resource usage metrics for the Sandbox.
294
- *
295
- * @param {Date} [start] - Start of the time range. Defaults to the Sandbox creation time.
296
- * @param {Date} [end] - End of the time range. Defaults to the current time.
297
- * @returns Time-ordered usage samples over the requested range.
298
- *
299
- * @example
300
- * const samples = await sandbox.getMetrics()
301
- * for (const s of samples) {
302
- * console.log(`${s.timestamp.toISOString()} CPU: ${s.cpuUsedPct}% mem: ${s.memUsed}/${s.memTotal}`)
303
- * }
304
- */
305
- async getMetrics(start, end) {
306
- const to = end ?? new Date();
307
- const from = start ?? (this.createdAt ? new Date(this.createdAt) : to);
308
- const analyticsApiUrl = await this.getAnalyticsApiUrl();
309
- if (analyticsApiUrl) {
310
- const response = await this.buildAnalyticsTelemetryApi(analyticsApiUrl).organizationOrganizationIdSandboxSandboxIdTelemetryMetricsGet(this.organizationId, this.id, from.toISOString(), to.toISOString(), SANDBOX_METRIC_NAMES.join(','));
311
- return pivotSandboxMetricPoints(response.data);
312
- }
313
- const response = await this.sandboxApi.getSandboxMetrics(this.id, from, to, undefined, SANDBOX_METRIC_NAMES);
314
- return pivotSandboxMetrics(response.data.series);
315
- }
316
- buildAnalyticsTelemetryApi(analyticsApiUrl) {
317
- const analyticsConfig = new AnalyticsConfiguration({
318
- basePath: analyticsApiUrl,
319
- apiKey: this.clientConfig.baseOptions?.headers?.Authorization,
320
- });
321
- return new AnalyticsTelemetryApi(analyticsConfig, undefined, Daytona.createAxiosInstance());
322
- }
323
275
  /**
324
276
  * @deprecated Use `getUserHomeDir` instead. This method will be removed in a future version.
325
277
  */
@@ -340,6 +292,71 @@ let Sandbox = (() => {
340
292
  const response = await this.infoApi.getWorkDir();
341
293
  return response.data.dir;
342
294
  }
295
+ /**
296
+ * Mounts a hotmount Volume into the running Sandbox on the fly.
297
+ *
298
+ * Unlike legacy and blockmount volumes (which are attached at Sandbox creation), hotmount
299
+ * volumes are mounted at runtime: this method requests a short-lived mount token from the API
300
+ * and bootstraps the hotmount agent inside the Sandbox (downloading and running the region's
301
+ * `init.sh`), mounting the filesystem at the given path.
302
+ *
303
+ * The Sandbox must have `/dev/fuse` available, outbound access to the region gateway (ports
304
+ * 443 and 18443) and binaries bucket, and passwordless sudo (or run as root).
305
+ *
306
+ * @param {Volume} volume - The hotmount Volume to mount. Must be of type `VolumeType.HOTMOUNT`.
307
+ * @param {string} mountPath - The absolute path inside the Sandbox to mount the Volume at
308
+ * @returns {Promise<void>}
309
+ * @throws {DaytonaError} If the Volume is not a hotmount volume, or if mounting fails
310
+ *
311
+ * @example
312
+ * const volume = await daytona.volume.get("shared-fs");
313
+ * await sandbox.mountVolume(volume, "/mnt/shared");
314
+ */
315
+ async mountVolume(volume, mountPath) {
316
+ if (volume.type !== VolumeType.HOTMOUNT) {
317
+ throw new DaytonaValidationError(`Only hotmount volumes can be mounted on the fly. Volume '${volume.name}' is of type '${volume.type}'.`);
318
+ }
319
+ if (!this.volumeService) {
320
+ throw new DaytonaError('Volume service is not available for this Sandbox instance.');
321
+ }
322
+ const mountToken = await this.volumeService.getMountToken(volume);
323
+ const command = buildHotmountMountCommand(mountToken, mountPath);
324
+ const response = await this.process.executeCommand(command);
325
+ if (response.exitCode !== 0) {
326
+ throw new DaytonaError(`Failed to mount hotmount volume '${volume.name}': ${response.result}`);
327
+ }
328
+ }
329
+ /**
330
+ * Pulls the latest state of the Sandbox's blockmount Volumes into the running Sandbox.
331
+ *
332
+ * Blockmount volumes reconcile in the background: each sandbox writes to a private scratch
333
+ * that is committed to the shared store periodically, but other sandboxes' commits only
334
+ * appear locally on re-materialize. This method makes them appear immediately, without
335
+ * stopping the Sandbox: it commits this Sandbox's local changes (so they participate in the
336
+ * merge), then applies the volume's latest merged state in place. Files modified locally
337
+ * after another sandbox's change keep the local version (last-change-wins by mtime).
338
+ *
339
+ * @param {Volume} [volume] - The blockmount Volume to pull. Omit to pull every blockmount
340
+ * Volume attached to the Sandbox.
341
+ * @returns {Promise<VolumePullResult[]>} Per-volume pull results
342
+ * @throws {DaytonaError} If the Sandbox has no matching mounted blockmount Volume
343
+ *
344
+ * @example
345
+ * // sandbox B picks up what sandbox A committed, while both keep running
346
+ * const results = await sandbox.pullVolumes();
347
+ * console.log(results); // [{ volumeId, upToDate, filesWritten, deleted, ... }]
348
+ */
349
+ async pullVolumes(volume) {
350
+ if (volume && volume.type !== VolumeType.BLOCKMOUNT) {
351
+ throw new DaytonaValidationError(`Only blockmount volumes can be pulled. Volume '${volume.name}' is of type '${volume.type}'.`);
352
+ }
353
+ // Raw toolbox call (the daemon's generated client has no pull op — the runner intercepts
354
+ // this path), so replicate what the generated clients do: resolve against this sandbox's
355
+ // clientConfig.basePath (the shared axios instance's default baseURL is mutated by every
356
+ // Sandbox construction and may point at another sandbox) and inject the auth headers.
357
+ const response = await this.axiosInstance.post(`${this.clientConfig.basePath}/volumes/pull`, volume ? { volumeId: volume.id } : {}, { headers: this.clientConfig.baseOptions?.headers });
358
+ return response.data.results ?? [];
359
+ }
343
360
  /**
344
361
  * Creates a new Language Server Protocol (LSP) server instance.
345
362
  *
@@ -477,16 +494,9 @@ let Sandbox = (() => {
477
494
  timeout: timeout * 1000,
478
495
  });
479
496
  const sandboxDto = response.data;
480
- const sandboxWithProxyUrl = sandboxDto.toolboxProxyUrl
481
- ? sandboxDto
482
- : {
483
- ...sandboxDto,
484
- toolboxProxyUrl: (await this.sandboxApi.getToolboxProxyUrl(sandboxDto.id)).data.url,
485
- };
486
- const forkedSandbox = new Sandbox(sandboxWithProxyUrl, structuredClone(this.clientConfig), Daytona.createAxiosInstance(), this.sandboxApi, this.getAnalyticsApiUrl, this.subscriptionManager);
497
+ const forkedSandbox = new Sandbox(sandboxDto, structuredClone(this.clientConfig), Daytona.createAxiosInstance(), this.sandboxApi);
487
498
  const timeElapsed = Date.now() - startTime;
488
- const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
489
- await forkedSandbox.waitUntilStarted(remainingTimeout);
499
+ await forkedSandbox.waitUntilStarted(timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout);
490
500
  return forkedSandbox;
491
501
  }
492
502
  /**
@@ -514,19 +524,34 @@ let Sandbox = (() => {
514
524
  }
515
525
  const startTime = Date.now();
516
526
  const req = { name };
517
- const response = await this.sandboxApi.createSandboxSnapshot(this.id, req, undefined, {
527
+ await this.sandboxApi.createSandboxSnapshot(this.id, req, undefined, {
518
528
  timeout: timeout * 1000,
519
529
  });
520
- this.processSandboxDto(response.data);
530
+ await this.refreshData();
521
531
  const timeElapsed = Date.now() - startTime;
522
532
  const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
523
533
  await this.waitForSnapshotComplete(remainingTimeout);
524
534
  }
525
535
  async waitForSnapshotComplete(timeout) {
526
- const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
527
- const excludeStates = new Set([SandboxState.SNAPSHOTTING, ...errorStates]);
528
- const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
529
- return this.waitForState(targetStates, errorStates, timeout, 'Sandbox snapshot did not complete within the timeout period', (state) => `Sandbox ${this.id} snapshot failed with state: ${state}, error reason: ${this.errorReason}`);
536
+ let checkInterval = 100;
537
+ const startTime = Date.now();
538
+ while (this.state === SandboxState.SNAPSHOTTING) {
539
+ await this.refreshData();
540
+ // @ts-expect-error this.refreshData() can modify this.state so this check is fine
541
+ if (this.state === SandboxState.ERROR || this.state === SandboxState.BUILD_FAILED) {
542
+ throw new DaytonaError(`Sandbox ${this.id} snapshot failed with state: ${this.state}, error reason: ${this.errorReason}`);
543
+ }
544
+ if (this.state !== SandboxState.SNAPSHOTTING) {
545
+ return;
546
+ }
547
+ if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
548
+ throw new DaytonaTimeoutError('Sandbox snapshot did not complete within the timeout period');
549
+ }
550
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
551
+ if (Date.now() - startTime > 5000) {
552
+ checkInterval = Math.min(checkInterval * 1.1, 1000);
553
+ }
554
+ }
530
555
  }
531
556
  /**
532
557
  * Pauses the Sandbox, freezing all running processes.
@@ -557,48 +582,32 @@ let Sandbox = (() => {
557
582
  await this.refreshData();
558
583
  const timeElapsed = Date.now() - startTime;
559
584
  const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
560
- // Main's contract: pause completes when the sandbox has *left* PAUSING
561
- // (paused, stopped, archived, ...), not only on exactly PAUSED.
562
- const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
563
- const excludeStates = new Set([SandboxState.PAUSING, ...errorStates]);
564
- const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
565
- await this.waitForState(targetStates, errorStates, remainingTimeout, 'Sandbox failed to become paused within the timeout period', (state) => `Sandbox ${this.id} pause failed with state: ${state}, error reason: ${this.errorReason}`);
585
+ await this.waitForPauseComplete(remainingTimeout);
566
586
  }
567
- /**
568
- * Deletes the Sandbox.
569
- *
570
- * By default this returns as soon as the deletion request is accepted (matching
571
- * historical behavior). Pass `wait = true` to block until the Sandbox reaches
572
- * the 'destroyed' state.
573
- *
574
- * @param {number} [timeout] - Timeout in seconds for the request — and, when
575
- * `wait` is true, for reaching 'destroyed'. 0 means no timeout.
576
- * Defaults to 60-second timeout.
577
- * @param {boolean} [wait] - If true, wait until the Sandbox is destroyed. Defaults to false.
578
- * @returns {Promise<void>}
579
- */
580
- async delete(timeout = 60, wait = false) {
581
- if (timeout < 0) {
582
- throw new DaytonaValidationError('Timeout must be a non-negative number');
583
- }
587
+ async waitForPauseComplete(timeout) {
588
+ let checkInterval = 100;
584
589
  const startTime = Date.now();
585
- const response = await this.sandboxApi.deleteSandbox(this.id, undefined, { timeout: timeout * 1000 });
586
- if (response.data) {
587
- this.processSandboxDto(response.data);
588
- }
589
- try {
590
- if (wait && this.state !== SandboxState.DESTROYED) {
591
- const timeElapsed = Date.now() - startTime;
592
- await this.waitForState([SandboxState.DESTROYED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout, 'Sandbox failed to be destroyed within the timeout period', (state) => `Sandbox ${this.id} failed to delete with state: ${state}, error reason: ${this.errorReason}`, true);
590
+ while (this.state === SandboxState.PAUSING) {
591
+ await this.refreshData();
592
+ // @ts-expect-error this.refreshData() can modify this.state so this check is fine
593
+ if (this.state === SandboxState.ERROR) {
594
+ throw new DaytonaError(`Sandbox ${this.id} pause failed with state: ${this.state}, error reason: ${this.errorReason}`);
593
595
  }
594
- }
595
- finally {
596
- if (this.subId) {
597
- this.subscriptionManager.unsubscribe(this.subId);
598
- this.subId = undefined;
596
+ if (timeout > 0 && (Date.now() - startTime) / 1000 >= timeout) {
597
+ throw new DaytonaError(`Sandbox ${this.id} failed to pause within ${timeout} seconds`);
599
598
  }
599
+ await new Promise((resolve) => globalThis.setTimeout(resolve, checkInterval));
600
+ checkInterval = Math.min(checkInterval * 1.5, 1000);
600
601
  }
601
602
  }
603
+ /**
604
+ * Deletes the Sandbox.
605
+ * @returns {Promise<void>}
606
+ */
607
+ async delete(timeout = 60) {
608
+ await this.sandboxApi.deleteSandbox(this.id, undefined, { timeout: timeout * 1000 });
609
+ this.refreshDataSafe();
610
+ }
602
611
  /**
603
612
  * Waits for the Sandbox to reach the 'started' state.
604
613
  *
@@ -614,10 +623,26 @@ let Sandbox = (() => {
614
623
  if (timeout < 0) {
615
624
  throw new DaytonaValidationError('Timeout must be a non-negative number');
616
625
  }
617
- if (this.state === SandboxState.STARTED) {
618
- return;
626
+ let checkInterval = 100;
627
+ const startTime = Date.now();
628
+ while (this.state !== 'started') {
629
+ await this.refreshData();
630
+ // @ts-expect-error this.refreshData() can modify this.state so this check is fine
631
+ if (this.state === 'started') {
632
+ return;
633
+ }
634
+ if (this.state === 'error') {
635
+ const errMsg = `Sandbox ${this.id} failed to start with status: ${this.state}, error reason: ${this.errorReason}`;
636
+ throw new DaytonaError(errMsg);
637
+ }
638
+ if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
639
+ throw new DaytonaTimeoutError('Sandbox failed to become ready within the timeout period');
640
+ }
641
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
642
+ if (Date.now() - startTime > 5000) {
643
+ checkInterval = Math.min(checkInterval * 1.1, 1000);
644
+ }
619
645
  }
620
- return this.waitForState([SandboxState.STARTED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout, 'Sandbox failed to become ready within the timeout period', (state) => `Sandbox ${this.id} failed to start with status: ${state}, error reason: ${this.errorReason}`);
621
646
  }
622
647
  /**
623
648
  * Wait for Sandbox to reach 'stopped' state.
@@ -634,11 +659,27 @@ let Sandbox = (() => {
634
659
  if (timeout < 0) {
635
660
  throw new DaytonaValidationError('Timeout must be a non-negative number');
636
661
  }
662
+ let checkInterval = 100;
663
+ const startTime = Date.now();
637
664
  // Treat destroyed as stopped to cover ephemeral sandboxes that are automatically deleted after stopping
638
- if (this.state === SandboxState.STOPPED || this.state === SandboxState.DESTROYED) {
639
- return;
665
+ while (this.state !== 'stopped' && this.state !== 'destroyed') {
666
+ this.refreshDataSafe();
667
+ // @ts-expect-error this.refreshData() can modify this.state so this check is fine
668
+ if (this.state === 'stopped' || this.state === 'destroyed') {
669
+ return;
670
+ }
671
+ if (this.state === 'error') {
672
+ const errMsg = `Sandbox failed to stop with status: ${this.state}, error reason: ${this.errorReason}`;
673
+ throw new DaytonaError(errMsg);
674
+ }
675
+ if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
676
+ throw new DaytonaTimeoutError('Sandbox failed to become stopped within the timeout period');
677
+ }
678
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
679
+ if (Date.now() - startTime > 5000) {
680
+ checkInterval = Math.min(checkInterval * 1.1, 1000);
681
+ }
640
682
  }
641
- return this.waitForState([SandboxState.STOPPED, SandboxState.DESTROYED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout, 'Sandbox failed to become stopped within the timeout period', (state) => `Sandbox failed to stop with status: ${state}, error reason: ${this.errorReason}`, true);
642
683
  }
643
684
  /**
644
685
  * Refreshes the Sandbox data from the API.
@@ -695,36 +736,6 @@ let Sandbox = (() => {
695
736
  await this.sandboxApi.setAutostopInterval(this.id, interval);
696
737
  this.autoStopInterval = interval;
697
738
  }
698
- /**
699
- * Set the auto-pause interval for the Sandbox.
700
- *
701
- * The Sandbox will automatically pause after being idle (no new events) for the specified interval.
702
- * Events include any state changes or interactions with the Sandbox through the sdk.
703
- * Interactions using Sandbox Previews are not included.
704
- *
705
- * Only supported for sandbox classes that support pausing. At most one of the auto-stop
706
- * and auto-pause intervals may be non-zero, so disable auto-stop first by setting its
707
- * interval to 0.
708
- *
709
- * @param {number} interval - Number of minutes of inactivity before auto-pausing.
710
- * Set to 0 to disable auto-pause. For pause-supporting sandbox
711
- * classes, creation defaults to 60 minutes when neither interval is provided.
712
- * @returns {Promise<void>}
713
- * @throws {DaytonaError} - `DaytonaError` - If interval is not a non-negative integer
714
- *
715
- * @example
716
- * // Auto-pause after 1 hour
717
- * await sandbox.setAutoPauseInterval(60);
718
- * // Or disable auto-pause
719
- * await sandbox.setAutoPauseInterval(0);
720
- */
721
- async setAutoPauseInterval(interval) {
722
- if (!Number.isInteger(interval) || interval < 0) {
723
- throw new DaytonaValidationError('autoPauseInterval must be a non-negative integer');
724
- }
725
- await this.sandboxApi.setAutoPauseInterval(this.id, interval);
726
- this.autoPauseInterval = interval;
727
- }
728
739
  /**
729
740
  * Set the auto-archive interval for the Sandbox.
730
741
  *
@@ -947,13 +958,25 @@ let Sandbox = (() => {
947
958
  if (timeout < 0) {
948
959
  throw new DaytonaValidationError('Timeout must be a non-negative number');
949
960
  }
950
- if (this.state !== SandboxState.RESIZING) {
951
- return;
961
+ let checkInterval = 100;
962
+ const startTime = Date.now();
963
+ while (this.state === SandboxState.RESIZING) {
964
+ await this.refreshData();
965
+ // @ts-expect-error this.refreshData() can modify this.state so this check is fine
966
+ if (this.state === SandboxState.ERROR || this.state === SandboxState.BUILD_FAILED) {
967
+ throw new DaytonaError(`Sandbox ${this.id} resize failed with state: ${this.state}, error reason: ${this.errorReason}`);
968
+ }
969
+ if (this.state !== SandboxState.RESIZING) {
970
+ return;
971
+ }
972
+ if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
973
+ throw new DaytonaTimeoutError('Sandbox resize did not complete within the timeout period');
974
+ }
975
+ await new Promise((resolve) => setTimeout(resolve, checkInterval));
976
+ if (Date.now() - startTime > 5000) {
977
+ checkInterval = Math.min(checkInterval * 1.1, 1000);
978
+ }
952
979
  }
953
- const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
954
- const excludeStates = new Set([SandboxState.RESIZING, ...errorStates]);
955
- const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
956
- return this.waitForState(targetStates, errorStates, timeout, 'Sandbox resize did not complete within the timeout period', (state) => `Sandbox ${this.id} resize failed with state: ${state}, error reason: ${this.errorReason}`);
957
980
  }
958
981
  /**
959
982
  * Creates an SSH access token for the sandbox.
@@ -982,172 +1005,6 @@ let Sandbox = (() => {
982
1005
  async validateSshAccess(token) {
983
1006
  return (await this.sandboxApi.validateSshAccess(token)).data;
984
1007
  }
985
- /**
986
- * Subscribes to real-time events for this sandbox.
987
- * Auto-updates sandbox metadata on every event.
988
- */
989
- subscribeToEvents() {
990
- if (this.subId) {
991
- return;
992
- }
993
- this.subId = this.subscriptionManager.subscribe(this.id, this.handleEvent.bind(this), [
994
- 'sandbox.state.updated',
995
- 'sandbox.created',
996
- ]);
997
- }
998
- ensureSubscribed() {
999
- if (this.subId) {
1000
- if (this.subscriptionManager.refresh(this.subId)) {
1001
- return;
1002
- }
1003
- this.subId = undefined;
1004
- }
1005
- this.subscribeToEvents();
1006
- }
1007
- handleEvent(eventName, data) {
1008
- if (!data || typeof data !== 'object')
1009
- return;
1010
- const raw = data.sandbox ?? data;
1011
- if (!raw || typeof raw !== 'object')
1012
- return;
1013
- if (eventName === 'sandbox.created') {
1014
- this.processSandboxDto(raw);
1015
- return;
1016
- }
1017
- const newState = raw.state ?? data.newState;
1018
- if (newState) {
1019
- this.applyState(newState);
1020
- }
1021
- }
1022
- /**
1023
- * Waits for the sandbox to reach one of the target states.
1024
- * Throws on error states or timeout.
1025
- *
1026
- * @param targetStates - States that indicate success.
1027
- * @param errorStates - States that indicate failure.
1028
- * @param timeout - Maximum time to wait in seconds. 0 means no timeout.
1029
- * @param timeoutMessage - Error message when timeout is reached.
1030
- * @param errorMessageFn - Function that produces an error message from the current state.
1031
- * @param safeRefresh - If true, use refreshDataSafe() for polling (for delete operations where 404 is expected).
1032
- */
1033
- waitForState(targetStates, errorStates, timeout, timeoutMessage, errorMessageFn, safeRefresh = false) {
1034
- this.ensureSubscribed();
1035
- return new Promise((resolve, reject) => {
1036
- let timeoutTimer = null;
1037
- let pollTimer = null;
1038
- let settled = false;
1039
- const waiter = {
1040
- targetStates: new Set(targetStates),
1041
- errorStates: new Set(errorStates),
1042
- resolve: (_) => {
1043
- if (settled)
1044
- return;
1045
- cleanup();
1046
- resolve();
1047
- },
1048
- reject: (err) => {
1049
- if (settled)
1050
- return;
1051
- cleanup();
1052
- reject(err);
1053
- },
1054
- };
1055
- this.stateWaiters.push(waiter);
1056
- this.stateWaiterErrorMessageFns.set(waiter, errorMessageFn);
1057
- const cleanup = () => {
1058
- if (settled)
1059
- return;
1060
- settled = true;
1061
- if (timeoutTimer)
1062
- clearTimeout(timeoutTimer);
1063
- if (pollTimer)
1064
- clearTimeout(pollTimer);
1065
- this.removeStateWaiter(waiter);
1066
- };
1067
- // Fast-path only on cached *target* states (parity with main's pre-check).
1068
- // Cached error states are deliberately NOT evaluated here — main always
1069
- // refreshed before failing, so a stale ERROR must survive one refresh.
1070
- if (this.state && waiter.targetStates.has(this.state)) {
1071
- waiter.resolve(this.state);
1072
- return;
1073
- }
1074
- if (timeout !== 0) {
1075
- timeoutTimer = setTimeout(() => {
1076
- void (async () => {
1077
- if (settled)
1078
- return;
1079
- // Parity with main: complete one final refresh-then-evaluate before
1080
- // rejecting, so a clamped/short timeout still observes the latest state.
1081
- let refreshed = false;
1082
- try {
1083
- if (safeRefresh) {
1084
- refreshed = await this.refreshDataSafe();
1085
- }
1086
- else {
1087
- await this.refreshData();
1088
- refreshed = true;
1089
- }
1090
- }
1091
- catch {
1092
- // fall through to the timeout rejection below
1093
- }
1094
- if (!settled) {
1095
- // Explicit evaluation: applyState() no-ops on unchanged state, so a
1096
- // persistent error state would otherwise time out generically here.
1097
- // Only evaluate when the refresh succeeded — a failed refresh leaves
1098
- // the cached state stale and must not be treated as authoritative.
1099
- if (refreshed && this.checkStateWaiter(waiter, this.state)) {
1100
- return;
1101
- }
1102
- cleanup();
1103
- reject(new DaytonaTimeoutError(timeoutMessage));
1104
- }
1105
- })();
1106
- }, timeout * 1000);
1107
- }
1108
- // Poll as a safety net for missed state changes. With an active event subscription
1109
- // a sparse 1s cadence suffices; without events (polling mode) replicate main's
1110
- // cadence exactly: 100ms steady for the first 5s, then exponential backoff capped at 1s.
1111
- const streaming = !!this.subId;
1112
- const pollStart = Date.now();
1113
- let pollInterval = streaming ? 1000 : 100;
1114
- const doPoll = async () => {
1115
- if (settled)
1116
- return;
1117
- let refreshed = false;
1118
- try {
1119
- if (safeRefresh) {
1120
- refreshed = await this.refreshDataSafe();
1121
- }
1122
- else {
1123
- await this.refreshData();
1124
- refreshed = true;
1125
- }
1126
- }
1127
- catch (error) {
1128
- waiter.reject(error instanceof Error ? error : new DaytonaError(String(error)));
1129
- return;
1130
- }
1131
- if (!settled) {
1132
- // Evaluate the refreshed state explicitly: applyState() no-ops when the
1133
- // state is unchanged, so a persistent error state would otherwise never
1134
- // reject the waiter. Only evaluate when the refresh succeeded — a failed
1135
- // safe refresh leaves the cached state stale, so keep polling instead.
1136
- if (refreshed && this.checkStateWaiter(waiter, this.state)) {
1137
- return;
1138
- }
1139
- if (!streaming && Date.now() - pollStart > 5000) {
1140
- pollInterval = Math.min(pollInterval * 1.1, 1000);
1141
- }
1142
- pollTimer = setTimeout(() => {
1143
- void doPoll();
1144
- }, pollInterval);
1145
- }
1146
- };
1147
- // First poll runs immediately (main refreshed before any state evaluation).
1148
- void doPoll();
1149
- });
1150
- }
1151
1008
  /**
1152
1009
  * Assigns the API sandbox data to the Sandbox object.
1153
1010
  *
@@ -1155,7 +1012,6 @@ let Sandbox = (() => {
1155
1012
  * @returns {void}
1156
1013
  */
1157
1014
  processSandboxDto(sandboxDto) {
1158
- const newState = sandboxDto.state;
1159
1015
  this.id = sandboxDto.id;
1160
1016
  this.name = sandboxDto.name;
1161
1017
  this.organizationId = sandboxDto.organizationId;
@@ -1168,16 +1024,17 @@ let Sandbox = (() => {
1168
1024
  this.gpu = sandboxDto.gpu;
1169
1025
  this.memory = sandboxDto.memory;
1170
1026
  this.disk = sandboxDto.disk;
1027
+ this.state = sandboxDto.state;
1171
1028
  this.errorReason = sandboxDto.errorReason;
1172
1029
  this.recoverable = sandboxDto.recoverable;
1173
1030
  this.backupState = sandboxDto.backupState;
1174
1031
  this.autoStopInterval = sandboxDto.autoStopInterval;
1175
- this.autoPauseInterval = sandboxDto.autoPauseInterval;
1176
1032
  this.autoArchiveInterval = sandboxDto.autoArchiveInterval;
1177
1033
  this.autoDeleteInterval = sandboxDto.autoDeleteInterval;
1178
1034
  this.createdAt = sandboxDto.createdAt;
1179
1035
  this.updatedAt = sandboxDto.updatedAt;
1180
1036
  this.lastActivityAt = sandboxDto.lastActivityAt;
1037
+ this.toolboxProxyUrl = sandboxDto.toolboxProxyUrl;
1181
1038
  // Fields only present in the full SandboxDto (not returned by list endpoint)
1182
1039
  if ('env' in sandboxDto) {
1183
1040
  this.env = sandboxDto.env;
@@ -1189,21 +1046,6 @@ let Sandbox = (() => {
1189
1046
  this.buildInfo = sandboxDto.buildInfo;
1190
1047
  this.backupCreatedAt = sandboxDto.backupCreatedAt;
1191
1048
  }
1192
- const newProxyUrl = sandboxDto.toolboxProxyUrl;
1193
- if (newProxyUrl && newProxyUrl !== this.toolboxProxyUrl && this.axiosInstance) {
1194
- let baseUrl = newProxyUrl;
1195
- if (!baseUrl.endsWith('/')) {
1196
- baseUrl += '/';
1197
- }
1198
- this.axiosInstance.defaults.baseURL = baseUrl + this.id;
1199
- this.clientConfig.basePath = this.axiosInstance.defaults.baseURL;
1200
- }
1201
- if (newProxyUrl) {
1202
- this.toolboxProxyUrl = newProxyUrl;
1203
- }
1204
- if (newState) {
1205
- this.applyState(newState);
1206
- }
1207
1049
  }
1208
1050
  /**
1209
1051
  * Refreshes the Sandbox data from the API, but does not throw an error if the sandbox has been deleted.
@@ -1211,113 +1053,17 @@ let Sandbox = (() => {
1211
1053
  *
1212
1054
  * @returns {Promise<void>}
1213
1055
  */
1214
- /**
1215
- * @returns true when the refresh produced authoritative state (success, or 404
1216
- * mapped to DESTROYED); false when a transient error was swallowed and
1217
- * the cached state is stale.
1218
- */
1219
1056
  async refreshDataSafe() {
1220
1057
  try {
1221
1058
  await this.refreshData();
1222
- return true;
1223
1059
  }
1224
1060
  catch (error) {
1225
1061
  if (error instanceof DaytonaNotFoundError) {
1226
- this.applyState(SandboxState.DESTROYED);
1227
- return true;
1062
+ this.state = SandboxState.DESTROYED;
1228
1063
  }
1229
- // Other errors are deliberately swallowed (parity with main): transient
1230
- // failures (e.g. 502) mid-poll must not abort stop()/delete() waits.
1231
- return false;
1232
- }
1233
- }
1234
- applyState(newState) {
1235
- if (newState === this.state) {
1236
- return;
1237
- }
1238
- this.state = newState;
1239
- for (const waiter of [...this.stateWaiters]) {
1240
- this.checkStateWaiter(waiter, this.state);
1241
- }
1242
- }
1243
- checkStateWaiter(waiter, state) {
1244
- if (!state) {
1245
- return false;
1246
- }
1247
- if (waiter.targetStates.has(state)) {
1248
- waiter.resolve(state);
1249
- return true;
1250
- }
1251
- if (waiter.errorStates.has(state)) {
1252
- const errorMessageFn = this.stateWaiterErrorMessageFns.get(waiter);
1253
- waiter.reject(new DaytonaError(errorMessageFn ? errorMessageFn(state) : `Sandbox ${this.id} failed with state: ${state}`));
1254
- return true;
1255
1064
  }
1256
- return false;
1257
- }
1258
- removeStateWaiter(waiter) {
1259
- const index = this.stateWaiters.indexOf(waiter);
1260
- if (index !== -1) {
1261
- this.stateWaiters.splice(index, 1);
1262
- }
1263
- this.stateWaiterErrorMessageFns.delete(waiter);
1264
1065
  }
1265
1066
  };
1266
1067
  })();
1267
1068
  export { Sandbox };
1268
- const SANDBOX_METRIC_FIELD_BY_NAME = {
1269
- 'daytona.sandbox.cpu.utilization': 'cpuUsedPct',
1270
- 'daytona.sandbox.cpu.limit': 'cpuCount',
1271
- 'daytona.sandbox.memory.usage': 'memUsed',
1272
- 'daytona.sandbox.memory.limit': 'memTotal',
1273
- 'daytona.sandbox.memory.cache': 'memCache',
1274
- 'daytona.sandbox.filesystem.usage': 'diskUsed',
1275
- 'daytona.sandbox.filesystem.total': 'diskTotal',
1276
- };
1277
- const SANDBOX_METRIC_NAMES = Object.keys(SANDBOX_METRIC_FIELD_BY_NAME);
1278
- function sandboxMetricsFromSystemMetrics(m) {
1279
- return {
1280
- cpuCount: m.cpuCount ?? 0,
1281
- cpuUsedPct: m.cpuUsedPct ?? 0,
1282
- diskTotal: m.diskTotal ?? 0,
1283
- diskUsed: m.diskUsed ?? 0,
1284
- memTotal: m.memTotal ?? 0,
1285
- memUsed: m.memUsed ?? 0,
1286
- memCache: m.memCache ?? 0,
1287
- timestamp: m.timestamp ? new Date(m.timestamp) : new Date(),
1288
- };
1289
- }
1290
- function buildSandboxMetrics(buckets) {
1291
- return [...buckets.keys()].sort().map((ts) => {
1292
- const v = buckets.get(ts);
1293
- return {
1294
- cpuCount: v.cpuCount ?? 0,
1295
- cpuUsedPct: v.cpuUsedPct ?? 0,
1296
- diskTotal: v.diskTotal ?? 0,
1297
- diskUsed: v.diskUsed ?? 0,
1298
- memTotal: v.memTotal ?? 0,
1299
- memUsed: v.memUsed ?? 0,
1300
- memCache: v.memCache ?? 0,
1301
- timestamp: new Date(ts),
1302
- };
1303
- });
1304
- }
1305
- function pivotMetricTriples(triples) {
1306
- const buckets = new Map();
1307
- for (const [name, timestamp, value] of triples) {
1308
- const field = name ? SANDBOX_METRIC_FIELD_BY_NAME[name] : undefined;
1309
- if (!field || timestamp === undefined || value === undefined)
1310
- continue;
1311
- const bucket = buckets.get(timestamp) ?? {};
1312
- bucket[field] = value;
1313
- buckets.set(timestamp, bucket);
1314
- }
1315
- return buildSandboxMetrics(buckets);
1316
- }
1317
- function pivotSandboxMetrics(series) {
1318
- return pivotMetricTriples((series ?? []).flatMap((s) => (s.dataPoints ?? []).map((p) => [s.metricName, p.timestamp, p.value])));
1319
- }
1320
- function pivotSandboxMetricPoints(points) {
1321
- return pivotMetricTriples((points ?? []).map((p) => [p.metricName, p.timestamp, p.value]));
1322
- }
1323
1069
  //# sourceMappingURL=Sandbox.js.map