@banata-boxes/sdk 0.2.1 → 0.2.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/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // sdk/src/index.ts
2
- // Customer-facing TypeScript SDK for Browser-as-a-Service
2
+ // Customer-facing TypeScript SDK for Banata sandboxes
3
3
  //
4
4
  // M7 FIX: Added retry with exponential backoff on transient errors (5xx, network).
5
5
 
6
- export interface BrowserConfig {
6
+ interface BrowserConfig {
7
7
  /** Session weight: light (512MB, default), medium (1GB), heavy (2GB — Pro+) */
8
8
  weight?: 'light' | 'medium' | 'heavy';
9
9
  /** Isolation mode: shared (default) or dedicated (Pro+) */
@@ -43,7 +43,7 @@ export interface BrowserConfig {
43
43
  timeout?: number;
44
44
  }
45
45
 
46
- export interface BrowserSession {
46
+ interface BrowserSession {
47
47
  id: string;
48
48
  status: 'queued' | 'assigning' | 'ready' | 'active' | 'ending' | 'ended' | 'failed';
49
49
  waitTimedOut?: boolean;
@@ -64,6 +64,11 @@ export interface BrowserSession {
64
64
  export interface UsageInfo {
65
65
  totalSessions: number;
66
66
  totalBrowserHours: number;
67
+ totalSandboxSessions?: number;
68
+ totalSandboxHours?: number;
69
+ sandboxSizeBreakdown?: {
70
+ standard: number;
71
+ };
67
72
  weightBreakdown: {
68
73
  light: number;
69
74
  medium: number;
@@ -78,6 +83,8 @@ export interface BillingInfo {
78
83
  currentPeriod: {
79
84
  totalSessions: number;
80
85
  totalBrowserHours: number;
86
+ totalSandboxSessions?: number;
87
+ totalSandboxHours?: number;
81
88
  weightBreakdown: {
82
89
  light: number;
83
90
  medium: number;
@@ -86,7 +93,7 @@ export interface BillingInfo {
86
93
  };
87
94
  }
88
95
 
89
- export interface LaunchedBrowserSession {
96
+ interface LaunchedBrowserSession {
90
97
  cdpUrl: string;
91
98
  sessionId: string;
92
99
  previewViewerUrl: string | null;
@@ -263,6 +270,15 @@ export class BanataError extends Error {
263
270
  /** @deprecated Use BanataError instead */
264
271
  export const BrowserServiceError = BanataError;
265
272
 
273
+ const BROWSER_SESSIONS_UNSUPPORTED_MESSAGE =
274
+ "Browser-only sessions are not currently supported. Use BanataSandbox with browser capabilities instead.";
275
+
276
+ function throwBrowserSessionsUnsupported(): never {
277
+ throw new BanataError(BROWSER_SESSIONS_UNSUPPORTED_MESSAGE, 501, {
278
+ code: "NOT_SUPPORTED",
279
+ });
280
+ }
281
+
266
282
  /** Check if an HTTP status is retryable (5xx or 429) */
267
283
  function isRetryableStatus(status: number): boolean {
268
284
  return status >= 500 || status === 429;
@@ -284,10 +300,8 @@ function derivePreviewConnection(
284
300
  const parsed = new URL(rawUrl);
285
301
  const token = parsed.searchParams.get("token");
286
302
  const sessionId = parsed.searchParams.get("session");
303
+ const key = parsed.searchParams.get("key");
287
304
  const machine = parsed.searchParams.get("machine");
288
- if (!token || !sessionId) {
289
- return null;
290
- }
291
305
 
292
306
  const httpProtocol =
293
307
  parsed.protocol === "https:" || parsed.protocol === "wss:"
@@ -299,6 +313,27 @@ function derivePreviewConnection(
299
313
  : parsed.protocol === "http:"
300
314
  ? "ws:"
301
315
  : parsed.protocol;
316
+ const isDirectPreviewUrl =
317
+ parsed.pathname.endsWith("/ws") && parsed.pathname.includes("/preview/");
318
+
319
+ if (isDirectPreviewUrl && key) {
320
+ const basePath = parsed.pathname.slice(0, -3);
321
+ const search = new URLSearchParams({ key }).toString();
322
+ return {
323
+ rawUrl,
324
+ token: key,
325
+ sessionId: sessionId ?? "preview",
326
+ startUrl: `${httpProtocol}//${parsed.host}${basePath}/start?${search}`,
327
+ navigateUrl: `${httpProtocol}//${parsed.host}${basePath}/navigate?${search}`,
328
+ resizeUrl: `${httpProtocol}//${parsed.host}${basePath}/resize?${search}`,
329
+ wsUrl: `${wsProtocol}//${parsed.host}${parsed.pathname}?${search}`,
330
+ };
331
+ }
332
+
333
+ if (!token || !sessionId) {
334
+ return null;
335
+ }
336
+
302
337
  const searchParams = new URLSearchParams({
303
338
  token,
304
339
  session: sessionId,
@@ -307,8 +342,6 @@ function derivePreviewConnection(
307
342
  searchParams.set("machine", machine);
308
343
  }
309
344
  const search = searchParams.toString();
310
- const isDirectPreviewUrl =
311
- parsed.pathname.endsWith("/ws") && parsed.pathname.includes("/preview/");
312
345
 
313
346
  if (isDirectPreviewUrl) {
314
347
  const basePath = parsed.pathname.slice(0, -3);
@@ -337,25 +370,6 @@ function derivePreviewConnection(
337
370
  }
338
371
  }
339
372
 
340
- function withMachineQuery(
341
- rawUrl: string | null | undefined,
342
- machineId: string | null | undefined,
343
- ): string | null | undefined {
344
- if (!rawUrl || !machineId) {
345
- return rawUrl;
346
- }
347
-
348
- try {
349
- const parsed = new URL(rawUrl);
350
- if (!parsed.searchParams.get("machine")) {
351
- parsed.searchParams.set("machine", machineId);
352
- }
353
- return parsed.toString();
354
- } catch {
355
- return rawUrl;
356
- }
357
- }
358
-
359
373
  function deriveOpenCodeConnection(
360
374
  rawUrl: string | null | undefined,
361
375
  ): OpenCodeConnectionInfo | null {
@@ -391,6 +405,25 @@ function deriveOpenCodeConnection(
391
405
  }
392
406
  }
393
407
 
408
+ function withMachineQuery(
409
+ rawUrl: string | null | undefined,
410
+ machineId: string | null | undefined,
411
+ ): string | null | undefined {
412
+ if (!rawUrl || !machineId) {
413
+ return rawUrl;
414
+ }
415
+
416
+ try {
417
+ const parsed = new URL(rawUrl);
418
+ if (!parsed.searchParams.get("machine")) {
419
+ parsed.searchParams.set("machine", machineId);
420
+ }
421
+ return parsed.toString();
422
+ } catch {
423
+ return rawUrl;
424
+ }
425
+ }
426
+
394
427
  async function parseJsonResponse(response: Response): Promise<unknown> {
395
428
  const text = await response.text();
396
429
  if (!text) {
@@ -508,8 +541,7 @@ function isSandboxSessionOperationallyReady(
508
541
  return false;
509
542
  }
510
543
 
511
- const browserMode = session.capabilities?.browser?.mode ?? "none";
512
- if (browserMode !== "none") {
544
+ if (session.capabilities?.browser) {
513
545
  if (!session.pairedBrowser?.cdpUrl) {
514
546
  return false;
515
547
  }
@@ -528,7 +560,7 @@ function isSandboxSessionOperationallyReady(
528
560
  return true;
529
561
  }
530
562
 
531
- export class BrowserCloud {
563
+ class BrowserCloud {
532
564
  private apiKey: string;
533
565
  private baseUrl: string;
534
566
  private appUrl: string;
@@ -634,56 +666,37 @@ export class BrowserCloud {
634
666
 
635
667
  /** Create a new browser session */
636
668
  async createBrowser(config: BrowserConfig = {}): Promise<BrowserSession> {
637
- const { timeout, waitTimeoutMs, ...rest } = config;
638
- const requestBody: Record<string, unknown> = { ...rest };
639
- if (waitTimeoutMs !== undefined) {
640
- requestBody.waitTimeoutMs = waitTimeoutMs;
641
- }
642
- return this.request<BrowserSession>('/v1/browsers', {
643
- method: 'POST',
644
- body: JSON.stringify(requestBody),
645
- });
669
+ void config;
670
+ throwBrowserSessionsUnsupported();
646
671
  }
647
672
 
648
673
  /** Get session status */
649
674
  async getBrowser(sessionId: string): Promise<BrowserSession> {
650
- return this.request<BrowserSession>(
651
- `/v1/browsers?id=${encodeURIComponent(sessionId)}`
652
- );
675
+ void sessionId;
676
+ throwBrowserSessionsUnsupported();
653
677
  }
654
678
 
655
679
  /** End a session */
656
680
  async closeBrowser(sessionId: string): Promise<void> {
657
- await this.request(`/v1/browsers?id=${encodeURIComponent(sessionId)}`, {
658
- method: 'DELETE',
659
- });
681
+ void sessionId;
682
+ throwBrowserSessionsUnsupported();
660
683
  }
661
684
 
662
685
  /** Derive the preview websocket/start endpoints for a live browser session. */
663
686
  async getPreviewConnection(sessionId: string): Promise<PreviewConnectionInfo | null> {
664
- const session = await this.getBrowser(sessionId);
665
- return derivePreviewConnection(
666
- session.previewUrl ?? withMachineQuery(session.cdpUrl, session.machineId),
667
- );
687
+ void sessionId;
688
+ throwBrowserSessionsUnsupported();
668
689
  }
669
690
 
670
691
  async getPreviewViewerUrl(sessionId: string): Promise<string | null> {
671
- const session = await this.getBrowser(sessionId);
672
- return buildPreviewViewerUrl(
673
- session.previewUrl ?? withMachineQuery(session.cdpUrl, session.machineId),
674
- this.appUrl,
675
- );
692
+ void sessionId;
693
+ throwBrowserSessionsUnsupported();
676
694
  }
677
695
 
678
696
  /** Start the remote browser preview backend for a live browser session. */
679
697
  async startPreview(sessionId: string): Promise<PreviewCommandResponse> {
680
- const connection = await this.getPreviewConnection(sessionId);
681
- if (!connection) {
682
- throw new BanataError("Browser preview is not available for this session", 409);
683
- }
684
- return requestPreviewEndpoint<PreviewCommandResponse>(connection.startUrl, {
685
- method: "POST",
686
- });
698
+ void sessionId;
699
+ throwBrowserSessionsUnsupported();
687
700
  }
688
701
 
689
702
  /** Navigate the live browser preview session. */
@@ -691,15 +704,9 @@ export class BrowserCloud {
691
704
  sessionId: string,
692
705
  url: string,
693
706
  ): Promise<PreviewCommandResponse> {
694
- const connection = await this.getPreviewConnection(sessionId);
695
- if (!connection) {
696
- throw new BanataError("Browser preview is not available for this session", 409);
697
- }
698
- return requestPreviewEndpoint<PreviewCommandResponse>(connection.navigateUrl, {
699
- method: "POST",
700
- headers: { "Content-Type": "application/json" },
701
- body: JSON.stringify({ url }),
702
- });
707
+ void sessionId;
708
+ void url;
709
+ throwBrowserSessionsUnsupported();
703
710
  }
704
711
 
705
712
  /** Resize the live browser preview session if the preview backend supports it. */
@@ -707,15 +714,9 @@ export class BrowserCloud {
707
714
  sessionId: string,
708
715
  size: { width?: number; height?: number },
709
716
  ): Promise<PreviewCommandResponse> {
710
- const connection = await this.getPreviewConnection(sessionId);
711
- if (!connection) {
712
- throw new BanataError("Browser preview is not available for this session", 409);
713
- }
714
- return requestPreviewEndpoint<PreviewCommandResponse>(connection.resizeUrl, {
715
- method: "POST",
716
- headers: { "Content-Type": "application/json" },
717
- body: JSON.stringify(size),
718
- });
717
+ void sessionId;
718
+ void size;
719
+ throwBrowserSessionsUnsupported();
719
720
  }
720
721
 
721
722
  /** Wait until session is ready and return CDP URL */
@@ -723,28 +724,9 @@ export class BrowserCloud {
723
724
  sessionId: string,
724
725
  timeoutMs: number = 30_000
725
726
  ): Promise<string> {
726
- const start = Date.now();
727
-
728
- while (Date.now() - start < timeoutMs) {
729
- const session = await this.getBrowser(sessionId);
730
-
731
- if (isBrowserSessionOperationallyReady(session)) {
732
- return session.cdpUrl;
733
- }
734
- if (session.status === 'failed') {
735
- throw new BanataError('Session failed to start', 500);
736
- }
737
- if (session.status === 'ended' || session.status === 'ending') {
738
- throw new BanataError('Session ended before becoming ready', 410);
739
- }
740
-
741
- await sleep(500);
742
- }
743
-
744
- throw new BanataError(
745
- `Session ${sessionId} not ready within ${timeoutMs}ms`,
746
- 408
747
- );
727
+ void sessionId;
728
+ void timeoutMs;
729
+ throwBrowserSessionsUnsupported();
748
730
  }
749
731
 
750
732
  /**
@@ -763,10 +745,12 @@ export class BrowserCloud {
763
745
  async launch(
764
746
  config: BrowserConfig = {}
765
747
  ): Promise<LaunchedBrowserSession> {
748
+ void config;
749
+ throwBrowserSessionsUnsupported();
766
750
  const session = await this.createBrowser(config);
767
751
  let cdpUrl: string;
768
752
  if (isBrowserSessionOperationallyReady(session)) {
769
- cdpUrl = session.cdpUrl;
753
+ cdpUrl = session.cdpUrl!;
770
754
  } else {
771
755
  try {
772
756
  cdpUrl = await this.waitForReady(
@@ -864,19 +848,11 @@ export class BrowserCloud {
864
848
  // ── Sandbox types ──
865
849
 
866
850
  export interface SandboxConfig {
867
- /** Sandbox runtime: bun, python, or base shell */
868
- runtime?: 'bun' | 'python' | 'base';
869
- /** Sandbox compute shape. */
870
- size?: 'standard';
871
851
  /** Environment variables to inject into the sandbox */
872
852
  env?: Record<string, string>;
873
- /** Whether sandbox is ephemeral (destroyed on kill). Default: true */
874
- ephemeral?: boolean;
875
- /** Max session duration in ms */
876
- maxDurationMs?: number;
877
853
  /** Preferred region for sandbox placement */
878
854
  region?: string;
879
- /** Optional powerhouse capabilities to prelaunch inside the sandbox */
855
+ /** Supported capabilities to prelaunch inside the sandbox */
880
856
  capabilities?: SandboxCapabilities;
881
857
  /** Ask the API to wait briefly for the sandbox to become ready before returning */
882
858
  waitUntilReady?: boolean;
@@ -890,14 +866,14 @@ export interface SandboxCapabilities {
890
866
  opencode?: {
891
867
  enabled: boolean;
892
868
  defaultAgent?: "build" | "plan";
893
- allowPromptApi?: boolean;
894
869
  };
895
870
  browser?: {
896
- mode?: "none" | "paired-banata-browser" | "local-chromium";
871
+ /** Save a low-resolution MP4 recording as an artifact. Team plan only. */
897
872
  recording?: boolean;
898
- persistentProfile?: boolean;
899
- streamPreview?: boolean;
900
- humanInLoop?: boolean;
873
+ /** Route outbound traffic through your own proxy. Team plan only. */
874
+ byoProxyUrl?: string;
875
+ /** True when a BYO proxy is configured for this sandbox. */
876
+ byoProxyConfigured?: boolean;
901
877
  viewport?: {
902
878
  width?: number;
903
879
  height?: number;
@@ -907,18 +883,10 @@ export interface SandboxCapabilities {
907
883
  libreofficeHeadless?: boolean;
908
884
  };
909
885
  storage?: {
910
- workspace?: "ephemeral" | "checkpointed";
911
886
  artifactPrefix?: string;
912
887
  };
913
888
  }
914
889
 
915
- export interface SandboxCredentialDescriptor {
916
- name: string;
917
- kind: string;
918
- target: string;
919
- lastInjectedAt?: number;
920
- }
921
-
922
890
  export interface SandboxArtifactItem {
923
891
  key: string;
924
892
  path: string;
@@ -941,8 +909,6 @@ export interface SandboxPairedBrowser {
941
909
  sessionId?: string;
942
910
  cdpUrl?: string;
943
911
  previewUrl?: string;
944
- recording?: boolean;
945
- persistentProfile?: boolean;
946
912
  controlMode?: "ai" | "human" | "shared";
947
913
  controller?: string;
948
914
  handoffRequestedAt?: number;
@@ -1028,7 +994,6 @@ export interface SandboxSession {
1028
994
  terminalUrl: string | null;
1029
995
  previewBaseUrl?: string | null;
1030
996
  capabilities?: SandboxCapabilities | null;
1031
- credentialDescriptors?: SandboxCredentialDescriptor[] | null;
1032
997
  pairedBrowser?: SandboxPairedBrowser | null;
1033
998
  opencode?: SandboxOpencodeState | null;
1034
999
  browserPreview?: SandboxBrowserPreviewState | null;
@@ -1062,9 +1027,18 @@ export interface SandboxOpencodePromptResponse {
1062
1027
 
1063
1028
  export interface SandboxCheckpointResponse {
1064
1029
  ok: boolean;
1030
+ status?: number;
1031
+ checkpointId?: string | null;
1065
1032
  artifacts?: SandboxArtifacts;
1066
1033
  }
1067
1034
 
1035
+ export interface SandboxCheckpointInfo {
1036
+ id: string;
1037
+ createTimeMs: number;
1038
+ comment?: string;
1039
+ history?: string[];
1040
+ }
1041
+
1068
1042
  export interface SandboxArtifactDownload {
1069
1043
  id: string;
1070
1044
  key: string;
@@ -1072,6 +1046,15 @@ export interface SandboxArtifactDownload {
1072
1046
  expiresInSeconds: number;
1073
1047
  }
1074
1048
 
1049
+ export interface SandboxDocumentConversionResult {
1050
+ ok: boolean;
1051
+ inputPath: string;
1052
+ outputPath: string;
1053
+ format: string;
1054
+ artifact: SandboxArtifactItem | null;
1055
+ artifacts: SandboxArtifacts | null;
1056
+ }
1057
+
1075
1058
  export interface SandboxBrowserControlResponse {
1076
1059
  ok: boolean;
1077
1060
  preview: SandboxBrowserPreviewState;
@@ -1156,6 +1139,14 @@ export interface LaunchedSandbox {
1156
1139
  sessionId?: string;
1157
1140
  }) => AsyncGenerator<SandboxOpencodeStreamEvent, void, void>;
1158
1141
  checkpoint: () => Promise<SandboxCheckpointResponse>;
1142
+ listCheckpoints: () => Promise<SandboxCheckpointInfo[]>;
1143
+ restoreCheckpoint: (checkpointId: string) => Promise<{ ok: boolean; status: number; checkpointId: string }>;
1144
+ getArtifacts: () => Promise<{ id: string; artifacts: SandboxArtifacts | null }>;
1145
+ getArtifactDownloadUrl: (key: string, expiresInSeconds?: number) => Promise<SandboxArtifactDownload>;
1146
+ convertDocument: (
1147
+ inputPath: string,
1148
+ options?: { format?: string; outputDir?: string },
1149
+ ) => Promise<SandboxDocumentConversionResult>;
1159
1150
  getRuntime: () => Promise<SandboxRuntimeState>;
1160
1151
  getPreview: () => Promise<SandboxBrowserPreviewInfo>;
1161
1152
  getPreviewConnection: () => Promise<PreviewConnectionInfo | null>;
@@ -1347,10 +1338,111 @@ export class BanataSandbox {
1347
1338
 
1348
1339
  // ── Sandbox lifecycle ──
1349
1340
 
1341
+ async getUsage(): Promise<UsageInfo> {
1342
+ return this.request<UsageInfo>('/v1/usage');
1343
+ }
1344
+
1345
+ async getBilling(): Promise<BillingInfo> {
1346
+ return this.request<BillingInfo>('/v1/billing');
1347
+ }
1348
+
1349
+ async createCheckout(params: {
1350
+ plan: 'builder' | 'pro' | 'scale';
1351
+ successUrl?: string;
1352
+ }): Promise<{ checkoutUrl: string; checkoutId: string }> {
1353
+ return this.request<{ checkoutUrl: string; checkoutId: string }>('/v1/billing/checkout', {
1354
+ method: 'POST',
1355
+ body: JSON.stringify(params),
1356
+ });
1357
+ }
1358
+
1359
+ async listWebhooks(): Promise<WebhookEndpoint[]> {
1360
+ const response = await this.request<{ data: WebhookEndpoint[] }>('/v1/webhooks');
1361
+ return response.data;
1362
+ }
1363
+
1364
+ async createWebhook(params: {
1365
+ url: string;
1366
+ description?: string;
1367
+ eventTypes?: WebhookEventType[];
1368
+ signingSecret?: string;
1369
+ }): Promise<WebhookEndpoint & { signingSecret: string }> {
1370
+ return this.request<WebhookEndpoint & { signingSecret: string }>('/v1/webhooks', {
1371
+ method: 'POST',
1372
+ body: JSON.stringify(params),
1373
+ });
1374
+ }
1375
+
1376
+ async deleteWebhook(id: string): Promise<void> {
1377
+ await this.request(`/v1/webhooks?id=${encodeURIComponent(id)}`, {
1378
+ method: 'DELETE',
1379
+ });
1380
+ }
1381
+
1382
+ async listWebhookDeliveries(limit = 50): Promise<WebhookDelivery[]> {
1383
+ const response = await this.request<{ data: WebhookDelivery[] }>(
1384
+ `/v1/webhooks/deliveries?limit=${encodeURIComponent(String(limit))}`
1385
+ );
1386
+ return response.data;
1387
+ }
1388
+
1389
+ async testWebhook(id: string): Promise<{ eventId: string }> {
1390
+ return this.request<{ eventId: string }>('/v1/webhooks/test', {
1391
+ method: 'POST',
1392
+ body: JSON.stringify({ id }),
1393
+ });
1394
+ }
1395
+
1350
1396
  /** Create a new sandbox session */
1351
1397
  async create(config: SandboxConfig = {}): Promise<SandboxSession> {
1352
- const { timeout, waitTimeoutMs, ...rest } = config;
1398
+ const { timeout, waitTimeoutMs, capabilities, ...rest } = config;
1353
1399
  const requestBody: Record<string, unknown> = { ...rest };
1400
+ if (capabilities) {
1401
+ requestBody.capabilities = {
1402
+ ...(capabilities.opencode
1403
+ ? {
1404
+ opencode: {
1405
+ enabled: capabilities.opencode.enabled,
1406
+ ...(capabilities.opencode.defaultAgent
1407
+ ? { defaultAgent: capabilities.opencode.defaultAgent }
1408
+ : {}),
1409
+ },
1410
+ }
1411
+ : {}),
1412
+ ...(capabilities.browser?.viewport
1413
+ || capabilities.browser?.recording !== undefined
1414
+ || capabilities.browser?.byoProxyUrl
1415
+ ? {
1416
+ browser: {
1417
+ ...(capabilities.browser?.viewport
1418
+ ? { viewport: capabilities.browser.viewport }
1419
+ : {}),
1420
+ ...(capabilities.browser?.recording !== undefined
1421
+ ? { recording: capabilities.browser.recording }
1422
+ : {}),
1423
+ ...(capabilities.browser?.byoProxyUrl
1424
+ ? { byoProxyUrl: capabilities.browser.byoProxyUrl }
1425
+ : {}),
1426
+ },
1427
+ }
1428
+ : {}),
1429
+ ...(capabilities.documents
1430
+ ? {
1431
+ documents: {
1432
+ libreofficeHeadless:
1433
+ capabilities.documents.libreofficeHeadless ?? true,
1434
+ },
1435
+ }
1436
+ : {}),
1437
+ ...(capabilities.storage?.artifactPrefix
1438
+ ? {
1439
+ storage: {
1440
+ artifactPrefix: capabilities.storage.artifactPrefix,
1441
+ },
1442
+ }
1443
+ : {}),
1444
+ };
1445
+ }
1354
1446
  if (waitTimeoutMs !== undefined) {
1355
1447
  requestBody.waitTimeoutMs = waitTimeoutMs;
1356
1448
  }
@@ -1451,7 +1543,11 @@ export class BanataSandbox {
1451
1543
  /** Read a file from the sandbox */
1452
1544
  read: async (id: string, path: string): Promise<string> => {
1453
1545
  const result = await this.request<{ content: string }>(
1454
- `/v1/sandboxes/fs/read?id=${encodeURIComponent(id)}&path=${encodeURIComponent(path)}`
1546
+ '/v1/sandboxes/fs/read',
1547
+ {
1548
+ method: 'POST',
1549
+ body: JSON.stringify({ id, path }),
1550
+ },
1455
1551
  );
1456
1552
  return result.content;
1457
1553
  },
@@ -1466,9 +1562,10 @@ export class BanataSandbox {
1466
1562
 
1467
1563
  /** List files in a directory in the sandbox */
1468
1564
  list: async (id: string, path?: string): Promise<FsEntry[]> => {
1469
- return this.request<FsEntry[]>(
1470
- `/v1/sandboxes/fs/list?id=${encodeURIComponent(id)}&path=${encodeURIComponent(path ?? '/workspace')}`
1471
- );
1565
+ return this.request<FsEntry[]>('/v1/sandboxes/fs/list', {
1566
+ method: 'POST',
1567
+ body: JSON.stringify({ id, path: path ?? '/workspace' }),
1568
+ });
1472
1569
  },
1473
1570
  };
1474
1571
 
@@ -1527,7 +1624,9 @@ export class BanataSandbox {
1527
1624
  async getOpencodeConnection(id: string): Promise<OpenCodeConnectionInfo | null> {
1528
1625
  const preview = await this.getPreview(id);
1529
1626
  return deriveOpenCodeConnection(
1530
- preview.browserPreviewUrl ??
1627
+ preview.browserPreview?.websocketUrl ??
1628
+ preview.runtime?.websocketUrl ??
1629
+ preview.browserPreviewUrl ??
1531
1630
  preview.browserPreview?.publicUrl ??
1532
1631
  preview.pairedBrowser?.previewUrl ??
1533
1632
  null,
@@ -1744,6 +1843,24 @@ export class BanataSandbox {
1744
1843
  });
1745
1844
  }
1746
1845
 
1846
+ async listCheckpoints(id: string): Promise<SandboxCheckpointInfo[]> {
1847
+ const response = await this.request<{
1848
+ id: string;
1849
+ checkpoints: SandboxCheckpointInfo[];
1850
+ currentCheckpointId?: string | null;
1851
+ }>(
1852
+ `/v1/sandboxes/checkpoints?id=${encodeURIComponent(id)}`
1853
+ );
1854
+ return response.checkpoints;
1855
+ }
1856
+
1857
+ async restoreCheckpoint(id: string, checkpointId: string): Promise<{ ok: boolean; status: number; checkpointId: string }> {
1858
+ return this.request<{ ok: boolean; status: number; checkpointId: string }>('/v1/sandboxes/checkpoints/restore', {
1859
+ method: 'POST',
1860
+ body: JSON.stringify({ id, checkpointId }),
1861
+ });
1862
+ }
1863
+
1747
1864
  async getArtifacts(id: string): Promise<{ id: string; artifacts: SandboxArtifacts | null }> {
1748
1865
  return this.request(
1749
1866
  `/v1/sandboxes/artifacts?id=${encodeURIComponent(id)}`
@@ -1760,29 +1877,64 @@ export class BanataSandbox {
1760
1877
  );
1761
1878
  }
1762
1879
 
1880
+ async convertDocument(
1881
+ id: string,
1882
+ inputPath: string,
1883
+ options: {
1884
+ format?: string;
1885
+ outputDir?: string;
1886
+ } = {},
1887
+ ): Promise<SandboxDocumentConversionResult> {
1888
+ return this.request<SandboxDocumentConversionResult>('/v1/sandboxes/documents/convert', {
1889
+ method: 'POST',
1890
+ body: JSON.stringify({
1891
+ id,
1892
+ inputPath,
1893
+ format: options.format,
1894
+ outputDir: options.outputDir,
1895
+ }),
1896
+ });
1897
+ }
1898
+
1763
1899
  async getPreview(id: string): Promise<SandboxBrowserPreviewInfo> {
1764
1900
  const preview = await this.request<SandboxBrowserPreviewInfo>(
1765
1901
  `/v1/sandboxes/browser-preview?id=${encodeURIComponent(id)}`
1766
1902
  );
1767
1903
  return {
1768
1904
  ...preview,
1769
- browserPreviewViewerUrl: buildPreviewViewerUrl(
1770
- preview.browserPreviewUrl,
1771
- this.appUrl,
1772
- ),
1905
+ browserPreviewViewerUrl:
1906
+ preview.browserPreviewViewerUrl ??
1907
+ preview.browserPreview?.publicUrl ??
1908
+ preview.pairedBrowser?.previewUrl ??
1909
+ buildPreviewViewerUrl(
1910
+ preview.browserPreview?.websocketUrl ??
1911
+ preview.runtime?.websocketUrl ??
1912
+ preview.browserPreviewUrl,
1913
+ this.appUrl,
1914
+ ),
1773
1915
  };
1774
1916
  }
1775
1917
 
1776
1918
  async getPreviewConnection(id: string): Promise<PreviewConnectionInfo | null> {
1777
1919
  const preview = await this.getPreview(id);
1778
- return derivePreviewConnection(preview.browserPreviewUrl);
1920
+ return derivePreviewConnection(
1921
+ preview.browserPreview?.websocketUrl ??
1922
+ preview.runtime?.websocketUrl ??
1923
+ preview.browserPreviewUrl,
1924
+ );
1779
1925
  }
1780
1926
 
1781
1927
  async getPreviewViewerUrl(id: string): Promise<string | null> {
1782
1928
  const preview = await this.getPreview(id);
1783
1929
  return (
1784
1930
  preview.browserPreviewViewerUrl ??
1785
- buildPreviewViewerUrl(preview.browserPreviewUrl, this.appUrl)
1931
+ preview.browserPreview?.publicUrl ??
1932
+ buildPreviewViewerUrl(
1933
+ preview.browserPreview?.websocketUrl ??
1934
+ preview.runtime?.websocketUrl ??
1935
+ preview.browserPreviewUrl,
1936
+ this.appUrl,
1937
+ )
1786
1938
  );
1787
1939
  }
1788
1940
 
@@ -1911,7 +2063,7 @@ export class BanataSandbox {
1911
2063
  * Usage:
1912
2064
  * ```ts
1913
2065
  * const sandbox = new BanataSandbox({ apiKey: '...' });
1914
- * const session = await sandbox.launch({ runtime: 'bun', size: 'standard' });
2066
+ * const session = await sandbox.launch();
1915
2067
  *
1916
2068
  * const result = await session.exec('echo', ['Hello!']);
1917
2069
  * console.log(result.stdout);
@@ -1974,6 +2126,16 @@ export class BanataSandbox {
1974
2126
  streamOpencodeEvents: (options) =>
1975
2127
  this.streamOpencodeEvents(sessionId, options),
1976
2128
  checkpoint: () => this.checkpoint(sessionId),
2129
+ listCheckpoints: () => this.listCheckpoints(sessionId),
2130
+ restoreCheckpoint: (checkpointId: string) =>
2131
+ this.restoreCheckpoint(sessionId, checkpointId),
2132
+ getArtifacts: () => this.getArtifacts(sessionId),
2133
+ getArtifactDownloadUrl: (key: string, expiresInSeconds?: number) =>
2134
+ this.getArtifactDownloadUrl(sessionId, key, expiresInSeconds),
2135
+ convertDocument: (
2136
+ inputPath: string,
2137
+ options?: { format?: string; outputDir?: string },
2138
+ ) => this.convertDocument(sessionId, inputPath, options),
1977
2139
  getRuntime: () => this.getRuntime(sessionId),
1978
2140
  getPreview: () => this.getPreview(sessionId),
1979
2141
  getPreviewConnection: () => this.getPreviewConnection(sessionId),
@@ -2004,4 +2166,4 @@ export class BanataSandbox {
2004
2166
  }
2005
2167
 
2006
2168
  // Default export for convenience
2007
- export default BrowserCloud;
2169
+ export default BanataSandbox;