browser-use 0.6.1 → 0.7.1

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 (84) hide show
  1. package/README.md +24 -18
  2. package/dist/actor/element.js +24 -3
  3. package/dist/actor/mouse.js +21 -3
  4. package/dist/actor/page.js +33 -11
  5. package/dist/agent/gif.js +28 -3
  6. package/dist/agent/message-manager/service.js +2 -22
  7. package/dist/agent/message-manager/utils.js +15 -2
  8. package/dist/agent/message-manager/views.d.ts +7 -7
  9. package/dist/agent/message-manager/views.js +1 -0
  10. package/dist/agent/prompts.d.ts +3 -0
  11. package/dist/agent/prompts.js +22 -12
  12. package/dist/agent/service.d.ts +9 -1
  13. package/dist/agent/service.js +204 -79
  14. package/dist/agent/system_prompt.md +12 -11
  15. package/dist/agent/system_prompt_anthropic_flash.md +6 -5
  16. package/dist/agent/system_prompt_no_thinking.md +12 -11
  17. package/dist/agent/views.d.ts +2 -0
  18. package/dist/agent/views.js +48 -36
  19. package/dist/browser/extensions.js +20 -10
  20. package/dist/browser/profile.d.ts +4 -0
  21. package/dist/browser/profile.js +107 -4
  22. package/dist/browser/session.d.ts +28 -1
  23. package/dist/browser/session.js +1436 -528
  24. package/dist/browser/watchdogs/default-action-watchdog.js +32 -3
  25. package/dist/browser/watchdogs/downloads-watchdog.d.ts +4 -0
  26. package/dist/browser/watchdogs/downloads-watchdog.js +105 -9
  27. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +1 -0
  28. package/dist/browser/watchdogs/har-recording-watchdog.js +54 -2
  29. package/dist/browser/watchdogs/permissions-watchdog.d.ts +5 -0
  30. package/dist/browser/watchdogs/permissions-watchdog.js +106 -3
  31. package/dist/browser/watchdogs/recording-watchdog.d.ts +2 -0
  32. package/dist/browser/watchdogs/recording-watchdog.js +54 -2
  33. package/dist/browser/watchdogs/security-watchdog.d.ts +1 -0
  34. package/dist/browser/watchdogs/security-watchdog.js +47 -7
  35. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +6 -0
  36. package/dist/browser/watchdogs/storage-state-watchdog.js +206 -14
  37. package/dist/cli.d.ts +13 -2
  38. package/dist/cli.js +190 -9
  39. package/dist/code-use/namespace.js +52 -7
  40. package/dist/code-use/notebook-export.js +18 -2
  41. package/dist/code-use/service.js +1 -0
  42. package/dist/config.js +26 -4
  43. package/dist/controller/action-timeout.d.ts +9 -0
  44. package/dist/controller/action-timeout.js +95 -0
  45. package/dist/controller/registry/service.d.ts +1 -0
  46. package/dist/controller/registry/service.js +28 -1
  47. package/dist/controller/service.d.ts +2 -1
  48. package/dist/controller/service.js +494 -329
  49. package/dist/entrypoint.d.ts +1 -0
  50. package/dist/entrypoint.js +27 -0
  51. package/dist/filesystem/file-system.js +38 -8
  52. package/dist/integrations/gmail/service.js +30 -6
  53. package/dist/llm/browser-use/chat.js +2 -2
  54. package/dist/llm/codex/auth.d.ts +118 -0
  55. package/dist/llm/codex/auth.js +599 -0
  56. package/dist/llm/codex/chat.d.ts +70 -0
  57. package/dist/llm/codex/chat.js +392 -0
  58. package/dist/llm/codex/index.d.ts +2 -0
  59. package/dist/llm/codex/index.js +2 -0
  60. package/dist/llm/google/chat.js +18 -1
  61. package/dist/logging-config.js +22 -11
  62. package/dist/mcp/client.d.ts +1 -0
  63. package/dist/mcp/client.js +12 -10
  64. package/dist/mcp/redaction.d.ts +3 -0
  65. package/dist/mcp/redaction.js +132 -0
  66. package/dist/mcp/server.d.ts +2 -0
  67. package/dist/mcp/server.js +64 -22
  68. package/dist/screenshots/service.js +25 -2
  69. package/dist/skill-cli/direct.d.ts +4 -1
  70. package/dist/skill-cli/direct.js +263 -66
  71. package/dist/skill-cli/server.d.ts +1 -0
  72. package/dist/skill-cli/server.js +115 -25
  73. package/dist/skill-cli/tunnel.d.ts +1 -0
  74. package/dist/skill-cli/tunnel.js +16 -4
  75. package/dist/sync/auth.js +22 -9
  76. package/dist/telemetry/service.js +21 -2
  77. package/dist/telemetry/views.js +31 -8
  78. package/dist/tokens/custom-pricing.js +2 -2
  79. package/dist/tokens/openrouter-pricing.d.ts +11 -0
  80. package/dist/tokens/openrouter-pricing.js +102 -0
  81. package/dist/tokens/service.js +20 -16
  82. package/dist/utils.d.ts +3 -1
  83. package/dist/utils.js +3 -1
  84. package/package.json +68 -27
@@ -0,0 +1,392 @@
1
+ import OpenAI from 'openai';
2
+ import { ModelProviderError, ModelRateLimitError } from '../exceptions.js';
3
+ import { ResponsesAPIMessageSerializer, } from '../openai/responses-serializer.js';
4
+ import { SchemaOptimizer, zodSchemaToJsonSchema } from '../schema.js';
5
+ import { ChatInvokeCompletion } from '../views.js';
6
+ import { CodexAuthError, DEFAULT_CODEX_BASE_URL, getCodexCloudflareHeaders, resolveCodexRuntimeCredentials, } from './auth.js';
7
+ const DEFAULT_CODEX_INSTRUCTIONS = 'You are a helpful assistant.';
8
+ const isCodexBackendURL = (baseURL) => {
9
+ try {
10
+ const parsed = new URL(baseURL);
11
+ return (parsed.hostname === 'chatgpt.com' &&
12
+ parsed.pathname.replace(/\/+$/, '') === '/backend-api/codex');
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ };
18
+ export class ChatCodex {
19
+ model;
20
+ provider = 'codex';
21
+ apiKey;
22
+ baseURL;
23
+ timeout;
24
+ maxRetries;
25
+ defaultHeaders;
26
+ defaultQuery;
27
+ fetchImplementation;
28
+ fetchOptions;
29
+ reasoningEffort;
30
+ maxCompletionTokens;
31
+ topP;
32
+ seed;
33
+ serviceTier;
34
+ include;
35
+ addSchemaToSystemPrompt;
36
+ dontForceStructuredOutput;
37
+ removeMinItemsFromSchema;
38
+ removeDefaultsFromSchema;
39
+ configDir;
40
+ authStorePath;
41
+ refreshSkewSeconds;
42
+ constructor(options = {}) {
43
+ const { model = process.env.BROWSER_USE_CODEX_MODEL ?? 'gpt-5.5', apiKey = process.env.BROWSER_USE_CODEX_ACCESS_TOKEN ?? null, baseURL = process.env.BROWSER_USE_CODEX_BASE_URL ??
44
+ DEFAULT_CODEX_BASE_URL, timeout = null, maxRetries = 2, defaultHeaders = null, defaultQuery = null, fetchImplementation, fetchOptions = null, reasoningEffort = 'low', maxCompletionTokens = 4096, topP = null, seed = null, serviceTier = null, include = null, addSchemaToSystemPrompt = false, dontForceStructuredOutput = false, removeMinItemsFromSchema = false, removeDefaultsFromSchema = false, configDir = null, authStorePath = null, refreshSkewSeconds, } = options;
45
+ this.model = model;
46
+ this.apiKey = apiKey?.trim() || null;
47
+ this.baseURL =
48
+ (baseURL ?? '').trim().replace(/\/+$/, '') || DEFAULT_CODEX_BASE_URL;
49
+ this.timeout = timeout;
50
+ this.maxRetries = maxRetries;
51
+ this.defaultHeaders = defaultHeaders;
52
+ this.defaultQuery = defaultQuery;
53
+ this.fetchImplementation = fetchImplementation;
54
+ this.fetchOptions = fetchOptions;
55
+ this.reasoningEffort = reasoningEffort;
56
+ this.maxCompletionTokens = maxCompletionTokens;
57
+ this.topP = topP;
58
+ this.seed = seed;
59
+ this.serviceTier = serviceTier;
60
+ this.include = include ? [...include] : null;
61
+ this.addSchemaToSystemPrompt = addSchemaToSystemPrompt;
62
+ this.dontForceStructuredOutput = dontForceStructuredOutput;
63
+ this.removeMinItemsFromSchema = removeMinItemsFromSchema;
64
+ this.removeDefaultsFromSchema = removeDefaultsFromSchema;
65
+ this.configDir = configDir;
66
+ this.authStorePath = authStorePath;
67
+ this.refreshSkewSeconds = refreshSkewSeconds;
68
+ }
69
+ get name() {
70
+ return this.model;
71
+ }
72
+ get model_name() {
73
+ return this.model;
74
+ }
75
+ async resolveClientConfig(forceRefresh = false) {
76
+ if (this.apiKey) {
77
+ return {
78
+ apiKey: this.apiKey,
79
+ baseURL: this.baseURL,
80
+ };
81
+ }
82
+ const credentials = await resolveCodexRuntimeCredentials({
83
+ configDir: this.configDir,
84
+ authStorePath: this.authStorePath,
85
+ baseURL: this.baseURL,
86
+ forceRefresh,
87
+ refreshSkewSeconds: this.refreshSkewSeconds,
88
+ fetchImplementation: this.fetchImplementation,
89
+ });
90
+ return {
91
+ apiKey: credentials.api_key,
92
+ baseURL: credentials.base_url,
93
+ };
94
+ }
95
+ createClient(config) {
96
+ const codexHeaders = isCodexBackendURL(config.baseURL)
97
+ ? getCodexCloudflareHeaders(config.apiKey)
98
+ : {};
99
+ return new OpenAI({
100
+ apiKey: config.apiKey,
101
+ baseURL: config.baseURL,
102
+ timeout: this.timeout ?? undefined,
103
+ maxRetries: this.maxRetries,
104
+ defaultHeaders: {
105
+ ...codexHeaders,
106
+ ...(this.defaultHeaders ?? {}),
107
+ },
108
+ defaultQuery: this.defaultQuery ?? undefined,
109
+ fetch: this.fetchImplementation,
110
+ fetchOptions: (this.fetchOptions ?? undefined),
111
+ });
112
+ }
113
+ getResponsesUsage(response) {
114
+ if (!response?.usage) {
115
+ return null;
116
+ }
117
+ return {
118
+ prompt_tokens: response.usage.input_tokens ?? 0,
119
+ prompt_cached_tokens: response.usage.input_tokens_details?.cached_tokens ?? null,
120
+ prompt_cache_creation_tokens: null,
121
+ prompt_image_tokens: null,
122
+ completion_tokens: response.usage.output_tokens ?? 0,
123
+ total_tokens: response.usage.total_tokens ?? 0,
124
+ };
125
+ }
126
+ getResponseOutputText(response) {
127
+ if (typeof response?.output_text === 'string') {
128
+ return response.output_text;
129
+ }
130
+ const outputs = Array.isArray(response?.output) ? response.output : [];
131
+ for (const item of outputs) {
132
+ if (Array.isArray(item?.content)) {
133
+ for (const part of item.content) {
134
+ if (typeof part?.text === 'string') {
135
+ return part.text;
136
+ }
137
+ if (typeof part?.output_text === 'string') {
138
+ return part.output_text;
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return '';
144
+ }
145
+ async collectResponse(responseOrStream) {
146
+ if (!responseOrStream ||
147
+ typeof responseOrStream[Symbol.asyncIterator] !== 'function') {
148
+ return responseOrStream;
149
+ }
150
+ let terminalResponse = null;
151
+ const outputItems = [];
152
+ const textDeltas = [];
153
+ for await (const event of responseOrStream) {
154
+ const eventType = event?.type;
155
+ if (eventType === 'response.output_text.delta' &&
156
+ typeof event?.delta === 'string') {
157
+ textDeltas.push(event.delta);
158
+ }
159
+ if (eventType === 'response.output_item.done' && event?.item) {
160
+ outputItems.push(event.item);
161
+ }
162
+ if (eventType === 'response.completed' ||
163
+ eventType === 'response.incomplete' ||
164
+ eventType === 'response.failed') {
165
+ terminalResponse = event.response ?? null;
166
+ }
167
+ }
168
+ const fallbackOutputText = textDeltas.join('');
169
+ if (!terminalResponse) {
170
+ return {
171
+ output_text: fallbackOutputText,
172
+ output: outputItems,
173
+ status: fallbackOutputText ? 'completed' : 'incomplete',
174
+ };
175
+ }
176
+ if (typeof terminalResponse.output_text !== 'string' &&
177
+ fallbackOutputText) {
178
+ terminalResponse.output_text = fallbackOutputText;
179
+ }
180
+ if ((!Array.isArray(terminalResponse.output) ||
181
+ terminalResponse.output.length === 0) &&
182
+ outputItems.length > 0) {
183
+ terminalResponse.output = outputItems;
184
+ }
185
+ return terminalResponse;
186
+ }
187
+ getInputText(content) {
188
+ if (typeof content === 'string') {
189
+ return content;
190
+ }
191
+ if (!Array.isArray(content)) {
192
+ return '';
193
+ }
194
+ return content
195
+ .map((part) => {
196
+ if (part &&
197
+ typeof part === 'object' &&
198
+ 'text' in part &&
199
+ typeof part.text === 'string') {
200
+ return part.text;
201
+ }
202
+ return '';
203
+ })
204
+ .filter(Boolean)
205
+ .join('\n');
206
+ }
207
+ getModelParamsForResponses(baseURL) {
208
+ const codexBackend = isCodexBackendURL(baseURL);
209
+ const modelParams = {
210
+ store: false,
211
+ reasoning: codexBackend
212
+ ? { effort: this.reasoningEffort, summary: 'auto' }
213
+ : { effort: this.reasoningEffort },
214
+ };
215
+ if (this.maxCompletionTokens !== null && !codexBackend) {
216
+ modelParams.max_output_tokens = this.maxCompletionTokens;
217
+ }
218
+ if (this.topP !== null && !codexBackend) {
219
+ modelParams.top_p = this.topP;
220
+ }
221
+ if (this.seed !== null && !codexBackend) {
222
+ modelParams.seed = this.seed;
223
+ }
224
+ if (this.serviceTier !== null) {
225
+ modelParams.service_tier = this.serviceTier;
226
+ }
227
+ if (codexBackend) {
228
+ modelParams.stream = true;
229
+ modelParams.include = this.include ?? ['reasoning.encrypted_content'];
230
+ modelParams.tools = [];
231
+ modelParams.tool_choice = 'auto';
232
+ modelParams.parallel_tool_calls = true;
233
+ }
234
+ else if (this.include !== null) {
235
+ modelParams.include = this.include;
236
+ }
237
+ return modelParams;
238
+ }
239
+ buildCodexBackendInput(inputMessages) {
240
+ const instructionParts = [];
241
+ const input = [];
242
+ for (const message of inputMessages) {
243
+ if (message.role === 'system') {
244
+ const text = this.getInputText(message.content).trim();
245
+ if (text) {
246
+ instructionParts.push(text);
247
+ }
248
+ continue;
249
+ }
250
+ input.push(message);
251
+ }
252
+ return {
253
+ input: input.length > 0 ? input : [{ role: 'user', content: '' }],
254
+ instructions: instructionParts.join('\n\n').trim() || DEFAULT_CODEX_INSTRUCTIONS,
255
+ };
256
+ }
257
+ getZodSchemaCandidate(output_format) {
258
+ const output = output_format;
259
+ if (output &&
260
+ typeof output === 'object' &&
261
+ typeof output.safeParse === 'function' &&
262
+ typeof output.parse === 'function') {
263
+ return output;
264
+ }
265
+ if (output &&
266
+ typeof output === 'object' &&
267
+ output.schema &&
268
+ typeof output.schema.safeParse === 'function' &&
269
+ typeof output.schema.parse === 'function') {
270
+ return output.schema;
271
+ }
272
+ return null;
273
+ }
274
+ buildRequest(messages, zodSchemaCandidate, baseURL) {
275
+ const serializer = new ResponsesAPIMessageSerializer();
276
+ const inputMessages = serializer.serialize(messages);
277
+ const codexBackend = isCodexBackendURL(baseURL);
278
+ const codexInput = codexBackend
279
+ ? this.buildCodexBackendInput(inputMessages)
280
+ : null;
281
+ const request = {
282
+ model: this.model,
283
+ input: codexInput?.input ?? inputMessages,
284
+ ...this.getModelParamsForResponses(baseURL),
285
+ };
286
+ if (codexInput) {
287
+ request.instructions = codexInput.instructions;
288
+ }
289
+ if (!zodSchemaCandidate) {
290
+ return request;
291
+ }
292
+ try {
293
+ const rawJsonSchema = zodSchemaToJsonSchema(zodSchemaCandidate, {
294
+ name: 'agent_output',
295
+ target: 'jsonSchema7',
296
+ });
297
+ const optimizedJsonSchema = SchemaOptimizer.createOptimizedJsonSchema(rawJsonSchema, {
298
+ removeMinItems: this.removeMinItemsFromSchema,
299
+ removeDefaults: this.removeDefaultsFromSchema,
300
+ });
301
+ if (this.addSchemaToSystemPrompt &&
302
+ (codexBackend ||
303
+ (inputMessages.length > 0 && inputMessages[0]?.role === 'system'))) {
304
+ const schemaText = `\n<json_schema>\n${JSON.stringify(optimizedJsonSchema)}\n</json_schema>`;
305
+ if (codexBackend) {
306
+ request.instructions = `${String(request.instructions ?? DEFAULT_CODEX_INSTRUCTIONS)}${schemaText}`;
307
+ }
308
+ else {
309
+ const firstInput = inputMessages[0];
310
+ const firstContent = firstInput?.content;
311
+ let patchedContent = firstContent ?? '';
312
+ if (typeof firstContent === 'string') {
313
+ patchedContent = firstContent + schemaText;
314
+ }
315
+ else if (Array.isArray(firstContent)) {
316
+ patchedContent = [
317
+ ...firstContent,
318
+ { type: 'input_text', text: schemaText },
319
+ ];
320
+ }
321
+ inputMessages[0] = {
322
+ ...inputMessages[0],
323
+ content: patchedContent,
324
+ };
325
+ request.input = inputMessages;
326
+ }
327
+ }
328
+ if (!this.dontForceStructuredOutput) {
329
+ request.text = {
330
+ format: {
331
+ type: 'json_schema',
332
+ name: 'agent_output',
333
+ strict: true,
334
+ schema: optimizedJsonSchema,
335
+ },
336
+ };
337
+ }
338
+ }
339
+ catch {
340
+ // Skip structured output forcing when schema conversion fails.
341
+ }
342
+ return request;
343
+ }
344
+ async ainvoke(messages, output_format, options = {}) {
345
+ return this.invokeResponses(messages, output_format, options, false);
346
+ }
347
+ async invokeResponses(messages, output_format, options, forceRefresh) {
348
+ const zodSchemaCandidate = this.getZodSchemaCandidate(output_format);
349
+ try {
350
+ const clientConfig = await this.resolveClientConfig(forceRefresh);
351
+ const request = this.buildRequest(messages, zodSchemaCandidate, clientConfig.baseURL);
352
+ const client = this.createClient(clientConfig);
353
+ const responseOrStream = await client.responses.create(request, options.signal ? { signal: options.signal } : undefined);
354
+ const response = await this.collectResponse(responseOrStream);
355
+ const content = this.getResponseOutputText(response);
356
+ const usage = this.getResponsesUsage(response);
357
+ const stopReason = response?.status ?? null;
358
+ let completion = content;
359
+ if (output_format) {
360
+ if (zodSchemaCandidate) {
361
+ const parsedJson = JSON.parse(content);
362
+ const output = output_format;
363
+ if (output &&
364
+ typeof output === 'object' &&
365
+ output.schema &&
366
+ typeof output.schema.parse === 'function') {
367
+ completion = output.schema.parse(parsedJson);
368
+ }
369
+ else {
370
+ completion = output.parse(parsedJson);
371
+ }
372
+ }
373
+ else {
374
+ completion = output_format.parse(content);
375
+ }
376
+ }
377
+ return new ChatInvokeCompletion(completion, usage, null, null, stopReason);
378
+ }
379
+ catch (error) {
380
+ if (!this.apiKey && !forceRefresh && error?.status === 401) {
381
+ return this.invokeResponses(messages, output_format, options, true);
382
+ }
383
+ if (error?.status === 429) {
384
+ throw new ModelRateLimitError(error?.message ?? 'Rate limit exceeded', 429, this.model);
385
+ }
386
+ if (error instanceof CodexAuthError) {
387
+ throw error;
388
+ }
389
+ throw new ModelProviderError(error?.message ?? String(error), error?.status ?? 500, this.model);
390
+ }
391
+ }
392
+ }
@@ -0,0 +1,2 @@
1
+ export * from './auth.js';
2
+ export * from './chat.js';
@@ -0,0 +1,2 @@
1
+ export * from './auth.js';
2
+ export * from './chat.js';
@@ -3,6 +3,22 @@ import { ModelProviderError } from '../exceptions.js';
3
3
  import { ChatInvokeCompletion } from '../views.js';
4
4
  import { SchemaOptimizer, zodSchemaToJsonSchema } from '../schema.js';
5
5
  import { GoogleMessageSerializer } from './serializer.js';
6
+ import { get_browser_use_version } from '../../utils.js';
7
+ const buildGoogleHttpOptions = (httpOptions) => {
8
+ const resolvedHttpOptions = {
9
+ ...(httpOptions ?? {}),
10
+ };
11
+ const existingHeaders = resolvedHttpOptions.headers;
12
+ const headers = existingHeaders &&
13
+ typeof existingHeaders === 'object' &&
14
+ !Array.isArray(existingHeaders)
15
+ ? Object.fromEntries(Object.entries(existingHeaders).map(([key, value]) => [String(key), String(value)]))
16
+ : {};
17
+ headers['x-goog-api-client'] =
18
+ `browser-use/${get_browser_use_version() || 'unknown'}`;
19
+ resolvedHttpOptions.headers = headers;
20
+ return resolvedHttpOptions;
21
+ };
6
22
  export class ChatGoogle {
7
23
  model;
8
24
  provider = 'google';
@@ -44,6 +60,7 @@ export class ChatGoogle {
44
60
  credentials,
45
61
  };
46
62
  const resolvedVertexAi = vertexai ?? vertexAi;
63
+ const resolvedHttpOptions = buildGoogleHttpOptions(httpOptions);
47
64
  const clientOptions = {
48
65
  ...(apiKey != null ? { apiKey } : {}),
49
66
  ...(baseUrl ? { baseUrl } : {}),
@@ -51,7 +68,7 @@ export class ChatGoogle {
51
68
  ...(resolvedVertexAi != null ? { vertexai: resolvedVertexAi } : {}),
52
69
  ...(project ? { project } : {}),
53
70
  ...(location ? { location } : {}),
54
- ...(httpOptions ? { httpOptions } : {}),
71
+ httpOptions: resolvedHttpOptions,
55
72
  ...(resolvedGoogleAuthOptions
56
73
  ? { googleAuthOptions: resolvedGoogleAuthOptions }
57
74
  : {}),
@@ -45,7 +45,26 @@ const writePayload = (stream, level, payload) => {
45
45
  }
46
46
  };
47
47
  const ensureFilePathReady = (filePath) => {
48
- fs.mkdirSync(path.dirname(path.resolve(filePath)), { recursive: true });
48
+ const dirPath = path.dirname(path.resolve(filePath));
49
+ const existed = fs.existsSync(dirPath);
50
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
51
+ if (!existed && process.platform !== 'win32') {
52
+ fs.chmodSync(dirPath, 0o700);
53
+ }
54
+ };
55
+ const createPrivateLogStream = (filePath) => {
56
+ const resolved = path.resolve(filePath);
57
+ ensureFilePathReady(resolved);
58
+ const fd = fs.openSync(resolved, 'a', 0o600);
59
+ if (process.platform !== 'win32') {
60
+ fs.chmodSync(resolved, 0o600);
61
+ }
62
+ return fs.createWriteStream(resolved, {
63
+ fd,
64
+ flags: 'a',
65
+ encoding: 'utf-8',
66
+ autoClose: true,
67
+ });
49
68
  };
50
69
  const closeFileStreams = () => {
51
70
  if (debugLogStream) {
@@ -121,19 +140,11 @@ export const setupLogging = (options = {}) => {
121
140
  consoleStream = options.stream || process.stderr;
122
141
  const debugLogFile = options.debugLogFile ?? process.env.BROWSER_USE_DEBUG_LOG_FILE ?? null;
123
142
  if (debugLogFile && debugLogFile.trim().length > 0) {
124
- ensureFilePathReady(debugLogFile);
125
- debugLogStream = fs.createWriteStream(path.resolve(debugLogFile), {
126
- flags: 'a',
127
- encoding: 'utf-8',
128
- });
143
+ debugLogStream = createPrivateLogStream(debugLogFile);
129
144
  }
130
145
  const infoLogFile = options.infoLogFile ?? process.env.BROWSER_USE_INFO_LOG_FILE ?? null;
131
146
  if (infoLogFile && infoLogFile.trim().length > 0) {
132
- ensureFilePathReady(infoLogFile);
133
- infoLogStream = fs.createWriteStream(path.resolve(infoLogFile), {
134
- flags: 'a',
135
- encoding: 'utf-8',
136
- });
147
+ infoLogStream = createPrivateLogStream(infoLogFile);
137
148
  }
138
149
  configured = true;
139
150
  return createLogger('browser_use');
@@ -25,6 +25,7 @@
25
25
  import { type Tool, type Prompt } from '@modelcontextprotocol/sdk/types.js';
26
26
  import type { Controller } from '../controller/service.js';
27
27
  import type { Tools } from '../tools/service.js';
28
+ export { formatMcpCommandForLog, formatMcpToolArgsForLog, redactMcpLogMessage, } from './redaction.js';
28
29
  export interface MCPClientOptions {
29
30
  /** Maximum number of connection retry attempts (default: 3) */
30
31
  maxRetries?: number;
@@ -31,7 +31,9 @@ import { ActionResult } from '../agent/views.js';
31
31
  import { productTelemetry } from '../telemetry/service.js';
32
32
  import { MCPClientTelemetryEvent } from '../telemetry/views.js';
33
33
  import { get_browser_use_version, retryAsync } from '../utils.js';
34
+ import { formatMcpCommandForLog, formatMcpToolArgsForLog, redactMcpLogMessage, } from './redaction.js';
34
35
  const logger = createLogger('browser_use.mcp.client');
36
+ export { formatMcpCommandForLog, formatMcpToolArgsForLog, redactMcpLogMessage, } from './redaction.js';
35
37
  export class MCPClient {
36
38
  client;
37
39
  command;
@@ -89,7 +91,7 @@ export class MCPClient {
89
91
  const startTime = Date.now() / 1000;
90
92
  let errorMsg = null;
91
93
  try {
92
- logger.info(`🔌 Connecting to MCP server '${this.serverName}': ${this.command} ${this.args.join(' ')}`);
94
+ logger.info(`🔌 Connecting to MCP server '${this.serverName}': ${formatMcpCommandForLog(this.command, this.args)}`);
93
95
  // Use retry logic for connection
94
96
  await retryAsync(async () => {
95
97
  // Create transport with environment variables
@@ -105,7 +107,7 @@ export class MCPClient {
105
107
  delayMs: 1000,
106
108
  backoffMultiplier: 2,
107
109
  onRetry: (error, attempt, delay) => {
108
- logger.warning(`Connection attempt ${attempt} failed for '${this.serverName}': ${error.message}. Retrying in ${delay}ms...`);
110
+ logger.warning(`Connection attempt ${attempt} failed for '${this.serverName}': ${redactMcpLogMessage(error)}. Retrying in ${delay}ms...`);
109
111
  },
110
112
  });
111
113
  this._connected = true;
@@ -125,7 +127,7 @@ export class MCPClient {
125
127
  this._startHealthCheck();
126
128
  }
127
129
  catch (error) {
128
- errorMsg = error instanceof Error ? error.message : String(error);
130
+ errorMsg = redactMcpLogMessage(error);
129
131
  this._connected = false;
130
132
  throw error;
131
133
  }
@@ -183,7 +185,7 @@ export class MCPClient {
183
185
  logger.info(`Disconnected from '${this.serverName}' (${stats.toolCallCount} tool calls, ${(stats.successRate * 100).toFixed(1)}% success rate)`);
184
186
  }
185
187
  catch (error) {
186
- errorMsg = error instanceof Error ? error.message : String(error);
188
+ errorMsg = redactMcpLogMessage(error);
187
189
  logger.error(`Error disconnecting from MCP server: ${errorMsg}`);
188
190
  }
189
191
  finally {
@@ -220,7 +222,7 @@ export class MCPClient {
220
222
  const startTime = Date.now() / 1000;
221
223
  let errorMsg = null;
222
224
  try {
223
- logger.debug(`🔧 Calling MCP tool '${name}' with params: ${JSON.stringify(args)}`);
225
+ logger.debug(`🔧 Calling MCP tool '${name}' with params: ${formatMcpToolArgsForLog(args)}`);
224
226
  this._toolCallCount++;
225
227
  const result = await this.client.request(CallToolRequestSchema, {
226
228
  name,
@@ -230,7 +232,7 @@ export class MCPClient {
230
232
  }
231
233
  catch (error) {
232
234
  this._errorCount++;
233
- errorMsg = error instanceof Error ? error.message : String(error);
235
+ errorMsg = redactMcpLogMessage(error);
234
236
  logger.error(`MCP tool '${name}' failed: ${errorMsg}`);
235
237
  throw error;
236
238
  }
@@ -308,7 +310,7 @@ export class MCPClient {
308
310
  });
309
311
  }
310
312
  catch (error) {
311
- const errorMsg = error instanceof Error ? error.message : String(error);
313
+ const errorMsg = redactMcpLogMessage(error);
312
314
  logger.error(`MCP tool '${tool.name}' failed: ${errorMsg}`);
313
315
  return new ActionResult({
314
316
  error: `MCP tool '${tool.name}' failed: ${errorMsg}`,
@@ -541,7 +543,7 @@ export class MCPClient {
541
543
  return result;
542
544
  }
543
545
  catch (error) {
544
- logger.error(`Failed to get prompt '${name}': ${error}`);
546
+ logger.error(`Failed to get prompt '${name}': ${redactMcpLogMessage(error)}`);
545
547
  throw error;
546
548
  }
547
549
  }
@@ -557,7 +559,7 @@ export class MCPClient {
557
559
  await this._performHealthCheck();
558
560
  }
559
561
  catch (error) {
560
- logger.warning(`Health check failed for '${this.serverName}': ${error}`);
562
+ logger.warning(`Health check failed for '${this.serverName}': ${redactMcpLogMessage(error)}`);
561
563
  if (this.autoReconnect) {
562
564
  await this._attemptReconnect();
563
565
  }
@@ -600,7 +602,7 @@ export class MCPClient {
600
602
  logger.info(`✅ Reconnected to '${this.serverName}'`);
601
603
  }
602
604
  catch (error) {
603
- logger.error(`Failed to reconnect to '${this.serverName}': ${error}`);
605
+ logger.error(`Failed to reconnect to '${this.serverName}': ${redactMcpLogMessage(error)}`);
604
606
  }
605
607
  }
606
608
  /**
@@ -0,0 +1,3 @@
1
+ export declare const formatMcpCommandForLog: (command: string, args: string[]) => string;
2
+ export declare const redactMcpLogMessage: (value: unknown) => string;
3
+ export declare const formatMcpToolArgsForLog: (args: unknown) => string;