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.
- package/README.md +295 -686
- package/dist/actor/element.d.ts +19 -0
- package/dist/actor/element.js +46 -0
- package/dist/actor/index.d.ts +4 -0
- package/dist/actor/index.js +4 -0
- package/dist/actor/mouse.d.ts +19 -0
- package/dist/actor/mouse.js +39 -0
- package/dist/actor/page.d.ts +29 -0
- package/dist/actor/page.js +88 -0
- package/dist/actor/utils.d.ts +4 -0
- package/dist/actor/utils.js +35 -0
- package/dist/agent/cloud-events.d.ts +18 -0
- package/dist/agent/cloud-events.js +65 -2
- package/dist/agent/gif.d.ts +1 -0
- package/dist/agent/gif.js +24 -2
- package/dist/agent/judge.d.ts +17 -0
- package/dist/agent/judge.js +197 -0
- package/dist/agent/message-manager/service.d.ts +12 -4
- package/dist/agent/message-manager/service.js +205 -39
- package/dist/agent/message-manager/utils.js +0 -1
- package/dist/agent/message-manager/views.d.ts +4 -0
- package/dist/agent/message-manager/views.js +11 -7
- package/dist/agent/prompts.d.ts +24 -3
- package/dist/agent/prompts.js +274 -59
- package/dist/agent/service.d.ts +103 -41
- package/dist/agent/service.js +2336 -472
- package/dist/agent/variable-detector.d.ts +12 -0
- package/dist/agent/variable-detector.js +211 -0
- package/dist/agent/views.d.ts +237 -18
- package/dist/agent/views.js +446 -33
- package/dist/browser/cloud/cloud.d.ts +20 -0
- package/dist/browser/cloud/cloud.js +129 -0
- package/dist/browser/cloud/index.d.ts +2 -0
- package/dist/browser/cloud/index.js +2 -0
- package/dist/browser/cloud/views.d.ts +41 -0
- package/dist/browser/cloud/views.js +35 -0
- package/dist/browser/events.d.ts +345 -0
- package/dist/browser/events.js +566 -0
- package/dist/browser/extensions.js +17 -17
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/profile.d.ts +10 -4
- package/dist/browser/profile.js +79 -12
- package/dist/browser/session-manager.d.ts +85 -0
- package/dist/browser/session-manager.js +208 -0
- package/dist/browser/session.d.ts +105 -9
- package/dist/browser/session.js +1166 -95
- package/dist/browser/types.d.ts +153 -156
- package/dist/browser/views.d.ts +39 -0
- package/dist/browser/views.js +32 -0
- package/dist/browser/watchdogs/aboutblank-watchdog.d.ts +12 -0
- package/dist/browser/watchdogs/aboutblank-watchdog.js +131 -0
- package/dist/browser/watchdogs/base.d.ts +21 -0
- package/dist/browser/watchdogs/base.js +81 -0
- package/dist/browser/watchdogs/cdp-session-watchdog.d.ts +14 -0
- package/dist/browser/watchdogs/cdp-session-watchdog.js +177 -0
- package/dist/browser/watchdogs/crash-watchdog.d.ts +38 -0
- package/dist/browser/watchdogs/crash-watchdog.js +296 -0
- package/dist/browser/watchdogs/default-action-watchdog.d.ts +49 -0
- package/dist/browser/watchdogs/default-action-watchdog.js +212 -0
- package/dist/browser/watchdogs/dom-watchdog.d.ts +8 -0
- package/dist/browser/watchdogs/dom-watchdog.js +31 -0
- package/dist/browser/watchdogs/downloads-watchdog.d.ts +77 -0
- package/dist/browser/watchdogs/downloads-watchdog.js +409 -0
- package/dist/browser/watchdogs/har-recording-watchdog.d.ts +19 -0
- package/dist/browser/watchdogs/har-recording-watchdog.js +317 -0
- package/dist/browser/watchdogs/index.d.ts +15 -0
- package/dist/browser/watchdogs/index.js +15 -0
- package/dist/browser/watchdogs/local-browser-watchdog.d.ts +10 -0
- package/dist/browser/watchdogs/local-browser-watchdog.js +32 -0
- package/dist/browser/watchdogs/permissions-watchdog.d.ts +8 -0
- package/dist/browser/watchdogs/permissions-watchdog.js +73 -0
- package/dist/browser/watchdogs/popups-watchdog.d.ts +13 -0
- package/dist/browser/watchdogs/popups-watchdog.js +77 -0
- package/dist/browser/watchdogs/recording-watchdog.d.ts +27 -0
- package/dist/browser/watchdogs/recording-watchdog.js +249 -0
- package/dist/browser/watchdogs/screenshot-watchdog.d.ts +6 -0
- package/dist/browser/watchdogs/screenshot-watchdog.js +13 -0
- package/dist/browser/watchdogs/security-watchdog.d.ts +10 -0
- package/dist/browser/watchdogs/security-watchdog.js +84 -0
- package/dist/browser/watchdogs/storage-state-watchdog.d.ts +24 -0
- package/dist/browser/watchdogs/storage-state-watchdog.js +288 -0
- package/dist/cli.d.ts +7 -2
- package/dist/cli.js +182 -25
- package/dist/code-use/formatting.d.ts +3 -0
- package/dist/code-use/formatting.js +18 -0
- package/dist/code-use/index.d.ts +6 -0
- package/dist/code-use/index.js +6 -0
- package/dist/code-use/namespace.d.ts +5 -0
- package/dist/code-use/namespace.js +81 -0
- package/dist/code-use/notebook-export.d.ts +3 -0
- package/dist/code-use/notebook-export.js +56 -0
- package/dist/code-use/service.d.ts +24 -0
- package/dist/code-use/service.js +104 -0
- package/dist/code-use/utils.d.ts +4 -0
- package/dist/code-use/utils.js +98 -0
- package/dist/code-use/views.d.ts +108 -0
- package/dist/code-use/views.js +165 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +109 -7
- package/dist/controller/registry/service.d.ts +10 -1
- package/dist/controller/registry/service.js +266 -10
- package/dist/controller/registry/views.d.ts +4 -1
- package/dist/controller/registry/views.js +25 -2
- package/dist/controller/service.d.ts +10 -1
- package/dist/controller/service.js +1814 -268
- package/dist/controller/views.d.ts +78 -155
- package/dist/controller/views.js +61 -12
- package/dist/dom/history-tree-processor/service.d.ts +5 -0
- package/dist/dom/history-tree-processor/service.js +169 -14
- package/dist/dom/history-tree-processor/view.d.ts +7 -1
- package/dist/dom/history-tree-processor/view.js +10 -1
- package/dist/dom/markdown-extractor.d.ts +37 -0
- package/dist/dom/markdown-extractor.js +345 -0
- package/dist/dom/service.d.ts +3 -1
- package/dist/dom/service.js +76 -0
- package/dist/dom/views.d.ts +1 -0
- package/dist/dom/views.js +45 -0
- package/dist/event-bus.d.ts +107 -7
- package/dist/event-bus.js +313 -10
- package/dist/exceptions.d.ts +0 -3
- package/dist/exceptions.js +0 -7
- package/dist/filesystem/file-system.d.ts +18 -0
- package/dist/filesystem/file-system.js +503 -42
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/integrations/gmail/actions.d.ts +3 -3
- package/dist/integrations/gmail/actions.js +4 -4
- package/dist/llm/anthropic/chat.d.ts +18 -1
- package/dist/llm/anthropic/chat.js +123 -55
- package/dist/llm/anthropic/serializer.d.ts +2 -0
- package/dist/llm/anthropic/serializer.js +81 -9
- package/dist/llm/aws/chat-anthropic.d.ts +17 -0
- package/dist/llm/aws/chat-anthropic.js +126 -26
- package/dist/llm/aws/chat-bedrock.d.ts +28 -1
- package/dist/llm/aws/chat-bedrock.js +161 -34
- package/dist/llm/aws/serializer.d.ts +13 -1
- package/dist/llm/aws/serializer.js +56 -17
- package/dist/llm/azure/chat.d.ts +53 -2
- package/dist/llm/azure/chat.js +366 -54
- package/dist/llm/base.d.ts +2 -0
- package/dist/llm/browser-use/chat.d.ts +40 -0
- package/dist/llm/browser-use/chat.js +305 -0
- package/dist/llm/browser-use/index.d.ts +1 -0
- package/dist/llm/browser-use/index.js +1 -0
- package/dist/llm/cerebras/chat.d.ts +39 -0
- package/dist/llm/cerebras/chat.js +178 -0
- package/dist/llm/cerebras/index.d.ts +2 -0
- package/dist/llm/cerebras/index.js +2 -0
- package/dist/llm/cerebras/serializer.d.ts +7 -0
- package/dist/llm/cerebras/serializer.js +82 -0
- package/dist/llm/deepseek/chat.d.ts +19 -2
- package/dist/llm/deepseek/chat.js +138 -25
- package/dist/llm/google/chat.d.ts +46 -2
- package/dist/llm/google/chat.js +267 -64
- package/dist/llm/google/serializer.d.ts +9 -1
- package/dist/llm/google/serializer.js +141 -34
- package/dist/llm/groq/chat.d.ts +21 -2
- package/dist/llm/groq/chat.js +125 -26
- package/dist/llm/groq/parser.js +3 -1
- package/dist/llm/mistral/chat.d.ts +43 -0
- package/dist/llm/mistral/chat.js +154 -0
- package/dist/llm/mistral/index.d.ts +2 -0
- package/dist/llm/mistral/index.js +2 -0
- package/dist/llm/mistral/schema.d.ts +8 -0
- package/dist/llm/mistral/schema.js +27 -0
- package/dist/llm/models.d.ts +2 -0
- package/dist/llm/models.js +317 -0
- package/dist/llm/ollama/chat.d.ts +13 -1
- package/dist/llm/ollama/chat.js +110 -19
- package/dist/llm/ollama/serializer.d.ts +1 -0
- package/dist/llm/ollama/serializer.js +34 -12
- package/dist/llm/openai/chat.d.ts +16 -0
- package/dist/llm/openai/chat.js +94 -44
- package/dist/llm/openai/like.d.ts +5 -3
- package/dist/llm/openai/like.js +7 -3
- package/dist/llm/openai/responses-serializer.d.ts +18 -0
- package/dist/llm/openai/responses-serializer.js +72 -0
- package/dist/llm/openrouter/chat.d.ts +28 -2
- package/dist/llm/openrouter/chat.js +115 -29
- package/dist/llm/schema.d.ts +11 -1
- package/dist/llm/schema.js +109 -4
- package/dist/llm/vercel/chat.d.ts +50 -0
- package/dist/llm/vercel/chat.js +276 -0
- package/dist/llm/vercel/index.d.ts +1 -0
- package/dist/llm/vercel/index.js +1 -0
- package/dist/llm/vercel/serializer.d.ts +5 -0
- package/dist/llm/vercel/serializer.js +7 -0
- package/dist/llm/views.d.ts +2 -1
- package/dist/llm/views.js +3 -1
- package/dist/logging-config.d.ts +2 -0
- package/dist/logging-config.js +82 -29
- package/dist/mcp/client.d.ts +10 -5
- package/dist/mcp/client.js +14 -9
- package/dist/mcp/controller.d.ts +42 -3
- package/dist/mcp/controller.js +56 -31
- package/dist/mcp/server.d.ts +15 -0
- package/dist/mcp/server.js +261 -52
- package/dist/observability.js +10 -4
- package/dist/sandbox/index.d.ts +2 -0
- package/dist/sandbox/index.js +2 -0
- package/dist/sandbox/sandbox.d.ts +19 -0
- package/dist/sandbox/sandbox.js +140 -0
- package/dist/sandbox/views.d.ts +67 -0
- package/dist/sandbox/views.js +121 -0
- package/dist/skill-cli/index.d.ts +3 -0
- package/dist/skill-cli/index.js +3 -0
- package/dist/skill-cli/protocol.d.ts +30 -0
- package/dist/skill-cli/protocol.js +48 -0
- package/dist/skill-cli/server.d.ts +11 -0
- package/dist/skill-cli/server.js +85 -0
- package/dist/skill-cli/sessions.d.ts +24 -0
- package/dist/skill-cli/sessions.js +47 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.js +3 -0
- package/dist/skills/service.d.ts +27 -0
- package/dist/skills/service.js +266 -0
- package/dist/skills/utils.d.ts +6 -0
- package/dist/skills/utils.js +53 -0
- package/dist/skills/views.d.ts +40 -0
- package/dist/skills/views.js +10 -0
- package/dist/sync/auth.js +8 -3
- package/dist/sync/service.d.ts +6 -6
- package/dist/sync/service.js +54 -89
- package/dist/telemetry/views.d.ts +20 -6
- package/dist/telemetry/views.js +23 -5
- package/dist/tokens/custom-pricing.d.ts +2 -0
- package/dist/tokens/custom-pricing.js +22 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/mappings.d.ts +1 -0
- package/dist/tokens/mappings.js +3 -0
- package/dist/tokens/service.js +27 -8
- package/dist/tools/extraction/index.d.ts +2 -0
- package/dist/tools/extraction/index.js +2 -0
- package/dist/tools/extraction/schema-utils.d.ts +6 -0
- package/dist/tools/extraction/schema-utils.js +237 -0
- package/dist/tools/extraction/views.d.ts +7 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/registry/index.d.ts +2 -0
- package/dist/tools/registry/index.js +2 -0
- package/dist/tools/registry/service.d.ts +1 -0
- package/dist/tools/registry/service.js +1 -0
- package/dist/tools/registry/views.d.ts +1 -0
- package/dist/tools/registry/views.js +1 -0
- package/dist/tools/service.d.ts +2 -0
- package/dist/tools/service.js +1 -0
- package/dist/tools/utils.d.ts +2 -0
- package/dist/tools/utils.js +57 -0
- package/dist/tools/views.d.ts +1 -0
- package/dist/tools/views.js +1 -0
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +70 -3
- package/package.json +116 -49
- package/dist/dom/playground/process-dom.js +0 -5
- package/dist/dom/playground/test-accessibility.d.ts +0 -44
- package/dist/dom/playground/test-accessibility.js +0 -111
- /package/dist/{dom/playground/process-dom.d.ts → tools/extraction/views.js} +0 -0
package/dist/mcp/server.js
CHANGED
|
@@ -29,15 +29,16 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
29
29
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
30
30
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
31
31
|
import { z } from 'zod';
|
|
32
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
33
32
|
import { createLogger } from '../logging-config.js';
|
|
34
33
|
import { Controller as DefaultController } from '../controller/service.js';
|
|
35
34
|
import { Agent } from '../agent/service.js';
|
|
36
35
|
import { BrowserSession } from '../browser/session.js';
|
|
36
|
+
import { BrowserStateRequestEvent } from '../browser/events.js';
|
|
37
37
|
import { BrowserProfile } from '../browser/profile.js';
|
|
38
38
|
import { FileSystem } from '../filesystem/file-system.js';
|
|
39
|
-
import {
|
|
39
|
+
import { getLlmByName } from '../llm/models.js';
|
|
40
40
|
import { load_browser_use_config, get_default_llm, get_default_profile, } from '../config.js';
|
|
41
|
+
import { zodSchemaToJsonSchema } from '../llm/schema.js';
|
|
41
42
|
import { productTelemetry } from '../telemetry/service.js';
|
|
42
43
|
import { MCPServerTelemetryEvent } from '../telemetry/views.js';
|
|
43
44
|
import { get_browser_use_version } from '../utils.js';
|
|
@@ -64,6 +65,9 @@ export class MCPServer {
|
|
|
64
65
|
toolExecutionCount = 0;
|
|
65
66
|
errorCount = 0;
|
|
66
67
|
abortController = null;
|
|
68
|
+
activeSessions = new Map();
|
|
69
|
+
sessionTimeoutMinutes = 10;
|
|
70
|
+
sessionCleanupInterval = null;
|
|
67
71
|
constructor(name, version) {
|
|
68
72
|
this.server = new Server({
|
|
69
73
|
name,
|
|
@@ -75,6 +79,11 @@ export class MCPServer {
|
|
|
75
79
|
},
|
|
76
80
|
});
|
|
77
81
|
this.config = load_browser_use_config();
|
|
82
|
+
const configuredTimeout = Number(process.env.BROWSER_USE_MCP_SESSION_TIMEOUT_MINUTES ?? '10');
|
|
83
|
+
this.sessionTimeoutMinutes =
|
|
84
|
+
Number.isFinite(configuredTimeout) && configuredTimeout > 0
|
|
85
|
+
? configuredTimeout
|
|
86
|
+
: 10;
|
|
78
87
|
this.startTime = Date.now() / 1000;
|
|
79
88
|
this.setupHandlers();
|
|
80
89
|
this.registerDefaultPrompts();
|
|
@@ -94,6 +103,26 @@ export class MCPServer {
|
|
|
94
103
|
const llm = get_default_llm(this.config);
|
|
95
104
|
return llm && typeof llm === 'object' ? { ...llm } : {};
|
|
96
105
|
}
|
|
106
|
+
isPlaceholderOpenAiApiKey(apiKey) {
|
|
107
|
+
const normalized = apiKey.trim().toLowerCase();
|
|
108
|
+
return (normalized === 'your-openai-api-key-here' ||
|
|
109
|
+
normalized === 'your-openai-api-key');
|
|
110
|
+
}
|
|
111
|
+
seedOpenAiApiKeyFromConfig(llmConfig) {
|
|
112
|
+
if (typeof process.env.OPENAI_API_KEY === 'string' &&
|
|
113
|
+
process.env.OPENAI_API_KEY.trim()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const configuredApiKey = typeof llmConfig.api_key === 'string' ? llmConfig.api_key.trim() : '';
|
|
117
|
+
if (!configuredApiKey || this.isPlaceholderOpenAiApiKey(configuredApiKey)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
process.env.OPENAI_API_KEY = configuredApiKey;
|
|
121
|
+
}
|
|
122
|
+
createLlmFromModelName(modelName, llmConfig) {
|
|
123
|
+
this.seedOpenAiApiKeyFromConfig(llmConfig);
|
|
124
|
+
return getLlmByName(modelName);
|
|
125
|
+
}
|
|
97
126
|
sanitizeProfileConfig(profileConfig) {
|
|
98
127
|
const sanitized = { ...profileConfig };
|
|
99
128
|
delete sanitized.id;
|
|
@@ -104,7 +133,7 @@ export class MCPServer {
|
|
|
104
133
|
buildDirectSessionProfile(profileConfig) {
|
|
105
134
|
const merged = {
|
|
106
135
|
downloads_path: '~/Downloads/browser-use-mcp',
|
|
107
|
-
wait_between_actions: 0.
|
|
136
|
+
wait_between_actions: 0.1,
|
|
108
137
|
keep_alive: true,
|
|
109
138
|
user_data_dir: '~/.config/browseruse/profiles/default',
|
|
110
139
|
is_mobile: false,
|
|
@@ -124,6 +153,11 @@ export class MCPServer {
|
|
|
124
153
|
.map((entry) => String(entry).trim())
|
|
125
154
|
.filter(Boolean);
|
|
126
155
|
}
|
|
156
|
+
if (Array.isArray(merged.prohibited_domains)) {
|
|
157
|
+
merged.prohibited_domains = merged.prohibited_domains
|
|
158
|
+
.map((entry) => String(entry).trim())
|
|
159
|
+
.filter(Boolean);
|
|
160
|
+
}
|
|
127
161
|
return new BrowserProfile(merged);
|
|
128
162
|
}
|
|
129
163
|
buildRetryProfile(profileConfig, allowedDomains) {
|
|
@@ -147,6 +181,11 @@ export class MCPServer {
|
|
|
147
181
|
.map((entry) => String(entry).trim())
|
|
148
182
|
.filter(Boolean);
|
|
149
183
|
}
|
|
184
|
+
if (Array.isArray(merged.prohibited_domains)) {
|
|
185
|
+
merged.prohibited_domains = merged.prohibited_domains
|
|
186
|
+
.map((entry) => String(entry).trim())
|
|
187
|
+
.filter(Boolean);
|
|
188
|
+
}
|
|
150
189
|
return new BrowserProfile(merged);
|
|
151
190
|
}
|
|
152
191
|
initializeLlmForDirectTools() {
|
|
@@ -154,23 +193,15 @@ export class MCPServer {
|
|
|
154
193
|
return;
|
|
155
194
|
}
|
|
156
195
|
const llmConfig = this.getDefaultLlmConfig();
|
|
157
|
-
const configuredApiKey = typeof llmConfig.api_key === 'string' ? llmConfig.api_key.trim() : '';
|
|
158
|
-
const envApiKey = typeof process.env.OPENAI_API_KEY === 'string'
|
|
159
|
-
? process.env.OPENAI_API_KEY.trim()
|
|
160
|
-
: '';
|
|
161
|
-
const apiKey = configuredApiKey || envApiKey;
|
|
162
|
-
if (!apiKey) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
196
|
const model = typeof llmConfig.model === 'string' && llmConfig.model.trim()
|
|
166
197
|
? llmConfig.model.trim()
|
|
167
198
|
: 'gpt-4o-mini';
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
199
|
+
try {
|
|
200
|
+
this.llm = this.createLlmFromModelName(model, llmConfig);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
logger.debug(`Skipping MCP direct-tools LLM initialization for model "${model}": ${error instanceof Error ? error.message : String(error)}`);
|
|
204
|
+
}
|
|
174
205
|
}
|
|
175
206
|
initializeFileSystem(profileConfig) {
|
|
176
207
|
if (this.fileSystem) {
|
|
@@ -212,6 +243,126 @@ export class MCPServer {
|
|
|
212
243
|
}
|
|
213
244
|
return results.join('\n');
|
|
214
245
|
}
|
|
246
|
+
trackSession(session) {
|
|
247
|
+
const now = Date.now() / 1000;
|
|
248
|
+
const existing = this.activeSessions.get(session.id);
|
|
249
|
+
if (existing) {
|
|
250
|
+
existing.session = session;
|
|
251
|
+
existing.last_activity = now;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
this.activeSessions.set(session.id, {
|
|
255
|
+
session,
|
|
256
|
+
created_at: now,
|
|
257
|
+
last_activity: now,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
updateSessionActivity(session) {
|
|
261
|
+
if (!session) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const tracked = this.activeSessions.get(session.id);
|
|
265
|
+
if (!tracked) {
|
|
266
|
+
this.trackSession(session);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
tracked.last_activity = Date.now() / 1000;
|
|
270
|
+
}
|
|
271
|
+
serializeTrackedSessions() {
|
|
272
|
+
const now = Date.now() / 1000;
|
|
273
|
+
return Array.from(this.activeSessions.entries()).map(([session_id, tracked]) => ({
|
|
274
|
+
session_id,
|
|
275
|
+
created_at: tracked.created_at,
|
|
276
|
+
last_activity: tracked.last_activity,
|
|
277
|
+
age_minutes: (now - tracked.created_at) / 60,
|
|
278
|
+
active: Boolean(tracked.session?.initialized),
|
|
279
|
+
current_session: this.browserSession?.id === session_id,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
async shutdownSession(session) {
|
|
283
|
+
const withKill = session;
|
|
284
|
+
if (typeof withKill.kill === 'function') {
|
|
285
|
+
await withKill.kill();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (typeof session.stop === 'function') {
|
|
289
|
+
await session.stop();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async closeSessionById(sessionId) {
|
|
293
|
+
const tracked = this.activeSessions.get(sessionId);
|
|
294
|
+
if (!tracked) {
|
|
295
|
+
return {
|
|
296
|
+
session_id: sessionId,
|
|
297
|
+
closed: false,
|
|
298
|
+
message: `Session ${sessionId} not found`,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
await this.shutdownSession(tracked.session);
|
|
303
|
+
this.activeSessions.delete(sessionId);
|
|
304
|
+
if (this.browserSession?.id === sessionId) {
|
|
305
|
+
this.browserSession = null;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
session_id: sessionId,
|
|
309
|
+
closed: true,
|
|
310
|
+
message: `Closed session ${sessionId}`,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return {
|
|
315
|
+
session_id: sessionId,
|
|
316
|
+
closed: false,
|
|
317
|
+
message: `Failed to close session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async closeAllTrackedSessions() {
|
|
322
|
+
const sessionIds = Array.from(this.activeSessions.keys());
|
|
323
|
+
const results = await Promise.all(sessionIds.map((sessionId) => this.closeSessionById(sessionId)));
|
|
324
|
+
const closedCount = results.filter((result) => result.closed).length;
|
|
325
|
+
return {
|
|
326
|
+
closed_count: closedCount,
|
|
327
|
+
total_count: sessionIds.length,
|
|
328
|
+
results,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async cleanupExpiredSessions() {
|
|
332
|
+
const now = Date.now() / 1000;
|
|
333
|
+
const timeoutSeconds = this.sessionTimeoutMinutes * 60;
|
|
334
|
+
const expiredSessionIds = [];
|
|
335
|
+
for (const [sessionId, tracked] of this.activeSessions.entries()) {
|
|
336
|
+
if (now - tracked.last_activity > timeoutSeconds) {
|
|
337
|
+
expiredSessionIds.push(sessionId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const sessionId of expiredSessionIds) {
|
|
341
|
+
const result = await this.closeSessionById(sessionId);
|
|
342
|
+
if (!result.closed) {
|
|
343
|
+
logger.warning(result.message);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
startSessionCleanupLoop() {
|
|
348
|
+
if (this.sessionCleanupInterval) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
this.sessionCleanupInterval = setInterval(() => {
|
|
352
|
+
this.cleanupExpiredSessions().catch((error) => {
|
|
353
|
+
logger.warning(`MCP session cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
354
|
+
});
|
|
355
|
+
}, 120_000);
|
|
356
|
+
// Do not keep the Node process alive solely because of the cleanup loop.
|
|
357
|
+
this.sessionCleanupInterval.unref?.();
|
|
358
|
+
}
|
|
359
|
+
stopSessionCleanupLoop() {
|
|
360
|
+
if (!this.sessionCleanupInterval) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
clearInterval(this.sessionCleanupInterval);
|
|
364
|
+
this.sessionCleanupInterval = null;
|
|
365
|
+
}
|
|
215
366
|
setupHandlers() {
|
|
216
367
|
// List available tools
|
|
217
368
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -315,19 +466,23 @@ export class MCPServer {
|
|
|
315
466
|
const profileConfig = this.getDefaultProfileConfig();
|
|
316
467
|
const profile = this.buildDirectSessionProfile(profileConfig);
|
|
317
468
|
this.browserSession = new BrowserSession({ browser_profile: profile });
|
|
469
|
+
this.trackSession(this.browserSession);
|
|
318
470
|
this.initializeLlmForDirectTools();
|
|
319
471
|
this.initializeFileSystem(profileConfig);
|
|
320
472
|
}
|
|
321
473
|
if (!this.browserSession.initialized) {
|
|
322
474
|
await this.browserSession.start();
|
|
323
475
|
}
|
|
476
|
+
this.trackSession(this.browserSession);
|
|
477
|
+
this.updateSessionActivity(this.browserSession);
|
|
324
478
|
return this.browserSession;
|
|
325
479
|
}
|
|
326
480
|
async executeControllerAction(actionName, args) {
|
|
327
481
|
const controller = await this.ensureController();
|
|
328
482
|
const browserSession = await this.ensureBrowserSession();
|
|
483
|
+
this.updateSessionActivity(browserSession);
|
|
329
484
|
if (actionName === 'extract_structured_data' && !this.llm) {
|
|
330
|
-
throw new Error('LLM not initialized
|
|
485
|
+
throw new Error('LLM not initialized. Set provider API key env vars and configure BROWSER_USE_LLM_MODEL/DEFAULT_LLM to a supported model.');
|
|
331
486
|
}
|
|
332
487
|
return await controller.registry.execute_action(actionName, args, {
|
|
333
488
|
browser_session: browserSession,
|
|
@@ -409,13 +564,26 @@ export class MCPServer {
|
|
|
409
564
|
this.registerTool('browser_get_state', 'Get the current state of the page including interactive elements', z
|
|
410
565
|
.object({
|
|
411
566
|
include_screenshot: z.boolean().default(false),
|
|
567
|
+
include_recent_events: z.boolean().default(false),
|
|
412
568
|
})
|
|
413
|
-
.default({ include_screenshot: false }), async (args) => {
|
|
569
|
+
.default({ include_screenshot: false, include_recent_events: false }), async (args) => {
|
|
414
570
|
const browserSession = await this.ensureBrowserSession();
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
571
|
+
let state = null;
|
|
572
|
+
if (typeof browserSession.dispatch_browser_event === 'function') {
|
|
573
|
+
const dispatchResult = await browserSession.dispatch_browser_event(new BrowserStateRequestEvent({
|
|
574
|
+
include_dom: true,
|
|
575
|
+
include_screenshot: Boolean(args?.include_screenshot),
|
|
576
|
+
include_recent_events: Boolean(args?.include_recent_events),
|
|
577
|
+
}));
|
|
578
|
+
state = dispatchResult?.event?.event_result ?? null;
|
|
579
|
+
}
|
|
580
|
+
if (!state) {
|
|
581
|
+
state = await browserSession.get_browser_state_with_recovery({
|
|
582
|
+
include_screenshot: Boolean(args?.include_screenshot),
|
|
583
|
+
include_recent_events: Boolean(args?.include_recent_events),
|
|
584
|
+
cache_clickable_elements_hashes: true,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
419
587
|
return {
|
|
420
588
|
url: state.url,
|
|
421
589
|
title: state.title,
|
|
@@ -425,6 +593,10 @@ export class MCPServer {
|
|
|
425
593
|
pixels_below: state.pixels_below,
|
|
426
594
|
browser_errors: state.browser_errors,
|
|
427
595
|
loading_status: state.loading_status,
|
|
596
|
+
recent_events: state.recent_events,
|
|
597
|
+
pending_network_requests: state.pending_network_requests,
|
|
598
|
+
pagination_buttons: state.pagination_buttons,
|
|
599
|
+
closed_popup_messages: state.closed_popup_messages,
|
|
428
600
|
screenshot: state.screenshot,
|
|
429
601
|
interactive_elements: state.element_tree.clickable_elements_to_string(),
|
|
430
602
|
interactive_count: Object.keys(state.selector_map ?? {}).length,
|
|
@@ -450,16 +622,57 @@ export class MCPServer {
|
|
|
450
622
|
const browserSession = await this.ensureBrowserSession();
|
|
451
623
|
return browserSession.get_tabs_info();
|
|
452
624
|
});
|
|
453
|
-
this.registerTool('browser_switch_tab', 'Switch to a tab by
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
page_id:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
625
|
+
this.registerTool('browser_switch_tab', 'Switch to a tab by tab_id (or page_id/tab_index for compatibility)', z
|
|
626
|
+
.object({
|
|
627
|
+
tab_id: z.string().trim().length(4).optional(),
|
|
628
|
+
page_id: z.number().int().optional(),
|
|
629
|
+
tab_index: z.number().int().optional(),
|
|
630
|
+
})
|
|
631
|
+
.refine((value) => value.tab_id != null ||
|
|
632
|
+
value.page_id != null ||
|
|
633
|
+
value.tab_index != null, { message: 'Provide tab_id, page_id, or tab_index' }), async (args) => {
|
|
634
|
+
const tabId = typeof args?.tab_id === 'string' && args.tab_id.trim()
|
|
635
|
+
? args.tab_id.trim()
|
|
636
|
+
: null;
|
|
637
|
+
if (tabId) {
|
|
638
|
+
return this.executeControllerAction('switch_tab', { tab_id: tabId });
|
|
639
|
+
}
|
|
640
|
+
const pageId = typeof args?.page_id === 'number' && Number.isFinite(args.page_id)
|
|
641
|
+
? Number(args.page_id)
|
|
642
|
+
: Number(args?.tab_index);
|
|
643
|
+
return this.executeControllerAction('switch_tab', {
|
|
644
|
+
page_id: pageId,
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
this.registerTool('browser_close_tab', 'Close a tab by tab_id (or page_id/tab_index for compatibility)', z
|
|
648
|
+
.object({
|
|
649
|
+
tab_id: z.string().trim().length(4).optional(),
|
|
650
|
+
page_id: z.number().int().optional(),
|
|
651
|
+
tab_index: z.number().int().optional(),
|
|
652
|
+
})
|
|
653
|
+
.refine((value) => value.tab_id != null ||
|
|
654
|
+
value.page_id != null ||
|
|
655
|
+
value.tab_index != null, { message: 'Provide tab_id, page_id, or tab_index' }), async (args) => {
|
|
656
|
+
const tabId = typeof args?.tab_id === 'string' && args.tab_id.trim()
|
|
657
|
+
? args.tab_id.trim()
|
|
658
|
+
: null;
|
|
659
|
+
if (tabId) {
|
|
660
|
+
return this.executeControllerAction('close_tab', { tab_id: tabId });
|
|
661
|
+
}
|
|
662
|
+
const pageId = typeof args?.page_id === 'number' && Number.isFinite(args.page_id)
|
|
663
|
+
? Number(args.page_id)
|
|
664
|
+
: Number(args?.tab_index);
|
|
665
|
+
return this.executeControllerAction('close_tab', {
|
|
666
|
+
page_id: pageId,
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
this.registerTool('browser_list_sessions', 'List active browser sessions managed by this MCP server', z.object({}).strict(), async () => {
|
|
670
|
+
return this.serializeTrackedSessions();
|
|
671
|
+
});
|
|
672
|
+
this.registerTool('browser_close_session', 'Close a specific browser session by session_id', z.object({
|
|
673
|
+
session_id: z.string().trim().min(1),
|
|
674
|
+
}), async (args) => this.closeSessionById(String(args?.session_id ?? '')));
|
|
675
|
+
this.registerTool('browser_close_all', 'Close all active browser sessions managed by this MCP server', z.object({}).strict(), async () => this.closeAllTrackedSessions());
|
|
463
676
|
this.registerTool('retry_with_browser_use_agent', 'Retry a complex task with the browser-use autonomous agent', z.object({
|
|
464
677
|
task: z.string(),
|
|
465
678
|
max_steps: z.number().int().optional().default(100),
|
|
@@ -480,26 +693,17 @@ export class MCPServer {
|
|
|
480
693
|
.filter(Boolean)
|
|
481
694
|
: [];
|
|
482
695
|
const llmConfig = this.getDefaultLlmConfig();
|
|
483
|
-
const configuredApiKey = typeof llmConfig.api_key === 'string' ? llmConfig.api_key.trim() : '';
|
|
484
|
-
const envApiKey = typeof process.env.OPENAI_API_KEY === 'string'
|
|
485
|
-
? process.env.OPENAI_API_KEY.trim()
|
|
486
|
-
: '';
|
|
487
|
-
const apiKey = configuredApiKey || envApiKey;
|
|
488
|
-
if (!apiKey) {
|
|
489
|
-
return 'Error: OPENAI_API_KEY not set in config or environment';
|
|
490
|
-
}
|
|
491
696
|
const configuredModel = typeof llmConfig.model === 'string' && llmConfig.model.trim()
|
|
492
697
|
? llmConfig.model.trim()
|
|
493
698
|
: 'gpt-4o';
|
|
494
699
|
const llmModel = model || configuredModel;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
});
|
|
700
|
+
let llm;
|
|
701
|
+
try {
|
|
702
|
+
llm = this.createLlmFromModelName(llmModel, llmConfig);
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
return `Error: Failed to initialize LLM "${llmModel}": ${error instanceof Error ? error.message : String(error)}`;
|
|
706
|
+
}
|
|
503
707
|
const profileConfig = this.getDefaultProfileConfig();
|
|
504
708
|
const profile = this.buildRetryProfile(profileConfig, allowedDomains);
|
|
505
709
|
const retryBrowserSession = new BrowserSession({
|
|
@@ -596,7 +800,7 @@ export class MCPServer {
|
|
|
596
800
|
this.tools[name] = {
|
|
597
801
|
description,
|
|
598
802
|
inputSchema: inputSchema instanceof z.ZodType
|
|
599
|
-
?
|
|
803
|
+
? zodSchemaToJsonSchema(inputSchema)
|
|
600
804
|
: inputSchema,
|
|
601
805
|
handler,
|
|
602
806
|
};
|
|
@@ -624,7 +828,10 @@ export class MCPServer {
|
|
|
624
828
|
*/
|
|
625
829
|
async initBrowserSession(browserSession) {
|
|
626
830
|
this.browserSession = browserSession;
|
|
831
|
+
this.trackSession(browserSession);
|
|
627
832
|
await this.browserSession.start();
|
|
833
|
+
this.trackSession(this.browserSession);
|
|
834
|
+
this.updateSessionActivity(this.browserSession);
|
|
628
835
|
logger.info('Browser session initialized');
|
|
629
836
|
}
|
|
630
837
|
/**
|
|
@@ -644,6 +851,7 @@ export class MCPServer {
|
|
|
644
851
|
const transport = new StdioServerTransport();
|
|
645
852
|
await this.server.connect(transport);
|
|
646
853
|
this.isRunning = true;
|
|
854
|
+
this.startSessionCleanupLoop();
|
|
647
855
|
logger.info(`🔌 MCP Server started (${this.getToolCount()} tools, ${this.getPromptCount()} prompts registered)`);
|
|
648
856
|
}
|
|
649
857
|
catch (error) {
|
|
@@ -662,14 +870,15 @@ export class MCPServer {
|
|
|
662
870
|
}
|
|
663
871
|
try {
|
|
664
872
|
this.isRunning = false;
|
|
873
|
+
this.stopSessionCleanupLoop();
|
|
665
874
|
// Cancel any pending operations
|
|
666
875
|
if (this.abortController) {
|
|
667
876
|
this.abortController.abort();
|
|
668
877
|
this.abortController = null;
|
|
669
878
|
}
|
|
670
879
|
// Close browser session if active
|
|
671
|
-
if (this.
|
|
672
|
-
await this.
|
|
880
|
+
if (this.activeSessions.size > 0) {
|
|
881
|
+
await this.closeAllTrackedSessions();
|
|
673
882
|
this.browserSession = null;
|
|
674
883
|
logger.info('Browser session closed');
|
|
675
884
|
}
|
package/dist/observability.js
CHANGED
|
@@ -12,7 +12,7 @@ try {
|
|
|
12
12
|
lmnrObserve = (options) => lmnr.observe(options);
|
|
13
13
|
lmnrAvailable = true;
|
|
14
14
|
if (process.env.BROWSER_USE_VERBOSE_OBSERVABILITY?.toLowerCase() === 'true') {
|
|
15
|
-
logger.
|
|
15
|
+
logger.debug('Lmnr is available for observability');
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -20,7 +20,7 @@ catch (error) {
|
|
|
20
20
|
lmnrObserve = null;
|
|
21
21
|
lmnrAvailable = false;
|
|
22
22
|
if (process.env.BROWSER_USE_VERBOSE_OBSERVABILITY?.toLowerCase() === 'true') {
|
|
23
|
-
logger.
|
|
23
|
+
logger.debug(`Lmnr is not available for observability (${error.message})`);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
const isDebugModeEnv = () => process.env.LMNR_LOGGING_LEVEL?.toLowerCase() === 'debug';
|
|
@@ -34,14 +34,20 @@ const normalizeOptions = (options = {}) => ({
|
|
|
34
34
|
...options,
|
|
35
35
|
});
|
|
36
36
|
export const observe = (options = {}) => {
|
|
37
|
-
const normalized = normalizeOptions(
|
|
37
|
+
const normalized = normalizeOptions({
|
|
38
|
+
tags: ['observe', 'observe_debug'],
|
|
39
|
+
...options,
|
|
40
|
+
});
|
|
38
41
|
if (lmnrAvailable && lmnrObserve) {
|
|
39
42
|
return lmnrObserve(normalized);
|
|
40
43
|
}
|
|
41
44
|
return createNoopDecorator();
|
|
42
45
|
};
|
|
43
46
|
export const observeDebug = (options = {}) => {
|
|
44
|
-
const normalized = normalizeOptions(
|
|
47
|
+
const normalized = normalizeOptions({
|
|
48
|
+
tags: ['observe_debug'],
|
|
49
|
+
...options,
|
|
50
|
+
});
|
|
45
51
|
if (lmnrAvailable && lmnrObserve && isDebugModeEnv()) {
|
|
46
52
|
return lmnrObserve(normalized);
|
|
47
53
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BrowserCreatedData, ErrorData, LogData, ResultData, SandboxError } from './views.js';
|
|
2
|
+
export interface SandboxOptions {
|
|
3
|
+
api_key?: string | null;
|
|
4
|
+
server_url?: string | null;
|
|
5
|
+
log_level?: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | string;
|
|
6
|
+
quiet?: boolean;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
cloud_profile_id?: string | null;
|
|
9
|
+
cloud_proxy_country_code?: string | null;
|
|
10
|
+
cloud_timeout?: number | null;
|
|
11
|
+
fetch_impl?: typeof fetch;
|
|
12
|
+
on_browser_created?: (event: BrowserCreatedData) => void | Promise<void>;
|
|
13
|
+
on_instance_ready?: () => void | Promise<void>;
|
|
14
|
+
on_log?: (event: LogData) => void | Promise<void>;
|
|
15
|
+
on_result?: (event: ResultData) => void | Promise<void>;
|
|
16
|
+
on_error?: (event: ErrorData) => void | Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare const sandbox: (options?: SandboxOptions) => <TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult> | TResult) => (...args: TArgs) => Promise<TResult>;
|
|
19
|
+
export { SandboxError };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createLogger } from '../logging-config.js';
|
|
2
|
+
import { BrowserCreatedData, ErrorData, LogData, ResultData, SandboxError, SSEEvent, SSEEventType, } from './views.js';
|
|
3
|
+
const logger = createLogger('browser_use.sandbox');
|
|
4
|
+
const defaultServerUrl = 'https://sandbox.api.browser-use.com/sandbox-stream';
|
|
5
|
+
const maybeInvoke = async (callback, data) => {
|
|
6
|
+
if (!callback) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
await callback(data);
|
|
10
|
+
};
|
|
11
|
+
const parseSSEChunks = async (response, onEvent) => {
|
|
12
|
+
const processLine = async (line) => {
|
|
13
|
+
if (!line.startsWith('data:')) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const jsonPayload = line.slice(5).trim();
|
|
17
|
+
if (!jsonPayload) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
await onEvent(SSEEvent.from_json(jsonPayload));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Ignore malformed SSE entries.
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
if (!response.body) {
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
for (const line of text.split(/\r?\n/)) {
|
|
30
|
+
await processLine(line);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const reader = response.body.getReader();
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
let buffer = '';
|
|
37
|
+
while (true) {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (done) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
buffer += decoder.decode(value, { stream: true });
|
|
43
|
+
const lines = buffer.split(/\r?\n/);
|
|
44
|
+
buffer = lines.pop() ?? '';
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
await processLine(line);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (buffer) {
|
|
50
|
+
await processLine(buffer);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const shouldUseRemoteSandbox = (options) => Boolean(options.server_url ||
|
|
54
|
+
options.api_key ||
|
|
55
|
+
options.cloud_profile_id ||
|
|
56
|
+
options.cloud_proxy_country_code ||
|
|
57
|
+
options.cloud_timeout);
|
|
58
|
+
export const sandbox = (options = {}) => (fn) => async (...args) => {
|
|
59
|
+
const remoteMode = shouldUseRemoteSandbox(options);
|
|
60
|
+
if (!remoteMode) {
|
|
61
|
+
return await fn(...args);
|
|
62
|
+
}
|
|
63
|
+
const apiKey = options.api_key?.trim() || process.env.BROWSER_USE_API_KEY?.trim();
|
|
64
|
+
if (!apiKey) {
|
|
65
|
+
throw new SandboxError('BROWSER_USE_API_KEY is required for remote sandbox execution');
|
|
66
|
+
}
|
|
67
|
+
const fetch_impl = options.fetch_impl ?? fetch;
|
|
68
|
+
const server_url = options.server_url ?? defaultServerUrl;
|
|
69
|
+
const payload = {
|
|
70
|
+
code: Buffer.from(String(fn)).toString('base64'),
|
|
71
|
+
args: Buffer.from(JSON.stringify(args)).toString('base64'),
|
|
72
|
+
env: {
|
|
73
|
+
LOG_LEVEL: String(options.log_level ?? 'INFO').toUpperCase(),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
if (options.cloud_profile_id != null) {
|
|
77
|
+
payload.cloud_profile_id = options.cloud_profile_id;
|
|
78
|
+
}
|
|
79
|
+
if (options.cloud_proxy_country_code != null) {
|
|
80
|
+
payload.cloud_proxy_country_code = options.cloud_proxy_country_code;
|
|
81
|
+
}
|
|
82
|
+
if (options.cloud_timeout != null) {
|
|
83
|
+
payload.cloud_timeout = options.cloud_timeout;
|
|
84
|
+
}
|
|
85
|
+
const response = await fetch_impl(server_url, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'X-API-Key': apiKey,
|
|
89
|
+
'Content-Type': 'application/json',
|
|
90
|
+
...(options.headers ?? {}),
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(payload),
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new SandboxError(`Sandbox request failed with status ${response.status}`);
|
|
96
|
+
}
|
|
97
|
+
let executionResult = null;
|
|
98
|
+
let hasResult = false;
|
|
99
|
+
await parseSSEChunks(response, async (event) => {
|
|
100
|
+
if (event.type === SSEEventType.BROWSER_CREATED &&
|
|
101
|
+
event.data instanceof BrowserCreatedData) {
|
|
102
|
+
await maybeInvoke(options.on_browser_created, event.data);
|
|
103
|
+
if (!options.quiet && event.data.live_url) {
|
|
104
|
+
logger.info(`🔗 Live URL: ${event.data.live_url}`);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (event.type === SSEEventType.INSTANCE_READY) {
|
|
109
|
+
await maybeInvoke(options.on_instance_ready, undefined);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (event.type === SSEEventType.LOG && event.data instanceof LogData) {
|
|
113
|
+
await maybeInvoke(options.on_log, event.data);
|
|
114
|
+
if (!options.quiet) {
|
|
115
|
+
logger.info(event.data.message);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (event.type === SSEEventType.RESULT &&
|
|
120
|
+
event.data instanceof ResultData) {
|
|
121
|
+
await maybeInvoke(options.on_result, event.data);
|
|
122
|
+
if (!event.data.execution_response.success) {
|
|
123
|
+
throw new SandboxError(`Execution failed: ${event.data.execution_response.error ?? 'unknown error'}`);
|
|
124
|
+
}
|
|
125
|
+
executionResult = event.data.execution_response.result;
|
|
126
|
+
hasResult = true;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (event.type === SSEEventType.ERROR &&
|
|
130
|
+
event.data instanceof ErrorData) {
|
|
131
|
+
await maybeInvoke(options.on_error, event.data);
|
|
132
|
+
throw new SandboxError(`Execution failed: ${event.data.error || 'unknown error'}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (!hasResult) {
|
|
136
|
+
throw new SandboxError('No result received from sandbox execution');
|
|
137
|
+
}
|
|
138
|
+
return executionResult;
|
|
139
|
+
};
|
|
140
|
+
export { SandboxError };
|