browser-use 0.0.1 → 0.0.2
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/LICENSE +21 -0
- package/README.md +761 -0
- package/dist/agent/cloud-events.d.ts +264 -0
- package/dist/agent/cloud-events.js +318 -0
- package/dist/agent/gif.d.ts +15 -0
- package/dist/agent/gif.js +215 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/message-manager/service.d.ts +30 -0
- package/dist/agent/message-manager/service.js +208 -0
- package/dist/agent/message-manager/utils.d.ts +2 -0
- package/dist/agent/message-manager/utils.js +41 -0
- package/dist/agent/message-manager/views.d.ts +26 -0
- package/dist/agent/message-manager/views.js +73 -0
- package/dist/agent/prompts.d.ts +52 -0
- package/dist/agent/prompts.js +259 -0
- package/dist/agent/service.d.ts +290 -0
- package/dist/agent/service.js +2200 -0
- package/dist/agent/views.d.ts +741 -0
- package/dist/agent/views.js +537 -0
- package/dist/browser/browser.d.ts +7 -0
- package/dist/browser/browser.js +5 -0
- package/dist/browser/context.d.ts +8 -0
- package/dist/browser/context.js +4 -0
- package/dist/browser/dvd-screensaver.d.ts +101 -0
- package/dist/browser/dvd-screensaver.js +270 -0
- package/dist/browser/extensions.d.ts +63 -0
- package/dist/browser/extensions.js +359 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +9 -0
- package/dist/browser/playwright-manager.d.ts +47 -0
- package/dist/browser/playwright-manager.js +146 -0
- package/dist/browser/profile.d.ts +196 -0
- package/dist/browser/profile.js +815 -0
- package/dist/browser/session.d.ts +505 -0
- package/dist/browser/session.js +3409 -0
- package/dist/browser/types.d.ts +1184 -0
- package/dist/browser/types.js +1 -0
- package/dist/browser/utils.d.ts +1 -0
- package/dist/browser/utils.js +19 -0
- package/dist/browser/views.d.ts +78 -0
- package/dist/browser/views.js +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +44 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.js +430 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/index.js +3 -0
- package/dist/controller/registry/index.d.ts +2 -0
- package/dist/controller/registry/index.js +2 -0
- package/dist/controller/registry/service.d.ts +45 -0
- package/dist/controller/registry/service.js +184 -0
- package/dist/controller/registry/views.d.ts +55 -0
- package/dist/controller/registry/views.js +174 -0
- package/dist/controller/service.d.ts +49 -0
- package/dist/controller/service.js +1176 -0
- package/dist/controller/views.d.ts +241 -0
- package/dist/controller/views.js +88 -0
- package/dist/dom/clickable-element-processor/service.d.ts +11 -0
- package/dist/dom/clickable-element-processor/service.js +60 -0
- package/dist/dom/dom_tree/index.js +1400 -0
- package/dist/dom/history-tree-processor/service.d.ts +14 -0
- package/dist/dom/history-tree-processor/service.js +75 -0
- package/dist/dom/history-tree-processor/view.d.ts +54 -0
- package/dist/dom/history-tree-processor/view.js +56 -0
- package/dist/dom/playground/extraction.d.ts +19 -0
- package/dist/dom/playground/extraction.js +187 -0
- package/dist/dom/playground/process-dom.d.ts +1 -0
- package/dist/dom/playground/process-dom.js +5 -0
- package/dist/dom/playground/test-accessibility.d.ts +44 -0
- package/dist/dom/playground/test-accessibility.js +111 -0
- package/dist/dom/service.d.ts +19 -0
- package/dist/dom/service.js +227 -0
- package/dist/dom/utils.d.ts +1 -0
- package/dist/dom/utils.js +6 -0
- package/dist/dom/views.d.ts +61 -0
- package/dist/dom/views.js +247 -0
- package/dist/event-bus.d.ts +11 -0
- package/dist/event-bus.js +19 -0
- package/dist/exceptions.d.ts +10 -0
- package/dist/exceptions.js +22 -0
- package/dist/filesystem/file-system.d.ts +68 -0
- package/dist/filesystem/file-system.js +412 -0
- package/dist/filesystem/index.d.ts +1 -0
- package/dist/filesystem/index.js +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/dist/integrations/gmail/actions.d.ts +12 -0
- package/dist/integrations/gmail/actions.js +113 -0
- package/dist/integrations/gmail/index.d.ts +2 -0
- package/dist/integrations/gmail/index.js +2 -0
- package/dist/integrations/gmail/service.d.ts +61 -0
- package/dist/integrations/gmail/service.js +260 -0
- package/dist/llm/anthropic/chat.d.ts +28 -0
- package/dist/llm/anthropic/chat.js +126 -0
- package/dist/llm/anthropic/index.d.ts +2 -0
- package/dist/llm/anthropic/index.js +2 -0
- package/dist/llm/anthropic/serializer.d.ts +68 -0
- package/dist/llm/anthropic/serializer.js +285 -0
- package/dist/llm/aws/chat-anthropic.d.ts +61 -0
- package/dist/llm/aws/chat-anthropic.js +176 -0
- package/dist/llm/aws/chat-bedrock.d.ts +15 -0
- package/dist/llm/aws/chat-bedrock.js +80 -0
- package/dist/llm/aws/index.d.ts +3 -0
- package/dist/llm/aws/index.js +3 -0
- package/dist/llm/aws/serializer.d.ts +5 -0
- package/dist/llm/aws/serializer.js +68 -0
- package/dist/llm/azure/chat.d.ts +15 -0
- package/dist/llm/azure/chat.js +83 -0
- package/dist/llm/azure/index.d.ts +1 -0
- package/dist/llm/azure/index.js +1 -0
- package/dist/llm/base.d.ts +16 -0
- package/dist/llm/base.js +1 -0
- package/dist/llm/deepseek/chat.d.ts +15 -0
- package/dist/llm/deepseek/chat.js +51 -0
- package/dist/llm/deepseek/index.d.ts +2 -0
- package/dist/llm/deepseek/index.js +2 -0
- package/dist/llm/deepseek/serializer.d.ts +6 -0
- package/dist/llm/deepseek/serializer.js +57 -0
- package/dist/llm/exceptions.d.ts +10 -0
- package/dist/llm/exceptions.js +18 -0
- package/dist/llm/google/chat.d.ts +20 -0
- package/dist/llm/google/chat.js +144 -0
- package/dist/llm/google/index.d.ts +2 -0
- package/dist/llm/google/index.js +2 -0
- package/dist/llm/google/serializer.d.ts +6 -0
- package/dist/llm/google/serializer.js +64 -0
- package/dist/llm/groq/chat.d.ts +15 -0
- package/dist/llm/groq/chat.js +52 -0
- package/dist/llm/groq/index.d.ts +3 -0
- package/dist/llm/groq/index.js +3 -0
- package/dist/llm/groq/parser.d.ts +32 -0
- package/dist/llm/groq/parser.js +189 -0
- package/dist/llm/groq/serializer.d.ts +6 -0
- package/dist/llm/groq/serializer.js +56 -0
- package/dist/llm/messages.d.ts +77 -0
- package/dist/llm/messages.js +157 -0
- package/dist/llm/ollama/chat.d.ts +15 -0
- package/dist/llm/ollama/chat.js +77 -0
- package/dist/llm/ollama/index.d.ts +2 -0
- package/dist/llm/ollama/index.js +2 -0
- package/dist/llm/ollama/serializer.d.ts +6 -0
- package/dist/llm/ollama/serializer.js +53 -0
- package/dist/llm/openai/chat.d.ts +38 -0
- package/dist/llm/openai/chat.js +174 -0
- package/dist/llm/openai/index.d.ts +3 -0
- package/dist/llm/openai/index.js +3 -0
- package/dist/llm/openai/like.d.ts +17 -0
- package/dist/llm/openai/like.js +19 -0
- package/dist/llm/openai/serializer.d.ts +6 -0
- package/dist/llm/openai/serializer.js +57 -0
- package/dist/llm/openrouter/chat.d.ts +15 -0
- package/dist/llm/openrouter/chat.js +74 -0
- package/dist/llm/openrouter/index.d.ts +2 -0
- package/dist/llm/openrouter/index.js +2 -0
- package/dist/llm/openrouter/serializer.d.ts +3 -0
- package/dist/llm/openrouter/serializer.js +3 -0
- package/dist/llm/schema.d.ts +6 -0
- package/dist/llm/schema.js +77 -0
- package/dist/llm/views.d.ts +15 -0
- package/dist/llm/views.js +12 -0
- package/dist/logging-config.d.ts +25 -0
- package/dist/logging-config.js +89 -0
- package/dist/mcp/client.d.ts +142 -0
- package/dist/mcp/client.js +638 -0
- package/dist/mcp/controller.d.ts +6 -0
- package/dist/mcp/controller.js +38 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/server.d.ts +134 -0
- package/dist/mcp/server.js +759 -0
- package/dist/observability-decorators.d.ts +158 -0
- package/dist/observability-decorators.js +286 -0
- package/dist/observability.d.ts +23 -0
- package/dist/observability.js +58 -0
- package/dist/screenshots/index.d.ts +1 -0
- package/dist/screenshots/index.js +1 -0
- package/dist/screenshots/service.d.ts +6 -0
- package/dist/screenshots/service.js +28 -0
- package/dist/sync/auth.d.ts +27 -0
- package/dist/sync/auth.js +205 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/service.d.ts +21 -0
- package/dist/sync/service.js +146 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/telemetry/service.d.ts +12 -0
- package/dist/telemetry/service.js +85 -0
- package/dist/telemetry/views.d.ts +112 -0
- package/dist/telemetry/views.js +112 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/service.d.ts +35 -0
- package/dist/tokens/service.js +423 -0
- package/dist/tokens/views.d.ts +58 -0
- package/dist/tokens/views.js +1 -0
- package/dist/utils.d.ts +128 -0
- package/dist/utils.js +529 -0
- package/package.json +94 -5
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { CONFIG } from '../config.js';
|
|
5
|
+
import { createLogger } from '../logging-config.js';
|
|
6
|
+
import { uuid7str } from '../utils.js';
|
|
7
|
+
export const TEMP_USER_ID = '99999999-9999-9999-9999-999999999999';
|
|
8
|
+
const logger = createLogger('browser_use.sync.auth');
|
|
9
|
+
const CONFIG_DIR = () => CONFIG.BROWSER_USE_CONFIG_DIR ?? path.join(process.cwd(), '.browseruse');
|
|
10
|
+
const DEVICE_ID_PATH = () => path.join(CONFIG_DIR(), 'device_id');
|
|
11
|
+
const CLOUD_AUTH_PATH = () => path.join(CONFIG_DIR(), 'cloud_auth.json');
|
|
12
|
+
const ensureDir = () => {
|
|
13
|
+
fs.mkdirSync(CONFIG_DIR(), { recursive: true });
|
|
14
|
+
};
|
|
15
|
+
const loadAuthConfig = () => {
|
|
16
|
+
try {
|
|
17
|
+
const contents = fs.readFileSync(CLOUD_AUTH_PATH(), 'utf-8');
|
|
18
|
+
const parsed = JSON.parse(contents);
|
|
19
|
+
return {
|
|
20
|
+
api_token: parsed.api_token ?? null,
|
|
21
|
+
user_id: parsed.user_id ?? null,
|
|
22
|
+
authorized_at: parsed.authorized_at ?? null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { api_token: null, user_id: null, authorized_at: null };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const saveAuthConfig = (config) => {
|
|
30
|
+
ensureDir();
|
|
31
|
+
fs.writeFileSync(CLOUD_AUTH_PATH(), JSON.stringify(config, null, 2), 'utf-8');
|
|
32
|
+
try {
|
|
33
|
+
fs.chmodSync(CLOUD_AUTH_PATH(), 0o600);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* noop */
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const getOrCreateDeviceId = () => {
|
|
40
|
+
ensureDir();
|
|
41
|
+
try {
|
|
42
|
+
const existing = fs.readFileSync(DEVICE_ID_PATH(), 'utf-8').trim();
|
|
43
|
+
if (existing) {
|
|
44
|
+
return existing;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* continue */
|
|
49
|
+
}
|
|
50
|
+
const deviceId = uuid7str();
|
|
51
|
+
fs.writeFileSync(DEVICE_ID_PATH(), deviceId, 'utf-8');
|
|
52
|
+
return deviceId;
|
|
53
|
+
};
|
|
54
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
|
+
const stripTrailingSlash = (input) => input.replace(/\/+$/, '');
|
|
56
|
+
const terminalWidth = () => Math.max((process.stdout?.columns ?? 80) - 40, 20);
|
|
57
|
+
export class DeviceAuthClient {
|
|
58
|
+
baseUrl;
|
|
59
|
+
clientId = 'library';
|
|
60
|
+
scope = 'read write';
|
|
61
|
+
httpClient;
|
|
62
|
+
authConfig;
|
|
63
|
+
_deviceId;
|
|
64
|
+
constructor(baseUrl, httpClient) {
|
|
65
|
+
this.baseUrl = stripTrailingSlash(baseUrl ?? CONFIG.BROWSER_USE_CLOUD_API_URL);
|
|
66
|
+
this.httpClient = httpClient;
|
|
67
|
+
this.authConfig = loadAuthConfig();
|
|
68
|
+
this._deviceId = getOrCreateDeviceId();
|
|
69
|
+
}
|
|
70
|
+
get device_id() {
|
|
71
|
+
return this._deviceId;
|
|
72
|
+
}
|
|
73
|
+
get is_authenticated() {
|
|
74
|
+
return Boolean(this.authConfig.api_token && this.authConfig.user_id);
|
|
75
|
+
}
|
|
76
|
+
get api_token() {
|
|
77
|
+
return this.authConfig.api_token;
|
|
78
|
+
}
|
|
79
|
+
get user_id() {
|
|
80
|
+
return this.authConfig.user_id ?? TEMP_USER_ID;
|
|
81
|
+
}
|
|
82
|
+
get client() {
|
|
83
|
+
return this.httpClient ?? axios;
|
|
84
|
+
}
|
|
85
|
+
buildUrl(pathname) {
|
|
86
|
+
return `${this.baseUrl}${pathname}`;
|
|
87
|
+
}
|
|
88
|
+
async postForm(pathname, data) {
|
|
89
|
+
const form = new URLSearchParams();
|
|
90
|
+
for (const [key, value] of Object.entries(data)) {
|
|
91
|
+
if (value !== undefined && value !== null) {
|
|
92
|
+
form.append(key, String(value));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return this.client.post(this.buildUrl(pathname), form, {
|
|
96
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async start_device_authorization(agent_session_id) {
|
|
100
|
+
const response = await this.postForm('/api/v1/oauth/device/authorize', {
|
|
101
|
+
client_id: this.clientId,
|
|
102
|
+
scope: this.scope,
|
|
103
|
+
agent_session_id: agent_session_id ?? undefined,
|
|
104
|
+
device_id: this.device_id,
|
|
105
|
+
});
|
|
106
|
+
return response.data;
|
|
107
|
+
}
|
|
108
|
+
async poll_for_token(device_code, interval = 3, timeout = 1800) {
|
|
109
|
+
const started = Date.now();
|
|
110
|
+
let delay = interval;
|
|
111
|
+
while (Date.now() - started < timeout * 1000) {
|
|
112
|
+
try {
|
|
113
|
+
const response = await this.postForm('/api/v1/oauth/device/token', {
|
|
114
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
115
|
+
device_code,
|
|
116
|
+
client_id: this.clientId,
|
|
117
|
+
});
|
|
118
|
+
const data = response.data;
|
|
119
|
+
if (data.error === 'authorization_pending') {
|
|
120
|
+
await sleep(delay * 1000);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (data.error === 'slow_down') {
|
|
124
|
+
delay = data.interval ?? delay * 2;
|
|
125
|
+
await sleep(delay * 1000);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (data.error) {
|
|
129
|
+
logger.warning(`Device token error: ${data.error}`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (data.access_token) {
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const status = error?.response?.status;
|
|
138
|
+
const payload = error?.response?.data;
|
|
139
|
+
if (status === 400 &&
|
|
140
|
+
payload?.error &&
|
|
141
|
+
['authorization_pending', 'slow_down'].includes(payload.error)) {
|
|
142
|
+
if (payload.error === 'slow_down') {
|
|
143
|
+
delay = payload.interval ?? delay * 2;
|
|
144
|
+
}
|
|
145
|
+
await sleep(delay * 1000);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
logger.debug(`Error polling for token: ${error?.message ?? error}`);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
await sleep(delay * 1000);
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
async authenticate(agent_session_id, show_instructions = true) {
|
|
156
|
+
try {
|
|
157
|
+
const deviceAuth = await this.start_device_authorization(agent_session_id);
|
|
158
|
+
const frontendBase = CONFIG.BROWSER_USE_CLOUD_UI_URL ||
|
|
159
|
+
this.baseUrl.replace('//api.', '//cloud.');
|
|
160
|
+
const replaceHost = (value) => value?.replace(this.baseUrl, frontendBase);
|
|
161
|
+
const verificationUri = replaceHost(deviceAuth.verification_uri);
|
|
162
|
+
const verificationUriComplete = replaceHost(deviceAuth.verification_uri_complete);
|
|
163
|
+
if (show_instructions) {
|
|
164
|
+
const divider = '─'.repeat(terminalWidth());
|
|
165
|
+
logger.info(divider);
|
|
166
|
+
logger.info('🌐 View the details of this run in Browser Use Cloud:');
|
|
167
|
+
logger.info(` 👉 ${verificationUriComplete}`);
|
|
168
|
+
logger.info(divider + '\n');
|
|
169
|
+
}
|
|
170
|
+
const tokenData = await this.poll_for_token(deviceAuth.device_code, deviceAuth.interval ?? 5);
|
|
171
|
+
if (tokenData?.access_token) {
|
|
172
|
+
this.authConfig = {
|
|
173
|
+
api_token: tokenData.access_token,
|
|
174
|
+
user_id: tokenData.user_id ?? this.user_id,
|
|
175
|
+
authorized_at: new Date().toISOString(),
|
|
176
|
+
};
|
|
177
|
+
saveAuthConfig(this.authConfig);
|
|
178
|
+
if (show_instructions) {
|
|
179
|
+
logger.debug('✅ Authentication successful, cloud sync enabled.');
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
const status = error?.response?.status;
|
|
186
|
+
if (status === 404) {
|
|
187
|
+
logger.warning('Cloud sync authentication endpoint not found (404).');
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
logger.warning(`Cloud sync auth error: ${error?.message ?? error}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (show_instructions) {
|
|
194
|
+
logger.debug(`❌ Sync authentication failed for ${this.baseUrl}`);
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
get_headers() {
|
|
199
|
+
return this.api_token ? { Authorization: `Bearer ${this.api_token}` } : {};
|
|
200
|
+
}
|
|
201
|
+
clear_auth() {
|
|
202
|
+
this.authConfig = { api_token: null, user_id: null, authorized_at: null };
|
|
203
|
+
saveAuthConfig(this.authConfig);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BaseEvent } from '../agent/cloud-events.js';
|
|
2
|
+
import { DeviceAuthClient } from './auth.js';
|
|
3
|
+
export interface CloudSyncOptions {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
enableAuth?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class CloudSync {
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
private readonly enableAuth;
|
|
10
|
+
readonly auth_client?: DeviceAuthClient;
|
|
11
|
+
private pendingEvents;
|
|
12
|
+
private authTask;
|
|
13
|
+
private sessionId;
|
|
14
|
+
constructor(options?: CloudSyncOptions);
|
|
15
|
+
handle_event(event: BaseEvent): Promise<void>;
|
|
16
|
+
private sendEvent;
|
|
17
|
+
private backgroundAuth;
|
|
18
|
+
private resendPendingEvents;
|
|
19
|
+
wait_for_auth(): Promise<void>;
|
|
20
|
+
authenticate(showInstructions?: boolean): Promise<boolean>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { createLogger } from '../logging-config.js';
|
|
3
|
+
import { CONFIG } from '../config.js';
|
|
4
|
+
import { DeviceAuthClient, TEMP_USER_ID } from './auth.js';
|
|
5
|
+
const logger = createLogger('browser_use.sync');
|
|
6
|
+
const stripTrailingSlash = (input) => input.replace(/\/+$/, '');
|
|
7
|
+
const ensureArray = (value) => Array.isArray(value) ? value : [value];
|
|
8
|
+
export class CloudSync {
|
|
9
|
+
baseUrl;
|
|
10
|
+
enableAuth;
|
|
11
|
+
auth_client;
|
|
12
|
+
pendingEvents = [];
|
|
13
|
+
authTask = null;
|
|
14
|
+
sessionId = null;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.baseUrl = stripTrailingSlash(options.baseUrl ?? CONFIG.BROWSER_USE_CLOUD_API_URL);
|
|
17
|
+
this.enableAuth = options.enableAuth ?? true;
|
|
18
|
+
this.auth_client = this.enableAuth
|
|
19
|
+
? new DeviceAuthClient(this.baseUrl)
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
async handle_event(event) {
|
|
23
|
+
try {
|
|
24
|
+
if (event.event_type === 'CreateAgentSessionEvent' &&
|
|
25
|
+
event?.id) {
|
|
26
|
+
this.sessionId = String(event.id);
|
|
27
|
+
}
|
|
28
|
+
if (event.event_type === 'CreateAgentStepEvent') {
|
|
29
|
+
const raw = event;
|
|
30
|
+
const step = raw?.step ?? raw?.payload?.step;
|
|
31
|
+
if (step === 2 &&
|
|
32
|
+
this.enableAuth &&
|
|
33
|
+
this.auth_client &&
|
|
34
|
+
!this.authTask) {
|
|
35
|
+
if (this.sessionId) {
|
|
36
|
+
this.authTask = this.backgroundAuth(this.sessionId);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
logger.warning('Cannot start cloud auth, session_id missing');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await this.sendEvent(event);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error(`Failed to handle ${event.event_type}: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async sendEvent(event) {
|
|
50
|
+
try {
|
|
51
|
+
const headers = {};
|
|
52
|
+
const authClient = this.auth_client;
|
|
53
|
+
const userId = authClient ? authClient.user_id : TEMP_USER_ID;
|
|
54
|
+
event.user_id = userId;
|
|
55
|
+
if (authClient) {
|
|
56
|
+
Object.assign(headers, authClient.get_headers());
|
|
57
|
+
event.device_id = authClient.device_id;
|
|
58
|
+
}
|
|
59
|
+
const payload = typeof event?.toJSON === 'function'
|
|
60
|
+
? event.toJSON()
|
|
61
|
+
: { ...event };
|
|
62
|
+
const events = ensureArray(payload);
|
|
63
|
+
await axios.post(`${this.baseUrl}/api/v1/events`, {
|
|
64
|
+
events: events.map((entry) => ({
|
|
65
|
+
...entry,
|
|
66
|
+
device_id: entry.device_id ?? authClient?.device_id,
|
|
67
|
+
})),
|
|
68
|
+
}, { headers, timeout: 10_000 });
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const status = error?.response?.status;
|
|
72
|
+
if (status === 401 &&
|
|
73
|
+
this.auth_client &&
|
|
74
|
+
!this.auth_client.is_authenticated) {
|
|
75
|
+
this.pendingEvents.push(event);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (status) {
|
|
79
|
+
logger.debug(`Cloud sync HTTP ${status}: ${error?.response?.data ?? error}`);
|
|
80
|
+
}
|
|
81
|
+
else if (error?.code === 'ECONNABORTED') {
|
|
82
|
+
logger.warning(`Cloud sync timeout sending ${event.event_type}`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
logger.warning(`Cloud sync error: ${error?.message ?? error}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async backgroundAuth(agentSessionId) {
|
|
90
|
+
const authClient = this.auth_client;
|
|
91
|
+
if (!authClient) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
if (authClient.is_authenticated) {
|
|
96
|
+
const frontend = CONFIG.BROWSER_USE_CLOUD_UI_URL ||
|
|
97
|
+
this.baseUrl.replace('//api.', '//cloud.');
|
|
98
|
+
const sessionUrl = `${stripTrailingSlash(frontend)}/agent/${agentSessionId}`;
|
|
99
|
+
const divider = '─'.repeat(Math.max((process.stdout?.columns ?? 80) - 40, 20));
|
|
100
|
+
logger.info(divider);
|
|
101
|
+
logger.info('🌐 View the details of this run in Browser Use Cloud:');
|
|
102
|
+
logger.info(` 👉 ${sessionUrl}`);
|
|
103
|
+
logger.info(divider + '\n');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const success = await authClient.authenticate(agentSessionId, true);
|
|
107
|
+
if (success) {
|
|
108
|
+
await this.resendPendingEvents();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.debug(`Cloud sync auth error: ${error.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async resendPendingEvents() {
|
|
116
|
+
if (!this.pendingEvents.length) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const events = [...this.pendingEvents];
|
|
120
|
+
this.pendingEvents = [];
|
|
121
|
+
for (const event of events) {
|
|
122
|
+
try {
|
|
123
|
+
await this.sendEvent(event);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
logger.warning(`Failed to resend event ${event.event_type}: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async wait_for_auth() {
|
|
131
|
+
if (this.authTask) {
|
|
132
|
+
try {
|
|
133
|
+
await this.authTask;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* ignore */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async authenticate(showInstructions = true) {
|
|
141
|
+
if (!this.auth_client) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return this.auth_client.authenticate(this.sessionId, showInstructions);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BaseTelemetryEvent } from './views.js';
|
|
2
|
+
export declare class ProductTelemetry {
|
|
3
|
+
private client;
|
|
4
|
+
private debugLogging;
|
|
5
|
+
private userIdFile;
|
|
6
|
+
private cachedUserId;
|
|
7
|
+
constructor();
|
|
8
|
+
capture(event: BaseTelemetryEvent): void;
|
|
9
|
+
flush(): void;
|
|
10
|
+
get userId(): string;
|
|
11
|
+
}
|
|
12
|
+
export declare const productTelemetry: ProductTelemetry;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { PostHog } from 'posthog-node';
|
|
4
|
+
import { createLogger } from '../logging-config.js';
|
|
5
|
+
import { CONFIG } from '../config.js';
|
|
6
|
+
import { uuid7str } from '../utils.js';
|
|
7
|
+
const logger = createLogger('browser_use.telemetry');
|
|
8
|
+
const POSTHOG_EVENT_SETTINGS = {
|
|
9
|
+
process_person_profile: true,
|
|
10
|
+
};
|
|
11
|
+
export class ProductTelemetry {
|
|
12
|
+
client = null;
|
|
13
|
+
debugLogging;
|
|
14
|
+
userIdFile;
|
|
15
|
+
cachedUserId = null;
|
|
16
|
+
constructor() {
|
|
17
|
+
this.debugLogging = CONFIG.BROWSER_USE_LOGGING_LEVEL === 'debug';
|
|
18
|
+
this.userIdFile = path.join(CONFIG.BROWSER_USE_CONFIG_DIR, 'device_id');
|
|
19
|
+
if (!CONFIG.ANONYMIZED_TELEMETRY) {
|
|
20
|
+
logger.debug('Telemetry disabled');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
this.client = new PostHog('phc_F8JMNjW1i2KbGUTaW1unnDdLSPCoyc52SGRU0JecaUh', {
|
|
25
|
+
host: 'https://eu.i.posthog.com',
|
|
26
|
+
disableGeoip: false,
|
|
27
|
+
enableExceptionAutocapture: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
logger.error(`Failed to initialize PostHog client: ${error.message}`);
|
|
32
|
+
this.client = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
capture(event) {
|
|
36
|
+
if (!this.client) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
this.client.capture({
|
|
41
|
+
distinctId: this.userId,
|
|
42
|
+
event: event.name,
|
|
43
|
+
properties: {
|
|
44
|
+
...event.properties(),
|
|
45
|
+
...POSTHOG_EVENT_SETTINGS,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
logger.error(`Failed to send telemetry event ${event.name}: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
flush() {
|
|
54
|
+
if (!this.client) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
this.client.flush();
|
|
59
|
+
logger.debug('PostHog client telemetry queue flushed.');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
logger.error(`Failed to flush PostHog client: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
get userId() {
|
|
66
|
+
if (this.cachedUserId) {
|
|
67
|
+
return this.cachedUserId;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
if (!fs.existsSync(this.userIdFile)) {
|
|
71
|
+
fs.mkdirSync(path.dirname(this.userIdFile), { recursive: true });
|
|
72
|
+
this.cachedUserId = uuid7str();
|
|
73
|
+
fs.writeFileSync(this.userIdFile, this.cachedUserId, 'utf-8');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this.cachedUserId = fs.readFileSync(this.userIdFile, 'utf-8');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
this.cachedUserId = 'UNKNOWN_USER_ID';
|
|
81
|
+
}
|
|
82
|
+
return this.cachedUserId;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export const productTelemetry = new ProductTelemetry();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export declare abstract class BaseTelemetryEvent {
|
|
2
|
+
abstract name: string;
|
|
3
|
+
properties(): Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
type BaseSequence = Array<string | null | undefined> | undefined;
|
|
6
|
+
export interface AgentTelemetryPayload {
|
|
7
|
+
task: string;
|
|
8
|
+
model: string;
|
|
9
|
+
model_provider: string;
|
|
10
|
+
planner_llm: string | null;
|
|
11
|
+
max_steps: number;
|
|
12
|
+
max_actions_per_step: number;
|
|
13
|
+
use_vision: boolean;
|
|
14
|
+
use_validation: boolean;
|
|
15
|
+
version: string;
|
|
16
|
+
source: string;
|
|
17
|
+
cdp_url: string | null;
|
|
18
|
+
action_errors: BaseSequence;
|
|
19
|
+
action_history: Array<Array<Record<string, unknown>> | null> | undefined;
|
|
20
|
+
urls_visited: BaseSequence;
|
|
21
|
+
steps: number;
|
|
22
|
+
total_input_tokens: number;
|
|
23
|
+
total_duration_seconds: number;
|
|
24
|
+
success: boolean | null;
|
|
25
|
+
final_result_response: string | null;
|
|
26
|
+
error_message: string | null;
|
|
27
|
+
}
|
|
28
|
+
export declare class AgentTelemetryEvent extends BaseTelemetryEvent implements AgentTelemetryPayload {
|
|
29
|
+
name: string;
|
|
30
|
+
task: string;
|
|
31
|
+
model: string;
|
|
32
|
+
model_provider: string;
|
|
33
|
+
planner_llm: string | null;
|
|
34
|
+
max_steps: number;
|
|
35
|
+
max_actions_per_step: number;
|
|
36
|
+
use_vision: boolean;
|
|
37
|
+
use_validation: boolean;
|
|
38
|
+
version: string;
|
|
39
|
+
source: string;
|
|
40
|
+
cdp_url: string | null;
|
|
41
|
+
action_errors: BaseSequence;
|
|
42
|
+
action_history: Array<Array<Record<string, unknown>> | null> | undefined;
|
|
43
|
+
urls_visited: BaseSequence;
|
|
44
|
+
steps: number;
|
|
45
|
+
total_input_tokens: number;
|
|
46
|
+
total_duration_seconds: number;
|
|
47
|
+
success: boolean | null;
|
|
48
|
+
final_result_response: string | null;
|
|
49
|
+
error_message: string | null;
|
|
50
|
+
constructor(payload: AgentTelemetryPayload);
|
|
51
|
+
}
|
|
52
|
+
export interface MCPClientTelemetryPayload {
|
|
53
|
+
server_name: string;
|
|
54
|
+
command: string;
|
|
55
|
+
tools_discovered: number;
|
|
56
|
+
version: string;
|
|
57
|
+
action: string;
|
|
58
|
+
tool_name?: string | null;
|
|
59
|
+
duration_seconds?: number | null;
|
|
60
|
+
error_message?: string | null;
|
|
61
|
+
}
|
|
62
|
+
export declare class MCPClientTelemetryEvent extends BaseTelemetryEvent implements MCPClientTelemetryPayload {
|
|
63
|
+
name: string;
|
|
64
|
+
server_name: string;
|
|
65
|
+
command: string;
|
|
66
|
+
tools_discovered: number;
|
|
67
|
+
version: string;
|
|
68
|
+
action: string;
|
|
69
|
+
tool_name: string | null;
|
|
70
|
+
duration_seconds: number | null;
|
|
71
|
+
error_message: string | null;
|
|
72
|
+
constructor(payload: MCPClientTelemetryPayload);
|
|
73
|
+
}
|
|
74
|
+
export interface MCPServerTelemetryPayload {
|
|
75
|
+
version: string;
|
|
76
|
+
action: string;
|
|
77
|
+
tool_name?: string | null;
|
|
78
|
+
duration_seconds?: number | null;
|
|
79
|
+
error_message?: string | null;
|
|
80
|
+
parent_process_cmdline?: string | null;
|
|
81
|
+
}
|
|
82
|
+
export declare class MCPServerTelemetryEvent extends BaseTelemetryEvent implements MCPServerTelemetryPayload {
|
|
83
|
+
name: string;
|
|
84
|
+
version: string;
|
|
85
|
+
action: string;
|
|
86
|
+
tool_name: string | null;
|
|
87
|
+
duration_seconds: number | null;
|
|
88
|
+
error_message: string | null;
|
|
89
|
+
parent_process_cmdline: string | null;
|
|
90
|
+
constructor(payload: MCPServerTelemetryPayload);
|
|
91
|
+
}
|
|
92
|
+
export interface CLITelemetryPayload {
|
|
93
|
+
version: string;
|
|
94
|
+
action: string;
|
|
95
|
+
mode: string;
|
|
96
|
+
model?: string | null;
|
|
97
|
+
model_provider?: string | null;
|
|
98
|
+
duration_seconds?: number | null;
|
|
99
|
+
error_message?: string | null;
|
|
100
|
+
}
|
|
101
|
+
export declare class CLITelemetryEvent extends BaseTelemetryEvent implements CLITelemetryPayload {
|
|
102
|
+
name: string;
|
|
103
|
+
version: string;
|
|
104
|
+
action: string;
|
|
105
|
+
mode: string;
|
|
106
|
+
model: string | null;
|
|
107
|
+
model_provider: string | null;
|
|
108
|
+
duration_seconds: number | null;
|
|
109
|
+
error_message: string | null;
|
|
110
|
+
constructor(payload: CLITelemetryPayload);
|
|
111
|
+
}
|
|
112
|
+
export {};
|