@browser_use/pi 0.1.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/.env.example +6 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/agent.d.ts +15 -0
- package/dist/agent.js +381 -0
- package/dist/agent.js.map +1 -0
- package/dist/browser.d.ts +52 -0
- package/dist/browser.js +264 -0
- package/dist/browser.js.map +1 -0
- package/dist/cdp.d.ts +39 -0
- package/dist/cdp.js +291 -0
- package/dist/cdp.js.map +1 -0
- package/dist/context.d.ts +24 -0
- package/dist/context.js +143 -0
- package/dist/context.js.map +1 -0
- package/dist/control.d.ts +18 -0
- package/dist/control.js +84 -0
- package/dist/control.js.map +1 -0
- package/dist/events.d.ts +40 -0
- package/dist/events.js +79 -0
- package/dist/events.js.map +1 -0
- package/dist/highlight.d.ts +3 -0
- package/dist/highlight.js +102 -0
- package/dist/highlight.js.map +1 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +126 -0
- package/dist/history.js.map +1 -0
- package/dist/images.d.ts +14 -0
- package/dist/images.js +104 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +422 -0
- package/dist/index.js.map +1 -0
- package/dist/model-stream.d.ts +3 -0
- package/dist/model-stream.js +78 -0
- package/dist/model-stream.js.map +1 -0
- package/dist/observer.d.ts +16 -0
- package/dist/observer.js +56 -0
- package/dist/observer.js.map +1 -0
- package/dist/page.d.ts +58 -0
- package/dist/page.js +189 -0
- package/dist/page.js.map +1 -0
- package/dist/policy.d.ts +18 -0
- package/dist/policy.js +195 -0
- package/dist/policy.js.map +1 -0
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +41 -0
- package/dist/prompt.js.map +1 -0
- package/dist/protocol.d.ts +70 -0
- package/dist/protocol.js +7 -0
- package/dist/protocol.js.map +1 -0
- package/dist/recording.d.ts +44 -0
- package/dist/recording.js +120 -0
- package/dist/recording.js.map +1 -0
- package/dist/research-tools.d.ts +3 -0
- package/dist/research-tools.js +67 -0
- package/dist/research-tools.js.map +1 -0
- package/dist/runtime.d.ts +39 -0
- package/dist/runtime.js +268 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +251 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry.d.ts +3 -0
- package/dist/telemetry.js +41 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/video.d.ts +18 -0
- package/dist/video.js +177 -0
- package/dist/video.js.map +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +329 -0
- package/dist/worker.js.map +1 -0
- package/examples/README.md +58 -0
- package/examples/apply-to-job.ts +57 -0
- package/examples/ehr.ts +53 -0
- package/examples/extract.ts +50 -0
- package/examples/form.ts +36 -0
- package/examples/onepassword.ts +66 -0
- package/examples/qa.ts +72 -0
- package/examples/research.ts +35 -0
- package/examples/stripe-link.ts +166 -0
- package/package.json +66 -0
package/dist/worker.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { Session } from 'node:inspector';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { createContext, constants } from 'node:vm';
|
|
5
|
+
import { inspect } from 'node:util';
|
|
6
|
+
import { writeFile, rename } from 'node:fs/promises';
|
|
7
|
+
import { appendFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { CDP } from './cdp.js';
|
|
11
|
+
import { Page, Tabs } from './page.js';
|
|
12
|
+
import { installDomainPolicy, fillSecret } from './policy.js';
|
|
13
|
+
import { redact } from './history.js';
|
|
14
|
+
import { actionHighlighter } from './highlight.js';
|
|
15
|
+
import { prepareModelImages } from './images.js';
|
|
16
|
+
// IPC initialization keeps connection details out of argv and environment.
|
|
17
|
+
process.on('disconnect', () => process.exit(0));
|
|
18
|
+
const config = await new Promise((resolve) => process.once('message', resolve));
|
|
19
|
+
const clean = (value) => redact(value, config.redact ?? []);
|
|
20
|
+
const send = (message) => process.send(clean(message));
|
|
21
|
+
process.chdir(config.workspace);
|
|
22
|
+
let browser = CDP.lazy(config.endpoint, config.operationTimeoutMs, config.approveConnection);
|
|
23
|
+
installDomainPolicy(browser, config, (id) => send({ type: 'owned', targetId: id }));
|
|
24
|
+
let highlight = config.highlightActions ? actionHighlighter(browser) : undefined;
|
|
25
|
+
let tabs = new Tabs(browser, (id) => send({ type: 'owned', targetId: id }));
|
|
26
|
+
function deferredPage(targetId) {
|
|
27
|
+
return Page.deferred(browser, async () => {
|
|
28
|
+
if (targetId) {
|
|
29
|
+
// Only create a replacement when the target is actually absent, not on an attach timeout.
|
|
30
|
+
const existing = await tabs.list();
|
|
31
|
+
if (existing.some((t) => t.targetId === targetId))
|
|
32
|
+
return tabs.get(targetId);
|
|
33
|
+
}
|
|
34
|
+
return tabs.open();
|
|
35
|
+
}, targetId);
|
|
36
|
+
}
|
|
37
|
+
const page = deferredPage(config.targetId);
|
|
38
|
+
let outputFile;
|
|
39
|
+
let runId;
|
|
40
|
+
let output = '';
|
|
41
|
+
let images = [];
|
|
42
|
+
let captureResponse;
|
|
43
|
+
let overflow = false;
|
|
44
|
+
// Bound memory even when generated code writes an unbounded amount of output.
|
|
45
|
+
const hardLimit = 1_000_000;
|
|
46
|
+
let pendingText = '';
|
|
47
|
+
const tailLength = Math.max(0, ...(config.redact ?? []).map((value) => value.length));
|
|
48
|
+
function captureText(text) {
|
|
49
|
+
if (output.length + text.length > hardLimit)
|
|
50
|
+
overflow = true;
|
|
51
|
+
const captured = text.slice(0, Math.max(0, hardLimit - output.length));
|
|
52
|
+
output += captured;
|
|
53
|
+
if (outputFile && captured)
|
|
54
|
+
appendFileSync(outputFile, captured);
|
|
55
|
+
}
|
|
56
|
+
const sink = new Writable({
|
|
57
|
+
write(chunk, _encoding, callback) {
|
|
58
|
+
pendingText += chunk.toString();
|
|
59
|
+
// Redact before splitting, and retain the raw suffix across chunk boundaries.
|
|
60
|
+
const cut = Math.max(0, pendingText.length - tailLength);
|
|
61
|
+
let safeCut = cut;
|
|
62
|
+
for (const secret of config.redact ?? []) {
|
|
63
|
+
if (!secret)
|
|
64
|
+
continue;
|
|
65
|
+
let at = pendingText.indexOf(secret);
|
|
66
|
+
while (at >= 0 && at < cut) {
|
|
67
|
+
if (at + secret.length > cut)
|
|
68
|
+
safeCut = Math.min(safeCut, at);
|
|
69
|
+
at = pendingText.indexOf(secret, at + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
captureText(clean(pendingText.slice(0, safeCut)));
|
|
73
|
+
pendingText = pendingText.slice(safeCut);
|
|
74
|
+
callback();
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const evaluator = new Session();
|
|
78
|
+
evaluator.connect();
|
|
79
|
+
let executionContextId;
|
|
80
|
+
evaluator.on('Runtime.executionContextCreated', ({ params }) => {
|
|
81
|
+
if (params.context.name === 'browser-use')
|
|
82
|
+
executionContextId = params.context.id;
|
|
83
|
+
});
|
|
84
|
+
evaluator.post('Runtime.enable');
|
|
85
|
+
const realm = createContext({}, {
|
|
86
|
+
name: 'browser-use',
|
|
87
|
+
importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
|
|
88
|
+
});
|
|
89
|
+
if (executionContextId === undefined)
|
|
90
|
+
throw new Error('Could not initialize the JavaScript context.');
|
|
91
|
+
Object.assign(realm, {
|
|
92
|
+
global: realm, // Node's global alias refers to this REPL realm, not the worker host.
|
|
93
|
+
// Reject values JSON would silently drop or change. Dates/toJSON use normal JSON semantics.
|
|
94
|
+
__serializeResult(value) {
|
|
95
|
+
const json = JSON.stringify(value, (_key, item) => {
|
|
96
|
+
if (['undefined', 'function', 'symbol', 'bigint'].includes(typeof item) ||
|
|
97
|
+
(typeof item === 'number' && !Number.isFinite(item)))
|
|
98
|
+
throw new Error('Result must contain JSON values: no undefined, functions, symbols, bigint, or non-finite numbers.');
|
|
99
|
+
return item;
|
|
100
|
+
});
|
|
101
|
+
if (json === undefined)
|
|
102
|
+
throw new Error('Result must be JSON serializable.');
|
|
103
|
+
if (Buffer.byteLength(json) > 16_000_000)
|
|
104
|
+
throw new Error('Result exceeds 16 MB; save an artifact and return its path.');
|
|
105
|
+
return json;
|
|
106
|
+
},
|
|
107
|
+
process,
|
|
108
|
+
Buffer,
|
|
109
|
+
URL,
|
|
110
|
+
URLSearchParams,
|
|
111
|
+
fetch,
|
|
112
|
+
AbortController,
|
|
113
|
+
AbortSignal,
|
|
114
|
+
setTimeout,
|
|
115
|
+
clearTimeout,
|
|
116
|
+
setInterval,
|
|
117
|
+
clearInterval,
|
|
118
|
+
queueMicrotask,
|
|
119
|
+
structuredClone,
|
|
120
|
+
TextEncoder,
|
|
121
|
+
TextDecoder,
|
|
122
|
+
browser,
|
|
123
|
+
tabs,
|
|
124
|
+
page,
|
|
125
|
+
workspace: config.workspace,
|
|
126
|
+
async reconnect() {
|
|
127
|
+
const targetId = Reflect.get(realm, 'page')?.targetId;
|
|
128
|
+
browser.close();
|
|
129
|
+
browser = CDP.lazy(config.endpoint, config.operationTimeoutMs, config.approveConnection);
|
|
130
|
+
installDomainPolicy(browser, config, (id) => send({ type: 'owned', targetId: id }));
|
|
131
|
+
highlight = config.highlightActions ? actionHighlighter(browser) : undefined;
|
|
132
|
+
browser.observeResponse = captureResponse;
|
|
133
|
+
tabs = new Tabs(browser, (id) => send({ type: 'owned', targetId: id }));
|
|
134
|
+
Object.assign(realm, { browser, tabs, page: deferredPage(targetId) });
|
|
135
|
+
observe();
|
|
136
|
+
return 'Connection reset. Inspect the page; no browser action was replayed. Reacquire other page/frame handles.';
|
|
137
|
+
},
|
|
138
|
+
async fillSecret(name, backendNodeId, target = Reflect.get(realm, 'page')) {
|
|
139
|
+
await target.info();
|
|
140
|
+
const result = await fillSecret(browser, target.sessionId, name, backendNodeId, config.sensitiveData ?? {});
|
|
141
|
+
void highlight?.('DOM.focus', { backendNodeId }, target.sessionId);
|
|
142
|
+
return result;
|
|
143
|
+
},
|
|
144
|
+
async checkpoint(name, value, options = {}) {
|
|
145
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,119}$/.test(name))
|
|
146
|
+
throw new Error('Use a plain checkpoint filename.');
|
|
147
|
+
const checkpointRun = runId;
|
|
148
|
+
const valueJson = JSON.stringify(clean(value));
|
|
149
|
+
if (typeof valueJson !== 'string')
|
|
150
|
+
throw new Error('Checkpoint must be JSON serializable.');
|
|
151
|
+
if (options.partial && Buffer.byteLength(valueJson) > 16_000_000)
|
|
152
|
+
throw new Error('Partial result exceeds 16 MB; checkpoint smaller batches or return file paths.');
|
|
153
|
+
const path = join(config.workspace, name);
|
|
154
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
155
|
+
await writeFile(temporary, valueJson, { flag: 'wx', mode: 0o600 });
|
|
156
|
+
await rename(temporary, path);
|
|
157
|
+
if (options.partial)
|
|
158
|
+
await new Promise((resolve, reject) => process.send({
|
|
159
|
+
type: 'partial',
|
|
160
|
+
...(checkpointRun ? { runId: checkpointRun } : {}),
|
|
161
|
+
path,
|
|
162
|
+
valueJson,
|
|
163
|
+
}, (error) => (error ? reject(error) : resolve())));
|
|
164
|
+
return path;
|
|
165
|
+
},
|
|
166
|
+
require: createRequire(join(config.workspace, 'package.json')),
|
|
167
|
+
async screenshot() {
|
|
168
|
+
const current = Reflect.get(realm, 'page');
|
|
169
|
+
if (images.length >= 4)
|
|
170
|
+
throw new Error('At most four screenshots per cell.');
|
|
171
|
+
const count = images.length;
|
|
172
|
+
await current.screenshot({ quality: 70 });
|
|
173
|
+
return images.length > count ? 'Screenshot captured.' : 'Screenshot omitted; see warning.';
|
|
174
|
+
},
|
|
175
|
+
async snapshot() {
|
|
176
|
+
const current = Reflect.get(realm, 'page');
|
|
177
|
+
return current.snapshot();
|
|
178
|
+
},
|
|
179
|
+
async artifact(name, data) {
|
|
180
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,119}$/.test(name))
|
|
181
|
+
throw new Error('Use a plain filename, max 120 characters.');
|
|
182
|
+
const path = join(config.workspace, name);
|
|
183
|
+
await writeFile(path, data, { flag: 'wx' });
|
|
184
|
+
return path;
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
function observe() {
|
|
188
|
+
if (config.recording)
|
|
189
|
+
browser.observeCommand = (method, raw, sessionId) => {
|
|
190
|
+
const params = raw;
|
|
191
|
+
const protocolTarget = sessionId ? browser.targetForSession(sessionId) : undefined;
|
|
192
|
+
const targetId = protocolTarget ? browser.observationTargetId : undefined;
|
|
193
|
+
if (!targetId)
|
|
194
|
+
return;
|
|
195
|
+
if (method === 'Input.dispatchMouseEvent' &&
|
|
196
|
+
['mouseReleased', 'mouseWheel'].includes(params.type ?? ''))
|
|
197
|
+
send({
|
|
198
|
+
type: 'action',
|
|
199
|
+
action: {
|
|
200
|
+
kind: params.type === 'mouseReleased' ? 'Click' : 'Scroll',
|
|
201
|
+
targetId,
|
|
202
|
+
...(protocolTarget === targetId && params.x !== undefined ? { x: params.x } : {}),
|
|
203
|
+
...(protocolTarget === targetId && params.y !== undefined ? { y: params.y } : {}),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
else if (method === 'Input.insertText' || method === 'Page.navigate')
|
|
207
|
+
send({
|
|
208
|
+
type: 'action',
|
|
209
|
+
action: { kind: method === 'Page.navigate' ? 'Navigate' : 'Type', targetId },
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
observe();
|
|
214
|
+
realm.console = new (await import('node:console')).Console(sink, sink);
|
|
215
|
+
async function evaluate(code, captureJson = false) {
|
|
216
|
+
try {
|
|
217
|
+
// V8 supports replMode; Node 22's generated protocol types omit this field.
|
|
218
|
+
const parameters = {
|
|
219
|
+
expression: captureJson ? `__serializeResult(await (${code}\n))` : code,
|
|
220
|
+
contextId: executionContextId,
|
|
221
|
+
awaitPromise: true,
|
|
222
|
+
replMode: true,
|
|
223
|
+
objectGroup: 'cell',
|
|
224
|
+
};
|
|
225
|
+
const { result, exceptionDetails } = await new Promise((resolve, reject) => {
|
|
226
|
+
evaluator.post('Runtime.evaluate', parameters, (error, response) => {
|
|
227
|
+
if (error)
|
|
228
|
+
reject(error);
|
|
229
|
+
else
|
|
230
|
+
resolve(response);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
if (exceptionDetails)
|
|
234
|
+
throw new Error(exceptionDetails.exception?.description ?? exceptionDetails.text);
|
|
235
|
+
if (captureJson) {
|
|
236
|
+
if (typeof result.value !== 'string')
|
|
237
|
+
throw new Error('Result serialization returned no JSON.');
|
|
238
|
+
return JSON.stringify(clean(JSON.parse(result.value)));
|
|
239
|
+
}
|
|
240
|
+
if (result.objectId) {
|
|
241
|
+
await new Promise((resolve, reject) => evaluator.post('Runtime.callFunctionOn', {
|
|
242
|
+
objectId: result.objectId,
|
|
243
|
+
functionDeclaration: 'function() { console.log(this); }',
|
|
244
|
+
returnByValue: true,
|
|
245
|
+
}, (error) => {
|
|
246
|
+
if (error)
|
|
247
|
+
reject(error);
|
|
248
|
+
else
|
|
249
|
+
resolve();
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
else if (result.type !== 'undefined') {
|
|
253
|
+
sink.write(result.unserializableValue ?? inspect(result.value, { maxStringLength: 20_000 }));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
evaluator.post('Runtime.releaseObjectGroup', { objectGroup: 'cell' });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
process.on('message', async (message) => {
|
|
261
|
+
if (message.type === 'close') {
|
|
262
|
+
browser.close();
|
|
263
|
+
evaluator.disconnect();
|
|
264
|
+
send({ type: 'closed' });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
runId = message.runId;
|
|
268
|
+
output = '';
|
|
269
|
+
pendingText = '';
|
|
270
|
+
images = [];
|
|
271
|
+
const cellImages = images;
|
|
272
|
+
let active = true;
|
|
273
|
+
let warned = false;
|
|
274
|
+
captureResponse = (method, params, result, sessionId) => {
|
|
275
|
+
if (active)
|
|
276
|
+
void highlight?.(method, params, sessionId);
|
|
277
|
+
if (!active || method !== 'Page.captureScreenshot')
|
|
278
|
+
return;
|
|
279
|
+
const data = result?.data;
|
|
280
|
+
if (typeof data !== 'string')
|
|
281
|
+
return;
|
|
282
|
+
if (cellImages.length >= 4 || Buffer.byteLength(data, 'base64') > 8_000_000) {
|
|
283
|
+
if (!warned)
|
|
284
|
+
sink.write('[Screenshot omitted from model vision: four-image/8 MB limit.]\n');
|
|
285
|
+
warned = true;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const format = params?.format ?? 'png';
|
|
289
|
+
const mimeType = format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png';
|
|
290
|
+
cellImages.push({ type: 'image', data, mimeType });
|
|
291
|
+
};
|
|
292
|
+
browser.observeResponse = captureResponse;
|
|
293
|
+
overflow = false;
|
|
294
|
+
outputFile = message.outputFile;
|
|
295
|
+
let valueJson;
|
|
296
|
+
let failure;
|
|
297
|
+
try {
|
|
298
|
+
valueJson = await evaluate(message.code, message.captureJson);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
active = false;
|
|
305
|
+
browser.observeResponse = undefined;
|
|
306
|
+
captureResponse = undefined;
|
|
307
|
+
}
|
|
308
|
+
captureText(clean(pendingText));
|
|
309
|
+
pendingText = '';
|
|
310
|
+
if (overflow)
|
|
311
|
+
output += '\n[Output exceeded the 1 MB capture limit.]';
|
|
312
|
+
if (output.length > config.maxOutputChars)
|
|
313
|
+
output = `${output.slice(0, config.maxOutputChars)}\n[Truncated. Full captured output: ${outputFile}]`;
|
|
314
|
+
const previews = await prepareModelImages(images);
|
|
315
|
+
const result = {
|
|
316
|
+
text: [output, ...previews.notes].filter(Boolean).join('\n') || '(no output)',
|
|
317
|
+
images: previews.images,
|
|
318
|
+
targetId: Reflect.get(realm, 'page')?.targetId,
|
|
319
|
+
...(browser.observationTargetId ? { observationTargetId: browser.observationTargetId } : {}),
|
|
320
|
+
...(valueJson !== undefined ? { valueJson } : {}),
|
|
321
|
+
...(outputFile ? { outputFile } : {}),
|
|
322
|
+
};
|
|
323
|
+
if (failure)
|
|
324
|
+
send({ type: 'error', message: failure, result });
|
|
325
|
+
else
|
|
326
|
+
send({ type: 'result', result });
|
|
327
|
+
});
|
|
328
|
+
send({ type: 'ready', targetId: page.targetId });
|
|
329
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { Writable } from 'node:stream';\nimport { Session, type Runtime } from 'node:inspector';\nimport { createRequire } from 'node:module';\nimport { createContext, constants } from 'node:vm';\nimport { inspect } from 'node:util';\nimport { writeFile, rename } from 'node:fs/promises';\nimport { appendFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport { CDP } from './cdp.js';\nimport { Page, Tabs } from './page.js';\nimport type { Image, WorkerConfig, WorkerRequest, WorkerResponse } from './protocol.js';\nimport { installDomainPolicy, fillSecret } from './policy.js';\nimport { redact } from './history.js';\nimport { actionHighlighter } from './highlight.js';\nimport { prepareModelImages } from './images.js';\n\n// IPC initialization keeps connection details out of argv and environment.\nprocess.on('disconnect', () => process.exit(0));\nconst config = await new Promise<WorkerConfig>((resolve) => process.once('message', resolve));\nconst clean = <T>(value: T): T => redact(value, config.redact ?? []);\nconst send = (message: WorkerResponse) => process.send!(clean(message));\nprocess.chdir(config.workspace);\nlet browser = CDP.lazy(config.endpoint, config.operationTimeoutMs, config.approveConnection);\ninstallDomainPolicy(browser, config, (id) => send({ type: 'owned', targetId: id }));\nlet highlight = config.highlightActions ? actionHighlighter(browser) : undefined;\nlet tabs = new Tabs(browser, (id) => send({ type: 'owned', targetId: id }));\nfunction deferredPage(targetId?: string) {\n return Page.deferred(\n browser,\n async () => {\n if (targetId) {\n // Only create a replacement when the target is actually absent, not on an attach timeout.\n const existing = await tabs.list();\n if (existing.some((t) => t.targetId === targetId)) return tabs.get(targetId);\n }\n return tabs.open();\n },\n targetId,\n );\n}\nconst page = deferredPage(config.targetId);\nlet outputFile: string | undefined;\nlet runId: string | undefined;\nlet output = '';\nlet images: Image[] = [];\nlet captureResponse: CDP['observeResponse'];\nlet overflow = false;\n// Bound memory even when generated code writes an unbounded amount of output.\nconst hardLimit = 1_000_000;\nlet pendingText = '';\nconst tailLength = Math.max(0, ...(config.redact ?? []).map((value) => value.length));\nfunction captureText(text: string) {\n if (output.length + text.length > hardLimit) overflow = true;\n const captured = text.slice(0, Math.max(0, hardLimit - output.length));\n output += captured;\n if (outputFile && captured) appendFileSync(outputFile, captured);\n}\nconst sink = new Writable({\n write(chunk: Buffer, _encoding, callback) {\n pendingText += chunk.toString();\n // Redact before splitting, and retain the raw suffix across chunk boundaries.\n const cut = Math.max(0, pendingText.length - tailLength);\n let safeCut = cut;\n for (const secret of config.redact ?? []) {\n if (!secret) continue;\n let at = pendingText.indexOf(secret);\n while (at >= 0 && at < cut) {\n if (at + secret.length > cut) safeCut = Math.min(safeCut, at);\n at = pendingText.indexOf(secret, at + 1);\n }\n }\n captureText(clean(pendingText.slice(0, safeCut)));\n pendingText = pendingText.slice(safeCut);\n callback();\n },\n});\nconst evaluator = new Session();\nevaluator.connect();\nlet executionContextId: number | undefined;\nevaluator.on('Runtime.executionContextCreated', ({ params }) => {\n if (params.context.name === 'browser-use') executionContextId = params.context.id;\n});\nevaluator.post('Runtime.enable');\nconst realm = createContext(\n {},\n {\n name: 'browser-use',\n importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,\n },\n);\nif (executionContextId === undefined)\n throw new Error('Could not initialize the JavaScript context.');\nObject.assign(realm, {\n global: realm, // Node's global alias refers to this REPL realm, not the worker host.\n // Reject values JSON would silently drop or change. Dates/toJSON use normal JSON semantics.\n __serializeResult(value: unknown) {\n const json = JSON.stringify(value, (_key, item: unknown) => {\n if (\n ['undefined', 'function', 'symbol', 'bigint'].includes(typeof item) ||\n (typeof item === 'number' && !Number.isFinite(item))\n )\n throw new Error(\n 'Result must contain JSON values: no undefined, functions, symbols, bigint, or non-finite numbers.',\n );\n return item;\n });\n if (json === undefined) throw new Error('Result must be JSON serializable.');\n if (Buffer.byteLength(json) > 16_000_000)\n throw new Error('Result exceeds 16 MB; save an artifact and return its path.');\n return json;\n },\n process,\n Buffer,\n URL,\n URLSearchParams,\n fetch,\n AbortController,\n AbortSignal,\n setTimeout,\n clearTimeout,\n setInterval,\n clearInterval,\n queueMicrotask,\n structuredClone,\n TextEncoder,\n TextDecoder,\n browser,\n tabs,\n page,\n workspace: config.workspace,\n async reconnect() {\n const targetId = (Reflect.get(realm, 'page') as Page)?.targetId;\n browser.close();\n browser = CDP.lazy(config.endpoint, config.operationTimeoutMs, config.approveConnection);\n installDomainPolicy(browser, config, (id) => send({ type: 'owned', targetId: id }));\n highlight = config.highlightActions ? actionHighlighter(browser) : undefined;\n browser.observeResponse = captureResponse;\n tabs = new Tabs(browser, (id) => send({ type: 'owned', targetId: id }));\n Object.assign(realm, { browser, tabs, page: deferredPage(targetId) });\n observe();\n return 'Connection reset. Inspect the page; no browser action was replayed. Reacquire other page/frame handles.';\n },\n async fillSecret(\n name: string,\n backendNodeId: number,\n target: Page = Reflect.get(realm, 'page') as Page,\n ) {\n await target.info();\n const result = await fillSecret(\n browser,\n target.sessionId,\n name,\n backendNodeId,\n config.sensitiveData ?? {},\n );\n void highlight?.('DOM.focus', { backendNodeId }, target.sessionId);\n return result;\n },\n async checkpoint(name: string, value: unknown, options: { partial?: boolean } = {}) {\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,119}$/.test(name))\n throw new Error('Use a plain checkpoint filename.');\n const checkpointRun = runId;\n const valueJson = JSON.stringify(clean(value));\n if (typeof valueJson !== 'string') throw new Error('Checkpoint must be JSON serializable.');\n if (options.partial && Buffer.byteLength(valueJson) > 16_000_000)\n throw new Error(\n 'Partial result exceeds 16 MB; checkpoint smaller batches or return file paths.',\n );\n const path = join(config.workspace, name);\n const temporary = `${path}.${randomUUID()}.tmp`;\n await writeFile(temporary, valueJson, { flag: 'wx', mode: 0o600 });\n await rename(temporary, path);\n if (options.partial)\n await new Promise<void>((resolve, reject) =>\n process.send!(\n {\n type: 'partial',\n ...(checkpointRun ? { runId: checkpointRun } : {}),\n path,\n valueJson,\n } satisfies WorkerResponse,\n (error) => (error ? reject(error) : resolve()),\n ),\n );\n return path;\n },\n require: createRequire(join(config.workspace, 'package.json')),\n async screenshot() {\n const current = Reflect.get(realm, 'page') as Page;\n if (images.length >= 4) throw new Error('At most four screenshots per cell.');\n const count = images.length;\n await current.screenshot({ quality: 70 });\n return images.length > count ? 'Screenshot captured.' : 'Screenshot omitted; see warning.';\n },\n async snapshot() {\n const current = Reflect.get(realm, 'page') as Page;\n return current.snapshot();\n },\n async artifact(name: string, data: string | Uint8Array) {\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,119}$/.test(name))\n throw new Error('Use a plain filename, max 120 characters.');\n const path = join(config.workspace, name);\n await writeFile(path, data, { flag: 'wx' });\n return path;\n },\n});\nfunction observe() {\n if (config.recording)\n browser.observeCommand = (method, raw, sessionId) => {\n const params = raw as { type?: string; x?: number; y?: number };\n const protocolTarget = sessionId ? browser.targetForSession(sessionId) : undefined;\n const targetId = protocolTarget ? browser.observationTargetId : undefined;\n if (!targetId) return;\n if (\n method === 'Input.dispatchMouseEvent' &&\n ['mouseReleased', 'mouseWheel'].includes(params.type ?? '')\n )\n send({\n type: 'action',\n action: {\n kind: params.type === 'mouseReleased' ? 'Click' : 'Scroll',\n targetId,\n ...(protocolTarget === targetId && params.x !== undefined ? { x: params.x } : {}),\n ...(protocolTarget === targetId && params.y !== undefined ? { y: params.y } : {}),\n },\n });\n else if (method === 'Input.insertText' || method === 'Page.navigate')\n send({\n type: 'action',\n action: { kind: method === 'Page.navigate' ? 'Navigate' : 'Type', targetId },\n });\n };\n}\nobserve();\nrealm.console = new (await import('node:console')).Console(sink, sink);\n\nasync function evaluate(code: string, captureJson = false): Promise<string | undefined> {\n try {\n // V8 supports replMode; Node 22's generated protocol types omit this field.\n const parameters = {\n expression: captureJson ? `__serializeResult(await (${code}\\n))` : code,\n contextId: executionContextId,\n awaitPromise: true,\n replMode: true,\n objectGroup: 'cell',\n };\n const { result, exceptionDetails } = await new Promise<Runtime.EvaluateReturnType>(\n (resolve, reject) => {\n evaluator.post(\n 'Runtime.evaluate',\n parameters,\n (error: Error | null, response: Runtime.EvaluateReturnType) => {\n if (error) reject(error);\n else resolve(response);\n },\n );\n },\n );\n if (exceptionDetails)\n throw new Error(exceptionDetails.exception?.description ?? exceptionDetails.text);\n if (captureJson) {\n if (typeof result.value !== 'string')\n throw new Error('Result serialization returned no JSON.');\n return JSON.stringify(clean(JSON.parse(result.value)));\n }\n if (result.objectId) {\n await new Promise<void>((resolve, reject) =>\n evaluator.post(\n 'Runtime.callFunctionOn',\n {\n objectId: result.objectId,\n functionDeclaration: 'function() { console.log(this); }',\n returnByValue: true,\n },\n (error) => {\n if (error) reject(error);\n else resolve();\n },\n ),\n );\n } else if (result.type !== 'undefined') {\n sink.write(result.unserializableValue ?? inspect(result.value, { maxStringLength: 20_000 }));\n }\n } finally {\n evaluator.post('Runtime.releaseObjectGroup', { objectGroup: 'cell' });\n }\n}\n\nprocess.on('message', async (message: WorkerRequest) => {\n if (message.type === 'close') {\n browser.close();\n evaluator.disconnect();\n send({ type: 'closed' });\n return;\n }\n runId = message.runId;\n output = '';\n pendingText = '';\n images = [];\n const cellImages = images;\n let active = true;\n let warned = false;\n captureResponse = (method, params, result, sessionId) => {\n if (active) void highlight?.(method, params, sessionId);\n if (!active || method !== 'Page.captureScreenshot') return;\n const data = (result as { data?: unknown })?.data;\n if (typeof data !== 'string') return;\n if (cellImages.length >= 4 || Buffer.byteLength(data, 'base64') > 8_000_000) {\n if (!warned) sink.write('[Screenshot omitted from model vision: four-image/8 MB limit.]\\n');\n warned = true;\n return;\n }\n const format = (params as { format?: string })?.format ?? 'png';\n const mimeType =\n format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png';\n cellImages.push({ type: 'image', data, mimeType });\n };\n browser.observeResponse = captureResponse;\n overflow = false;\n outputFile = message.outputFile;\n let valueJson: string | undefined;\n let failure: string | undefined;\n try {\n valueJson = await evaluate(message.code, message.captureJson);\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n } finally {\n active = false;\n browser.observeResponse = undefined;\n captureResponse = undefined;\n }\n captureText(clean(pendingText));\n pendingText = '';\n if (overflow) output += '\\n[Output exceeded the 1 MB capture limit.]';\n if (output.length > config.maxOutputChars)\n output = `${output.slice(0, config.maxOutputChars)}\\n[Truncated. Full captured output: ${outputFile}]`;\n const previews = await prepareModelImages(images);\n const result = {\n text: [output, ...previews.notes].filter(Boolean).join('\\n') || '(no output)',\n images: previews.images,\n targetId: (Reflect.get(realm, 'page') as Page)?.targetId,\n ...(browser.observationTargetId ? { observationTargetId: browser.observationTargetId } : {}),\n ...(valueJson !== undefined ? { valueJson } : {}),\n ...(outputFile ? { outputFile } : {}),\n };\n if (failure) send({ type: 'error', message: failure, result });\n else send({ type: 'result', result });\n});\nsend({ type: 'ready', targetId: page.targetId });\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Plain TypeScript. `.mjs` is JavaScript explicitly marked as an ES module; the examples use `.ts` so your editor checks the SDK calls.
|
|
4
|
+
|
|
5
|
+
From this checkout:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm ci && npm run build
|
|
9
|
+
cp .env.example .env # fill the two API keys
|
|
10
|
+
node --env-file=.env examples/extract.ts
|
|
11
|
+
# or: bun --env-file=.env examples/extract.ts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Node 22.19+ runs these files directly. Bun 1.3.14+ also needs Node for the execution worker. npm and pnpm are package managers, not runtimes.
|
|
15
|
+
|
|
16
|
+
Set `BROWSER=cloud` for Browser Use Cloud, or `BROWSER=local` for isolated local Chrome. Local Chrome must be installed. Each script closes its browser in `finally`. Use `WORKSPACE` to choose where files go.
|
|
17
|
+
|
|
18
|
+
| Example | What you get | Extra environment variables |
|
|
19
|
+
| ---------------------------------- | -------------------------------------------------- | ------------------------------------------------------------- |
|
|
20
|
+
| [extract.ts](extract.ts) | Ten verified books, typed records, CSV | Optional `START_URL` |
|
|
21
|
+
| [qa.ts](qa.ts) | Bug report, screenshots, partial findings, GIF | `START_URL` (staging); ffmpeg + local Chrome for GIF export |
|
|
22
|
+
| [ehr.ts](ehr.ts) | An unsigned note for a synthetic patient | `EHR_URL`; optional cloud `BROWSER_PROFILE_ID` |
|
|
23
|
+
| [form.ts](form.ts) | A submitted demo pizza form, verified response | Optional `START_URL` |
|
|
24
|
+
| [stripe-link.ts](stripe-link.ts) | Link-approved test card prefilling | `CHECKOUT_URL`, `PURCHASE`; Link CLI login |
|
|
25
|
+
| [onepassword.ts](onepassword.ts) | Login using two selected vault secrets | `LOGIN_URL`, `OP_USERNAME_REF`, `OP_PASSWORD_REF`; `op` login |
|
|
26
|
+
| [apply-to-job.ts](apply-to-job.ts) | Filled application, attached PDF, review checklist | `JOB_URL`, `RESUME_PDF`, `APPLICANT_JSON` |
|
|
27
|
+
| [research.ts](research.ts) | A streamed answer to your own task | Task as command-line argument |
|
|
28
|
+
|
|
29
|
+
## Credentials and payments
|
|
30
|
+
|
|
31
|
+
**1Password:** install the [official CLI](https://developer.1password.com/docs/cli/), sign in, and select references such as `op://Test/Login/username` and `op://Test/Login/password`. The host reads those two fields. The model receives names and uses `fillSecret`; it gets no vault-reading tool. MFA stops for a human.
|
|
32
|
+
|
|
33
|
+
**Link:** install `@stripe/link-cli@0.18.0` and put its `link-cli` executable on PATH. Check `link-cli auth status --format json`. For a new session:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
link-cli auth login --client-name "Browser Use Pi" --scope "userinfo:read payment_methods.agentic"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For an existing session missing payment access, use `auth upgrade` with the same scope. The example inspects a test checkout, creates a `--test` card request, prints its approval URL, waits up to eight minutes, then prefills and cancels the unused request. It never clicks Pay. It supports ordinary card forms, not Link Pay Tokens or HTTP 402 flows. Keep the checkout directly revisitable: preflight and filling use separate browsers.
|
|
40
|
+
|
|
41
|
+
Credentials stay out of normal logs and recordings are disabled in these examples. Screenshots are **not pixel-redacted**, and agent-written code has filesystem/network access. Domain rules are navigation controls, not a sandbox. Use test accounts in an isolated environment.
|
|
42
|
+
|
|
43
|
+
## EHR and job applications
|
|
44
|
+
|
|
45
|
+
The EHR example requires a sandbox containing patient `TEST-1001`, Avery Example, born `1990-01-02`. It checks both identifiers and saves only an unsigned draft. Local login persists in `artifacts/ehr-profile`; cloud login needs your profile ID.
|
|
46
|
+
|
|
47
|
+
The job example reads facts from your JSON file and attaches a real PDF by transferring bytes into the browser, so uploads also work remotely. It leaves unknown answers blank and stops before submission. For example:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"name": "Avery Example",
|
|
52
|
+
"email": "avery@example.com",
|
|
53
|
+
"phone": "202-555-0142",
|
|
54
|
+
"experience": "Two years building TypeScript applications"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each script caps steps, time and cost. A stopped run can return partial progress; inspect its status before treating the task as complete.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Browser, BrowserUse, Type } from '@browser_use/pi';
|
|
2
|
+
import { copyFile, mkdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const url = process.env.JOB_URL;
|
|
6
|
+
const resume = process.env.RESUME_PDF;
|
|
7
|
+
const details = process.env.APPLICANT_JSON;
|
|
8
|
+
if (!url || !resume || !details)
|
|
9
|
+
throw new Error('Set JOB_URL, RESUME_PDF and APPLICANT_JSON (path to your application details).');
|
|
10
|
+
const pdf = await readFile(resume);
|
|
11
|
+
if (pdf.subarray(0, 5).toString() !== '%PDF-') throw new Error('RESUME_PDF must be a PDF file.');
|
|
12
|
+
const applicant: unknown = JSON.parse(await readFile(details, 'utf8'));
|
|
13
|
+
const workspace = resolve(process.env.WORKSPACE || './artifacts/apply-to-job');
|
|
14
|
+
await mkdir(workspace, { recursive: true });
|
|
15
|
+
if (resolve(resume) !== join(workspace, 'resume.pdf'))
|
|
16
|
+
await copyFile(resume, join(workspace, 'resume.pdf'));
|
|
17
|
+
const agent = await BrowserUse.create({
|
|
18
|
+
model: process.env.MODEL || 'openrouter/openai/gpt-5.6-luna',
|
|
19
|
+
browser:
|
|
20
|
+
process.env.BROWSER === 'cloud'
|
|
21
|
+
? Browser.cloud({ apiKey: process.env.BROWSER_USE_API_KEY ?? '' })
|
|
22
|
+
: Browser.chromium(),
|
|
23
|
+
workspace,
|
|
24
|
+
researchTools: true,
|
|
25
|
+
telemetry: false,
|
|
26
|
+
log: false,
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
const result = await agent.run(
|
|
30
|
+
`Prepare the application at ${url} using ONLY these facts:
|
|
31
|
+
${JSON.stringify(applicant)}
|
|
32
|
+
The real PDF is resume.pdf in the workspace. Attach it and verify the filename in the form.
|
|
33
|
+
For uploads that work with local AND remote Chrome: read the PDF in Node, send its base64
|
|
34
|
+
to page.evaluate, construct a File with type application/pdf from the decoded bytes,
|
|
35
|
+
assign it through DataTransfer to the observed file input, then dispatch input/change events.
|
|
36
|
+
DOM.setFileInputFiles with a local path cannot upload a host file to a remote browser.
|
|
37
|
+
Leave unknown fields blank and report required ones. Do not guess eligibility or protected
|
|
38
|
+
characteristics. Stop at final review, BEFORE submitting or accepting legal declarations.
|
|
39
|
+
Save application-review.md listing filled fields, missing answers and the attached file.`,
|
|
40
|
+
{
|
|
41
|
+
schema: Type.Object({
|
|
42
|
+
resumeAttached: Type.Boolean(),
|
|
43
|
+
filled: Type.Array(Type.String()),
|
|
44
|
+
needsAnswers: Type.Array(Type.String()),
|
|
45
|
+
reviewUrl: Type.String(),
|
|
46
|
+
submitted: Type.Literal(false),
|
|
47
|
+
}),
|
|
48
|
+
maxSteps: 40,
|
|
49
|
+
timeoutMs: 300_000,
|
|
50
|
+
maxCostUsd: 2,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
console.log(result.status, result.status === 'completed' ? result.output : result.text);
|
|
54
|
+
if (result.status !== 'completed') process.exitCode = 1;
|
|
55
|
+
} finally {
|
|
56
|
+
await agent.close();
|
|
57
|
+
}
|
package/examples/ehr.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Browser, BrowserUse, Type } from '@browser_use/pi';
|
|
2
|
+
|
|
3
|
+
const url = process.env.EHR_URL;
|
|
4
|
+
if (!url) throw new Error('Set EHR_URL to an EHR sandbox containing synthetic patient TEST-1001.');
|
|
5
|
+
const agent = await BrowserUse.create({
|
|
6
|
+
model: process.env.MODEL || 'openrouter/openai/gpt-5.6-luna',
|
|
7
|
+
browser:
|
|
8
|
+
process.env.BROWSER === 'cloud'
|
|
9
|
+
? Browser.cloud({
|
|
10
|
+
apiKey: process.env.BROWSER_USE_API_KEY ?? '',
|
|
11
|
+
...(process.env.BROWSER_PROFILE_ID ? { profileId: process.env.BROWSER_PROFILE_ID } : {}),
|
|
12
|
+
})
|
|
13
|
+
: Browser.chromium({ profileDir: `${process.env.WORKSPACE || './artifacts/ehr'}-profile` }),
|
|
14
|
+
workspace: process.env.WORKSPACE || './artifacts/ehr',
|
|
15
|
+
telemetry: false,
|
|
16
|
+
log: false,
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
// Synthetic source note. Reuse a sandbox login through the browser profile.
|
|
20
|
+
const note = {
|
|
21
|
+
patientId: 'TEST-1001',
|
|
22
|
+
patientName: 'Avery Example',
|
|
23
|
+
dateOfBirth: '1990-01-02',
|
|
24
|
+
subjective: 'Demo visit: patient reports mild left ankle pain after a walk.',
|
|
25
|
+
objective: 'Demo measurements: pulse 72 bpm; blood pressure 118/76 mmHg.',
|
|
26
|
+
assessment: 'Assessment pending clinician review.',
|
|
27
|
+
plan: 'Plan pending clinician review.',
|
|
28
|
+
};
|
|
29
|
+
const result = await agent.run(
|
|
30
|
+
`Open the EHR sandbox at ${url}.
|
|
31
|
+
Find exactly this synthetic patient and verify BOTH identifier and date of birth.
|
|
32
|
+
If either does not match, stop. Create an UNSIGNED DRAFT visit note from this source:
|
|
33
|
+
${JSON.stringify(note)}
|
|
34
|
+
Preserve the source wording. Do not infer diagnoses, add orders, change medications,
|
|
35
|
+
sign/finalize notes or contact anyone. Save the draft and verify it appears as unsigned.
|
|
36
|
+
If login is needed, stop and report it.`,
|
|
37
|
+
{
|
|
38
|
+
schema: Type.Object({
|
|
39
|
+
patientId: Type.String(),
|
|
40
|
+
draftSaved: Type.Boolean(),
|
|
41
|
+
evidence: Type.String(),
|
|
42
|
+
needsReview: Type.Array(Type.String()),
|
|
43
|
+
}),
|
|
44
|
+
maxSteps: 30,
|
|
45
|
+
timeoutMs: 300_000,
|
|
46
|
+
maxCostUsd: 2,
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
console.log(result.status, result.status === 'completed' ? result.output : result.text);
|
|
50
|
+
if (result.status !== 'completed') process.exitCode = 1;
|
|
51
|
+
} finally {
|
|
52
|
+
await agent.close();
|
|
53
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Browser, BrowserUse, Type } from '@browser_use/pi';
|
|
2
|
+
|
|
3
|
+
// A website becomes a typed dataset and a CSV, with source URLs for every row.
|
|
4
|
+
const agent = await BrowserUse.create({
|
|
5
|
+
model: process.env.MODEL || 'openrouter/openai/gpt-5.6-luna',
|
|
6
|
+
browser:
|
|
7
|
+
process.env.BROWSER === 'cloud'
|
|
8
|
+
? Browser.cloud({ apiKey: process.env.BROWSER_USE_API_KEY ?? '' })
|
|
9
|
+
: Browser.chromium(),
|
|
10
|
+
workspace: process.env.WORKSPACE || './artifacts/extract',
|
|
11
|
+
researchTools: true,
|
|
12
|
+
log: 'pretty',
|
|
13
|
+
highlightActions: true,
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
const result = await agent.run(
|
|
17
|
+
`Visit ${process.env.START_URL || 'https://books.toscrape.com/'}.
|
|
18
|
+
Collect the first 10 books in displayed order. Open their detail pages to verify title,
|
|
19
|
+
price, currency and stock status. Keep missing values null; do not guess.
|
|
20
|
+
Save books.csv in the workspace and publish a checkpoint after each verified book.
|
|
21
|
+
Return the records and the CSV path.`,
|
|
22
|
+
{
|
|
23
|
+
schema: Type.Object({
|
|
24
|
+
books: Type.Array(
|
|
25
|
+
Type.Object({
|
|
26
|
+
title: Type.String(),
|
|
27
|
+
price: Type.Union([Type.Number(), Type.Null()]),
|
|
28
|
+
currency: Type.Union([Type.String(), Type.Null()]),
|
|
29
|
+
inStock: Type.Union([Type.Boolean(), Type.Null()]),
|
|
30
|
+
url: Type.String(),
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
csv: Type.String(),
|
|
34
|
+
}),
|
|
35
|
+
maxSteps: 40,
|
|
36
|
+
timeoutMs: 300_000,
|
|
37
|
+
maxCostUsd: 2,
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
console.log(
|
|
41
|
+
JSON.stringify(
|
|
42
|
+
result.status === 'completed' ? result.output : (result.partial?.value ?? result.text),
|
|
43
|
+
null,
|
|
44
|
+
2,
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
if (result.status !== 'completed') process.exitCode = 1;
|
|
48
|
+
} finally {
|
|
49
|
+
await agent.close();
|
|
50
|
+
}
|
package/examples/form.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Browser, BrowserUse, Type } from '@browser_use/pi';
|
|
2
|
+
|
|
3
|
+
const agent = await BrowserUse.create({
|
|
4
|
+
model: process.env.MODEL || 'openrouter/openai/gpt-5.6-luna',
|
|
5
|
+
browser:
|
|
6
|
+
process.env.BROWSER === 'cloud'
|
|
7
|
+
? Browser.cloud({ apiKey: process.env.BROWSER_USE_API_KEY ?? '' })
|
|
8
|
+
: Browser.chromium(),
|
|
9
|
+
workspace: process.env.WORKSPACE || './artifacts/form',
|
|
10
|
+
highlightActions: true,
|
|
11
|
+
log: 'pretty',
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
const result = await agent.run(
|
|
15
|
+
`Open ${process.env.START_URL || 'https://httpbin.org/forms/post'}.
|
|
16
|
+
Fill the test pizza form: customer Avery Example, phone 202-555-0142,
|
|
17
|
+
email avery@example.com, medium pizza, cheese and mushroom toppings,
|
|
18
|
+
delivery time 18:30, comment "Synthetic Browser Use Pi demo".
|
|
19
|
+
Submit this demo form once. Inspect the response and verify the submitted fields.
|
|
20
|
+
If the submission result is ambiguous, report that instead of submitting again.`,
|
|
21
|
+
{
|
|
22
|
+
schema: Type.Object({
|
|
23
|
+
submitted: Type.Boolean(),
|
|
24
|
+
verifiedFields: Type.Record(Type.String(), Type.String()),
|
|
25
|
+
mismatches: Type.Array(Type.String()),
|
|
26
|
+
}),
|
|
27
|
+
maxSteps: 20,
|
|
28
|
+
timeoutMs: 180_000,
|
|
29
|
+
maxCostUsd: 1,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
console.log(result.status, result.status === 'completed' ? result.output : result.text);
|
|
33
|
+
if (result.status !== 'completed') process.exitCode = 1;
|
|
34
|
+
} finally {
|
|
35
|
+
await agent.close();
|
|
36
|
+
}
|