agent-browser 0.3.1 → 0.3.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/benchmark/run.ts DELETED
@@ -1,322 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * Comprehensive Benchmark: agent-browser vs playwright-mcp
4
- *
5
- * Tests realistic AI agent workflows on real websites.
6
- */
7
-
8
- import { execSync, spawn } from 'child_process';
9
- import * as path from 'path';
10
-
11
- interface Result {
12
- tool: string;
13
- workflow: string;
14
- operation: string;
15
- timeMs: number;
16
- outputBytes: number;
17
- }
18
-
19
- const results: Result[] = [];
20
-
21
- function formatTime(ms: number): string {
22
- if (ms < 1000) return `${ms.toFixed(0)}ms`;
23
- return `${(ms / 1000).toFixed(2)}s`;
24
- }
25
-
26
- function formatBytes(bytes: number): string {
27
- if (bytes < 1024) return `${bytes}B`;
28
- return `${(bytes / 1024).toFixed(1)}KB`;
29
- }
30
-
31
- // ============================================================================
32
- // Agent-Browser Runner
33
- // ============================================================================
34
-
35
- function runAB(args: string[], session: string = 'bench'): { ms: number; output: string } {
36
- const start = performance.now();
37
- let output = '';
38
- try {
39
- output = execSync(`./bin/agent-browser ${args.join(' ')}`, {
40
- stdio: 'pipe',
41
- timeout: 30000,
42
- env: { ...process.env, AGENT_BROWSER_SESSION: session },
43
- }).toString();
44
- } catch (e: any) {
45
- output = e.stdout?.toString() || e.message || '';
46
- }
47
- return { ms: performance.now() - start, output };
48
- }
49
-
50
- // ============================================================================
51
- // Playwright-MCP Runner
52
- // ============================================================================
53
-
54
- interface MCPClient {
55
- call: (tool: string, args: Record<string, unknown>) => Promise<{ ms: number; output: string }>;
56
- close: () => void;
57
- }
58
-
59
- async function createMCPClient(): Promise<MCPClient> {
60
- const mcpPath = path.join(process.cwd(), 'opensrc/repos/github.com/microsoft/playwright-mcp/cli.js');
61
- const proc = spawn('node', [mcpPath, '--headless'], { stdio: ['pipe', 'pipe', 'pipe'] });
62
-
63
- let buffer = '';
64
- let requestId = 0;
65
- const pending = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>();
66
-
67
- proc.stdout!.on('data', (data: Buffer) => {
68
- buffer += data.toString();
69
- const lines = buffer.split('\n');
70
- buffer = lines.pop() || '';
71
- for (const line of lines) {
72
- if (!line.trim()) continue;
73
- try {
74
- const msg = JSON.parse(line);
75
- if (msg.id !== undefined && pending.has(msg.id)) {
76
- pending.get(msg.id)!.resolve(msg);
77
- pending.delete(msg.id);
78
- }
79
- } catch {}
80
- }
81
- });
82
-
83
- const send = (method: string, params: Record<string, unknown>): Promise<any> => {
84
- const id = ++requestId;
85
- return new Promise((resolve, reject) => {
86
- pending.set(id, { resolve, reject });
87
- proc.stdin!.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
88
- setTimeout(() => {
89
- if (pending.has(id)) {
90
- pending.delete(id);
91
- reject(new Error('Timeout'));
92
- }
93
- }, 30000);
94
- });
95
- };
96
-
97
- // Initialize
98
- await send('initialize', {
99
- protocolVersion: '2024-11-05',
100
- capabilities: {},
101
- clientInfo: { name: 'benchmark', version: '1.0.0' },
102
- });
103
- proc.stdin!.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }) + '\n');
104
-
105
- return {
106
- call: async (tool: string, args: Record<string, unknown>) => {
107
- const start = performance.now();
108
- const result = await send('tools/call', { name: tool, arguments: args });
109
- return { ms: performance.now() - start, output: JSON.stringify(result) };
110
- },
111
- close: () => proc.kill(),
112
- };
113
- }
114
-
115
- // ============================================================================
116
- // Workflows
117
- // ============================================================================
118
-
119
- interface Workflow {
120
- name: string;
121
- description: string;
122
- steps: Array<{
123
- name: string;
124
- ab: string[];
125
- mcp: { tool: string; args: Record<string, unknown> };
126
- }>;
127
- }
128
-
129
- const workflows: Workflow[] = [
130
- {
131
- name: 'Wikipedia Research',
132
- description: 'Navigate Wikipedia, read content, follow links',
133
- steps: [
134
- { name: 'Navigate', ab: ['open', 'https://en.wikipedia.org/wiki/Artificial_intelligence'], mcp: { tool: 'browser_navigate', args: { url: 'https://en.wikipedia.org/wiki/Artificial_intelligence' } } },
135
- { name: 'Snapshot', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
136
- { name: 'Get title', ab: ['get', 'title'], mcp: { tool: 'browser_snapshot', args: {} } },
137
- { name: 'Snapshot 2', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
138
- { name: 'Click link', ab: ['click', 'a[href="/wiki/Machine_learning"]'], mcp: { tool: 'browser_click', args: { element: 'Machine learning', ref: 'internal link' } } },
139
- { name: 'Snapshot 3', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
140
- ],
141
- },
142
- {
143
- name: 'GitHub Browse',
144
- description: 'Browse a GitHub repository',
145
- steps: [
146
- { name: 'Navigate', ab: ['open', 'https://github.com/anthropics/anthropic-cookbook'], mcp: { tool: 'browser_navigate', args: { url: 'https://github.com/anthropics/anthropic-cookbook' } } },
147
- { name: 'Snapshot', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
148
- { name: 'Get URL', ab: ['get', 'url'], mcp: { tool: 'browser_snapshot', args: {} } },
149
- { name: 'Eval (stars)', ab: ['eval', 'document.querySelector("#repo-stars-counter-star")?.textContent'], mcp: { tool: 'browser_snapshot', args: {} } },
150
- { name: 'Snapshot 2', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
151
- ],
152
- },
153
- {
154
- name: 'Hacker News',
155
- description: 'Browse Hacker News front page',
156
- steps: [
157
- { name: 'Navigate', ab: ['open', 'https://news.ycombinator.com'], mcp: { tool: 'browser_navigate', args: { url: 'https://news.ycombinator.com' } } },
158
- { name: 'Snapshot', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
159
- { name: 'Eval (count)', ab: ['eval', 'document.querySelectorAll(".athing").length'], mcp: { tool: 'browser_snapshot', args: {} } },
160
- { name: 'Snapshot 2', ab: ['snapshot', '-i'], mcp: { tool: 'browser_snapshot', args: {} } },
161
- { name: 'Get title', ab: ['get', 'title'], mcp: { tool: 'browser_snapshot', args: {} } },
162
- ],
163
- },
164
- ];
165
-
166
- // ============================================================================
167
- // Run Benchmarks
168
- // ============================================================================
169
-
170
- async function runAgentBrowser(workflow: Workflow, session: string): Promise<void> {
171
- // Cleanup
172
- try { runAB(['close'], session); } catch {}
173
- await sleep(100);
174
-
175
- for (const step of workflow.steps) {
176
- const r = runAB(step.ab, session);
177
- results.push({
178
- tool: 'agent-browser',
179
- workflow: workflow.name,
180
- operation: step.name,
181
- timeMs: r.ms,
182
- outputBytes: r.output.length,
183
- });
184
- }
185
-
186
- try { runAB(['close'], session); } catch {}
187
- }
188
-
189
- async function runPlaywrightMCP(workflow: Workflow): Promise<void> {
190
- let client: MCPClient | null = null;
191
- try {
192
- client = await createMCPClient();
193
-
194
- for (const step of workflow.steps) {
195
- const r = await client.call(step.mcp.tool, step.mcp.args);
196
- results.push({
197
- tool: 'playwright-mcp',
198
- workflow: workflow.name,
199
- operation: step.name,
200
- timeMs: r.ms,
201
- outputBytes: r.output.length,
202
- });
203
- }
204
-
205
- await client.call('browser_close', {});
206
- } catch (e) {
207
- console.log(` ⚠️ MCP error: ${e}`);
208
- } finally {
209
- client?.close();
210
- }
211
- }
212
-
213
- // ============================================================================
214
- // Reporting
215
- // ============================================================================
216
-
217
- function printResults(): void {
218
- console.log('\n' + '═'.repeat(80));
219
- console.log('📊 DETAILED RESULTS');
220
- console.log('═'.repeat(80));
221
-
222
- for (const workflow of workflows) {
223
- console.log(`\n📋 ${workflow.name}`);
224
- console.log('─'.repeat(70));
225
- console.log('│ Operation │ agent-browser │ playwright-mcp │ Diff │');
226
- console.log('├────────────────────┼───────────────┼────────────────┼───────────┤');
227
-
228
- let abTotal = 0, mcpTotal = 0;
229
-
230
- for (const step of workflow.steps) {
231
- const ab = results.find(r => r.tool === 'agent-browser' && r.workflow === workflow.name && r.operation === step.name);
232
- const mcp = results.find(r => r.tool === 'playwright-mcp' && r.workflow === workflow.name && r.operation === step.name);
233
-
234
- const abTime = ab?.timeMs || 0;
235
- const mcpTime = mcp?.timeMs || 0;
236
- abTotal += abTime;
237
- mcpTotal += mcpTime;
238
-
239
- const diff = mcpTime - abTime;
240
- const diffStr = diff > 0 ? `+${formatTime(diff)}` : formatTime(diff);
241
-
242
- console.log(`│ ${step.name.padEnd(18)} │ ${formatTime(abTime).padEnd(13)} │ ${formatTime(mcpTime).padEnd(14)} │ ${diffStr.padEnd(9)} │`);
243
- }
244
-
245
- console.log('├────────────────────┼───────────────┼────────────────┼───────────┤');
246
- const totalDiff = mcpTotal - abTotal;
247
- const totalDiffStr = totalDiff > 0 ? `+${formatTime(totalDiff)}` : formatTime(totalDiff);
248
- console.log(`│ ${'TOTAL'.padEnd(18)} │ ${formatTime(abTotal).padEnd(13)} │ ${formatTime(mcpTotal).padEnd(14)} │ ${totalDiffStr.padEnd(9)} │`);
249
- console.log('└────────────────────┴───────────────┴────────────────┴───────────┘');
250
- }
251
-
252
- // Summary
253
- const abTotalAll = results.filter(r => r.tool === 'agent-browser').reduce((s, r) => s + r.timeMs, 0);
254
- const mcpTotalAll = results.filter(r => r.tool === 'playwright-mcp').reduce((s, r) => s + r.timeMs, 0);
255
- const abOps = results.filter(r => r.tool === 'agent-browser').length;
256
- const mcpOps = results.filter(r => r.tool === 'playwright-mcp').length;
257
-
258
- console.log('\n' + '═'.repeat(80));
259
- console.log('📈 SUMMARY');
260
- console.log('═'.repeat(80));
261
- console.log(`\n Workflows tested: ${workflows.length}`);
262
- console.log(` Total operations: ${abOps} (agent-browser), ${mcpOps} (playwright-mcp)`);
263
- console.log(`\n agent-browser total: ${formatTime(abTotalAll)} (${(abTotalAll / abOps).toFixed(0)}ms avg/op)`);
264
- console.log(` playwright-mcp total: ${formatTime(mcpTotalAll)} (${(mcpTotalAll / mcpOps).toFixed(0)}ms avg/op)`);
265
-
266
- if (abTotalAll < mcpTotalAll) {
267
- console.log(`\n ✅ agent-browser is ${((mcpTotalAll - abTotalAll) / 1000).toFixed(2)}s faster overall`);
268
- } else {
269
- console.log(`\n ⏱️ playwright-mcp is ${((abTotalAll - mcpTotalAll) / 1000).toFixed(2)}s faster overall`);
270
- }
271
-
272
- // Context usage
273
- const abBytes = results.filter(r => r.tool === 'agent-browser').reduce((s, r) => s + r.outputBytes, 0);
274
- const mcpBytes = results.filter(r => r.tool === 'playwright-mcp').reduce((s, r) => s + r.outputBytes, 0);
275
-
276
- console.log(`\n Context usage:`);
277
- console.log(` agent-browser: ${formatBytes(abBytes)} (~${Math.ceil(abBytes / 4)} tokens)`);
278
- console.log(` playwright-mcp: ${formatBytes(mcpBytes)} (~${Math.ceil(mcpBytes / 4)} tokens)`);
279
-
280
- console.log('\n' + '═'.repeat(80));
281
- }
282
-
283
- function sleep(ms: number): Promise<void> {
284
- return new Promise(r => setTimeout(r, ms));
285
- }
286
-
287
- // ============================================================================
288
- // Main
289
- // ============================================================================
290
-
291
- async function main(): Promise<void> {
292
- console.log('═'.repeat(80));
293
- console.log('🚀 COMPREHENSIVE BENCHMARK: agent-browser vs playwright-mcp');
294
- console.log('═'.repeat(80));
295
- console.log('\nWorkflows:');
296
- for (const w of workflows) {
297
- console.log(` • ${w.name}: ${w.description} (${w.steps.length} steps)`);
298
- }
299
-
300
- console.log('\n🔨 Building...');
301
- execSync('pnpm build', { cwd: process.cwd(), stdio: 'inherit' });
302
-
303
- for (const workflow of workflows) {
304
- console.log(`\n${'─'.repeat(80)}`);
305
- console.log(`📋 Running: ${workflow.name}`);
306
- console.log('─'.repeat(80));
307
-
308
- console.log('\n agent-browser:');
309
- await runAgentBrowser(workflow, `ab-${workflow.name.toLowerCase().replace(/\s+/g, '-')}`);
310
- const abTime = results.filter(r => r.tool === 'agent-browser' && r.workflow === workflow.name).reduce((s, r) => s + r.timeMs, 0);
311
- console.log(` ✓ Completed in ${formatTime(abTime)}`);
312
-
313
- console.log('\n playwright-mcp:');
314
- await runPlaywrightMCP(workflow);
315
- const mcpTime = results.filter(r => r.tool === 'playwright-mcp' && r.workflow === workflow.name).reduce((s, r) => s + r.timeMs, 0);
316
- console.log(` ✓ Completed in ${formatTime(mcpTime)}`);
317
- }
318
-
319
- printResults();
320
- }
321
-
322
- main().catch(console.error);
package/cli/Cargo.lock DELETED
@@ -1,114 +0,0 @@
1
- # This file is automatically @generated by Cargo.
2
- # It is not intended for manual editing.
3
- version = 4
4
-
5
- [[package]]
6
- name = "agent-browser"
7
- version = "0.1.0"
8
- dependencies = [
9
- "libc",
10
- "serde",
11
- "serde_json",
12
- ]
13
-
14
- [[package]]
15
- name = "itoa"
16
- version = "1.0.17"
17
- source = "registry+https://github.com/rust-lang/crates.io-index"
18
- checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
19
-
20
- [[package]]
21
- name = "libc"
22
- version = "0.2.180"
23
- source = "registry+https://github.com/rust-lang/crates.io-index"
24
- checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
25
-
26
- [[package]]
27
- name = "memchr"
28
- version = "2.7.6"
29
- source = "registry+https://github.com/rust-lang/crates.io-index"
30
- checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
31
-
32
- [[package]]
33
- name = "proc-macro2"
34
- version = "1.0.105"
35
- source = "registry+https://github.com/rust-lang/crates.io-index"
36
- checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
37
- dependencies = [
38
- "unicode-ident",
39
- ]
40
-
41
- [[package]]
42
- name = "quote"
43
- version = "1.0.43"
44
- source = "registry+https://github.com/rust-lang/crates.io-index"
45
- checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
46
- dependencies = [
47
- "proc-macro2",
48
- ]
49
-
50
- [[package]]
51
- name = "serde"
52
- version = "1.0.228"
53
- source = "registry+https://github.com/rust-lang/crates.io-index"
54
- checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
55
- dependencies = [
56
- "serde_core",
57
- "serde_derive",
58
- ]
59
-
60
- [[package]]
61
- name = "serde_core"
62
- version = "1.0.228"
63
- source = "registry+https://github.com/rust-lang/crates.io-index"
64
- checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
65
- dependencies = [
66
- "serde_derive",
67
- ]
68
-
69
- [[package]]
70
- name = "serde_derive"
71
- version = "1.0.228"
72
- source = "registry+https://github.com/rust-lang/crates.io-index"
73
- checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
74
- dependencies = [
75
- "proc-macro2",
76
- "quote",
77
- "syn",
78
- ]
79
-
80
- [[package]]
81
- name = "serde_json"
82
- version = "1.0.149"
83
- source = "registry+https://github.com/rust-lang/crates.io-index"
84
- checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
85
- dependencies = [
86
- "itoa",
87
- "memchr",
88
- "serde",
89
- "serde_core",
90
- "zmij",
91
- ]
92
-
93
- [[package]]
94
- name = "syn"
95
- version = "2.0.114"
96
- source = "registry+https://github.com/rust-lang/crates.io-index"
97
- checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
98
- dependencies = [
99
- "proc-macro2",
100
- "quote",
101
- "unicode-ident",
102
- ]
103
-
104
- [[package]]
105
- name = "unicode-ident"
106
- version = "1.0.22"
107
- source = "registry+https://github.com/rust-lang/crates.io-index"
108
- checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
109
-
110
- [[package]]
111
- name = "zmij"
112
- version = "1.0.12"
113
- source = "registry+https://github.com/rust-lang/crates.io-index"
114
- checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
package/cli/Cargo.toml DELETED
@@ -1,17 +0,0 @@
1
- [package]
2
- name = "agent-browser"
3
- version = "0.1.0"
4
- edition = "2021"
5
- description = "Fast browser automation CLI for AI agents"
6
- license = "Apache-2.0"
7
-
8
- [dependencies]
9
- serde = { version = "1.0", features = ["derive"] }
10
- serde_json = "1.0"
11
- libc = "0.2"
12
-
13
- [profile.release]
14
- opt-level = 3
15
- lto = true
16
- codegen-units = 1
17
- strip = true