@watasu/sdk 0.1.53 → 0.1.67

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
@@ -156,6 +156,15 @@ await sbx.files.writeFiles([
156
156
  { path: '/workspace/project/a.txt', data: 'alpha' },
157
157
  { path: '/workspace/project/b.bin', data: new Uint8Array([0, 1, 2]) },
158
158
  ])
159
+ const patch = await sbx.files.applyDiff(
160
+ `*** Begin Patch
161
+ *** Update File: /workspace/project/a.txt
162
+ @@
163
+ -alpha
164
+ +beta
165
+ *** End Patch`
166
+ )
167
+ console.log(patch.status)
159
168
 
160
169
  const watcher = sbx.files.watchDir('/workspace/project')
161
170
  watcher.addEventListener((event) => {
@@ -6,7 +6,7 @@ export { ControlClient as ApiClient } from './transport.js';
6
6
  export { ALL_TRAFFIC, SandboxPaginator, SnapshotPaginator, getSignature, } from './sandbox.js';
7
7
  export type { CreateSnapshotOpts, FileUrlInfo, McpServer, McpServerName, RestoreSnapshotOpts, SandboxApiOpts, SandboxConnectOpts, SandboxCreateOpts, SandboxInfo, SandboxInfoLifecycle, SandboxLifecycle, SandboxListOpts, SandboxMetrics, SandboxMetricsOpts, SandboxNetworkInfo, SandboxNetworkOpts, SandboxNetworkRule, SandboxNetworkRuleInfo, SandboxNetworkRules, SandboxNetworkSelector, SandboxNetworkSelectorContext, SandboxNetworkTransform, SandboxNetworkUpdate, SandboxNetworkUpdateOpts, SandboxOpts, SandboxState, SandboxUrlOpts, SignatureOpts, SnapshotInfo, SnapshotListOpts, } from './sandbox.js';
8
8
  export { CommandExitError, CommandHandle, Commands } from './commands.js';
9
- export type { CommandConnectOpts, CommandRequestOpts, CommandResult, CommandStartOpts, ProcessInfo, PtyOutput, Stderr, Stdout, } from './commands.js';
9
+ export type { CommandConnectOpts, CommandRequestOpts, CommandResult, CommandStartOpts, ProcessInfo, ProcessOutputEvent, ProcessOutputSnapshot, ProcessStatus, PtyOutput, ReadProcessOutputOptions, Stderr, StopProcessOptions, Stdout, } from './commands.js';
10
10
  export { Process, ProcessManager, ProcessMessage, ProcessOutput } from './process.js';
11
11
  export type { ProcessOpts } from './process.js';
12
12
  export { FileType, Filesystem, FilesystemEventType, FilesystemWatcher, WatchHandle, } from './filesystem.js';
@@ -26,6 +26,45 @@ export interface ProcessInfo {
26
26
  envs: Record<string, string>;
27
27
  cwd?: string;
28
28
  }
29
+ export interface ProcessStatus {
30
+ pid: number | string;
31
+ id?: number | string;
32
+ osPid?: number;
33
+ command?: string;
34
+ args: string[];
35
+ cwd?: string;
36
+ user?: string;
37
+ pty?: boolean;
38
+ status: string;
39
+ startedAt?: string;
40
+ finishedAt?: string;
41
+ exitCode?: number;
42
+ }
43
+ export interface ProcessOutputEvent {
44
+ cursor: number;
45
+ type: 'stdout' | 'stderr' | 'pty' | string;
46
+ data: Uint8Array;
47
+ }
48
+ export interface ProcessOutputSnapshot {
49
+ pid: number | string;
50
+ status: string;
51
+ exitCode?: number;
52
+ finishedAt?: string;
53
+ nextCursor: number;
54
+ truncatedBeforeCursor: boolean;
55
+ events: ProcessOutputEvent[];
56
+ }
57
+ export interface ReadProcessOutputOptions extends CommandRequestOpts {
58
+ since?: number;
59
+ limitBytes?: number;
60
+ }
61
+ export interface StopProcessOptions {
62
+ signal?: string;
63
+ killGroup?: boolean;
64
+ graceMs?: number;
65
+ requestTimeoutMs?: number;
66
+ abortSignal?: AbortSignal;
67
+ }
29
68
  export interface CommandStartOpts {
30
69
  /** Return a `CommandHandle` immediately instead of waiting for exit. */
31
70
  background?: boolean;
@@ -125,6 +164,14 @@ export declare class Commands {
125
164
  run(cmd: string, opts?: CommandStartOpts): Promise<CommandResult>;
126
165
  /** Reconnect to a live process stream by pid. */
127
166
  connect(pid: number | string, opts?: CommandStartOpts): Promise<CommandHandle>;
167
+ /** Reconnect to a live process stream by pid starting at a cursor. */
168
+ connectSince(pid: number | string, cursor?: number, opts?: CommandStartOpts): Promise<CommandHandle>;
169
+ /** Look up process status without attaching a WebSocket. */
170
+ process(pid: number | string, opts?: CommandRequestOpts): Promise<ProcessStatus>;
171
+ /** Read available process output since a cursor without blocking. */
172
+ readProcessOutput(pid: number | string, opts?: ReadProcessOutputOptions): Promise<ProcessOutputSnapshot>;
173
+ /** Stop a process, optionally signalling the full process group. */
174
+ stopProcess(pid: number | string, opts?: StopProcessOptions): Promise<ProcessStatus>;
128
175
  /** Start a command and return a live handle immediately. */
129
176
  start(cmd: string, opts?: CommandStartOpts): Promise<CommandHandle>;
130
177
  }
package/dist/commands.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { withQuery } from './transport.js';
1
2
  import { ProcessSocket, base64DecodeBytes, base64DecodeText } from './processSocket.js';
2
3
  import { SandboxError, TimeoutError } from './errors.js';
3
4
  /** Error thrown by `CommandHandle.wait()` when a process exits non-zero. */
@@ -136,9 +137,10 @@ export class Commands {
136
137
  }
137
138
  /** Send SIGKILL to a process by pid. */
138
139
  async kill(pid, opts = {}) {
139
- await this.dataPlane.postJson(`/runtime/v1/process/${pid}/signal`, {
140
- ...opts,
141
- json: { signal: 'SIGKILL' },
140
+ await this.stopProcess(pid, {
141
+ signal: 'SIGKILL',
142
+ requestTimeoutMs: opts.requestTimeoutMs,
143
+ abortSignal: opts.signal,
142
144
  });
143
145
  return true;
144
146
  }
@@ -171,11 +173,41 @@ export class Commands {
171
173
  }
172
174
  /** Reconnect to a live process stream by pid. */
173
175
  async connect(pid, opts = {}) {
174
- const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, `/runtime/v1/process/${pid}/connect?since=0`, opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
176
+ return this.connectSince(pid, 0, opts);
177
+ }
178
+ /** Reconnect to a live process stream by pid starting at a cursor. */
179
+ async connectSince(pid, cursor = 0, opts = {}) {
180
+ const encodedPid = encodeURIComponent(String(pid));
181
+ const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, withQuery(`/runtime/v1/process/${encodedPid}/connect`, { since: cursor }), opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
175
182
  const first = await nextStarted(socket);
176
183
  const actualPid = framePid(first) ?? pid;
177
184
  return new CommandHandle(actualPid, socket, () => this.kill(actualPid), socket, opts.onStdout, opts.onStderr, opts.onPty);
178
185
  }
186
+ /** Look up process status without attaching a WebSocket. */
187
+ async process(pid, opts = {}) {
188
+ const payload = await this.dataPlane.getJson(`/runtime/v1/process/${encodeURIComponent(String(pid))}`, opts);
189
+ return processStatus(payload);
190
+ }
191
+ /** Read available process output since a cursor without blocking. */
192
+ async readProcessOutput(pid, opts = {}) {
193
+ const payload = await this.dataPlane.getJson(withQuery(`/runtime/v1/process/${encodeURIComponent(String(pid))}/output`, {
194
+ since: opts.since,
195
+ limit_bytes: opts.limitBytes,
196
+ }), opts);
197
+ return processOutputSnapshot(payload);
198
+ }
199
+ /** Stop a process, optionally signalling the full process group. */
200
+ async stopProcess(pid, opts = {}) {
201
+ const payload = await this.dataPlane.deleteJson(withQuery(`/runtime/v1/process/${encodeURIComponent(String(pid))}`, {
202
+ signal: opts.signal,
203
+ kill_group: opts.killGroup ?? true,
204
+ grace_ms: opts.graceMs,
205
+ }), {
206
+ requestTimeoutMs: opts.requestTimeoutMs,
207
+ signal: opts.abortSignal,
208
+ });
209
+ return processStatus(payload);
210
+ }
179
211
  /** Start a command and return a live handle immediately. */
180
212
  async start(cmd, opts = {}) {
181
213
  const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, '/runtime/v1/process', opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
@@ -243,8 +275,61 @@ function processInfo(value) {
243
275
  cwd: typeof process.cwd === 'string' ? process.cwd : undefined,
244
276
  };
245
277
  }
278
+ function processStatus(value) {
279
+ const item = value && typeof value === 'object' ? value : {};
280
+ const process = item.process && typeof item.process === 'object' ? item.process : item;
281
+ const pid = scalar(process.pid ?? process.id) ?? '';
282
+ return {
283
+ pid,
284
+ id: scalar(process.id),
285
+ osPid: numberValue(process.os_pid ?? process.osPid),
286
+ command: stringValue(process.command ?? process.cmd),
287
+ args: arrayOfStrings(process.args ?? process.arguments),
288
+ cwd: stringValue(process.cwd ?? process.working_directory),
289
+ user: stringValue(process.user),
290
+ pty: typeof process.pty === 'boolean' ? process.pty : undefined,
291
+ status: stringValue(process.status) ?? '',
292
+ startedAt: stringValue(process.started_at ?? process.startedAt),
293
+ finishedAt: stringValue(process.finished_at ?? process.finishedAt),
294
+ exitCode: numberValue(process.exit_code ?? process.exitCode),
295
+ };
296
+ }
297
+ function processOutputSnapshot(value) {
298
+ const payload = value && typeof value === 'object' ? value : {};
299
+ return {
300
+ pid: scalar(payload.pid ?? payload.id) ?? '',
301
+ status: stringValue(payload.status) ?? '',
302
+ exitCode: numberValue(payload.exit_code ?? payload.exitCode),
303
+ finishedAt: stringValue(payload.finished_at ?? payload.finishedAt),
304
+ nextCursor: numberValue(payload.next_cursor ?? payload.nextCursor) ?? 0,
305
+ truncatedBeforeCursor: payload.truncated_before_cursor === true || payload.truncatedBeforeCursor === true,
306
+ events: Array.isArray(payload.events) ? payload.events.map(processOutputEvent) : [],
307
+ };
308
+ }
309
+ function processOutputEvent(value) {
310
+ const event = value && typeof value === 'object' ? value : {};
311
+ return {
312
+ cursor: numberValue(event.cursor) ?? 0,
313
+ type: stringValue(event.type) ?? '',
314
+ data: base64DecodeBytes(stringValue(event.data) ?? ''),
315
+ };
316
+ }
246
317
  function recordOfStrings(value) {
247
318
  if (!value || typeof value !== 'object')
248
319
  return {};
249
320
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, String(item)]));
250
321
  }
322
+ function scalar(value) {
323
+ return typeof value === 'number' || typeof value === 'string' ? value : undefined;
324
+ }
325
+ function stringValue(value) {
326
+ return typeof value === 'string' ? value : undefined;
327
+ }
328
+ function numberValue(value) {
329
+ if (typeof value === 'number' && Number.isFinite(value))
330
+ return value;
331
+ return undefined;
332
+ }
333
+ function arrayOfStrings(value) {
334
+ return Array.isArray(value) ? value.map(String) : [];
335
+ }
@@ -68,6 +68,41 @@ export interface FilesystemWriteOpts extends FilesystemRequestOpts {
68
68
  useOctetStream?: boolean;
69
69
  metadata?: Record<string, string>;
70
70
  }
71
+ export interface ApplyDiffOpts extends FilesystemRequestOpts {
72
+ cwd?: string;
73
+ }
74
+ export interface ApplyDiffFailedHunk {
75
+ index: number;
76
+ oldStart: number;
77
+ }
78
+ export interface ApplyDiffFailure {
79
+ path: string;
80
+ error: string;
81
+ failedHunk?: ApplyDiffFailedHunk;
82
+ }
83
+ export interface ApplyDiffFileSummary {
84
+ path: string;
85
+ sourcePath?: string;
86
+ kind: string;
87
+ added: number;
88
+ removed: number;
89
+ }
90
+ export interface ApplyDiffSummary {
91
+ requested: number;
92
+ applied: number;
93
+ failed: number;
94
+ }
95
+ export interface ApplyDiffReport {
96
+ status: 'applied' | 'partial' | 'failed' | string;
97
+ parsedDiffBlocks: number;
98
+ patches: number;
99
+ files: ApplyDiffFileSummary[];
100
+ summary: ApplyDiffSummary;
101
+ applied: string[];
102
+ failed: ApplyDiffFailure[];
103
+ touched: string[];
104
+ raw: Record<string, unknown>;
105
+ }
71
106
  /** Live filesystem watcher. Call `stop()` to close the local watch stream. */
72
107
  export declare class WatchHandle {
73
108
  private readonly socket;
@@ -130,6 +165,8 @@ export declare class Filesystem {
130
165
  remove(path: string, opts?: FilesystemRequestOpts): Promise<void>;
131
166
  /** Move or rename a file. */
132
167
  rename(oldPath: string, newPath: string, opts?: FilesystemRequestOpts): Promise<EntryInfo>;
168
+ /** Apply a git-style unified diff or Codex apply_patch payload inside the sandbox. */
169
+ applyDiff(diff: string, opts?: ApplyDiffOpts): Promise<ApplyDiffReport>;
133
170
  /** Create a directory. */
134
171
  makeDir(path: string, opts?: FilesystemRequestOpts): Promise<boolean>;
135
172
  /** Start watching a directory for filesystem events. */
@@ -183,6 +183,17 @@ export class Filesystem {
183
183
  });
184
184
  return entryInfo(payload.file ?? payload);
185
185
  }
186
+ /** Apply a git-style unified diff or Codex apply_patch payload inside the sandbox. */
187
+ async applyDiff(diff, opts = {}) {
188
+ const payload = await this.dataPlane.postJson('/runtime/v1/files/apply_diff', {
189
+ ...requestOpts(opts),
190
+ json: {
191
+ diff,
192
+ ...(opts.cwd ? { cwd: opts.cwd } : {}),
193
+ },
194
+ });
195
+ return applyDiffReport(payload);
196
+ }
186
197
  /** Create a directory. */
187
198
  async makeDir(path, opts = {}) {
188
199
  await this.dataPlane.postJson(withQuery('/runtime/v1/directories', { path }), opts);
@@ -312,6 +323,60 @@ function filesystemEvent(value) {
312
323
  raw: item,
313
324
  };
314
325
  }
326
+ function applyDiffReport(value) {
327
+ const item = value && typeof value === 'object' ? value : {};
328
+ return {
329
+ status: String(item.status ?? ''),
330
+ parsedDiffBlocks: requiredNumber(item.parsed_diff_blocks),
331
+ patches: requiredNumber(item.patches),
332
+ files: Array.isArray(item.files) ? item.files.map(applyDiffFileSummary) : [],
333
+ summary: applyDiffSummary(item.summary),
334
+ applied: stringArray(item.applied),
335
+ failed: Array.isArray(item.failed) ? item.failed.map(applyDiffFailure) : [],
336
+ touched: stringArray(item.touched),
337
+ raw: item,
338
+ };
339
+ }
340
+ function applyDiffSummary(value) {
341
+ const item = value && typeof value === 'object' ? value : {};
342
+ return {
343
+ requested: requiredNumber(item.requested),
344
+ applied: requiredNumber(item.applied),
345
+ failed: requiredNumber(item.failed),
346
+ };
347
+ }
348
+ function applyDiffFileSummary(value) {
349
+ const item = value && typeof value === 'object' ? value : {};
350
+ return {
351
+ path: String(item.path ?? ''),
352
+ sourcePath: typeof item.source_path === 'string' ? item.source_path : undefined,
353
+ kind: String(item.kind ?? ''),
354
+ added: requiredNumber(item.added),
355
+ removed: requiredNumber(item.removed),
356
+ };
357
+ }
358
+ function applyDiffFailure(value) {
359
+ const item = value && typeof value === 'object' ? value : {};
360
+ const failedHunk = item.failed_hunk && typeof item.failed_hunk === 'object'
361
+ ? item.failed_hunk
362
+ : undefined;
363
+ return {
364
+ path: String(item.path ?? ''),
365
+ error: String(item.error ?? ''),
366
+ failedHunk: failedHunk
367
+ ? {
368
+ index: requiredNumber(failedHunk.index),
369
+ oldStart: requiredNumber(failedHunk.old_start),
370
+ }
371
+ : undefined,
372
+ };
373
+ }
374
+ function requiredNumber(value) {
375
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
376
+ }
377
+ function stringArray(value) {
378
+ return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
379
+ }
315
380
  function normalizeEventType(value) {
316
381
  if (value === 'delete')
317
382
  return FilesystemEventType.REMOVE;
package/dist/index.d.ts CHANGED
@@ -8,11 +8,11 @@ export type { CreateSnapshotOpts, RestoreSnapshotOpts, SandboxApiOpts, SandboxCr
8
8
  export type { CreateCodeContextOpts, RunCodeLanguage, RunCodeOpts, } from './codeInterpreter.js';
9
9
  export { Context as CodeInterpreterContext, Execution as CodeInterpreterExecution, ExecutionError as CodeInterpreterExecutionError, OutputMessage as CodeInterpreterOutputMessage, Result as CodeInterpreterResult, } from './codeInterpreter.js';
10
10
  export { CommandExitError, CommandHandle, Commands } from './commands.js';
11
- export type { CommandConnectOpts, CommandRequestOpts, CommandResult, CommandStartOpts, ProcessInfo, PtyOutput, Stderr, Stdout, } from './commands.js';
11
+ export type { CommandConnectOpts, CommandRequestOpts, CommandResult, CommandStartOpts, ProcessInfo, ProcessOutputEvent, ProcessOutputSnapshot, ProcessStatus, PtyOutput, ReadProcessOutputOptions, Stderr, StopProcessOptions, Stdout, } from './commands.js';
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.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { CommandHandle } from './commands.js';
2
2
  import { ProcessSocket } from './processSocket.js';
3
+ import { withQuery } from './transport.js';
3
4
  import { SandboxError } from './errors.js';
4
5
  /** PTY helper backed by the sandbox process WebSocket runtime. */
5
6
  export class Pty {
@@ -35,7 +36,7 @@ export class Pty {
35
36
  }
36
37
  /** Connect to a running PTY by pid. */
37
38
  async connect(pid, opts = {}) {
38
- const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, `/runtime/v1/process/${pid}/connect?since=0`, opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
39
+ const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, withQuery(`/runtime/v1/process/${encodeURIComponent(String(pid))}/connect`, { since: 0 }), opts.requestTimeoutMs ?? this.config.requestTimeoutMs, this.config.headers).connect();
39
40
  const first = await nextStarted(socket);
40
41
  const actualPid = framePid(first) ?? pid;
41
42
  return new CommandHandle(actualPid, socket, () => this.kill(actualPid), withFirst(first, socket), undefined, undefined, opts.onData);
@@ -66,8 +67,10 @@ export class Pty {
66
67
  }
67
68
  /** Kill a running PTY. */
68
69
  async kill(pid, opts = {}) {
69
- await this.dataPlane.postJson(`/runtime/v1/process/${pid}/signal`, {
70
- json: { signal: 'SIGKILL' },
70
+ await this.dataPlane.deleteJson(withQuery(`/runtime/v1/process/${encodeURIComponent(String(pid))}`, {
71
+ signal: 'SIGKILL',
72
+ kill_group: true,
73
+ }), {
71
74
  requestTimeoutMs: opts.requestTimeoutMs,
72
75
  signal: opts.signal,
73
76
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@watasu/sdk",
3
- "version": "0.1.53",
3
+ "version": "0.1.67",
4
4
  "type": "module",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "description": "TypeScript SDK for Watasu",