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 +38 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +51 -12
- package/package.json +2 -1
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
|
-
|
|
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
|
|
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
|
|
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
|
|
706
|
-
|
|
707
|
-
|
|
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.
|
|
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",
|