browser-use 0.2.0 → 0.4.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.
Files changed (259) hide show
  1. package/README.md +295 -686
  2. package/dist/actor/element.d.ts +19 -0
  3. package/dist/actor/element.js +46 -0
  4. package/dist/actor/index.d.ts +4 -0
  5. package/dist/actor/index.js +4 -0
  6. package/dist/actor/mouse.d.ts +19 -0
  7. package/dist/actor/mouse.js +39 -0
  8. package/dist/actor/page.d.ts +29 -0
  9. package/dist/actor/page.js +88 -0
  10. package/dist/actor/utils.d.ts +4 -0
  11. package/dist/actor/utils.js +35 -0
  12. package/dist/agent/cloud-events.d.ts +18 -0
  13. package/dist/agent/cloud-events.js +65 -2
  14. package/dist/agent/gif.d.ts +1 -0
  15. package/dist/agent/gif.js +24 -2
  16. package/dist/agent/judge.d.ts +17 -0
  17. package/dist/agent/judge.js +197 -0
  18. package/dist/agent/message-manager/service.d.ts +12 -4
  19. package/dist/agent/message-manager/service.js +205 -39
  20. package/dist/agent/message-manager/utils.js +0 -1
  21. package/dist/agent/message-manager/views.d.ts +4 -0
  22. package/dist/agent/message-manager/views.js +11 -7
  23. package/dist/agent/prompts.d.ts +24 -3
  24. package/dist/agent/prompts.js +274 -59
  25. package/dist/agent/service.d.ts +103 -41
  26. package/dist/agent/service.js +2336 -472
  27. package/dist/agent/variable-detector.d.ts +12 -0
  28. package/dist/agent/variable-detector.js +211 -0
  29. package/dist/agent/views.d.ts +237 -18
  30. package/dist/agent/views.js +446 -33
  31. package/dist/browser/cloud/cloud.d.ts +20 -0
  32. package/dist/browser/cloud/cloud.js +129 -0
  33. package/dist/browser/cloud/index.d.ts +2 -0
  34. package/dist/browser/cloud/index.js +2 -0
  35. package/dist/browser/cloud/views.d.ts +41 -0
  36. package/dist/browser/cloud/views.js +35 -0
  37. package/dist/browser/events.d.ts +345 -0
  38. package/dist/browser/events.js +566 -0
  39. package/dist/browser/extensions.js +17 -17
  40. package/dist/browser/index.d.ts +4 -0
  41. package/dist/browser/index.js +4 -0
  42. package/dist/browser/profile.d.ts +10 -4
  43. package/dist/browser/profile.js +79 -12
  44. package/dist/browser/session-manager.d.ts +85 -0
  45. package/dist/browser/session-manager.js +208 -0
  46. package/dist/browser/session.d.ts +105 -9
  47. package/dist/browser/session.js +1166 -95
  48. package/dist/browser/types.d.ts +153 -156
  49. package/dist/browser/views.d.ts +39 -0
  50. package/dist/browser/views.js +32 -0
  51. package/dist/browser/watchdogs/aboutblank-watchdog.d.ts +12 -0
  52. package/dist/browser/watchdogs/aboutblank-watchdog.js +131 -0
  53. package/dist/browser/watchdogs/base.d.ts +21 -0
  54. package/dist/browser/watchdogs/base.js +81 -0
  55. package/dist/browser/watchdogs/cdp-session-watchdog.d.ts +14 -0
  56. package/dist/browser/watchdogs/cdp-session-watchdog.js +177 -0
  57. package/dist/browser/watchdogs/crash-watchdog.d.ts +38 -0
  58. package/dist/browser/watchdogs/crash-watchdog.js +296 -0
  59. package/dist/browser/watchdogs/default-action-watchdog.d.ts +49 -0
  60. package/dist/browser/watchdogs/default-action-watchdog.js +212 -0
  61. package/dist/browser/watchdogs/dom-watchdog.d.ts +8 -0
  62. package/dist/browser/watchdogs/dom-watchdog.js +31 -0
  63. package/dist/browser/watchdogs/downloads-watchdog.d.ts +77 -0
  64. package/dist/browser/watchdogs/downloads-watchdog.js +409 -0
  65. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +19 -0
  66. package/dist/browser/watchdogs/har-recording-watchdog.js +317 -0
  67. package/dist/browser/watchdogs/index.d.ts +15 -0
  68. package/dist/browser/watchdogs/index.js +15 -0
  69. package/dist/browser/watchdogs/local-browser-watchdog.d.ts +10 -0
  70. package/dist/browser/watchdogs/local-browser-watchdog.js +32 -0
  71. package/dist/browser/watchdogs/permissions-watchdog.d.ts +8 -0
  72. package/dist/browser/watchdogs/permissions-watchdog.js +73 -0
  73. package/dist/browser/watchdogs/popups-watchdog.d.ts +13 -0
  74. package/dist/browser/watchdogs/popups-watchdog.js +77 -0
  75. package/dist/browser/watchdogs/recording-watchdog.d.ts +27 -0
  76. package/dist/browser/watchdogs/recording-watchdog.js +249 -0
  77. package/dist/browser/watchdogs/screenshot-watchdog.d.ts +6 -0
  78. package/dist/browser/watchdogs/screenshot-watchdog.js +13 -0
  79. package/dist/browser/watchdogs/security-watchdog.d.ts +10 -0
  80. package/dist/browser/watchdogs/security-watchdog.js +84 -0
  81. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +24 -0
  82. package/dist/browser/watchdogs/storage-state-watchdog.js +288 -0
  83. package/dist/cli.d.ts +7 -2
  84. package/dist/cli.js +182 -25
  85. package/dist/code-use/formatting.d.ts +3 -0
  86. package/dist/code-use/formatting.js +18 -0
  87. package/dist/code-use/index.d.ts +6 -0
  88. package/dist/code-use/index.js +6 -0
  89. package/dist/code-use/namespace.d.ts +5 -0
  90. package/dist/code-use/namespace.js +81 -0
  91. package/dist/code-use/notebook-export.d.ts +3 -0
  92. package/dist/code-use/notebook-export.js +56 -0
  93. package/dist/code-use/service.d.ts +24 -0
  94. package/dist/code-use/service.js +104 -0
  95. package/dist/code-use/utils.d.ts +4 -0
  96. package/dist/code-use/utils.js +98 -0
  97. package/dist/code-use/views.d.ts +108 -0
  98. package/dist/code-use/views.js +165 -0
  99. package/dist/config.d.ts +15 -0
  100. package/dist/config.js +109 -7
  101. package/dist/controller/registry/service.d.ts +10 -1
  102. package/dist/controller/registry/service.js +266 -10
  103. package/dist/controller/registry/views.d.ts +4 -1
  104. package/dist/controller/registry/views.js +25 -2
  105. package/dist/controller/service.d.ts +10 -1
  106. package/dist/controller/service.js +1814 -268
  107. package/dist/controller/views.d.ts +78 -155
  108. package/dist/controller/views.js +61 -12
  109. package/dist/dom/history-tree-processor/service.d.ts +5 -0
  110. package/dist/dom/history-tree-processor/service.js +169 -14
  111. package/dist/dom/history-tree-processor/view.d.ts +7 -1
  112. package/dist/dom/history-tree-processor/view.js +10 -1
  113. package/dist/dom/markdown-extractor.d.ts +37 -0
  114. package/dist/dom/markdown-extractor.js +345 -0
  115. package/dist/dom/service.d.ts +3 -1
  116. package/dist/dom/service.js +76 -0
  117. package/dist/dom/views.d.ts +1 -0
  118. package/dist/dom/views.js +45 -0
  119. package/dist/event-bus.d.ts +107 -7
  120. package/dist/event-bus.js +313 -10
  121. package/dist/exceptions.d.ts +0 -3
  122. package/dist/exceptions.js +0 -7
  123. package/dist/filesystem/file-system.d.ts +18 -0
  124. package/dist/filesystem/file-system.js +503 -42
  125. package/dist/index.d.ts +7 -0
  126. package/dist/index.js +6 -0
  127. package/dist/integrations/gmail/actions.d.ts +3 -3
  128. package/dist/integrations/gmail/actions.js +4 -4
  129. package/dist/llm/anthropic/chat.d.ts +18 -1
  130. package/dist/llm/anthropic/chat.js +123 -55
  131. package/dist/llm/anthropic/serializer.d.ts +2 -0
  132. package/dist/llm/anthropic/serializer.js +81 -9
  133. package/dist/llm/aws/chat-anthropic.d.ts +17 -0
  134. package/dist/llm/aws/chat-anthropic.js +126 -26
  135. package/dist/llm/aws/chat-bedrock.d.ts +28 -1
  136. package/dist/llm/aws/chat-bedrock.js +161 -34
  137. package/dist/llm/aws/serializer.d.ts +13 -1
  138. package/dist/llm/aws/serializer.js +56 -17
  139. package/dist/llm/azure/chat.d.ts +53 -2
  140. package/dist/llm/azure/chat.js +366 -54
  141. package/dist/llm/base.d.ts +2 -0
  142. package/dist/llm/browser-use/chat.d.ts +40 -0
  143. package/dist/llm/browser-use/chat.js +305 -0
  144. package/dist/llm/browser-use/index.d.ts +1 -0
  145. package/dist/llm/browser-use/index.js +1 -0
  146. package/dist/llm/cerebras/chat.d.ts +39 -0
  147. package/dist/llm/cerebras/chat.js +178 -0
  148. package/dist/llm/cerebras/index.d.ts +2 -0
  149. package/dist/llm/cerebras/index.js +2 -0
  150. package/dist/llm/cerebras/serializer.d.ts +7 -0
  151. package/dist/llm/cerebras/serializer.js +82 -0
  152. package/dist/llm/deepseek/chat.d.ts +19 -2
  153. package/dist/llm/deepseek/chat.js +138 -25
  154. package/dist/llm/google/chat.d.ts +46 -2
  155. package/dist/llm/google/chat.js +267 -64
  156. package/dist/llm/google/serializer.d.ts +9 -1
  157. package/dist/llm/google/serializer.js +141 -34
  158. package/dist/llm/groq/chat.d.ts +21 -2
  159. package/dist/llm/groq/chat.js +125 -26
  160. package/dist/llm/groq/parser.js +3 -1
  161. package/dist/llm/mistral/chat.d.ts +43 -0
  162. package/dist/llm/mistral/chat.js +154 -0
  163. package/dist/llm/mistral/index.d.ts +2 -0
  164. package/dist/llm/mistral/index.js +2 -0
  165. package/dist/llm/mistral/schema.d.ts +8 -0
  166. package/dist/llm/mistral/schema.js +27 -0
  167. package/dist/llm/models.d.ts +2 -0
  168. package/dist/llm/models.js +317 -0
  169. package/dist/llm/ollama/chat.d.ts +13 -1
  170. package/dist/llm/ollama/chat.js +110 -19
  171. package/dist/llm/ollama/serializer.d.ts +1 -0
  172. package/dist/llm/ollama/serializer.js +34 -12
  173. package/dist/llm/openai/chat.d.ts +16 -0
  174. package/dist/llm/openai/chat.js +94 -44
  175. package/dist/llm/openai/like.d.ts +5 -3
  176. package/dist/llm/openai/like.js +7 -3
  177. package/dist/llm/openai/responses-serializer.d.ts +18 -0
  178. package/dist/llm/openai/responses-serializer.js +72 -0
  179. package/dist/llm/openrouter/chat.d.ts +28 -2
  180. package/dist/llm/openrouter/chat.js +115 -29
  181. package/dist/llm/schema.d.ts +11 -1
  182. package/dist/llm/schema.js +109 -4
  183. package/dist/llm/vercel/chat.d.ts +50 -0
  184. package/dist/llm/vercel/chat.js +276 -0
  185. package/dist/llm/vercel/index.d.ts +1 -0
  186. package/dist/llm/vercel/index.js +1 -0
  187. package/dist/llm/vercel/serializer.d.ts +5 -0
  188. package/dist/llm/vercel/serializer.js +7 -0
  189. package/dist/llm/views.d.ts +2 -1
  190. package/dist/llm/views.js +3 -1
  191. package/dist/logging-config.d.ts +2 -0
  192. package/dist/logging-config.js +82 -29
  193. package/dist/mcp/client.d.ts +10 -5
  194. package/dist/mcp/client.js +14 -9
  195. package/dist/mcp/controller.d.ts +42 -3
  196. package/dist/mcp/controller.js +56 -31
  197. package/dist/mcp/server.d.ts +15 -0
  198. package/dist/mcp/server.js +261 -52
  199. package/dist/observability.js +10 -4
  200. package/dist/sandbox/index.d.ts +2 -0
  201. package/dist/sandbox/index.js +2 -0
  202. package/dist/sandbox/sandbox.d.ts +19 -0
  203. package/dist/sandbox/sandbox.js +140 -0
  204. package/dist/sandbox/views.d.ts +67 -0
  205. package/dist/sandbox/views.js +121 -0
  206. package/dist/skill-cli/index.d.ts +3 -0
  207. package/dist/skill-cli/index.js +3 -0
  208. package/dist/skill-cli/protocol.d.ts +30 -0
  209. package/dist/skill-cli/protocol.js +48 -0
  210. package/dist/skill-cli/server.d.ts +11 -0
  211. package/dist/skill-cli/server.js +85 -0
  212. package/dist/skill-cli/sessions.d.ts +24 -0
  213. package/dist/skill-cli/sessions.js +47 -0
  214. package/dist/skills/index.d.ts +3 -0
  215. package/dist/skills/index.js +3 -0
  216. package/dist/skills/service.d.ts +27 -0
  217. package/dist/skills/service.js +266 -0
  218. package/dist/skills/utils.d.ts +6 -0
  219. package/dist/skills/utils.js +53 -0
  220. package/dist/skills/views.d.ts +40 -0
  221. package/dist/skills/views.js +10 -0
  222. package/dist/sync/auth.js +8 -3
  223. package/dist/sync/service.d.ts +6 -6
  224. package/dist/sync/service.js +54 -89
  225. package/dist/telemetry/views.d.ts +20 -6
  226. package/dist/telemetry/views.js +23 -5
  227. package/dist/tokens/custom-pricing.d.ts +2 -0
  228. package/dist/tokens/custom-pricing.js +22 -0
  229. package/dist/tokens/index.d.ts +2 -0
  230. package/dist/tokens/index.js +2 -0
  231. package/dist/tokens/mappings.d.ts +1 -0
  232. package/dist/tokens/mappings.js +3 -0
  233. package/dist/tokens/service.js +27 -8
  234. package/dist/tools/extraction/index.d.ts +2 -0
  235. package/dist/tools/extraction/index.js +2 -0
  236. package/dist/tools/extraction/schema-utils.d.ts +6 -0
  237. package/dist/tools/extraction/schema-utils.js +237 -0
  238. package/dist/tools/extraction/views.d.ts +7 -0
  239. package/dist/tools/index.d.ts +5 -0
  240. package/dist/tools/index.js +5 -0
  241. package/dist/tools/registry/index.d.ts +2 -0
  242. package/dist/tools/registry/index.js +2 -0
  243. package/dist/tools/registry/service.d.ts +1 -0
  244. package/dist/tools/registry/service.js +1 -0
  245. package/dist/tools/registry/views.d.ts +1 -0
  246. package/dist/tools/registry/views.js +1 -0
  247. package/dist/tools/service.d.ts +2 -0
  248. package/dist/tools/service.js +1 -0
  249. package/dist/tools/utils.d.ts +2 -0
  250. package/dist/tools/utils.js +57 -0
  251. package/dist/tools/views.d.ts +1 -0
  252. package/dist/tools/views.js +1 -0
  253. package/dist/utils.d.ts +10 -1
  254. package/dist/utils.js +70 -3
  255. package/package.json +116 -49
  256. package/dist/dom/playground/process-dom.js +0 -5
  257. package/dist/dom/playground/test-accessibility.d.ts +0 -44
  258. package/dist/dom/playground/test-accessibility.js +0 -111
  259. /package/dist/{dom/playground/process-dom.d.ts → tools/extraction/views.js} +0 -0
package/dist/config.js CHANGED
@@ -92,6 +92,12 @@ class OldConfig {
92
92
  }
93
93
  return url;
94
94
  }
95
+ get BROWSER_USE_DEBUG_LOG_FILE() {
96
+ return process.env.BROWSER_USE_DEBUG_LOG_FILE ?? null;
97
+ }
98
+ get BROWSER_USE_INFO_LOG_FILE() {
99
+ return process.env.BROWSER_USE_INFO_LOG_FILE ?? null;
100
+ }
95
101
  get XDG_CACHE_HOME() {
96
102
  return resolve_path(process.env.XDG_CACHE_HOME ?? '~/.cache');
97
103
  }
@@ -134,8 +140,11 @@ class OldConfig {
134
140
  get DEEPSEEK_API_KEY() {
135
141
  return process.env.DEEPSEEK_API_KEY ?? '';
136
142
  }
143
+ get GROQ_API_KEY() {
144
+ return process.env.GROQ_API_KEY ?? process.env.GROK_API_KEY ?? '';
145
+ }
137
146
  get GROK_API_KEY() {
138
- return process.env.GROK_API_KEY ?? '';
147
+ return this.GROQ_API_KEY;
139
148
  }
140
149
  get NOVITA_API_KEY() {
141
150
  return process.env.NOVITA_API_KEY ?? '';
@@ -149,12 +158,18 @@ class OldConfig {
149
158
  get SKIP_LLM_API_KEY_VERIFICATION() {
150
159
  return string_to_bool(process.env.SKIP_LLM_API_KEY_VERIFICATION, false);
151
160
  }
161
+ get DEFAULT_LLM() {
162
+ return process.env.DEFAULT_LLM ?? '';
163
+ }
152
164
  get IN_DOCKER() {
153
165
  return (string_to_bool(process.env.IN_DOCKER, false) || is_running_in_docker());
154
166
  }
155
167
  get IS_IN_EVALS() {
156
168
  return string_to_bool(process.env.IS_IN_EVALS, false);
157
169
  }
170
+ get BROWSER_USE_VERSION_CHECK() {
171
+ return string_to_bool(process.env.BROWSER_USE_VERSION_CHECK, true);
172
+ }
158
173
  get WIN_FONT_DIR() {
159
174
  return process.env.WIN_FONT_DIR ?? 'C:\\Windows\\Fonts';
160
175
  }
@@ -188,6 +203,12 @@ class FlatEnvConfig {
188
203
  get BROWSER_USE_CLOUD_UI_URL() {
189
204
  return process.env.BROWSER_USE_CLOUD_UI_URL ?? '';
190
205
  }
206
+ get BROWSER_USE_DEBUG_LOG_FILE() {
207
+ return process.env.BROWSER_USE_DEBUG_LOG_FILE ?? null;
208
+ }
209
+ get BROWSER_USE_INFO_LOG_FILE() {
210
+ return process.env.BROWSER_USE_INFO_LOG_FILE ?? null;
211
+ }
191
212
  get XDG_CACHE_HOME() {
192
213
  return resolve_path(process.env.XDG_CACHE_HOME ?? '~/.cache');
193
214
  }
@@ -211,8 +232,11 @@ class FlatEnvConfig {
211
232
  get DEEPSEEK_API_KEY() {
212
233
  return process.env.DEEPSEEK_API_KEY ?? '';
213
234
  }
235
+ get GROQ_API_KEY() {
236
+ return process.env.GROQ_API_KEY ?? process.env.GROK_API_KEY ?? '';
237
+ }
214
238
  get GROK_API_KEY() {
215
- return process.env.GROK_API_KEY ?? '';
239
+ return this.GROQ_API_KEY;
216
240
  }
217
241
  get NOVITA_API_KEY() {
218
242
  return process.env.NOVITA_API_KEY ?? '';
@@ -226,6 +250,9 @@ class FlatEnvConfig {
226
250
  get SKIP_LLM_API_KEY_VERIFICATION() {
227
251
  return string_to_bool(process.env.SKIP_LLM_API_KEY_VERIFICATION, false);
228
252
  }
253
+ get DEFAULT_LLM() {
254
+ return process.env.DEFAULT_LLM ?? '';
255
+ }
229
256
  get IN_DOCKER() {
230
257
  const value = process.env.IN_DOCKER;
231
258
  return value === undefined ? null : string_to_bool(value);
@@ -233,6 +260,9 @@ class FlatEnvConfig {
233
260
  get IS_IN_EVALS() {
234
261
  return string_to_bool(process.env.IS_IN_EVALS, false);
235
262
  }
263
+ get BROWSER_USE_VERSION_CHECK() {
264
+ return string_to_bool(process.env.BROWSER_USE_VERSION_CHECK, true);
265
+ }
236
266
  get WIN_FONT_DIR() {
237
267
  return process.env.WIN_FONT_DIR ?? 'C:\\Windows\\Fonts';
238
268
  }
@@ -251,9 +281,25 @@ class FlatEnvConfig {
251
281
  get BROWSER_USE_LLM_MODEL() {
252
282
  return process.env.BROWSER_USE_LLM_MODEL ?? null;
253
283
  }
284
+ get BROWSER_USE_PROXY_URL() {
285
+ return process.env.BROWSER_USE_PROXY_URL ?? null;
286
+ }
287
+ get BROWSER_USE_NO_PROXY() {
288
+ return process.env.BROWSER_USE_NO_PROXY ?? null;
289
+ }
290
+ get BROWSER_USE_PROXY_USERNAME() {
291
+ return process.env.BROWSER_USE_PROXY_USERNAME ?? null;
292
+ }
293
+ get BROWSER_USE_PROXY_PASSWORD() {
294
+ return process.env.BROWSER_USE_PROXY_PASSWORD ?? null;
295
+ }
296
+ get BROWSER_USE_DISABLE_EXTENSIONS() {
297
+ const value = process.env.BROWSER_USE_DISABLE_EXTENSIONS;
298
+ return value === undefined ? null : string_to_bool(value);
299
+ }
254
300
  }
255
301
  const create_default_config = () => {
256
- logger.info('Creating fresh default config.json');
302
+ logger.debug('Creating fresh default config.json');
257
303
  const profile_id = randomUUID();
258
304
  const llm_id = randomUUID();
259
305
  const agent_id = randomUUID();
@@ -274,8 +320,8 @@ const create_default_config = () => {
274
320
  id: llm_id,
275
321
  default: true,
276
322
  created_at: new Date().toISOString(),
277
- model: 'gpt-4o',
278
- api_key: 'your-openai-api-key-here',
323
+ model: 'gpt-4.1-mini',
324
+ api_key: null,
279
325
  temperature: null,
280
326
  max_tokens: null,
281
327
  },
@@ -292,6 +338,36 @@ const create_default_config = () => {
292
338
  },
293
339
  };
294
340
  };
341
+ const OPENAI_API_KEY_PLACEHOLDERS = new Set([
342
+ 'your-openai-api-key-here',
343
+ 'your-openai-api-key',
344
+ ]);
345
+ const sanitize_llm_api_key = (apiKey) => {
346
+ if (typeof apiKey !== 'string') {
347
+ return null;
348
+ }
349
+ const trimmed = apiKey.trim();
350
+ if (!trimmed) {
351
+ return null;
352
+ }
353
+ if (OPENAI_API_KEY_PLACEHOLDERS.has(trimmed.toLowerCase())) {
354
+ return null;
355
+ }
356
+ return trimmed;
357
+ };
358
+ const sanitize_db_config = (config) => {
359
+ const sanitizedLlmEntries = Object.fromEntries(Object.entries(config.llm ?? {}).map(([id, entry]) => [
360
+ id,
361
+ {
362
+ ...entry,
363
+ api_key: sanitize_llm_api_key(entry.api_key),
364
+ },
365
+ ]));
366
+ return {
367
+ ...config,
368
+ llm: sanitizedLlmEntries,
369
+ };
370
+ };
295
371
  const looks_like_new_format = (data) => data &&
296
372
  typeof data === 'object' &&
297
373
  ['browser_profile', 'llm', 'agent'].every((key) => typeof data[key] === 'object') &&
@@ -307,9 +383,9 @@ const load_and_migrate_config = (config_path) => {
307
383
  try {
308
384
  const raw = JSON.parse(fs.readFileSync(config_path, 'utf-8'));
309
385
  if (looks_like_new_format(raw)) {
310
- return raw;
386
+ return sanitize_db_config(raw);
311
387
  }
312
- logger.info(`Old config format detected at ${config_path}, creating fresh config`);
388
+ logger.debug(`Old config format detected at ${config_path}, creating fresh config`);
313
389
  const fresh = create_default_config();
314
390
  fs.writeFileSync(config_path, JSON.stringify(fresh, null, 2), 'utf-8');
315
391
  return fresh;
@@ -374,12 +450,38 @@ class ConfigCore {
374
450
  .map((domain) => domain.trim())
375
451
  .filter(Boolean);
376
452
  }
453
+ const proxy = {};
454
+ if (env.BROWSER_USE_PROXY_URL) {
455
+ proxy.server = env.BROWSER_USE_PROXY_URL;
456
+ }
457
+ if (env.BROWSER_USE_NO_PROXY) {
458
+ proxy.bypass = env.BROWSER_USE_NO_PROXY.split(',')
459
+ .map((domain) => domain.trim())
460
+ .filter(Boolean)
461
+ .join(',');
462
+ }
463
+ if (env.BROWSER_USE_PROXY_USERNAME) {
464
+ proxy.username = env.BROWSER_USE_PROXY_USERNAME;
465
+ }
466
+ if (env.BROWSER_USE_PROXY_PASSWORD) {
467
+ proxy.password = env.BROWSER_USE_PROXY_PASSWORD;
468
+ }
469
+ if (Object.keys(proxy).length > 0) {
470
+ config.browser_profile.proxy = proxy;
471
+ }
377
472
  if (env.OPENAI_API_KEY) {
378
473
  config.llm.api_key = env.OPENAI_API_KEY;
379
474
  }
475
+ if (env.DEFAULT_LLM) {
476
+ config.llm.model = env.DEFAULT_LLM;
477
+ }
380
478
  if (env.BROWSER_USE_LLM_MODEL) {
381
479
  config.llm.model = env.BROWSER_USE_LLM_MODEL;
382
480
  }
481
+ if (env.BROWSER_USE_DISABLE_EXTENSIONS !== null) {
482
+ config.browser_profile.enable_default_extensions =
483
+ !env.BROWSER_USE_DISABLE_EXTENSIONS;
484
+ }
383
485
  return config;
384
486
  }
385
487
  _ensure_dirs() {
@@ -10,7 +10,12 @@ export interface SensitiveDataMap {
10
10
  export interface ExecuteActionContext<Context> {
11
11
  context?: Context;
12
12
  browser_session?: BrowserSession | null;
13
+ browser?: BrowserSession | null;
14
+ browser_context?: BrowserSession | null;
15
+ page_url?: string | null;
16
+ cdp_client?: unknown;
13
17
  page_extraction_llm?: BaseChatModel | null;
18
+ extraction_schema?: Record<string, unknown> | null;
14
19
  file_system?: FileSystem | null;
15
20
  available_file_paths?: string[] | null;
16
21
  sensitive_data?: SensitiveDataMap | null;
@@ -22,16 +27,20 @@ export type RegistryActionHandler<Params = any, Context = unknown> = (params: Pa
22
27
  }) => Promise<unknown> | unknown;
23
28
  export interface ActionOptions {
24
29
  param_model?: ZodTypeAny;
30
+ action_name?: string;
25
31
  domains?: string[] | null;
26
32
  allowed_domains?: string[] | null;
27
33
  page_filter?: ((page: Page) => boolean) | null;
34
+ terminates_sequence?: boolean;
28
35
  }
29
36
  export declare class Registry<Context = unknown> {
30
37
  private registry;
31
38
  private excludeActions;
32
39
  constructor(exclude_actions?: string[] | null);
33
- action(description: string, options?: ActionOptions): <Params = any>(handler: RegistryActionHandler<Params, Context>) => RegistryActionHandler<Params, Context>;
40
+ action(description: string, options?: ActionOptions): <Params = any>(handler: RegistryActionHandler<Params, Context>) => any;
34
41
  get_action(action_name: string): RegisteredAction | null;
42
+ exclude_action(action_name: string): void;
43
+ remove_action(action_name: string): void;
35
44
  get_all_actions(): Map<string, RegisteredAction>;
36
45
  execute_action: (...args: any[]) => any;
37
46
  private replace_sensitive_data;
@@ -1,10 +1,145 @@
1
1
  import { z } from 'zod';
2
+ import { createHmac } from 'node:crypto';
2
3
  import { createLogger } from '../../logging-config.js';
3
4
  import { observe_debug } from '../../observability.js';
4
5
  import { time_execution_async } from '../../utils.js';
5
6
  import { is_new_tab_page, match_url_with_domain_pattern } from '../../utils.js';
7
+ import { BrowserError } from '../../browser/views.js';
6
8
  import { ActionModel, ActionRegistry, RegisteredAction } from './views.js';
7
9
  const logger = createLogger('browser_use.controller.registry');
10
+ const SPECIAL_PARAM_NAMES = new Set([
11
+ 'context',
12
+ 'browser_session',
13
+ 'browser',
14
+ 'browser_context',
15
+ 'page',
16
+ 'page_url',
17
+ 'cdp_client',
18
+ 'page_extraction_llm',
19
+ 'available_file_paths',
20
+ 'has_sensitive_data',
21
+ 'file_system',
22
+ 'extraction_schema',
23
+ 'sensitive_data',
24
+ 'signal',
25
+ ]);
26
+ const splitTopLevelParameters = (paramsSource) => {
27
+ const segments = [];
28
+ let current = '';
29
+ let depthParen = 0;
30
+ let depthBrace = 0;
31
+ let depthBracket = 0;
32
+ let quote = null;
33
+ let escaped = false;
34
+ const flush = () => {
35
+ const trimmed = current.trim();
36
+ if (trimmed) {
37
+ segments.push(trimmed);
38
+ }
39
+ current = '';
40
+ };
41
+ for (const char of paramsSource) {
42
+ if (escaped) {
43
+ current += char;
44
+ escaped = false;
45
+ continue;
46
+ }
47
+ if (char === '\\') {
48
+ current += char;
49
+ escaped = true;
50
+ continue;
51
+ }
52
+ if (quote) {
53
+ current += char;
54
+ if (char === quote) {
55
+ quote = null;
56
+ }
57
+ continue;
58
+ }
59
+ if (char === "'" || char === '"' || char === '`') {
60
+ current += char;
61
+ quote = char;
62
+ continue;
63
+ }
64
+ if (char === '(')
65
+ depthParen += 1;
66
+ else if (char === ')')
67
+ depthParen -= 1;
68
+ else if (char === '{')
69
+ depthBrace += 1;
70
+ else if (char === '}')
71
+ depthBrace -= 1;
72
+ else if (char === '[')
73
+ depthBracket += 1;
74
+ else if (char === ']')
75
+ depthBracket -= 1;
76
+ if (char === ',' &&
77
+ depthParen === 0 &&
78
+ depthBrace === 0 &&
79
+ depthBracket === 0) {
80
+ flush();
81
+ continue;
82
+ }
83
+ current += char;
84
+ }
85
+ flush();
86
+ return segments;
87
+ };
88
+ const extractFunctionParameters = (fn) => {
89
+ const source = fn.toString().trim();
90
+ if (!source) {
91
+ return null;
92
+ }
93
+ let paramsSource;
94
+ const arrowIndex = source.indexOf('=>');
95
+ if (arrowIndex !== -1) {
96
+ let lhs = source.slice(0, arrowIndex).trim();
97
+ if (lhs.startsWith('async ')) {
98
+ lhs = lhs.slice('async '.length).trim();
99
+ }
100
+ if (lhs.startsWith('(') && lhs.endsWith(')')) {
101
+ paramsSource = lhs.slice(1, -1);
102
+ }
103
+ else {
104
+ paramsSource = lhs;
105
+ }
106
+ }
107
+ else {
108
+ const openIndex = source.indexOf('(');
109
+ const closeIndex = source.indexOf(')', openIndex + 1);
110
+ if (openIndex === -1 || closeIndex === -1) {
111
+ return null;
112
+ }
113
+ paramsSource = source.slice(openIndex + 1, closeIndex);
114
+ }
115
+ const tokens = splitTopLevelParameters(paramsSource);
116
+ if (!tokens.length) {
117
+ return [];
118
+ }
119
+ const parsed = [];
120
+ for (const token of tokens) {
121
+ if (token.startsWith('...')) {
122
+ const fnName = fn.name || '<anonymous>';
123
+ throw new Error(`Action '${fnName}' has ${token} which is not allowed. Actions must have explicit positional parameters only.`);
124
+ }
125
+ if (token.includes('{') ||
126
+ token.includes('}') ||
127
+ token.includes('[') ||
128
+ token.includes(']')) {
129
+ return null;
130
+ }
131
+ const eqIndex = token.indexOf('=');
132
+ const name = (eqIndex === -1 ? token : token.slice(0, eqIndex)).trim();
133
+ if (!name) {
134
+ return null;
135
+ }
136
+ parsed.push({
137
+ name,
138
+ hasDefault: eqIndex !== -1,
139
+ });
140
+ }
141
+ return parsed;
142
+ };
8
143
  const isAbortError = (error) => error instanceof Error && error.name === 'AbortError';
9
144
  const createAbortError = (reason) => {
10
145
  if (isAbortError(reason)) {
@@ -19,6 +154,9 @@ const createAbortError = (reason) => {
19
154
  return error;
20
155
  };
21
156
  const wrapActionExecutionError = (actionName, error) => {
157
+ if (error instanceof BrowserError) {
158
+ return error;
159
+ }
22
160
  const message = error instanceof Error ? error.message : String(error);
23
161
  const wrapped = new Error(`Error executing action ${actionName}: ${message}`);
24
162
  if (error !== undefined) {
@@ -26,6 +164,61 @@ const wrapActionExecutionError = (actionName, error) => {
26
164
  }
27
165
  return wrapped;
28
166
  };
167
+ const isSpecialContextMissingError = (error) => {
168
+ if (!(error instanceof Error)) {
169
+ return false;
170
+ }
171
+ return (error.message.includes('requires browser_session but none provided') ||
172
+ error.message.includes('requires page_extraction_llm but none provided'));
173
+ };
174
+ const safeJsonStringify = (value) => {
175
+ try {
176
+ return JSON.stringify(value);
177
+ }
178
+ catch {
179
+ return String(value);
180
+ }
181
+ };
182
+ const isTimeoutError = (error) => error instanceof Error && error.name === 'TimeoutError';
183
+ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
184
+ const decodeBase32Secret = (secret) => {
185
+ const sanitized = secret.toUpperCase().replace(/[^A-Z2-7]/g, '');
186
+ if (!sanitized.length) {
187
+ throw new Error('Invalid TOTP secret: empty base32 payload');
188
+ }
189
+ let bits = 0;
190
+ let bitBuffer = 0;
191
+ const bytes = [];
192
+ for (const char of sanitized) {
193
+ const value = BASE32_ALPHABET.indexOf(char);
194
+ if (value < 0) {
195
+ throw new Error(`Invalid base32 character in TOTP secret: ${char}`);
196
+ }
197
+ bitBuffer = (bitBuffer << 5) | value;
198
+ bits += 5;
199
+ while (bits >= 8) {
200
+ bytes.push((bitBuffer >>> (bits - 8)) & 0xff);
201
+ bits -= 8;
202
+ }
203
+ }
204
+ if (!bytes.length) {
205
+ throw new Error('Invalid TOTP secret: failed to decode base32 payload');
206
+ }
207
+ return Buffer.from(bytes);
208
+ };
209
+ const generateTotpCode = (secret) => {
210
+ const key = decodeBase32Secret(secret);
211
+ const counter = Math.floor(Date.now() / 1000 / 30);
212
+ const counterBuffer = Buffer.alloc(8);
213
+ counterBuffer.writeBigUInt64BE(BigInt(counter));
214
+ const hmac = createHmac('sha1', key).update(counterBuffer).digest();
215
+ const offset = hmac[hmac.length - 1] & 0x0f;
216
+ const binaryCode = ((hmac[offset] & 0x7f) << 24) |
217
+ ((hmac[offset + 1] & 0xff) << 16) |
218
+ ((hmac[offset + 2] & 0xff) << 8) |
219
+ (hmac[offset + 3] & 0xff);
220
+ return String(binaryCode % 1_000_000).padStart(6, '0');
221
+ };
29
222
  export class Registry {
30
223
  registry = new ActionRegistry();
31
224
  excludeActions;
@@ -33,21 +226,68 @@ export class Registry {
33
226
  this.excludeActions = new Set(exclude_actions ?? []);
34
227
  }
35
228
  action(description, options = {}) {
36
- const schema = options.param_model ?? z.object({}).strict();
229
+ if (options.allowed_domains && options.domains) {
230
+ throw new Error("Cannot specify both 'domains' and 'allowed_domains' - they are aliases for the same parameter");
231
+ }
232
+ let schema = options.param_model ?? z.object({}).strict();
233
+ const actionNameOverride = options.action_name ?? null;
37
234
  const domains = options.allowed_domains ?? options.domains ?? null;
38
235
  const pageFilter = options.page_filter ?? null;
236
+ const terminatesSequence = options.terminates_sequence ?? false;
39
237
  return (handler) => {
40
- if (this.excludeActions.has(handler.name)) {
238
+ const actionName = actionNameOverride ?? handler.name;
239
+ if (this.excludeActions.has(actionName)) {
41
240
  return handler;
42
241
  }
43
- const action = new RegisteredAction(handler.name, description, handler, schema, domains, pageFilter);
242
+ const parsedHandlerParams = extractFunctionParameters(handler);
243
+ let normalizedHandler = handler;
244
+ if (!options.param_model) {
245
+ const supportsCompatSignature = Boolean(parsedHandlerParams &&
246
+ parsedHandlerParams.length > 0 &&
247
+ !(parsedHandlerParams.length <= 2 &&
248
+ parsedHandlerParams[0]?.name === 'params'));
249
+ if (supportsCompatSignature && parsedHandlerParams) {
250
+ const actionParams = parsedHandlerParams.filter((entry) => !SPECIAL_PARAM_NAMES.has(entry.name));
251
+ const shape = Object.fromEntries(actionParams.map((entry) => [
252
+ entry.name,
253
+ entry.hasDefault ? z.any().optional() : z.any(),
254
+ ]));
255
+ schema = z.object(shape).strict();
256
+ normalizedHandler = ((params, ctx) => {
257
+ const args = parsedHandlerParams.map((entry) => {
258
+ if (SPECIAL_PARAM_NAMES.has(entry.name)) {
259
+ const value = ctx[entry.name];
260
+ if ((value === null || value === undefined) &&
261
+ !entry.hasDefault) {
262
+ throw new Error(`Action ${actionName} requires ${entry.name} but none provided.`);
263
+ }
264
+ return value;
265
+ }
266
+ const value = params[entry.name];
267
+ if (value === undefined && !entry.hasDefault) {
268
+ throw new Error(`${actionName}() missing required parameter '${entry.name}'`);
269
+ }
270
+ return value;
271
+ });
272
+ return handler(...args);
273
+ });
274
+ }
275
+ }
276
+ const action = new RegisteredAction(actionName, description, normalizedHandler, schema, domains, pageFilter, terminatesSequence);
44
277
  this.registry.register(action);
45
- return handler;
278
+ return normalizedHandler;
46
279
  };
47
280
  }
48
281
  get_action(action_name) {
49
282
  return this.registry.get(action_name);
50
283
  }
284
+ exclude_action(action_name) {
285
+ this.excludeActions.add(action_name);
286
+ this.registry.remove(action_name);
287
+ }
288
+ remove_action(action_name) {
289
+ this.registry.remove(action_name);
290
+ }
51
291
  get_all_actions() {
52
292
  return this.registry.actionsMap;
53
293
  }
@@ -55,14 +295,14 @@ export class Registry {
55
295
  name: 'execute_action',
56
296
  ignore_input: true,
57
297
  ignore_output: true,
58
- })(time_execution_async('--execute_action')(async (action_name, params, { browser_session = null, page_extraction_llm = null, file_system = null, sensitive_data = null, available_file_paths = null, signal = null, context = null, } = {}) => {
298
+ })(time_execution_async('--execute_action')(async (action_name, params, { browser_session = null, page_extraction_llm = null, extraction_schema = null, file_system = null, sensitive_data = null, available_file_paths = null, signal = null, context = null, } = {}) => {
59
299
  const action = this.registry.get(action_name);
60
300
  if (!action) {
61
301
  throw new Error(`Action ${action_name} not found`);
62
302
  }
63
303
  const parsed = action.paramSchema.safeParse(params);
64
304
  if (!parsed.success) {
65
- throw new Error(`Invalid parameters for action ${action_name}: ${parsed.error.message}`);
305
+ throw new Error(`Invalid parameters ${safeJsonStringify(params)} for action ${action_name}: ${parsed.error.message}`);
66
306
  }
67
307
  let validatedParams = parsed.data;
68
308
  let currentUrl = null;
@@ -86,11 +326,16 @@ export class Registry {
86
326
  browser: browser_session,
87
327
  browser_context: browser_session,
88
328
  page,
329
+ page_url: currentUrl,
330
+ cdp_client: browser_session?.cdp_client ?? null,
89
331
  page_extraction_llm,
332
+ extraction_schema,
90
333
  file_system,
91
334
  available_file_paths,
335
+ sensitive_data,
92
336
  signal,
93
- has_sensitive_data: action_name === 'input_text' && Boolean(sensitive_data),
337
+ has_sensitive_data: (action_name === 'input_text' || action_name === 'input') &&
338
+ Boolean(sensitive_data),
94
339
  };
95
340
  if (signal?.aborted) {
96
341
  throw createAbortError(signal.reason);
@@ -102,6 +347,12 @@ export class Registry {
102
347
  if (signal?.aborted || isAbortError(error)) {
103
348
  throw createAbortError(signal?.reason ?? error);
104
349
  }
350
+ if (isTimeoutError(error)) {
351
+ throw new Error(`Error executing action ${action_name} due to timeout.`, { cause: error });
352
+ }
353
+ if (isSpecialContextMissingError(error)) {
354
+ throw error;
355
+ }
105
356
  throw wrapActionExecutionError(action_name, error);
106
357
  }
107
358
  }));
@@ -134,10 +385,15 @@ export class Registry {
134
385
  const missing = new Set();
135
386
  const traverse = (value) => {
136
387
  if (typeof value === 'string') {
137
- return value.replace(secretPattern, (_, placeholder) => {
388
+ return value.replace(secretPattern, (_, placeholderValue) => {
389
+ const placeholder = String(placeholderValue);
138
390
  if (placeholder in applicableSecrets) {
139
391
  replaced.add(placeholder);
140
- return applicableSecrets[placeholder];
392
+ const replacement = applicableSecrets[placeholder];
393
+ if (placeholder.endsWith('bu_2fa_code')) {
394
+ return generateTotpCode(replacement);
395
+ }
396
+ return replacement;
141
397
  }
142
398
  missing.add(placeholder);
143
399
  return `<secret>${placeholder}</secret>`;
@@ -165,7 +421,7 @@ export class Registry {
165
421
  return;
166
422
  }
167
423
  const urlInfo = currentUrl && !is_new_tab_page(currentUrl) ? ` on ${currentUrl}` : '';
168
- logger.info(`🔒 Using sensitive data placeholders: ${Array.from(placeholders).join(', ')}${urlInfo}`);
424
+ logger.info(`🔒 Using sensitive data placeholders: ${Array.from(placeholders).sort().join(', ')}${urlInfo}`);
169
425
  }
170
426
  create_action_model(options = {}) {
171
427
  const { include_actions = null, page = null } = options;
@@ -11,7 +11,8 @@ export declare class RegisteredAction {
11
11
  readonly paramSchema: ZodTypeAny;
12
12
  readonly domains: string[] | null;
13
13
  readonly pageFilter: ((page: Page) => boolean) | null;
14
- constructor(name: string, description: string, handler: ActionHandler, paramSchema: ZodTypeAny, domains?: string[] | null, pageFilter?: ((page: Page) => boolean) | null);
14
+ readonly terminates_sequence: boolean;
15
+ constructor(name: string, description: string, handler: ActionHandler, paramSchema: ZodTypeAny, domains?: string[] | null, pageFilter?: ((page: Page) => boolean) | null, terminates_sequence?: boolean);
15
16
  promptDescription(): string;
16
17
  }
17
18
  export declare class ActionModel {
@@ -30,6 +31,7 @@ export declare class ActionModel {
30
31
  export declare class ActionRegistry {
31
32
  private actions;
32
33
  register(action: RegisteredAction): void;
34
+ remove(name: string): void;
33
35
  get(name: string): RegisteredAction | null;
34
36
  getAll(): RegisteredAction[];
35
37
  get actionsMap(): Map<string, RegisteredAction>;
@@ -46,6 +48,7 @@ export declare class SpecialActionParameters {
46
48
  browser_context: BrowserSession | null;
47
49
  page: Page | null;
48
50
  page_extraction_llm: BaseChatModel | null;
51
+ extraction_schema: Record<string, unknown> | null;
49
52
  file_system: FileSystem | null;
50
53
  available_file_paths: string[] | null;
51
54
  signal: AbortSignal | null;