@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/dist/index.js CHANGED
@@ -54,12 +54,27 @@ function derivePreviewConnection(rawUrl) {
54
54
  const parsed = new URL(rawUrl);
55
55
  const token = parsed.searchParams.get("token");
56
56
  const sessionId = parsed.searchParams.get("session");
57
+ const key = parsed.searchParams.get("key");
57
58
  const machine = parsed.searchParams.get("machine");
59
+ const httpProtocol = parsed.protocol === "https:" || parsed.protocol === "wss:" ? "https:" : "http:";
60
+ const wsProtocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
61
+ const isDirectPreviewUrl = parsed.pathname.endsWith("/ws") && parsed.pathname.includes("/preview/");
62
+ if (isDirectPreviewUrl && key) {
63
+ const basePath = parsed.pathname.slice(0, -3);
64
+ const search2 = new URLSearchParams({ key }).toString();
65
+ return {
66
+ rawUrl,
67
+ token: key,
68
+ sessionId: sessionId ?? "preview",
69
+ startUrl: `${httpProtocol}//${parsed.host}${basePath}/start?${search2}`,
70
+ navigateUrl: `${httpProtocol}//${parsed.host}${basePath}/navigate?${search2}`,
71
+ resizeUrl: `${httpProtocol}//${parsed.host}${basePath}/resize?${search2}`,
72
+ wsUrl: `${wsProtocol}//${parsed.host}${parsed.pathname}?${search2}`
73
+ };
74
+ }
58
75
  if (!token || !sessionId) {
59
76
  return null;
60
77
  }
61
- const httpProtocol = parsed.protocol === "https:" || parsed.protocol === "wss:" ? "https:" : "http:";
62
- const wsProtocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
63
78
  const searchParams = new URLSearchParams({
64
79
  token,
65
80
  session: sessionId
@@ -68,7 +83,6 @@ function derivePreviewConnection(rawUrl) {
68
83
  searchParams.set("machine", machine);
69
84
  }
70
85
  const search = searchParams.toString();
71
- const isDirectPreviewUrl = parsed.pathname.endsWith("/ws") && parsed.pathname.includes("/preview/");
72
86
  if (isDirectPreviewUrl) {
73
87
  const basePath = parsed.pathname.slice(0, -3);
74
88
  return {
@@ -94,20 +108,6 @@ function derivePreviewConnection(rawUrl) {
94
108
  return null;
95
109
  }
96
110
  }
97
- function withMachineQuery(rawUrl, machineId) {
98
- if (!rawUrl || !machineId) {
99
- return rawUrl;
100
- }
101
- try {
102
- const parsed = new URL(rawUrl);
103
- if (!parsed.searchParams.get("machine")) {
104
- parsed.searchParams.set("machine", machineId);
105
- }
106
- return parsed.toString();
107
- } catch {
108
- return rawUrl;
109
- }
110
- }
111
111
  function deriveOpenCodeConnection(rawUrl) {
112
112
  if (!rawUrl) {
113
113
  return null;
@@ -200,15 +200,11 @@ function parseSseBlock(block) {
200
200
  };
201
201
  }
202
202
  }
203
- function isBrowserSessionOperationallyReady(session) {
204
- return (session.status === "ready" || session.status === "active") && Boolean(session.cdpUrl);
205
- }
206
203
  function isSandboxSessionOperationallyReady(session) {
207
204
  if (session.status !== "ready" && session.status !== "active") {
208
205
  return false;
209
206
  }
210
- const browserMode = session.capabilities?.browser?.mode ?? "none";
211
- if (browserMode !== "none") {
207
+ if (session.capabilities?.browser) {
212
208
  if (!session.pairedBrowser?.cdpUrl) {
213
209
  return false;
214
210
  }
@@ -220,8 +216,7 @@ function isSandboxSessionOperationallyReady(session) {
220
216
  }
221
217
  return true;
222
218
  }
223
-
224
- class BrowserCloud {
219
+ class BanataSandbox {
225
220
  apiKey;
226
221
  baseUrl;
227
222
  appUrl;
@@ -283,6 +278,9 @@ class BrowserCloud {
283
278
  }
284
279
  throw error;
285
280
  }
281
+ if (res.status === 204) {
282
+ return;
283
+ }
286
284
  return res.json();
287
285
  } catch (err) {
288
286
  if (err instanceof BanataError) {
@@ -299,108 +297,6 @@ class BrowserCloud {
299
297
  }
300
298
  throw lastError ?? new Error("Request failed");
301
299
  }
302
- async createBrowser(config = {}) {
303
- const { timeout, waitTimeoutMs, ...rest } = config;
304
- const requestBody = { ...rest };
305
- if (waitTimeoutMs !== undefined) {
306
- requestBody.waitTimeoutMs = waitTimeoutMs;
307
- }
308
- return this.request("/v1/browsers", {
309
- method: "POST",
310
- body: JSON.stringify(requestBody)
311
- });
312
- }
313
- async getBrowser(sessionId) {
314
- return this.request(`/v1/browsers?id=${encodeURIComponent(sessionId)}`);
315
- }
316
- async closeBrowser(sessionId) {
317
- await this.request(`/v1/browsers?id=${encodeURIComponent(sessionId)}`, {
318
- method: "DELETE"
319
- });
320
- }
321
- async getPreviewConnection(sessionId) {
322
- const session = await this.getBrowser(sessionId);
323
- return derivePreviewConnection(session.previewUrl ?? withMachineQuery(session.cdpUrl, session.machineId));
324
- }
325
- async getPreviewViewerUrl(sessionId) {
326
- const session = await this.getBrowser(sessionId);
327
- return buildPreviewViewerUrl(session.previewUrl ?? withMachineQuery(session.cdpUrl, session.machineId), this.appUrl);
328
- }
329
- async startPreview(sessionId) {
330
- const connection = await this.getPreviewConnection(sessionId);
331
- if (!connection) {
332
- throw new BanataError("Browser preview is not available for this session", 409);
333
- }
334
- return requestPreviewEndpoint(connection.startUrl, {
335
- method: "POST"
336
- });
337
- }
338
- async navigatePreview(sessionId, url) {
339
- const connection = await this.getPreviewConnection(sessionId);
340
- if (!connection) {
341
- throw new BanataError("Browser preview is not available for this session", 409);
342
- }
343
- return requestPreviewEndpoint(connection.navigateUrl, {
344
- method: "POST",
345
- headers: { "Content-Type": "application/json" },
346
- body: JSON.stringify({ url })
347
- });
348
- }
349
- async resizePreview(sessionId, size) {
350
- const connection = await this.getPreviewConnection(sessionId);
351
- if (!connection) {
352
- throw new BanataError("Browser preview is not available for this session", 409);
353
- }
354
- return requestPreviewEndpoint(connection.resizeUrl, {
355
- method: "POST",
356
- headers: { "Content-Type": "application/json" },
357
- body: JSON.stringify(size)
358
- });
359
- }
360
- async waitForReady(sessionId, timeoutMs = 30000) {
361
- const start = Date.now();
362
- while (Date.now() - start < timeoutMs) {
363
- const session = await this.getBrowser(sessionId);
364
- if (isBrowserSessionOperationallyReady(session)) {
365
- return session.cdpUrl;
366
- }
367
- if (session.status === "failed") {
368
- throw new BanataError("Session failed to start", 500);
369
- }
370
- if (session.status === "ended" || session.status === "ending") {
371
- throw new BanataError("Session ended before becoming ready", 410);
372
- }
373
- await sleep(500);
374
- }
375
- throw new BanataError(`Session ${sessionId} not ready within ${timeoutMs}ms`, 408);
376
- }
377
- async launch(config = {}) {
378
- const session = await this.createBrowser(config);
379
- let cdpUrl;
380
- if (isBrowserSessionOperationallyReady(session)) {
381
- cdpUrl = session.cdpUrl;
382
- } else {
383
- try {
384
- cdpUrl = await this.waitForReady(session.id, config.timeout ?? 30000);
385
- } catch (err) {
386
- try {
387
- await this.closeBrowser(session.id);
388
- } catch {}
389
- throw err;
390
- }
391
- }
392
- return {
393
- cdpUrl,
394
- sessionId: session.id,
395
- previewViewerUrl: buildPreviewViewerUrl(session.previewUrl ?? withMachineQuery(cdpUrl, session.machineId), this.appUrl),
396
- close: () => this.closeBrowser(session.id),
397
- getPreviewConnection: () => this.getPreviewConnection(session.id),
398
- getPreviewViewerUrl: () => this.getPreviewViewerUrl(session.id),
399
- startPreview: () => this.startPreview(session.id),
400
- navigatePreview: (url) => this.navigatePreview(session.id, url),
401
- resizePreview: (size) => this.resizePreview(session.id, size)
402
- };
403
- }
404
300
  async getUsage() {
405
301
  return this.request("/v1/usage");
406
302
  }
@@ -438,92 +334,36 @@ class BrowserCloud {
438
334
  body: JSON.stringify({ id })
439
335
  });
440
336
  }
441
- }
442
-
443
- class BanataSandbox {
444
- apiKey;
445
- baseUrl;
446
- appUrl;
447
- retryConfig;
448
- constructor(config) {
449
- if (!config.apiKey)
450
- throw new Error("API key is required");
451
- this.apiKey = config.apiKey;
452
- this.baseUrl = config.baseUrl ?? "https://api.boxes.banata.dev";
453
- this.appUrl = config.appUrl ?? "https://boxes.banata.dev";
454
- this.retryConfig = {
455
- maxRetries: config.retry?.maxRetries ?? 3,
456
- baseDelayMs: config.retry?.baseDelayMs ?? 500,
457
- maxDelayMs: config.retry?.maxDelayMs ?? 1e4
458
- };
459
- }
460
- get headers() {
461
- return {
462
- Authorization: `Bearer ${this.apiKey}`,
463
- "Content-Type": "application/json"
464
- };
465
- }
466
- async request(path, options = {}) {
467
- const { maxRetries, baseDelayMs, maxDelayMs } = this.retryConfig;
468
- let lastError;
469
- for (let attempt = 0;attempt <= maxRetries; attempt++) {
470
- try {
471
- const res = await fetch(`${this.baseUrl}${path}`, {
472
- ...options,
473
- headers: { ...this.headers, ...options.headers }
474
- });
475
- if (!res.ok) {
476
- const body = await res.text();
477
- let message;
478
- let parsed = {};
479
- try {
480
- parsed = JSON.parse(body);
481
- message = parsed.error ?? body;
482
- } catch {
483
- message = body;
337
+ async create(config = {}) {
338
+ const { timeout, waitTimeoutMs, capabilities, ...rest } = config;
339
+ const requestBody = { ...rest };
340
+ if (capabilities) {
341
+ requestBody.capabilities = {
342
+ ...capabilities.opencode ? {
343
+ opencode: {
344
+ enabled: capabilities.opencode.enabled,
345
+ ...capabilities.opencode.defaultAgent ? { defaultAgent: capabilities.opencode.defaultAgent } : {}
484
346
  }
485
- const error = new BanataError(message, res.status, {
486
- code: parsed.code,
487
- requiredPlan: parsed.requiredPlan,
488
- currentPlan: parsed.currentPlan
489
- });
490
- if (isRetryableStatus(res.status) && attempt < maxRetries) {
491
- lastError = error;
492
- const retryAfterHeader = res.headers.get("Retry-After");
493
- let delay;
494
- if (retryAfterHeader) {
495
- delay = Math.min(parseInt(retryAfterHeader, 10) * 1000, maxDelayMs);
496
- } else {
497
- delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
498
- }
499
- delay += Math.random() * delay * 0.25;
500
- await sleep(delay);
501
- continue;
347
+ } : {},
348
+ ...capabilities.browser?.viewport || capabilities.browser?.recording !== undefined || capabilities.browser?.byoProxyUrl ? {
349
+ browser: {
350
+ ...capabilities.browser?.viewport ? { viewport: capabilities.browser.viewport } : {},
351
+ ...capabilities.browser?.recording !== undefined ? { recording: capabilities.browser.recording } : {},
352
+ ...capabilities.browser?.byoProxyUrl ? { byoProxyUrl: capabilities.browser.byoProxyUrl } : {}
502
353
  }
503
- throw error;
504
- }
505
- if (res.status === 204) {
506
- return;
507
- }
508
- return res.json();
509
- } catch (err) {
510
- if (err instanceof BanataError) {
511
- throw err;
512
- }
513
- lastError = err instanceof Error ? err : new Error(String(err));
514
- if (attempt < maxRetries) {
515
- const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
516
- await sleep(delay + Math.random() * delay * 0.25);
517
- continue;
518
- }
519
- throw new BanataError(`Network error after ${maxRetries + 1} attempts: ${lastError.message}`, 0);
520
- }
354
+ } : {},
355
+ ...capabilities.documents ? {
356
+ documents: {
357
+ libreofficeHeadless: capabilities.documents.libreofficeHeadless ?? true
358
+ }
359
+ } : {},
360
+ ...capabilities.storage?.artifactPrefix ? {
361
+ storage: {
362
+ artifactPrefix: capabilities.storage.artifactPrefix
363
+ }
364
+ } : {}
365
+ };
521
366
  }
522
- throw lastError ?? new Error("Request failed");
523
- }
524
- async create(config = {}) {
525
- const { timeout, waitTimeoutMs, ...rest } = config;
526
- const requestBody = { ...rest };
527
367
  if (waitTimeoutMs !== undefined) {
528
368
  requestBody.waitTimeoutMs = waitTimeoutMs;
529
369
  }
@@ -587,7 +427,10 @@ class BanataSandbox {
587
427
  }
588
428
  fs = {
589
429
  read: async (id, path) => {
590
- const result = await this.request(`/v1/sandboxes/fs/read?id=${encodeURIComponent(id)}&path=${encodeURIComponent(path)}`);
430
+ const result = await this.request("/v1/sandboxes/fs/read", {
431
+ method: "POST",
432
+ body: JSON.stringify({ id, path })
433
+ });
591
434
  return result.content;
592
435
  },
593
436
  write: async (id, path, content) => {
@@ -597,7 +440,10 @@ class BanataSandbox {
597
440
  });
598
441
  },
599
442
  list: async (id, path) => {
600
- return this.request(`/v1/sandboxes/fs/list?id=${encodeURIComponent(id)}&path=${encodeURIComponent(path ?? "/workspace")}`);
443
+ return this.request("/v1/sandboxes/fs/list", {
444
+ method: "POST",
445
+ body: JSON.stringify({ id, path: path ?? "/workspace" })
446
+ });
601
447
  }
602
448
  };
603
449
  async terminal(id) {
@@ -637,7 +483,7 @@ class BanataSandbox {
637
483
  }
638
484
  async getOpencodeConnection(id) {
639
485
  const preview = await this.getPreview(id);
640
- return deriveOpenCodeConnection(preview.browserPreviewUrl ?? preview.browserPreview?.publicUrl ?? preview.pairedBrowser?.previewUrl ?? null);
486
+ return deriveOpenCodeConnection(preview.browserPreview?.websocketUrl ?? preview.runtime?.websocketUrl ?? preview.browserPreviewUrl ?? preview.browserPreview?.publicUrl ?? preview.pairedBrowser?.previewUrl ?? null);
641
487
  }
642
488
  async listOpencodeMessages(id, options = {}) {
643
489
  const connection = await this.getOpencodeConnection(id);
@@ -797,26 +643,47 @@ class BanataSandbox {
797
643
  body: JSON.stringify({ id })
798
644
  });
799
645
  }
646
+ async listCheckpoints(id) {
647
+ const response = await this.request(`/v1/sandboxes/checkpoints?id=${encodeURIComponent(id)}`);
648
+ return response.checkpoints;
649
+ }
650
+ async restoreCheckpoint(id, checkpointId) {
651
+ return this.request("/v1/sandboxes/checkpoints/restore", {
652
+ method: "POST",
653
+ body: JSON.stringify({ id, checkpointId })
654
+ });
655
+ }
800
656
  async getArtifacts(id) {
801
657
  return this.request(`/v1/sandboxes/artifacts?id=${encodeURIComponent(id)}`);
802
658
  }
803
659
  async getArtifactDownloadUrl(id, key, expiresInSeconds = 3600) {
804
660
  return this.request(`/v1/sandboxes/artifacts/download?id=${encodeURIComponent(id)}&key=${encodeURIComponent(key)}&expiresIn=${encodeURIComponent(String(expiresInSeconds))}`);
805
661
  }
662
+ async convertDocument(id, inputPath, options = {}) {
663
+ return this.request("/v1/sandboxes/documents/convert", {
664
+ method: "POST",
665
+ body: JSON.stringify({
666
+ id,
667
+ inputPath,
668
+ format: options.format,
669
+ outputDir: options.outputDir
670
+ })
671
+ });
672
+ }
806
673
  async getPreview(id) {
807
674
  const preview = await this.request(`/v1/sandboxes/browser-preview?id=${encodeURIComponent(id)}`);
808
675
  return {
809
676
  ...preview,
810
- browserPreviewViewerUrl: buildPreviewViewerUrl(preview.browserPreviewUrl, this.appUrl)
677
+ browserPreviewViewerUrl: preview.browserPreviewViewerUrl ?? preview.browserPreview?.publicUrl ?? preview.pairedBrowser?.previewUrl ?? buildPreviewViewerUrl(preview.browserPreview?.websocketUrl ?? preview.runtime?.websocketUrl ?? preview.browserPreviewUrl, this.appUrl)
811
678
  };
812
679
  }
813
680
  async getPreviewConnection(id) {
814
681
  const preview = await this.getPreview(id);
815
- return derivePreviewConnection(preview.browserPreviewUrl);
682
+ return derivePreviewConnection(preview.browserPreview?.websocketUrl ?? preview.runtime?.websocketUrl ?? preview.browserPreviewUrl);
816
683
  }
817
684
  async getPreviewViewerUrl(id) {
818
685
  const preview = await this.getPreview(id);
819
- return preview.browserPreviewViewerUrl ?? buildPreviewViewerUrl(preview.browserPreviewUrl, this.appUrl);
686
+ return preview.browserPreviewViewerUrl ?? preview.browserPreview?.publicUrl ?? buildPreviewViewerUrl(preview.browserPreview?.websocketUrl ?? preview.runtime?.websocketUrl ?? preview.browserPreviewUrl, this.appUrl);
820
687
  }
821
688
  async startPreview(id) {
822
689
  const connection = await this.getPreviewConnection(id);
@@ -930,6 +797,11 @@ class BanataSandbox {
930
797
  listOpencodeMessages: (options) => this.listOpencodeMessages(sessionId, options),
931
798
  streamOpencodeEvents: (options) => this.streamOpencodeEvents(sessionId, options),
932
799
  checkpoint: () => this.checkpoint(sessionId),
800
+ listCheckpoints: () => this.listCheckpoints(sessionId),
801
+ restoreCheckpoint: (checkpointId) => this.restoreCheckpoint(sessionId, checkpointId),
802
+ getArtifacts: () => this.getArtifacts(sessionId),
803
+ getArtifactDownloadUrl: (key, expiresInSeconds) => this.getArtifactDownloadUrl(sessionId, key, expiresInSeconds),
804
+ convertDocument: (inputPath, options) => this.convertDocument(sessionId, inputPath, options),
933
805
  getRuntime: () => this.getRuntime(sessionId),
934
806
  getPreview: () => this.getPreview(sessionId),
935
807
  getPreviewConnection: () => this.getPreviewConnection(sessionId),
@@ -953,12 +825,11 @@ class BanataSandbox {
953
825
  };
954
826
  }
955
827
  }
956
- var src_default = BrowserCloud;
828
+ var src_default = BanataSandbox;
957
829
  export {
958
830
  src_default as default,
959
831
  buildPreviewViewerUrl,
960
832
  BrowserServiceError,
961
- BrowserCloud,
962
833
  BanataSandbox,
963
834
  BanataError
964
835
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@banata-boxes/sdk",
3
- "version": "0.2.1",
4
- "description": "TypeScript SDK for Banata browser sessions and code sandboxes",
3
+ "version": "0.2.3",
4
+ "description": "TypeScript SDK for Banata sandboxes, paired browser workflows, and OpenCode automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -34,12 +34,11 @@
34
34
  ],
35
35
  "keywords": [
36
36
  "banata",
37
- "browser",
38
37
  "sandbox",
39
38
  "automation",
40
- "puppeteer",
41
- "cdp",
42
- "headless"
39
+ "opencode",
40
+ "agent-browser",
41
+ "handoff"
43
42
  ],
44
43
  "license": "MIT",
45
44
  "repository": {