@watasu/sdk 0.1.51 → 0.1.66

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/git.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { InvalidArgumentError } from './errors.js';
1
2
  /** Git helper backed by sandbox data-plane routes. */
2
3
  export class Git {
3
4
  dataPlane;
@@ -162,6 +163,31 @@ export class Git {
162
163
  }, opts);
163
164
  return String(result.value ?? '');
164
165
  }
166
+ async getRemoteUrl(path, remote, opts = {}) {
167
+ const url = await this.remoteGet(path, remote, opts);
168
+ if (!url) {
169
+ throw new InvalidArgumentError(`Remote "${remote}" URL not found in repository.`);
170
+ }
171
+ return url;
172
+ }
173
+ async resolveRemoteName(path, remote, opts = {}) {
174
+ if (remote)
175
+ return remote;
176
+ const status = await this.status(path, opts);
177
+ const upstreamRemote = status.upstream?.split('/')[0];
178
+ if (upstreamRemote)
179
+ return upstreamRemote;
180
+ throw new InvalidArgumentError('Remote is required when the repository has no upstream remote.');
181
+ }
182
+ async hasUpstream(path, opts = {}) {
183
+ try {
184
+ const status = await this.status(path, opts);
185
+ return Boolean(status.upstream);
186
+ }
187
+ catch {
188
+ return false;
189
+ }
190
+ }
165
191
  async run(path, json, opts) {
166
192
  const payload = await this.dataPlane.postJson(path, {
167
193
  json: compact(json),
@@ -173,7 +199,7 @@ export class Git {
173
199
  }
174
200
  function gitOpts(opts) {
175
201
  return {
176
- env_vars: opts.envs,
202
+ envs: opts.envs,
177
203
  user: opts.user,
178
204
  cwd: opts.cwd,
179
205
  timeout_seconds: opts.timeoutMs === undefined ? undefined : Math.ceil(opts.timeoutMs / 1000),
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export type { CommandConnectOpts, CommandRequestOpts, CommandResult, CommandStar
12
12
  export { Process, ProcessManager, ProcessMessage, ProcessOutput } from './process.js';
13
13
  export type { ProcessOpts } from './process.js';
14
14
  export { FileType, Filesystem, FilesystemEventType, FilesystemWatcher, WatchHandle, } from './filesystem.js';
15
- export type { EntryInfo, FilesystemEvent, FilesystemReadOpts, FilesystemRequestOpts, FilesystemWriteOpts, WatchOpts, WriteData, WriteEntry, WriteInfo, } from './filesystem.js';
15
+ export type { ApplyDiffFailure, ApplyDiffFailedHunk, ApplyDiffFileSummary, ApplyDiffOpts, ApplyDiffReport, ApplyDiffSummary, EntryInfo, FilesystemEvent, FilesystemReadOpts, FilesystemRequestOpts, FilesystemWriteOpts, WatchOpts, WriteData, WriteEntry, WriteInfo, } from './filesystem.js';
16
16
  export { Git } from './git.js';
17
17
  export type { GitAddOpts, GitAuthOpts, GitBranches, GitBranchOpts, GitCloneOpts, GitCommandResult, GitConfigScope, GitConfigOpts, GitConfigureUserOpts, GitCredentialOpts, GitCommitOpts, GitDangerouslyAuthenticateOpts, GitDeleteBranchOpts, GitFileStatus, GitInitOpts, GitPullOpts, GitPushOpts, GitRemoteAddOpts, GitResetMode, GitResetOpts, GitRestoreOpts, GitRequestOpts, GitStatus, } from './git.js';
18
18
  export { Pty } from './pty.js';
package/dist/pty.d.ts CHANGED
@@ -1,16 +1,21 @@
1
- import { CommandHandle, CommandStartOpts } from './commands.js';
2
- import { ConnectionConfig, type ConnectionOpts } from './connectionConfig.js';
1
+ import { CommandHandle } from './commands.js';
2
+ import { ConnectionConfig, type ConnectionOpts, type Username } from './connectionConfig.js';
3
3
  import { DataPlaneClient } from './transport.js';
4
4
  export interface PtySize {
5
5
  cols: number;
6
6
  rows: number;
7
7
  }
8
- export interface PtyCreateOpts extends Omit<CommandStartOpts, 'background' | 'onStdout' | 'onStderr'> {
8
+ export interface PtyCreateOpts extends Pick<ConnectionOpts, 'requestTimeoutMs' | 'signal'> {
9
9
  cols?: number;
10
10
  rows?: number;
11
11
  size?: PtySize;
12
12
  cmd?: string;
13
+ cwd?: string;
14
+ user?: Username;
15
+ envs?: Record<string, string>;
16
+ timeoutMs?: number;
13
17
  onData?: (data: Uint8Array) => void | Promise<void>;
18
+ onPty?: (data: Uint8Array) => void | Promise<void>;
14
19
  }
15
20
  export interface PtyConnectOpts {
16
21
  onData?: (data: Uint8Array) => void | Promise<void>;
package/dist/sandbox.d.ts CHANGED
@@ -81,31 +81,55 @@ export interface SandboxListOpts extends ConnectionOpts {
81
81
  /** Team slug to list within. */
82
82
  team?: string;
83
83
  }
84
- type SandboxRequestOpts = Pick<ConnectionOpts, 'requestTimeoutMs' | 'signal'>;
85
84
  export interface SandboxInfo {
86
85
  sandboxId: string;
87
86
  templateId?: string;
88
87
  name?: string;
89
88
  state?: string;
89
+ cpuCount?: number;
90
+ memoryMB?: number;
91
+ envdVersion?: string;
92
+ allowInternetAccess?: boolean;
93
+ network?: SandboxNetworkInfo;
94
+ sandboxDomain?: string;
90
95
  lifecycle?: SandboxInfoLifecycle;
91
96
  volumeMounts?: Array<{
92
97
  name: string;
93
98
  path: string;
94
99
  }>;
95
100
  metadata: Record<string, string>;
96
- startedAt?: string;
97
- endAt?: string;
101
+ startedAt?: Date;
102
+ endAt?: Date;
98
103
  }
99
104
  export interface SandboxInfoLifecycle {
100
105
  onTimeout: 'kill' | 'pause' | string;
101
106
  autoResume: boolean;
102
107
  }
108
+ type SandboxConnectionDetails = {
109
+ sandboxId: string;
110
+ sandboxDomain: string | undefined;
111
+ envdVersion: string;
112
+ envdAccessToken: string | undefined;
113
+ trafficAccessToken: string | undefined;
114
+ connectionConfig: ConnectionConfig;
115
+ control: ControlClient;
116
+ session: unknown;
117
+ sandbox: Record<string, unknown>;
118
+ envs?: Record<string, string>;
119
+ };
103
120
  export interface SandboxMetrics {
121
+ timestamp?: Date;
104
122
  sandboxId?: string;
105
123
  state?: string;
106
124
  node?: string;
107
125
  backend?: string;
108
126
  cpuCount?: number;
127
+ cpuUsedPct?: number;
128
+ memUsed?: number;
129
+ memTotal?: number;
130
+ memCache?: number;
131
+ diskUsed?: number;
132
+ diskTotal?: number;
109
133
  memoryMb?: number;
110
134
  raw: Record<string, unknown>;
111
135
  }
@@ -117,6 +141,7 @@ export interface SandboxMetricsOpts extends ConnectionOpts {
117
141
  }
118
142
  export interface SnapshotInfo {
119
143
  snapshotId: string;
144
+ names: string[];
120
145
  sandboxId?: string;
121
146
  name?: string;
122
147
  status?: string;
@@ -196,22 +221,28 @@ export declare class SandboxPaginator {
196
221
  /** Running Watasu sandbox with ready `files` and `commands` helpers. */
197
222
  export declare class Sandbox {
198
223
  /** Default template slug used when create is called without a template. */
199
- static readonly defaultTemplate: string;
224
+ protected static readonly defaultTemplate: string;
200
225
  /** Default template slug used by MCP creation once Watasu supports it. */
201
- static readonly defaultMcpTemplate: string;
226
+ protected static readonly defaultMcpTemplate: string;
202
227
  /** Default sandbox lifetime in milliseconds. */
203
- static readonly defaultSandboxTimeoutMs = 300000;
204
- files: Filesystem;
205
- commands: Commands;
206
- process: ProcessManager;
207
- pty: Pty;
208
- terminal: TerminalManager;
209
- git: Git;
228
+ protected static readonly defaultSandboxTimeoutMs = 300000;
229
+ readonly files: Filesystem;
230
+ readonly commands: Commands;
231
+ readonly process: ProcessManager;
232
+ readonly pty: Pty;
233
+ readonly terminal: TerminalManager;
234
+ readonly git: Git;
210
235
  cwd: string | undefined;
211
236
  readonly sandboxId: string;
212
- private readonly mcpPort;
213
- private mcpToken;
214
- private readonly config;
237
+ readonly sandboxDomain: string;
238
+ readonly trafficAccessToken?: string;
239
+ protected readonly mcpPort = 50005;
240
+ protected readonly envdPort = 49983;
241
+ protected readonly connectionConfig: ConnectionConfig;
242
+ protected readonly envdAccessToken?: string;
243
+ private readonly envdApiUrl;
244
+ private readonly envdDirectUrl;
245
+ private mcpToken?;
215
246
  private readonly control;
216
247
  private readonly envs;
217
248
  private dataPlane;
@@ -226,10 +257,13 @@ export declare class Sandbox {
226
257
  });
227
258
  /** Unique sandbox identifier. */
228
259
  get id(): string;
229
- static create(opts?: SandboxCreateOpts): Promise<Sandbox>;
230
- static create(template: string, opts?: SandboxCreateOpts): Promise<Sandbox>;
260
+ static create<S extends typeof Sandbox>(this: S, opts?: SandboxOpts): Promise<InstanceType<S>>;
261
+ static create<S extends typeof Sandbox>(this: S, template: string, opts?: SandboxOpts): Promise<InstanceType<S>>;
262
+ protected static createSandbox(template: string, timeoutMs: number, opts?: SandboxCreateOpts): Promise<SandboxConnectionDetails>;
263
+ protected static createSandbox(template: string | undefined, timeoutMs: number, opts?: SandboxCreateOpts): Promise<SandboxConnectionDetails>;
231
264
  /** Connect to an existing sandbox and return it with a fresh data-plane session. */
232
- static connect(sandboxId: string, opts?: SandboxConnectOpts): Promise<Sandbox>;
265
+ static connect<S extends typeof Sandbox>(this: S, sandboxId: string, opts?: SandboxConnectOpts): Promise<InstanceType<S>>;
266
+ protected static connectSandbox(sandboxId: string, opts?: SandboxConnectOpts): Promise<SandboxConnectionDetails>;
233
267
  /** Refresh this sandbox's data-plane session in place. */
234
268
  connect(opts?: SandboxConnectOpts): Promise<this>;
235
269
  /** Resume a paused sandbox by id. */
@@ -239,7 +273,7 @@ export declare class Sandbox {
239
273
  /** Pause a sandbox by id. */
240
274
  static pause(sandboxId: string, opts?: ConnectionOpts): Promise<boolean>;
241
275
  /** Destroy a sandbox by id. */
242
- static kill(sandboxId: string, opts?: ConnectionOpts | string): Promise<boolean>;
276
+ static kill(sandboxId: string, opts?: ConnectionOpts): Promise<boolean>;
243
277
  /** Fetch sandbox metrics by id. */
244
278
  static getMetrics(sandboxId: string, opts?: SandboxMetricsOpts): Promise<SandboxMetrics[]>;
245
279
  /** Atomically replace a sandbox's network egress policy by id. */
@@ -252,19 +286,19 @@ export declare class Sandbox {
252
286
  /** Delete a snapshot by id. Returns `false` when the snapshot does not exist. */
253
287
  static deleteSnapshot(snapshotId: string, opts?: ConnectionOpts): Promise<boolean>;
254
288
  /** Destroy this sandbox. */
255
- kill(opts?: SandboxRequestOpts): Promise<boolean>;
289
+ kill(opts?: Pick<SandboxOpts, 'requestTimeoutMs' | 'signal'>): Promise<boolean>;
256
290
  /** Check if this sandbox is in a runtime-active lifecycle state. */
257
- isRunning(opts?: SandboxRequestOpts): Promise<boolean>;
291
+ isRunning(opts?: Pick<ConnectionOpts, 'requestTimeoutMs' | 'signal'>): Promise<boolean>;
258
292
  /** Set a sandbox's lifetime by id. */
259
293
  static setTimeout(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
260
294
  /** Set this sandbox's lifetime. */
261
- setTimeout(timeoutMs: number, opts?: SandboxRequestOpts): Promise<void>;
295
+ setTimeout(timeoutMs: number, opts?: Pick<SandboxOpts, 'requestTimeoutMs' | 'signal'>): Promise<void>;
262
296
  /** Fetch control-plane metadata for a sandbox by id. */
263
297
  static getInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
264
298
  /** Fetch full control-plane metadata for a sandbox by id. */
265
299
  static getFullInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
266
300
  /** Fetch the latest control-plane metadata for this sandbox. */
267
- getInfo(opts?: SandboxRequestOpts): Promise<SandboxInfo>;
301
+ getInfo(opts?: Pick<SandboxOpts, 'requestTimeoutMs' | 'signal'>): Promise<SandboxInfo>;
268
302
  /** Fetch latest sandbox metrics. */
269
303
  getMetrics(opts?: SandboxMetricsOpts): Promise<SandboxMetrics[]>;
270
304
  /** Create a Watasu checkpoint using snapshot naming. */
@@ -276,7 +310,7 @@ export declare class Sandbox {
276
310
  /** Restore a checkpoint into a new sandbox and return its control-plane info. */
277
311
  restore(opts?: RestoreSnapshotOpts | string | number): Promise<SandboxInfo>;
278
312
  /** Return a paginator for sandboxes visible to the configured API key. */
279
- static list(opts?: SandboxListOpts | string): SandboxPaginator;
313
+ static list(opts?: SandboxListOpts): SandboxPaginator;
280
314
  /** Return the public hostname for an exposed sandbox port. */
281
315
  getHost(port: number): string;
282
316
  /** Return the conventional MCP URL for this sandbox. */
@@ -292,7 +326,7 @@ export declare class Sandbox {
292
326
  /** Get signed download URL metadata for a sandbox file path. */
293
327
  downloadUrlInfo(path: string, opts?: SandboxUrlOpts): Promise<FileUrlInfo>;
294
328
  /** Atomically replace this sandbox's network egress policy. */
295
- updateNetwork(network: SandboxNetworkUpdate, opts?: SandboxNetworkUpdateOpts): Promise<void>;
329
+ updateNetwork(network: SandboxNetworkUpdate, opts?: Pick<SandboxOpts, 'requestTimeoutMs' | 'signal'>): Promise<void>;
296
330
  /** Pause this sandbox. Returns false when it was already paused. */
297
331
  betaPause(opts?: ConnectionOpts): Promise<boolean>;
298
332
  /** Pause this sandbox. Returns false when it was already paused. */
@@ -300,12 +334,14 @@ export declare class Sandbox {
300
334
  /** Resume this sandbox and refresh its data-plane session. */
301
335
  resume(opts?: SandboxConnectOpts): Promise<boolean>;
302
336
  private fileUrl;
337
+ /** Base URL for the sandbox data-plane runtime API. */
338
+ protected get runtimeBaseUrl(): string;
303
339
  /** POST JSON to the sandbox data-plane runtime API. */
304
340
  protected runtimePostJson(path: string, json: Record<string, unknown>, opts?: ConnectionOpts): Promise<Record<string, unknown>>;
305
341
  /** GET JSON from the sandbox data-plane runtime API. */
306
342
  protected runtimeGetJson(path: string, opts?: ConnectionOpts): Promise<Record<string, unknown>>;
307
343
  /** DELETE JSON from the sandbox data-plane runtime API. */
308
344
  protected runtimeDeleteJson(path: string, opts?: ConnectionOpts): Promise<Record<string, unknown>>;
309
- private configOptions;
345
+ private resolveApiOpts;
310
346
  }
311
347
  export {};
package/dist/sandbox.js CHANGED
@@ -101,25 +101,37 @@ export class Sandbox {
101
101
  git;
102
102
  cwd;
103
103
  sandboxId;
104
+ sandboxDomain;
105
+ trafficAccessToken;
104
106
  mcpPort = 50005;
107
+ envdPort = 49983;
108
+ connectionConfig;
109
+ envdAccessToken;
110
+ envdApiUrl;
111
+ envdDirectUrl;
105
112
  mcpToken;
106
- config;
107
113
  control;
108
114
  envs;
109
115
  dataPlane;
110
116
  sandbox;
111
117
  constructor(opts) {
112
118
  this.sandboxId = String(opts.sandboxId);
113
- this.config = opts.connectionConfig;
114
- this.control = opts.control ?? new ControlClient(this.config);
119
+ this.connectionConfig = opts.connectionConfig;
120
+ this.control = opts.control ?? new ControlClient(this.connectionConfig);
115
121
  this.envs = opts.envs ?? {};
116
122
  this.sandbox = opts.sandbox ?? {};
117
- const dataPlane = dataPlaneFromSession(opts.session, this.config);
123
+ const session = record(opts.session);
124
+ this.sandboxDomain = stringValue(session.sandbox_domain ?? session.sandboxDomain ?? this.sandbox.sandbox_domain ?? this.sandbox.domain) ?? '';
125
+ this.trafficAccessToken = stringValue(session.traffic_access_token ?? session.trafficAccessToken ?? this.sandbox.traffic_access_token ?? this.sandbox.trafficAccessToken);
126
+ this.envdAccessToken = stringValue(session.envd_access_token ?? session.envdAccessToken ?? session.token);
127
+ const dataPlane = dataPlaneFromSession(opts.session, this.connectionConfig);
118
128
  this.dataPlane = dataPlane;
129
+ this.envdApiUrl = dataPlane.baseUrl;
130
+ this.envdDirectUrl = dataPlane.baseUrl;
119
131
  this.files = new Filesystem(dataPlane);
120
- this.commands = new Commands(dataPlane, this.config, this.envs);
132
+ this.commands = new Commands(dataPlane, this.connectionConfig, this.envs);
121
133
  this.process = new ProcessManager(this.commands);
122
- this.pty = new Pty(dataPlane, this.config);
134
+ this.pty = new Pty(dataPlane, this.connectionConfig);
123
135
  this.terminal = new TerminalManager(this.pty);
124
136
  this.git = new Git(dataPlane);
125
137
  }
@@ -133,42 +145,62 @@ export class Sandbox {
133
145
  const template = typeof templateOrOpts === 'string'
134
146
  ? templateOrOpts
135
147
  : templateOrOpts?.template ?? (sandboxOpts.mcp === undefined ? this.defaultTemplate : undefined);
136
- const config = new ConnectionConfig(sandboxOpts);
148
+ const sandboxInfo = await this.createSandbox(template, sandboxOpts.timeoutMs ?? this.defaultSandboxTimeoutMs, sandboxOpts);
149
+ return new this({
150
+ sandboxId: sandboxInfo.sandboxId,
151
+ connectionConfig: sandboxInfo.connectionConfig,
152
+ control: sandboxInfo.control,
153
+ session: sandboxInfo.session,
154
+ sandbox: sandboxInfo.sandbox,
155
+ envs: sandboxInfo.envs,
156
+ });
157
+ }
158
+ static async createSandbox(template, timeoutMs, opts = {}) {
159
+ const config = new ConnectionConfig(opts);
137
160
  const control = new ControlClient(config);
138
161
  const sandboxPayload = {
139
- timeout: Math.ceil((sandboxOpts.timeoutMs ?? 300_000) / 1000),
140
- metadata: sandboxOpts.metadata ?? {},
141
- env_vars: sandboxOpts.envs ?? {},
142
- secure: sandboxOpts.secure ?? true,
143
- allow_internet_access: sandboxOpts.allowInternetAccess ?? true,
162
+ timeout: Math.ceil(timeoutMs / 1000),
163
+ metadata: opts.metadata ?? {},
164
+ envs: opts.envs ?? {},
165
+ secure: opts.secure ?? true,
166
+ allow_internet_access: opts.allowInternetAccess ?? true,
144
167
  };
145
- putIfPresent(sandboxPayload, 'template_id', template);
146
- putIfPresent(sandboxPayload, 'mcp', sandboxOpts.mcp);
147
- putIfPresent(sandboxPayload, 'lifecycle', lifecyclePayload(sandboxOpts.lifecycle));
148
- putIfPresent(sandboxPayload, 'volume_mounts', volumeMountsPayload(sandboxOpts.volumeMounts));
149
- Object.assign(sandboxPayload, networkUpdatePayload(sandboxOpts.network));
150
- putIfPresent(sandboxPayload, 'team', sandboxOpts.team);
168
+ putIfPresent(sandboxPayload, 'template', template);
169
+ putIfPresent(sandboxPayload, 'mcp', opts.mcp);
170
+ putIfPresent(sandboxPayload, 'lifecycle', lifecyclePayload(opts.lifecycle));
171
+ putIfPresent(sandboxPayload, 'volume_mounts', volumeMountsPayload(opts.volumeMounts));
172
+ Object.assign(sandboxPayload, networkUpdatePayload(opts.network));
173
+ putIfPresent(sandboxPayload, 'team', opts.team);
151
174
  const response = await control.post('/sandboxes', {
152
175
  json: sandboxPayload,
153
- requestTimeoutMs: sessionOperationRequestTimeout(config, sandboxOpts),
154
- signal: sandboxOpts.signal,
176
+ requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
177
+ signal: opts.signal,
155
178
  });
156
179
  const sandbox = record(response.sandbox ?? response);
157
180
  const sandboxId = sandbox.id ?? sandbox.sandbox_id;
158
181
  if (sandboxId === undefined)
159
182
  throw new SandboxError('create response did not include sandbox id');
160
- const sandboxInstance = new this({
183
+ return sandboxConnectionDetails({
161
184
  sandboxId: String(sandboxId),
162
- connectionConfig: config,
185
+ config,
163
186
  control,
164
187
  session: response.session,
165
188
  sandbox,
166
- envs: sandboxOpts.envs,
189
+ envs: opts.envs,
167
190
  });
168
- return sandboxInstance;
169
191
  }
170
192
  /** Connect to an existing sandbox and return it with a fresh data-plane session. */
171
193
  static async connect(sandboxId, opts = {}) {
194
+ const sandboxInfo = await this.connectSandbox(sandboxId, opts);
195
+ return new this({
196
+ sandboxId: sandboxInfo.sandboxId,
197
+ connectionConfig: sandboxInfo.connectionConfig,
198
+ control: sandboxInfo.control,
199
+ session: sandboxInfo.session,
200
+ sandbox: sandboxInfo.sandbox,
201
+ });
202
+ }
203
+ static async connectSandbox(sandboxId, opts = {}) {
172
204
  const config = new ConnectionConfig(opts);
173
205
  const control = new ControlClient(config);
174
206
  const info = await control.get(`/sandboxes/${sandboxId}`, {
@@ -180,9 +212,9 @@ export class Sandbox {
180
212
  requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
181
213
  signal: opts.signal,
182
214
  });
183
- return new this({
215
+ return sandboxConnectionDetails({
184
216
  sandboxId,
185
- connectionConfig: config,
217
+ config,
186
218
  control,
187
219
  session: response.session,
188
220
  sandbox: record(response.sandbox ?? info.sandbox ?? {}),
@@ -192,18 +224,19 @@ export class Sandbox {
192
224
  async connect(opts = {}) {
193
225
  const response = await this.control.post(`/sandboxes/${this.sandboxId}/resume`, {
194
226
  json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
195
- requestTimeoutMs: sessionOperationRequestTimeout(this.config, opts),
227
+ requestTimeoutMs: sessionOperationRequestTimeout(this.connectionConfig, opts),
196
228
  signal: opts.signal,
197
229
  });
198
230
  this.sandbox = record(response.sandbox ?? this.sandbox);
199
- const dataPlane = dataPlaneFromSession(response.session, this.config);
231
+ const dataPlane = dataPlaneFromSession(response.session, this.connectionConfig);
232
+ const mutable = this;
200
233
  this.dataPlane = dataPlane;
201
- this.files = new Filesystem(dataPlane);
202
- this.commands = new Commands(dataPlane, this.config, this.envs);
203
- this.process = new ProcessManager(this.commands);
204
- this.pty = new Pty(dataPlane, this.config);
205
- this.terminal = new TerminalManager(this.pty);
206
- this.git = new Git(dataPlane);
234
+ mutable.files = new Filesystem(dataPlane);
235
+ mutable.commands = new Commands(dataPlane, this.connectionConfig, this.envs);
236
+ mutable.process = new ProcessManager(this.commands);
237
+ mutable.pty = new Pty(dataPlane, this.connectionConfig);
238
+ mutable.terminal = new TerminalManager(this.pty);
239
+ mutable.git = new Git(dataPlane);
207
240
  return this;
208
241
  }
209
242
  /** Resume a paused sandbox by id. */
@@ -233,11 +266,10 @@ export class Sandbox {
233
266
  }
234
267
  /** Destroy a sandbox by id. */
235
268
  static async kill(sandboxId, opts = {}) {
236
- const requestOpts = typeof opts === 'string' ? {} : opts;
237
- const control = new ControlClient(new ConnectionConfig(typeof opts === 'string' ? { apiKey: opts } : opts));
269
+ const control = new ControlClient(new ConnectionConfig(opts));
238
270
  await control.delete(`/sandboxes/${sandboxId}`, {
239
- requestTimeoutMs: requestOpts.requestTimeoutMs,
240
- signal: requestOpts.signal,
271
+ requestTimeoutMs: opts.requestTimeoutMs,
272
+ signal: opts.signal,
241
273
  });
242
274
  return true;
243
275
  }
@@ -357,19 +389,19 @@ export class Sandbox {
357
389
  }
358
390
  /** Fetch latest sandbox metrics. */
359
391
  async getMetrics(opts = {}) {
360
- return Sandbox.getMetrics(this.sandboxId, { ...this.configOptions(), ...opts });
392
+ return Sandbox.getMetrics(this.sandboxId, { ...this.resolveApiOpts(), ...opts });
361
393
  }
362
394
  /** Create a Watasu checkpoint using snapshot naming. */
363
395
  async createSnapshot(opts = {}) {
364
- return Sandbox.createSnapshot(this.sandboxId, { ...this.configOptions(), ...opts });
396
+ return Sandbox.createSnapshot(this.sandboxId, { ...this.resolveApiOpts(), ...opts });
365
397
  }
366
398
  /** Delete a snapshot by id. */
367
399
  async deleteSnapshot(snapshotId, opts = {}) {
368
- return Sandbox.deleteSnapshot(snapshotId, { ...this.configOptions(), ...opts });
400
+ return Sandbox.deleteSnapshot(snapshotId, { ...this.resolveApiOpts(), ...opts });
369
401
  }
370
402
  /** List checkpoints for this sandbox using snapshot naming. */
371
403
  listSnapshots(opts = {}) {
372
- return Sandbox.listSnapshots({ ...this.configOptions(), ...opts, sandboxId: this.sandboxId });
404
+ return Sandbox.listSnapshots({ ...this.resolveApiOpts(), ...opts, sandboxId: this.sandboxId });
373
405
  }
374
406
  /** Restore a checkpoint into a new sandbox and return its control-plane info. */
375
407
  async restore(opts = {}) {
@@ -391,17 +423,16 @@ export class Sandbox {
391
423
  }
392
424
  /** Return a paginator for sandboxes visible to the configured API key. */
393
425
  static list(opts = {}) {
394
- const listOpts = typeof opts === 'string' ? { apiKey: opts } : opts;
395
- return new SandboxPaginator(listOpts);
426
+ return new SandboxPaginator(opts);
396
427
  }
397
428
  /** Return the public hostname for an exposed sandbox port. */
398
429
  getHost(port) {
399
430
  const routeToken = this.sandbox.route_token ??
400
431
  this.sandbox.routeToken ??
401
- routeTokenFromDataPlaneUrl(this.dataPlane.baseUrl, this.config.dataPlaneDomain);
432
+ routeTokenFromDataPlaneUrl(this.dataPlane.baseUrl, this.connectionConfig.dataPlaneDomain);
402
433
  if (typeof routeToken !== 'string')
403
434
  throw new SandboxError('port response did not include host or url');
404
- return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
435
+ return `p${port}-${routeToken}.sandbox.${this.connectionConfig.dataPlaneDomain}`;
405
436
  }
406
437
  /** Return the conventional MCP URL for this sandbox. */
407
438
  getMcpUrl() {
@@ -442,12 +473,12 @@ export class Sandbox {
442
473
  }
443
474
  /** Atomically replace this sandbox's network egress policy. */
444
475
  async updateNetwork(network, opts = {}) {
445
- const sandbox = await Sandbox.putNetwork(this.sandboxId, network, { ...this.configOptions(), ...opts });
476
+ const sandbox = await Sandbox.putNetwork(this.sandboxId, network, { ...this.resolveApiOpts(), ...opts });
446
477
  this.sandbox = sandbox ?? this.sandbox;
447
478
  }
448
479
  /** Pause this sandbox. Returns false when it was already paused. */
449
480
  async betaPause(opts = {}) {
450
- return Sandbox.betaPause(this.sandboxId, { ...this.configOptions(), ...opts });
481
+ return Sandbox.betaPause(this.sandboxId, { ...this.resolveApiOpts(), ...opts });
451
482
  }
452
483
  /** Pause this sandbox. Returns false when it was already paused. */
453
484
  async pause(opts = {}) {
@@ -471,6 +502,10 @@ export class Sandbox {
471
502
  });
472
503
  return fileUrlInfo(record(payload.file_url ?? payload));
473
504
  }
505
+ /** Base URL for the sandbox data-plane runtime API. */
506
+ get runtimeBaseUrl() {
507
+ return this.dataPlane.baseUrl;
508
+ }
474
509
  /** POST JSON to the sandbox data-plane runtime API. */
475
510
  async runtimePostJson(path, json, opts = {}) {
476
511
  return this.dataPlane.postJson(path, {
@@ -493,18 +528,18 @@ export class Sandbox {
493
528
  signal: opts.signal,
494
529
  });
495
530
  }
496
- configOptions() {
531
+ resolveApiOpts() {
497
532
  return {
498
- apiKey: this.config.apiKey,
499
- apiUrl: this.config.apiUrl,
500
- sandboxUrl: this.config.sandboxUrl,
501
- dataPlaneDomain: this.config.dataPlaneDomain,
502
- requestTimeoutMs: this.config.requestTimeoutMs,
503
- headers: this.config.headers,
504
- apiHeaders: this.config.apiHeaders,
505
- debug: this.config.debug,
506
- signal: this.config.signal,
507
- proxy: this.config.proxy,
533
+ apiKey: this.connectionConfig.apiKey,
534
+ apiUrl: this.connectionConfig.apiUrl,
535
+ sandboxUrl: this.connectionConfig.sandboxUrl,
536
+ dataPlaneDomain: this.connectionConfig.dataPlaneDomain,
537
+ requestTimeoutMs: this.connectionConfig.requestTimeoutMs,
538
+ headers: this.connectionConfig.headers,
539
+ apiHeaders: this.connectionConfig.apiHeaders,
540
+ debug: this.connectionConfig.debug,
541
+ signal: this.connectionConfig.signal,
542
+ proxy: this.connectionConfig.proxy,
508
543
  };
509
544
  }
510
545
  }
@@ -567,6 +602,21 @@ function fileUrlInfo(payload) {
567
602
  raw: payload,
568
603
  };
569
604
  }
605
+ function sandboxConnectionDetails(opts) {
606
+ const session = record(opts.session);
607
+ return {
608
+ sandboxId: opts.sandboxId,
609
+ sandboxDomain: stringValue(session.sandbox_domain ?? session.sandboxDomain ?? opts.sandbox.sandbox_domain ?? opts.sandbox.domain),
610
+ envdVersion: stringValue(session.envd_version ?? session.envdVersion ?? opts.sandbox.envd_version ?? opts.sandbox.envdVersion) ?? '',
611
+ envdAccessToken: stringValue(session.envd_access_token ?? session.envdAccessToken ?? session.token),
612
+ trafficAccessToken: stringValue(session.traffic_access_token ?? session.trafficAccessToken ?? opts.sandbox.traffic_access_token ?? opts.sandbox.trafficAccessToken),
613
+ connectionConfig: opts.config,
614
+ control: opts.control,
615
+ session: opts.session,
616
+ sandbox: opts.sandbox,
617
+ envs: opts.envs,
618
+ };
619
+ }
570
620
  function compactRecord(payload) {
571
621
  return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== undefined));
572
622
  }
@@ -581,15 +631,17 @@ function sandboxInfo(payload) {
581
631
  templateId: typeof payload.template_id === 'string' ? payload.template_id : templateSlug(payload.template),
582
632
  name: typeof payload.name === 'string' ? payload.name : undefined,
583
633
  state: typeof payload.state === 'string' ? payload.state : undefined,
634
+ cpuCount: numberValue(payload.cpu_count ?? payload.cpuCount),
635
+ memoryMB: numberValue(payload.memory_mb ?? payload.memoryMB ?? payload.memoryMb),
636
+ envdVersion: stringValue(payload.envd_version ?? payload.envdVersion),
637
+ allowInternetAccess: booleanValue(payload.allow_internet_access ?? payload.allowInternetAccess),
638
+ network: sandboxNetworkInfo(payload.network),
639
+ sandboxDomain: stringValue(payload.sandbox_domain ?? payload.sandboxDomain),
584
640
  lifecycle: sandboxLifecycleInfo(payload.lifecycle),
585
641
  volumeMounts: volumeMountsInfo(payload.volume_mounts ?? payload.volumeMounts),
586
642
  metadata: recordOfStrings(payload.metadata),
587
- startedAt: typeof payload.started_at === 'string'
588
- ? payload.started_at
589
- : typeof payload.created_at === 'string' ? payload.created_at : undefined,
590
- endAt: typeof payload.end_at === 'string'
591
- ? payload.end_at
592
- : typeof payload.deadline_at === 'string' ? payload.deadline_at : undefined,
643
+ startedAt: dateValue(payload.started_at ?? payload.startedAt ?? payload.created_at ?? payload.createdAt ?? payload.ready_at ?? payload.readyAt),
644
+ endAt: dateValue(payload.end_at ?? payload.endAt ?? payload.deadline_at ?? payload.deadlineAt),
593
645
  };
594
646
  }
595
647
  function lifecyclePayload(lifecycle) {
@@ -631,6 +683,10 @@ function sandboxLifecycleInfo(value) {
631
683
  autoResume: autoResume ?? false,
632
684
  };
633
685
  }
686
+ function sandboxNetworkInfo(value) {
687
+ const item = record(value);
688
+ return Object.keys(item).length > 0 ? item : undefined;
689
+ }
634
690
  function metricsList(value) {
635
691
  if (Array.isArray(value))
636
692
  return value.map((item) => metricsInfo(record(item)));
@@ -638,11 +694,18 @@ function metricsList(value) {
638
694
  }
639
695
  function metricsInfo(value) {
640
696
  return {
697
+ timestamp: dateValue(value.timestamp ?? value.time ?? value.created_at ?? value.createdAt),
641
698
  sandboxId: stringValue(value.sandbox_id ?? value.sandboxId),
642
699
  state: stringValue(value.state),
643
700
  node: stringValue(value.node),
644
701
  backend: stringValue(value.backend),
645
702
  cpuCount: numberValue(value.cpu_count ?? value.cpuCount),
703
+ cpuUsedPct: numberValue(value.cpu_used_pct ?? value.cpuUsedPct ?? value.cpu_pct ?? value.cpuPct),
704
+ memUsed: numberValue(value.mem_used ?? value.memUsed ?? value.memory_used ?? value.memoryUsed),
705
+ memTotal: numberValue(value.mem_total ?? value.memTotal ?? value.memory_total ?? value.memoryTotal),
706
+ memCache: numberValue(value.mem_cache ?? value.memCache ?? value.memory_cache ?? value.memoryCache),
707
+ diskUsed: numberValue(value.disk_used ?? value.diskUsed),
708
+ diskTotal: numberValue(value.disk_total ?? value.diskTotal),
646
709
  memoryMb: numberValue(value.memory_mb ?? value.memoryMb),
647
710
  raw: value,
648
711
  };
@@ -659,10 +722,12 @@ function snapshotInfo(value) {
659
722
  const id = value.snapshot_id ?? value.snapshotId ?? value.checkpoint_id ?? value.checkpointId ?? value.id;
660
723
  if (id === undefined)
661
724
  throw new SandboxError('snapshot response did not include id');
725
+ const name = stringValue(value.name);
662
726
  return {
663
727
  snapshotId: String(id),
728
+ names: arrayOfStrings(value.names ?? value.name ?? value.snapshot_names ?? value.snapshotNames),
664
729
  sandboxId: stringValue(value.sandbox_id ?? value.sandboxId),
665
- name: stringValue(value.name),
730
+ name,
666
731
  status: stringValue(value.status),
667
732
  sizeBytes: numberValue(value.size_bytes ?? value.sizeBytes),
668
733
  createdAt: stringValue(value.created_at ?? value.createdAt),
@@ -677,9 +742,26 @@ function stringValue(value) {
677
742
  return String(value);
678
743
  return undefined;
679
744
  }
745
+ function arrayOfStrings(value) {
746
+ if (Array.isArray(value))
747
+ return value.map(String);
748
+ if (value === undefined || value === null || value === '')
749
+ return [];
750
+ return [String(value)];
751
+ }
680
752
  function numberValue(value) {
681
753
  return typeof value === 'number' ? value : undefined;
682
754
  }
755
+ function dateValue(value) {
756
+ if (value instanceof Date)
757
+ return value;
758
+ if (typeof value === 'string' || typeof value === 'number') {
759
+ const date = new Date(value);
760
+ if (!Number.isNaN(date.getTime()))
761
+ return date;
762
+ }
763
+ return undefined;
764
+ }
683
765
  function booleanValue(value) {
684
766
  if (typeof value === 'boolean')
685
767
  return value;