@declaw/sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to the Declaw TypeScript / JavaScript SDK are documented in
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.2]
9
+
10
+ ### Added
11
+
12
+ - Real interactive PTY support. `sandbox.pty.create()` now returns a
13
+ `PtyHandle` with both an `onData` callback and an `async *stream()`
14
+ iterator that deliver raw terminal bytes as they arrive from the
15
+ sandbox (ANSI escapes included).
16
+ - `PtyHandle.sendInput`, `PtyHandle.resize`, `PtyHandle.kill`,
17
+ `PtyHandle.disconnect`, `PtyHandle.wait()` for full lifecycle control.
18
+ - `ApiClient.streamGet()` for SSE consumers.
19
+ - `PtyHandle` exported from the package root.
20
+
21
+ ### Changed
22
+
23
+ - `Pty.create()` accepts `onData` in `PtyCreateOpts` and returns the new
24
+ `PtyHandle` type. Existing fields are unchanged; callers that only
25
+ used the returned handle for `sendInput`/`resize`/`kill` continue to
26
+ work without edits.
27
+
8
28
  ## [1.0.0]
9
29
 
10
30
  First stable release. The public API described below is covered by
package/dist/index.cjs CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  NotFoundError: () => NotFoundError,
43
43
  PIIType: () => PIIType,
44
44
  Pty: () => Pty,
45
+ PtyHandle: () => PtyHandle,
45
46
  RedactionAction: () => RedactionAction,
46
47
  Sandbox: () => Sandbox,
47
48
  SandboxError: () => SandboxError,
@@ -263,6 +264,13 @@ var ApiClient = class {
263
264
  async stream(path, opts) {
264
265
  return this.requestWithRetry("POST", path, opts, true);
265
266
  }
267
+ /**
268
+ * Send a GET request and return the raw Response for SSE streaming.
269
+ * Does NOT parse the response body. Used by PTY stream consumers.
270
+ */
271
+ async streamGet(path, opts) {
272
+ return this.requestWithRetry("GET", path, opts, true);
273
+ }
266
274
  /**
267
275
  * Abort all in-flight requests and release resources.
268
276
  */
@@ -542,18 +550,12 @@ function parseInjectionDefenseConfig(data) {
542
550
  // src/security/audit.ts
543
551
  function createAuditConfig(opts) {
544
552
  return {
545
- enabled: opts?.enabled ?? false,
546
- logRequestBody: opts?.logRequestBody ?? true,
547
- logResponseBody: opts?.logResponseBody ?? false,
548
- retentionHours: opts?.retentionHours ?? 24
553
+ enabled: opts?.enabled ?? true
549
554
  };
550
555
  }
551
556
  function parseAuditConfig(data) {
552
557
  return {
553
- enabled: data.enabled ?? false,
554
- logRequestBody: data.log_request_body ?? data.logRequestBody ?? true,
555
- logResponseBody: data.log_response_body ?? data.logResponseBody ?? false,
556
- retentionHours: data.retention_hours ?? data.retentionHours ?? 24
558
+ enabled: data.enabled ?? true
557
559
  };
558
560
  }
559
561
  function parseAuditEntry(data) {
@@ -850,7 +852,7 @@ function createSecurityPolicy(opts) {
850
852
  injectionDefense: opts?.injectionDefense ?? false,
851
853
  transformations: opts?.transformations ?? [],
852
854
  network: opts?.network,
853
- audit: opts?.audit ?? false,
855
+ audit: opts?.audit ?? true,
854
856
  envSecurity: opts?.envSecurity ?? createEnvSecurityConfig(),
855
857
  toxicity: opts?.toxicity,
856
858
  codeSecurity: opts?.codeSecurity,
@@ -865,7 +867,7 @@ function parseSecurityPolicy(data) {
865
867
  injectionDefense: typeof injDef === "boolean" ? injDef : injDef ? parseInjectionDefenseConfig(injDef) : false,
866
868
  transformations: Array.isArray(data.transformations) ? data.transformations.map((t) => parseTransformationRule(t)) : [],
867
869
  network: data.network ? parseNetworkPolicy(data.network) : void 0,
868
- audit: typeof auditData === "boolean" ? auditData : auditData ? parseAuditConfig(auditData) : false,
870
+ audit: typeof auditData === "boolean" ? auditData : auditData ? parseAuditConfig(auditData) : true,
869
871
  envSecurity: data.env_security ?? data.envSecurity ? parseEnvSecurityConfig(data.env_security ?? data.envSecurity) : createEnvSecurityConfig(),
870
872
  toxicity: data.toxicity ? parseToxicityConfig(data.toxicity) : void 0,
871
873
  codeSecurity: data.code_security ?? data.codeSecurity ? parseCodeSecurityConfig(data.code_security ?? data.codeSecurity) : void 0,
@@ -894,10 +896,7 @@ function securityPolicyToJSON(policy) {
894
896
  }
895
897
  const auditConfig = typeof policy.audit === "boolean" ? createAuditConfig({ enabled: policy.audit }) : policy.audit;
896
898
  const audit = {
897
- enabled: auditConfig.enabled,
898
- log_request_body: auditConfig.logRequestBody,
899
- log_response_body: auditConfig.logResponseBody,
900
- retention_hours: auditConfig.retentionHours
899
+ enabled: auditConfig.enabled
901
900
  };
902
901
  const envSec = {
903
902
  mask_patterns: policy.envSecurity.maskPatterns,
@@ -1364,6 +1363,11 @@ var Filesystem = class {
1364
1363
  /**
1365
1364
  * Write data to a file.
1366
1365
  *
1366
+ * When `data` is a `Uint8Array`, the SDK streams the raw bytes to the
1367
+ * binary-safe `PUT /files/raw` endpoint (500 MiB cap). When it's a string,
1368
+ * the SDK uses the JSON `POST /files` endpoint. Callers do not need to
1369
+ * pick the transport.
1370
+ *
1367
1371
  * @param path - Absolute path to the file.
1368
1372
  * @param data - String or Uint8Array content to write.
1369
1373
  * @param opts - Optional user and request timeout.
@@ -1371,11 +1375,22 @@ var Filesystem = class {
1371
1375
  */
1372
1376
  async write(path, data, opts) {
1373
1377
  const user = opts?.user ?? DEFAULT_USER;
1374
- const strData = data instanceof Uint8Array ? new TextDecoder().decode(data) : data;
1378
+ if (data instanceof Uint8Array) {
1379
+ const result2 = await this.client.put(
1380
+ `/sandboxes/${this.sandboxId}/files/raw`,
1381
+ {
1382
+ params: { path, username: user },
1383
+ body: data,
1384
+ headers: { "Content-Type": "application/octet-stream" },
1385
+ timeout: opts?.requestTimeout
1386
+ }
1387
+ );
1388
+ return parseWriteInfo(result2);
1389
+ }
1375
1390
  const result = await this.client.post(
1376
1391
  `/sandboxes/${this.sandboxId}/files`,
1377
1392
  {
1378
- json: { path, data: strData, username: user },
1393
+ json: { path, data, username: user },
1379
1394
  timeout: opts?.requestTimeout
1380
1395
  }
1381
1396
  );
@@ -1384,24 +1399,58 @@ var Filesystem = class {
1384
1399
  /**
1385
1400
  * Write multiple files in a single batch request.
1386
1401
  *
1402
+ * The batch endpoint is JSON-only and cannot carry binary. Entries are
1403
+ * partitioned: string entries go through `POST /files/batch` in one call,
1404
+ * `Uint8Array` entries are streamed individually to `PUT /files/raw`.
1405
+ * Results are merged back in input order.
1406
+ *
1387
1407
  * @param files - Array of files to write.
1388
1408
  * @param opts - Optional user and request timeout.
1389
- * @returns Array of write info for each file.
1409
+ * @returns Array of write info for each file, in input order.
1390
1410
  */
1391
1411
  async writeFiles(files, opts) {
1392
1412
  const user = opts?.user ?? DEFAULT_USER;
1393
- const encodedFiles = files.map((f) => ({
1394
- path: f.path,
1395
- data: f.data instanceof Uint8Array ? new TextDecoder().decode(f.data) : f.data
1396
- }));
1397
- const result = await this.client.post(
1398
- `/sandboxes/${this.sandboxId}/files/batch`,
1399
- {
1400
- json: { files: encodedFiles, username: user },
1401
- timeout: opts?.requestTimeout
1413
+ const results = new Array(files.length);
1414
+ const strIndices = [];
1415
+ const bytesIndices = [];
1416
+ for (let i = 0; i < files.length; i++) {
1417
+ if (files[i].data instanceof Uint8Array) {
1418
+ bytesIndices.push(i);
1419
+ } else {
1420
+ strIndices.push(i);
1402
1421
  }
1403
- );
1404
- return (result ?? []).map(parseWriteInfo);
1422
+ }
1423
+ if (strIndices.length > 0) {
1424
+ const batchFiles = strIndices.map((i) => ({
1425
+ path: files[i].path,
1426
+ data: files[i].data
1427
+ }));
1428
+ const result = await this.client.post(
1429
+ `/sandboxes/${this.sandboxId}/files/batch`,
1430
+ {
1431
+ json: { files: batchFiles, username: user },
1432
+ timeout: opts?.requestTimeout
1433
+ }
1434
+ );
1435
+ const parsed = (result ?? []).map(parseWriteInfo);
1436
+ for (let k = 0; k < strIndices.length; k++) {
1437
+ results[strIndices[k]] = parsed[k];
1438
+ }
1439
+ }
1440
+ for (const i of bytesIndices) {
1441
+ const entry = files[i];
1442
+ const result = await this.client.put(
1443
+ `/sandboxes/${this.sandboxId}/files/raw`,
1444
+ {
1445
+ params: { path: entry.path, username: user },
1446
+ body: entry.data,
1447
+ headers: { "Content-Type": "application/octet-stream" },
1448
+ timeout: opts?.requestTimeout
1449
+ }
1450
+ );
1451
+ results[i] = parseWriteInfo(result);
1452
+ }
1453
+ return results.filter((r) => r !== void 0);
1405
1454
  }
1406
1455
  /**
1407
1456
  * List entries in a directory.
@@ -1540,19 +1589,159 @@ var Filesystem = class {
1540
1589
  };
1541
1590
 
1542
1591
  // src/sandbox/pty/pty.ts
1543
- var Pty = class {
1592
+ var PtyHandle = class {
1593
+ pid;
1544
1594
  sandboxId;
1545
1595
  client;
1546
- constructor(sandboxId, client) {
1596
+ exitPromise;
1597
+ resolveExit;
1598
+ aborter = new AbortController();
1599
+ constructor(pid, sandboxId, client, onData) {
1600
+ this.pid = pid;
1547
1601
  this.sandboxId = sandboxId;
1548
1602
  this.client = client;
1603
+ this.exitPromise = new Promise((resolve) => {
1604
+ this.resolveExit = resolve;
1605
+ });
1606
+ if (onData) {
1607
+ void this.consumeStream(onData);
1608
+ }
1609
+ }
1610
+ /** Forward keystrokes to the PTY. */
1611
+ async sendInput(data, requestTimeout) {
1612
+ const strData = typeof data === "string" ? data : new TextDecoder().decode(data);
1613
+ await this.client.post(
1614
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}/stdin`,
1615
+ { json: { data: strData }, timeout: requestTimeout }
1616
+ );
1617
+ }
1618
+ /** Update the terminal size (TIOCSWINSZ inside the VM). */
1619
+ async resize(size, requestTimeout) {
1620
+ await this.client.patch(
1621
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}`,
1622
+ {
1623
+ json: { size: { cols: size.cols, rows: size.rows } },
1624
+ timeout: requestTimeout
1625
+ }
1626
+ );
1627
+ }
1628
+ /** SIGKILL the remote process and close any open streams. */
1629
+ async kill(requestTimeout) {
1630
+ this.aborter.abort();
1631
+ const data = await this.client.delete(
1632
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}`,
1633
+ { timeout: requestTimeout }
1634
+ );
1635
+ return data.killed === true;
1636
+ }
1637
+ /**
1638
+ * Stop consuming output without killing the process. The PTY keeps
1639
+ * running server-side and a fresh `stream()` call reattaches.
1640
+ */
1641
+ disconnect() {
1642
+ this.aborter.abort();
1643
+ }
1644
+ /** Resolves with the remote exit code when the PTY process exits. */
1645
+ wait() {
1646
+ return this.exitPromise;
1549
1647
  }
1550
1648
  /**
1551
- * Create a new PTY session.
1649
+ * Async iterator over raw output chunks. Use when you want to drive
1650
+ * the stream yourself:
1651
+ *
1652
+ * for await (const chunk of handle.stream()) { ... }
1552
1653
  *
1553
- * Sends POST /sandboxes/:id/pty.
1554
- * @returns A CommandHandle for the PTY process.
1654
+ * Don't mix this with `onData` on the same handle — they both try to
1655
+ * consume the same underlying SSE connection.
1555
1656
  */
1657
+ async *stream() {
1658
+ const response = await this.client.streamGet(
1659
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}/stream`,
1660
+ { timeout: void 0 }
1661
+ );
1662
+ if (!response.body) {
1663
+ return;
1664
+ }
1665
+ const reader = response.body.getReader();
1666
+ const decoder = new TextDecoder();
1667
+ let buffer = "";
1668
+ try {
1669
+ while (true) {
1670
+ if (this.aborter.signal.aborted) return;
1671
+ const { value, done } = await reader.read();
1672
+ if (done) break;
1673
+ buffer += decoder.decode(value, { stream: true });
1674
+ let idx;
1675
+ while ((idx = buffer.indexOf("\n\n")) >= 0) {
1676
+ const frame = buffer.slice(0, idx);
1677
+ buffer = buffer.slice(idx + 2);
1678
+ const parsed = parseSSEFrame(frame);
1679
+ if (!parsed) continue;
1680
+ if (parsed.event === "exit") {
1681
+ this.resolveExit(parsed.exitCode ?? -1);
1682
+ return;
1683
+ }
1684
+ if (parsed.bytes) {
1685
+ yield parsed.bytes;
1686
+ }
1687
+ }
1688
+ }
1689
+ } finally {
1690
+ reader.releaseLock();
1691
+ }
1692
+ }
1693
+ async consumeStream(onData) {
1694
+ try {
1695
+ for await (const chunk of this.stream()) {
1696
+ onData(chunk);
1697
+ }
1698
+ } catch (err) {
1699
+ if (!this.aborter.signal.aborted) {
1700
+ throw err;
1701
+ }
1702
+ } finally {
1703
+ this.resolveExit(-1);
1704
+ }
1705
+ }
1706
+ };
1707
+ function parseSSEFrame(frame) {
1708
+ let event = "message";
1709
+ let data = "";
1710
+ for (const line of frame.split("\n")) {
1711
+ if (line.startsWith("event:")) event = line.slice(6).trim();
1712
+ else if (line.startsWith("data:")) data += line.slice(5).trim();
1713
+ }
1714
+ if (!event) return null;
1715
+ if (event === "exit") {
1716
+ try {
1717
+ const parsed = JSON.parse(data);
1718
+ return { event, exitCode: parsed.exit_code ?? -1 };
1719
+ } catch {
1720
+ return { event, exitCode: -1 };
1721
+ }
1722
+ }
1723
+ if (event === "data") {
1724
+ try {
1725
+ const parsed = JSON.parse(data);
1726
+ if (typeof parsed.data === "string") {
1727
+ const bin = atob(parsed.data);
1728
+ const bytes = new Uint8Array(bin.length);
1729
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
1730
+ return { event, bytes };
1731
+ }
1732
+ } catch {
1733
+ return null;
1734
+ }
1735
+ }
1736
+ return null;
1737
+ }
1738
+ var Pty = class {
1739
+ sandboxId;
1740
+ client;
1741
+ constructor(sandboxId, client) {
1742
+ this.sandboxId = sandboxId;
1743
+ this.client = client;
1744
+ }
1556
1745
  async create(opts) {
1557
1746
  const size = opts?.size ?? { cols: 80, rows: 24 };
1558
1747
  const user = opts?.user ?? "user";
@@ -1560,32 +1749,17 @@ var Pty = class {
1560
1749
  size: { cols: size.cols, rows: size.rows },
1561
1750
  user
1562
1751
  };
1563
- if (opts?.cwd !== void 0) {
1564
- body.cwd = opts.cwd;
1565
- }
1566
- if (opts?.envs !== void 0) {
1567
- body.envs = opts.envs;
1568
- }
1569
- if (opts?.timeout !== void 0) {
1570
- body.timeout = opts.timeout;
1571
- }
1752
+ if (opts?.cwd !== void 0) body.cwd = opts.cwd;
1753
+ if (opts?.envs !== void 0) body.envs = opts.envs;
1754
+ if (opts?.timeout !== void 0) body.timeout = opts.timeout;
1572
1755
  const data = await this.client.post(
1573
1756
  `/sandboxes/${this.sandboxId}/pty`,
1574
- {
1575
- json: body,
1576
- timeout: opts?.requestTimeout
1577
- }
1757
+ { json: body, timeout: opts?.requestTimeout }
1578
1758
  );
1579
- const response = data;
1580
- const pid = response.pid;
1581
- return new CommandHandle(pid, this.sandboxId, this.client);
1759
+ const pid = data.pid;
1760
+ return new PtyHandle(pid, this.sandboxId, this.client, opts?.onData);
1582
1761
  }
1583
- /**
1584
- * Kill a PTY session.
1585
- *
1586
- * Sends DELETE /sandboxes/:id/pty/:pid.
1587
- * @returns true if the process was killed, false if already dead.
1588
- */
1762
+ // --- Legacy low-level API (kept for callers that already hold a pid). ---
1589
1763
  async kill(pid, requestTimeout) {
1590
1764
  const data = await this.client.delete(
1591
1765
  `/sandboxes/${this.sandboxId}/pty/${pid}`,
@@ -1593,27 +1767,13 @@ var Pty = class {
1593
1767
  );
1594
1768
  return data.killed === true;
1595
1769
  }
1596
- /**
1597
- * Send input to a PTY session.
1598
- *
1599
- * Sends POST /sandboxes/:id/pty/:pid/stdin.
1600
- * If data is a Uint8Array, it is decoded to a string using TextDecoder.
1601
- */
1602
1770
  async sendStdin(pid, data, requestTimeout) {
1603
1771
  const strData = typeof data === "string" ? data : new TextDecoder().decode(data);
1604
1772
  await this.client.post(
1605
1773
  `/sandboxes/${this.sandboxId}/pty/${pid}/stdin`,
1606
- {
1607
- json: { data: strData },
1608
- timeout: requestTimeout
1609
- }
1774
+ { json: { data: strData }, timeout: requestTimeout }
1610
1775
  );
1611
1776
  }
1612
- /**
1613
- * Resize a PTY session.
1614
- *
1615
- * Sends PATCH /sandboxes/:id/pty/:pid.
1616
- */
1617
1777
  async resize(pid, size, requestTimeout) {
1618
1778
  await this.client.patch(
1619
1779
  `/sandboxes/${this.sandboxId}/pty/${pid}`,
@@ -2398,6 +2558,7 @@ var Template = class {
2398
2558
  NotFoundError,
2399
2559
  PIIType,
2400
2560
  Pty,
2561
+ PtyHandle,
2401
2562
  RedactionAction,
2402
2563
  Sandbox,
2403
2564
  SandboxError,