remote-agent-browser 1.2.0 → 1.3.0

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
@@ -3,6 +3,23 @@
3
3
  Run [agent-browser](https://github.com/vercel-labs/agent-browser) in the cloud
4
4
  with an isolated [Vercel Sandbox](https://vercel.com/docs/vercel-sandbox).
5
5
 
6
+ ## Benchmark
7
+
8
+ Loading and taking an interactive snapshot of 100 HTTPS pages in a fresh,
9
+ two-vCPU Vercel Sandbox:
10
+
11
+ | Metric | Result |
12
+ | --- | ---: |
13
+ | Execution time (100 pages) | 8.01s |
14
+ | Memory (peak, 100 pages) | 461.6MB PSS |
15
+
16
+ Execution time includes Sandbox creation. The published time is the median and
17
+ memory is the maximum from three successful runs using Chromium 151,
18
+ agent-browser 0.33.2, and `https://example.com/` with a unique query per page.
19
+ Each page must produce the expected content and interactive snapshot references.
20
+
21
+ See [the benchmark methodology](./benchmarks/BENCHMARK.md) for details.
22
+
6
23
  ## Install
7
24
 
8
25
  ```bash
@@ -31,6 +48,27 @@ references. `close()` closes Chromium and stops the Sandbox.
31
48
  Authentication is automatic when running on Vercel through
32
49
  `VERCEL_OIDC_TOKEN`.
33
50
 
51
+ To run browsers in another team/project, supply explicit short-lived credentials:
52
+
53
+ ```ts
54
+ const browser = AgentBrowser.session({
55
+ id: `preview:${chatId}`,
56
+ resolveSandboxCredentials: async () => ({
57
+ teamId: resourceTeamId,
58
+ projectId: resourceProjectId,
59
+ token: await getShortLivedTeamToken(),
60
+ }),
61
+ })
62
+ ```
63
+
64
+ The same option works with `AgentBrowser.create()`. Keep the team and project
65
+ fixed for the handle's lifetime; refresh only the token. The resolver is lazy
66
+ for named sessions and runs before creation, cached-runtime command/file access,
67
+ observation/keepalive, and cleanup. A resolver failure rejects the operation;
68
+ commands do not fall back to cached credentials. Returning `undefined` preserves
69
+ deployment authentication. Credentials are not passed into browser command env.
70
+
71
+
34
72
  For local development, link a Vercel project and pull its environment:
35
73
 
36
74
  ```bash
package/dist/index.d.ts CHANGED
@@ -174,8 +174,17 @@ interface AgentBrowserSession extends AgentBrowser$1 {
174
174
  /** Permanently remove the runtime currently associated with this id. */
175
175
  destroy(): Promise<void>;
176
176
  }
177
+ /** Explicit Sandbox scope; resolve a fresh token while keeping team/project fixed. */
178
+ type SandboxCredentials = {
179
+ token: string;
180
+ teamId: string;
181
+ projectId: string;
182
+ };
183
+ type ResolveSandboxCredentials = () => Promise<SandboxCredentials | undefined>;
177
184
 
178
185
  type CreateAgentBrowserOptions = {
186
+ /** Fresh credentials for a fixed Sandbox scope; omit for deployment auth. */
187
+ resolveSandboxCredentials?: ResolveSandboxCredentials;
179
188
  /** Named CLI session; state (page, cookies, refs) persists across calls. */
180
189
  session?: string;
181
190
  /**
@@ -249,4 +258,4 @@ declare const AgentBrowser: {
249
258
  type AgentBrowser = AgentBrowser$1;
250
259
 
251
260
  export { AgentBrowser, createAgentBrowser, createAgentBrowserSession, createBrowserClient };
252
- export type { AgentBrowserSession, BrowserClientOptions, BrowserCommandResult, BrowserFile, BrowserRunResult, BrowserRuntimeResetEvent, BrowserShellResult, CommandRunner, CreateAgentBrowserOptions, CreateAgentBrowserSessionOptions, ExecOptions, JsonExecOptions, KeepaliveOptions, ProxyOptions, RunOptions, ShellOptions };
261
+ export type { AgentBrowserSession, BrowserClientOptions, BrowserCommandResult, BrowserFile, BrowserRunResult, BrowserRuntimeResetEvent, BrowserShellResult, CommandRunner, CreateAgentBrowserOptions, CreateAgentBrowserSessionOptions, ExecOptions, JsonExecOptions, KeepaliveOptions, ProxyOptions, ResolveSandboxCredentials, RunOptions, SandboxCredentials, ShellOptions };
package/dist/index.js CHANGED
@@ -453,19 +453,24 @@ const BASE_ENV = {
453
453
  AGENT_BROWSER_DEFAULT_TIMEOUT: '25000'
454
454
  };
455
455
  /** CommandRunner backed by one Vercel Sandbox. */ class SandboxRunner {
456
- constructor(sandbox, env, timeoutMs = DEFAULT_TIMEOUT_MS){
456
+ constructor(sandbox, env, timeoutMs = DEFAULT_TIMEOUT_MS, resolveSandboxCredentials){
457
457
  this.keepaliveStops = new Set();
458
458
  this.sandbox = sandbox;
459
459
  this.env = env;
460
460
  this.timeoutMs = timeoutMs;
461
+ this.resolveSandboxCredentials = resolveSandboxCredentials;
461
462
  }
462
463
  async acquire() {
463
464
  if (!this.sandbox) throw new Error('browser sandbox is unavailable');
464
465
  return this.sandbox;
465
466
  }
466
467
  async observe() {
468
+ return this.observeWithCredentials(await this.resolveSandboxCredentials?.());
469
+ }
470
+ async observeWithCredentials(credentials) {
467
471
  if (!this.sandbox) return null;
468
472
  const sandbox = await Sandbox.get({
473
+ ...credentials,
469
474
  name: this.sandbox.name,
470
475
  resume: false
471
476
  });
@@ -473,6 +478,14 @@ const BASE_ENV = {
473
478
  return sandbox;
474
479
  }
475
480
  async destroyRuntime() {
481
+ if (this.resolveSandboxCredentials) {
482
+ const credentials = await this.resolveSandboxCredentials();
483
+ if (credentials) {
484
+ const sandbox = await this.observeWithCredentials(credentials);
485
+ await sandbox?.stop();
486
+ return;
487
+ }
488
+ }
476
489
  await this.sandbox?.stop().catch(()=>{});
477
490
  }
478
491
  async run(cmd, args, opts = {}) {
@@ -483,7 +496,14 @@ const BASE_ENV = {
483
496
  const hadSandbox = this.sandbox !== undefined;
484
497
  let sandbox = await this.acquire();
485
498
  if (hadSandbox) {
486
- sandbox = await this.observe().catch(()=>null) ?? sandbox;
499
+ const credentials = await this.resolveSandboxCredentials?.();
500
+ if (credentials) {
501
+ const observed = await this.observeWithCredentials(credentials);
502
+ if (!observed) throw new Error('browser sandbox is unavailable');
503
+ sandbox = observed;
504
+ } else {
505
+ sandbox = await this.observeWithCredentials(undefined).catch(()=>null) ?? sandbox;
506
+ }
487
507
  }
488
508
  await this.restoreLifetime(sandbox, this.timeoutMs).catch(()=>{});
489
509
  const command = await sandbox.runCommand({
@@ -566,7 +586,16 @@ const BASE_ENV = {
566
586
  return Buffer.from(stdout.trim(), 'base64');
567
587
  }
568
588
  async writeFile(path, bytes) {
569
- const sandbox = await this.acquire();
589
+ const hadSandbox = this.sandbox !== undefined;
590
+ let sandbox = await this.acquire();
591
+ if (hadSandbox) {
592
+ const credentials = await this.resolveSandboxCredentials?.();
593
+ if (credentials) {
594
+ const observed = await this.observeWithCredentials(credentials);
595
+ if (!observed) throw new Error('browser sandbox is unavailable');
596
+ sandbox = observed;
597
+ }
598
+ }
570
599
  await sandbox.writeFiles([
571
600
  {
572
601
  path,
@@ -648,7 +677,7 @@ class NamedSandboxRunner extends SandboxRunner {
648
677
  super(undefined, {
649
678
  ...BASE_ENV,
650
679
  ...opts.env
651
- }, timeoutMs), this.resetListeners = new Set(), this.onResume = async ()=>{
680
+ }, timeoutMs, opts.resolveSandboxCredentials), this.resetListeners = new Set(), this.onResume = async ()=>{
652
681
  const event = {
653
682
  id: this.id,
654
683
  reason: 'resumed'
@@ -673,6 +702,7 @@ class NamedSandboxRunner extends SandboxRunner {
673
702
  async acquire() {
674
703
  if (this.sandbox) return this.sandbox;
675
704
  this.sandbox = await Sandbox.getOrCreate({
705
+ ...await this.resolveSandboxCredentials?.(),
676
706
  name: this.name,
677
707
  image: this.image,
678
708
  resources: {
@@ -686,9 +716,10 @@ class NamedSandboxRunner extends SandboxRunner {
686
716
  });
687
717
  return this.sandbox;
688
718
  }
689
- async observe() {
719
+ async observeWithCredentials(credentials) {
690
720
  try {
691
721
  const sandbox = await Sandbox.get({
722
+ ...credentials,
692
723
  name: this.name,
693
724
  resume: false,
694
725
  onResume: this.onResume
@@ -702,10 +733,15 @@ class NamedSandboxRunner extends SandboxRunner {
702
733
  }
703
734
  async destroyRuntime() {
704
735
  try {
705
- const sandbox = this.sandbox ?? await Sandbox.get({
706
- name: this.name,
707
- resume: false
708
- });
736
+ const credentials = await this.resolveSandboxCredentials?.();
737
+ let sandbox = this.sandbox;
738
+ if (credentials || !sandbox) {
739
+ sandbox = await Sandbox.get({
740
+ ...credentials,
741
+ name: this.name,
742
+ resume: false
743
+ });
744
+ }
709
745
  await sandbox.delete();
710
746
  this.sandbox = undefined;
711
747
  } catch (error) {
@@ -730,6 +766,7 @@ class NamedSandboxRunner extends SandboxRunner {
730
766
  };
731
767
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
732
768
  const sandbox = await Sandbox.create({
769
+ ...await opts.resolveSandboxCredentials?.(),
733
770
  image,
734
771
  resources: {
735
772
  vcpus: opts.vcpus ?? 2
@@ -739,7 +776,7 @@ class NamedSandboxRunner extends SandboxRunner {
739
776
  env,
740
777
  persistent: false
741
778
  });
742
- return new SandboxRunner(sandbox, env, timeoutMs);
779
+ return new SandboxRunner(sandbox, env, timeoutMs, opts.resolveSandboxCredentials);
743
780
  }
744
781
 
745
782
  /**
@@ -768,7 +805,8 @@ class NamedSandboxRunner extends SandboxRunner {
768
805
  image: opts.image,
769
806
  timeoutMs: opts.timeoutMs,
770
807
  vcpus: opts.vcpus,
771
- env: opts.env
808
+ env: opts.env,
809
+ resolveSandboxCredentials: opts.resolveSandboxCredentials
772
810
  });
773
811
  return createBrowserClient(runner, {
774
812
  session: opts.session,
@@ -791,7 +829,8 @@ class NamedSandboxRunner extends SandboxRunner {
791
829
  image: opts.image,
792
830
  timeoutMs: opts.timeoutMs,
793
831
  vcpus: opts.vcpus,
794
- env: opts.env
832
+ env: opts.env,
833
+ resolveSandboxCredentials: opts.resolveSandboxCredentials
795
834
  });
796
835
  const browser = createBrowserClient(runner, {
797
836
  session: opts.session ?? runner.session,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-agent-browser",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Run agent-browser in a Vercel Sandbox created from a browser-ready container image",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "scripts": {
18
18
  "build": "bunchee --runtime node",
19
+ "benchmark": "node --run build && node benchmarks/chromium.mjs",
19
20
  "prepublishOnly": "node --run build",
20
21
  "typecheck": "tsc -p tsconfig.json --noEmit",
21
22
  "test": "node --run build && node --test tests/*.test.mjs",