@declaw/sdk 1.0.0 → 1.0.3

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/index.js CHANGED
@@ -162,6 +162,13 @@ var ApiClient = class {
162
162
  async stream(path, opts) {
163
163
  return this.requestWithRetry("POST", path, opts, true);
164
164
  }
165
+ /**
166
+ * Send a GET request and return the raw Response for SSE streaming.
167
+ * Does NOT parse the response body. Used by PTY stream consumers.
168
+ */
169
+ async streamGet(path, opts) {
170
+ return this.requestWithRetry("GET", path, opts, true);
171
+ }
165
172
  /**
166
173
  * Abort all in-flight requests and release resources.
167
174
  */
@@ -441,18 +448,12 @@ function parseInjectionDefenseConfig(data) {
441
448
  // src/security/audit.ts
442
449
  function createAuditConfig(opts) {
443
450
  return {
444
- enabled: opts?.enabled ?? false,
445
- logRequestBody: opts?.logRequestBody ?? true,
446
- logResponseBody: opts?.logResponseBody ?? false,
447
- retentionHours: opts?.retentionHours ?? 24
451
+ enabled: opts?.enabled ?? true
448
452
  };
449
453
  }
450
454
  function parseAuditConfig(data) {
451
455
  return {
452
- enabled: data.enabled ?? false,
453
- logRequestBody: data.log_request_body ?? data.logRequestBody ?? true,
454
- logResponseBody: data.log_response_body ?? data.logResponseBody ?? false,
455
- retentionHours: data.retention_hours ?? data.retentionHours ?? 24
456
+ enabled: data.enabled ?? true
456
457
  };
457
458
  }
458
459
  function parseAuditEntry(data) {
@@ -749,7 +750,7 @@ function createSecurityPolicy(opts) {
749
750
  injectionDefense: opts?.injectionDefense ?? false,
750
751
  transformations: opts?.transformations ?? [],
751
752
  network: opts?.network,
752
- audit: opts?.audit ?? false,
753
+ audit: opts?.audit ?? true,
753
754
  envSecurity: opts?.envSecurity ?? createEnvSecurityConfig(),
754
755
  toxicity: opts?.toxicity,
755
756
  codeSecurity: opts?.codeSecurity,
@@ -764,7 +765,7 @@ function parseSecurityPolicy(data) {
764
765
  injectionDefense: typeof injDef === "boolean" ? injDef : injDef ? parseInjectionDefenseConfig(injDef) : false,
765
766
  transformations: Array.isArray(data.transformations) ? data.transformations.map((t) => parseTransformationRule(t)) : [],
766
767
  network: data.network ? parseNetworkPolicy(data.network) : void 0,
767
- audit: typeof auditData === "boolean" ? auditData : auditData ? parseAuditConfig(auditData) : false,
768
+ audit: typeof auditData === "boolean" ? auditData : auditData ? parseAuditConfig(auditData) : true,
768
769
  envSecurity: data.env_security ?? data.envSecurity ? parseEnvSecurityConfig(data.env_security ?? data.envSecurity) : createEnvSecurityConfig(),
769
770
  toxicity: data.toxicity ? parseToxicityConfig(data.toxicity) : void 0,
770
771
  codeSecurity: data.code_security ?? data.codeSecurity ? parseCodeSecurityConfig(data.code_security ?? data.codeSecurity) : void 0,
@@ -793,10 +794,7 @@ function securityPolicyToJSON(policy) {
793
794
  }
794
795
  const auditConfig = typeof policy.audit === "boolean" ? createAuditConfig({ enabled: policy.audit }) : policy.audit;
795
796
  const audit = {
796
- enabled: auditConfig.enabled,
797
- log_request_body: auditConfig.logRequestBody,
798
- log_response_body: auditConfig.logResponseBody,
799
- retention_hours: auditConfig.retentionHours
797
+ enabled: auditConfig.enabled
800
798
  };
801
799
  const envSec = {
802
800
  mask_patterns: policy.envSecurity.maskPatterns,
@@ -1263,6 +1261,11 @@ var Filesystem = class {
1263
1261
  /**
1264
1262
  * Write data to a file.
1265
1263
  *
1264
+ * When `data` is a `Uint8Array`, the SDK streams the raw bytes to the
1265
+ * binary-safe `PUT /files/raw` endpoint (500 MiB cap). When it's a string,
1266
+ * the SDK uses the JSON `POST /files` endpoint. Callers do not need to
1267
+ * pick the transport.
1268
+ *
1266
1269
  * @param path - Absolute path to the file.
1267
1270
  * @param data - String or Uint8Array content to write.
1268
1271
  * @param opts - Optional user and request timeout.
@@ -1270,11 +1273,22 @@ var Filesystem = class {
1270
1273
  */
1271
1274
  async write(path, data, opts) {
1272
1275
  const user = opts?.user ?? DEFAULT_USER;
1273
- const strData = data instanceof Uint8Array ? new TextDecoder().decode(data) : data;
1276
+ if (data instanceof Uint8Array) {
1277
+ const result2 = await this.client.put(
1278
+ `/sandboxes/${this.sandboxId}/files/raw`,
1279
+ {
1280
+ params: { path, username: user },
1281
+ body: data,
1282
+ headers: { "Content-Type": "application/octet-stream" },
1283
+ timeout: opts?.requestTimeout
1284
+ }
1285
+ );
1286
+ return parseWriteInfo(result2);
1287
+ }
1274
1288
  const result = await this.client.post(
1275
1289
  `/sandboxes/${this.sandboxId}/files`,
1276
1290
  {
1277
- json: { path, data: strData, username: user },
1291
+ json: { path, data, username: user },
1278
1292
  timeout: opts?.requestTimeout
1279
1293
  }
1280
1294
  );
@@ -1283,24 +1297,58 @@ var Filesystem = class {
1283
1297
  /**
1284
1298
  * Write multiple files in a single batch request.
1285
1299
  *
1300
+ * The batch endpoint is JSON-only and cannot carry binary. Entries are
1301
+ * partitioned: string entries go through `POST /files/batch` in one call,
1302
+ * `Uint8Array` entries are streamed individually to `PUT /files/raw`.
1303
+ * Results are merged back in input order.
1304
+ *
1286
1305
  * @param files - Array of files to write.
1287
1306
  * @param opts - Optional user and request timeout.
1288
- * @returns Array of write info for each file.
1307
+ * @returns Array of write info for each file, in input order.
1289
1308
  */
1290
1309
  async writeFiles(files, opts) {
1291
1310
  const user = opts?.user ?? DEFAULT_USER;
1292
- const encodedFiles = files.map((f) => ({
1293
- path: f.path,
1294
- data: f.data instanceof Uint8Array ? new TextDecoder().decode(f.data) : f.data
1295
- }));
1296
- const result = await this.client.post(
1297
- `/sandboxes/${this.sandboxId}/files/batch`,
1298
- {
1299
- json: { files: encodedFiles, username: user },
1300
- timeout: opts?.requestTimeout
1311
+ const results = new Array(files.length);
1312
+ const strIndices = [];
1313
+ const bytesIndices = [];
1314
+ for (let i = 0; i < files.length; i++) {
1315
+ if (files[i].data instanceof Uint8Array) {
1316
+ bytesIndices.push(i);
1317
+ } else {
1318
+ strIndices.push(i);
1301
1319
  }
1302
- );
1303
- return (result ?? []).map(parseWriteInfo);
1320
+ }
1321
+ if (strIndices.length > 0) {
1322
+ const batchFiles = strIndices.map((i) => ({
1323
+ path: files[i].path,
1324
+ data: files[i].data
1325
+ }));
1326
+ const result = await this.client.post(
1327
+ `/sandboxes/${this.sandboxId}/files/batch`,
1328
+ {
1329
+ json: { files: batchFiles, username: user },
1330
+ timeout: opts?.requestTimeout
1331
+ }
1332
+ );
1333
+ const parsed = (result ?? []).map(parseWriteInfo);
1334
+ for (let k = 0; k < strIndices.length; k++) {
1335
+ results[strIndices[k]] = parsed[k];
1336
+ }
1337
+ }
1338
+ for (const i of bytesIndices) {
1339
+ const entry = files[i];
1340
+ const result = await this.client.put(
1341
+ `/sandboxes/${this.sandboxId}/files/raw`,
1342
+ {
1343
+ params: { path: entry.path, username: user },
1344
+ body: entry.data,
1345
+ headers: { "Content-Type": "application/octet-stream" },
1346
+ timeout: opts?.requestTimeout
1347
+ }
1348
+ );
1349
+ results[i] = parseWriteInfo(result);
1350
+ }
1351
+ return results.filter((r) => r !== void 0);
1304
1352
  }
1305
1353
  /**
1306
1354
  * List entries in a directory.
@@ -1439,52 +1487,188 @@ var Filesystem = class {
1439
1487
  };
1440
1488
 
1441
1489
  // src/sandbox/pty/pty.ts
1442
- var Pty = class {
1490
+ var PtyHandle = class {
1491
+ pid;
1443
1492
  sandboxId;
1444
1493
  client;
1445
- constructor(sandboxId, client) {
1494
+ exitPromise;
1495
+ resolveExit;
1496
+ aborter = new AbortController();
1497
+ constructor(pid, sandboxId, client, onData) {
1498
+ this.pid = pid;
1446
1499
  this.sandboxId = sandboxId;
1447
1500
  this.client = client;
1501
+ this.exitPromise = new Promise((resolve) => {
1502
+ this.resolveExit = resolve;
1503
+ });
1504
+ if (onData) {
1505
+ void this.consumeStream(onData);
1506
+ }
1507
+ }
1508
+ /** Forward keystrokes to the PTY. */
1509
+ async sendInput(data, requestTimeout) {
1510
+ const strData = typeof data === "string" ? data : new TextDecoder().decode(data);
1511
+ await this.client.post(
1512
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}/stdin`,
1513
+ { json: { data: strData }, timeout: requestTimeout }
1514
+ );
1515
+ }
1516
+ /** Update the terminal size (TIOCSWINSZ inside the VM). */
1517
+ async resize(size, requestTimeout) {
1518
+ await this.client.patch(
1519
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}`,
1520
+ {
1521
+ json: { size: { cols: size.cols, rows: size.rows } },
1522
+ timeout: requestTimeout
1523
+ }
1524
+ );
1525
+ }
1526
+ /** SIGKILL the remote process and close any open streams. */
1527
+ async kill(requestTimeout) {
1528
+ this.aborter.abort();
1529
+ const data = await this.client.delete(
1530
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}`,
1531
+ { timeout: requestTimeout }
1532
+ );
1533
+ return data.killed === true;
1534
+ }
1535
+ /**
1536
+ * Stop consuming output without killing the process. The PTY keeps
1537
+ * running server-side and a fresh `stream()` call reattaches.
1538
+ */
1539
+ disconnect() {
1540
+ this.aborter.abort();
1541
+ }
1542
+ /** Resolves with the remote exit result when the PTY process exits. */
1543
+ wait() {
1544
+ return this.exitPromise;
1448
1545
  }
1449
1546
  /**
1450
- * Create a new PTY session.
1547
+ * Async iterator over raw output chunks. Use when you want to drive
1548
+ * the stream yourself:
1549
+ *
1550
+ * for await (const chunk of handle.stream()) { ... }
1451
1551
  *
1452
- * Sends POST /sandboxes/:id/pty.
1453
- * @returns A CommandHandle for the PTY process.
1552
+ * Don't mix this with `onData` on the same handle — they both try to
1553
+ * consume the same underlying SSE connection.
1454
1554
  */
1555
+ async *stream() {
1556
+ const response = await this.client.streamGet(
1557
+ `/sandboxes/${this.sandboxId}/pty/${this.pid}/stream`,
1558
+ { timeout: void 0 }
1559
+ );
1560
+ if (!response.body) {
1561
+ return;
1562
+ }
1563
+ const reader = response.body.getReader();
1564
+ const decoder = new TextDecoder();
1565
+ let buffer = "";
1566
+ try {
1567
+ while (true) {
1568
+ if (this.aborter.signal.aborted) return;
1569
+ const { value, done } = await reader.read();
1570
+ if (done) break;
1571
+ buffer += decoder.decode(value, { stream: true });
1572
+ let idx;
1573
+ while ((idx = buffer.indexOf("\n\n")) >= 0) {
1574
+ const frame = buffer.slice(0, idx);
1575
+ buffer = buffer.slice(idx + 2);
1576
+ const parsed = parseSSEFrame(frame);
1577
+ if (!parsed) continue;
1578
+ if (parsed.event === "exit") {
1579
+ this.resolveExit({ exitCode: parsed.exitCode ?? -1 });
1580
+ return;
1581
+ }
1582
+ if (parsed.bytes) {
1583
+ yield parsed.bytes;
1584
+ }
1585
+ }
1586
+ }
1587
+ } finally {
1588
+ reader.releaseLock();
1589
+ }
1590
+ }
1591
+ async consumeStream(onData) {
1592
+ try {
1593
+ for await (const chunk of this.stream()) {
1594
+ onData(chunk);
1595
+ }
1596
+ } catch (err) {
1597
+ if (!this.aborter.signal.aborted) {
1598
+ throw err;
1599
+ }
1600
+ } finally {
1601
+ this.resolveExit({ exitCode: -1 });
1602
+ }
1603
+ }
1604
+ };
1605
+ function parseSSEFrame(frame) {
1606
+ let event = "message";
1607
+ let data = "";
1608
+ for (const line of frame.split("\n")) {
1609
+ if (line.startsWith("event:")) event = line.slice(6).trim();
1610
+ else if (line.startsWith("data:")) data += line.slice(5).trim();
1611
+ }
1612
+ if (!event) return null;
1613
+ if (event === "exit") {
1614
+ try {
1615
+ const parsed = JSON.parse(data);
1616
+ return { event, exitCode: parsed.exit_code ?? -1 };
1617
+ } catch {
1618
+ return { event, exitCode: -1 };
1619
+ }
1620
+ }
1621
+ if (event === "data") {
1622
+ try {
1623
+ const parsed = JSON.parse(data);
1624
+ if (typeof parsed.data === "string") {
1625
+ const bin = atob(parsed.data);
1626
+ const bytes = new Uint8Array(bin.length);
1627
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
1628
+ return { event, bytes };
1629
+ }
1630
+ } catch {
1631
+ return null;
1632
+ }
1633
+ }
1634
+ return null;
1635
+ }
1636
+ var Pty = class {
1637
+ sandboxId;
1638
+ client;
1639
+ constructor(sandboxId, client) {
1640
+ this.sandboxId = sandboxId;
1641
+ this.client = client;
1642
+ }
1455
1643
  async create(opts) {
1456
1644
  const size = opts?.size ?? { cols: 80, rows: 24 };
1457
1645
  const user = opts?.user ?? "user";
1458
1646
  const body = {
1459
1647
  size: { cols: size.cols, rows: size.rows },
1460
- user
1648
+ user,
1649
+ timeout: opts?.timeout ?? 3600
1461
1650
  };
1462
- if (opts?.cwd !== void 0) {
1463
- body.cwd = opts.cwd;
1464
- }
1465
- if (opts?.envs !== void 0) {
1466
- body.envs = opts.envs;
1467
- }
1468
- if (opts?.timeout !== void 0) {
1469
- body.timeout = opts.timeout;
1470
- }
1651
+ if (opts?.cwd !== void 0) body.cwd = opts.cwd;
1652
+ if (opts?.envs !== void 0) body.envs = opts.envs;
1471
1653
  const data = await this.client.post(
1472
1654
  `/sandboxes/${this.sandboxId}/pty`,
1473
- {
1474
- json: body,
1475
- timeout: opts?.requestTimeout
1476
- }
1655
+ { json: body, timeout: opts?.requestTimeout }
1477
1656
  );
1478
- const response = data;
1479
- const pid = response.pid;
1480
- return new CommandHandle(pid, this.sandboxId, this.client);
1657
+ const pid = data.pid;
1658
+ return new PtyHandle(pid, this.sandboxId, this.client, opts?.onData);
1481
1659
  }
1482
1660
  /**
1483
- * Kill a PTY session.
1661
+ * Reattach to an already-running PTY by its pid.
1484
1662
  *
1485
- * Sends DELETE /sandboxes/:id/pty/:pid.
1486
- * @returns true if the process was killed, false if already dead.
1663
+ * Returns a fresh `PtyHandle` that streams the live output of the
1664
+ * existing session. Multiple clients can subscribe to the same pid
1665
+ * concurrently — each receives output from the moment it connects
1666
+ * (no scrollback replay).
1487
1667
  */
1668
+ connect(pid, opts) {
1669
+ return new PtyHandle(pid, this.sandboxId, this.client, opts?.onData);
1670
+ }
1671
+ // --- Low-level API by pid (kept for callers that already hold one). ---
1488
1672
  async kill(pid, requestTimeout) {
1489
1673
  const data = await this.client.delete(
1490
1674
  `/sandboxes/${this.sandboxId}/pty/${pid}`,
@@ -1492,27 +1676,13 @@ var Pty = class {
1492
1676
  );
1493
1677
  return data.killed === true;
1494
1678
  }
1495
- /**
1496
- * Send input to a PTY session.
1497
- *
1498
- * Sends POST /sandboxes/:id/pty/:pid/stdin.
1499
- * If data is a Uint8Array, it is decoded to a string using TextDecoder.
1500
- */
1501
1679
  async sendStdin(pid, data, requestTimeout) {
1502
1680
  const strData = typeof data === "string" ? data : new TextDecoder().decode(data);
1503
1681
  await this.client.post(
1504
1682
  `/sandboxes/${this.sandboxId}/pty/${pid}/stdin`,
1505
- {
1506
- json: { data: strData },
1507
- timeout: requestTimeout
1508
- }
1683
+ { json: { data: strData }, timeout: requestTimeout }
1509
1684
  );
1510
1685
  }
1511
- /**
1512
- * Resize a PTY session.
1513
- *
1514
- * Sends PATCH /sandboxes/:id/pty/:pid.
1515
- */
1516
1686
  async resize(pid, size, requestTimeout) {
1517
1687
  await this.client.patch(
1518
1688
  `/sandboxes/${this.sandboxId}/pty/${pid}`,
@@ -2296,6 +2466,7 @@ export {
2296
2466
  NotFoundError,
2297
2467
  PIIType,
2298
2468
  Pty,
2469
+ PtyHandle,
2299
2470
  RedactionAction,
2300
2471
  Sandbox,
2301
2472
  SandboxError,