browser-use 0.1.0 → 0.3.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 +301 -636
- 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 +99 -40
- package/dist/agent/service.js +2282 -474
- package/dist/agent/variable-detector.d.ts +12 -0
- package/dist/agent/variable-detector.js +211 -0
- package/dist/agent/views.d.ts +237 -17
- package/dist/agent/views.js +446 -32
- 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 +8 -2
- 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 +100 -8
- package/dist/browser/session.js +1102 -63
- package/dist/browser/types.d.ts +0 -2
- 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 +41 -0
- package/dist/cli.js +820 -10
- 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 +13 -0
- package/dist/config.js +69 -3
- 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 +1849 -288
- 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/filesystem/file-system.d.ts +18 -0
- package/dist/filesystem/file-system.js +530 -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 +5 -5
- 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 +129 -40
- 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 -53
- 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 +268 -63
- 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/messages.d.ts +4 -4
- 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 +81 -1
- 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 +21 -15
- package/dist/mcp/controller.d.ts +42 -3
- package/dist/mcp/controller.js +56 -31
- package/dist/mcp/server.d.ts +14 -0
- package/dist/mcp/server.js +257 -51
- 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 +30 -12
- 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 +265 -28
- 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,20 @@ export class MCPServer {
|
|
|
94
103
|
const llm = get_default_llm(this.config);
|
|
95
104
|
return llm && typeof llm === 'object' ? { ...llm } : {};
|
|
96
105
|
}
|
|
106
|
+
seedOpenAiApiKeyFromConfig(llmConfig) {
|
|
107
|
+
if (typeof process.env.OPENAI_API_KEY === 'string' &&
|
|
108
|
+
process.env.OPENAI_API_KEY.trim()) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const configuredApiKey = typeof llmConfig.api_key === 'string' ? llmConfig.api_key.trim() : '';
|
|
112
|
+
if (configuredApiKey) {
|
|
113
|
+
process.env.OPENAI_API_KEY = configuredApiKey;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
createLlmFromModelName(modelName, llmConfig) {
|
|
117
|
+
this.seedOpenAiApiKeyFromConfig(llmConfig);
|
|
118
|
+
return getLlmByName(modelName);
|
|
119
|
+
}
|
|
97
120
|
sanitizeProfileConfig(profileConfig) {
|
|
98
121
|
const sanitized = { ...profileConfig };
|
|
99
122
|
delete sanitized.id;
|
|
@@ -104,7 +127,7 @@ export class MCPServer {
|
|
|
104
127
|
buildDirectSessionProfile(profileConfig) {
|
|
105
128
|
const merged = {
|
|
106
129
|
downloads_path: '~/Downloads/browser-use-mcp',
|
|
107
|
-
wait_between_actions: 0.
|
|
130
|
+
wait_between_actions: 0.1,
|
|
108
131
|
keep_alive: true,
|
|
109
132
|
user_data_dir: '~/.config/browseruse/profiles/default',
|
|
110
133
|
is_mobile: false,
|
|
@@ -124,6 +147,11 @@ export class MCPServer {
|
|
|
124
147
|
.map((entry) => String(entry).trim())
|
|
125
148
|
.filter(Boolean);
|
|
126
149
|
}
|
|
150
|
+
if (Array.isArray(merged.prohibited_domains)) {
|
|
151
|
+
merged.prohibited_domains = merged.prohibited_domains
|
|
152
|
+
.map((entry) => String(entry).trim())
|
|
153
|
+
.filter(Boolean);
|
|
154
|
+
}
|
|
127
155
|
return new BrowserProfile(merged);
|
|
128
156
|
}
|
|
129
157
|
buildRetryProfile(profileConfig, allowedDomains) {
|
|
@@ -147,6 +175,11 @@ export class MCPServer {
|
|
|
147
175
|
.map((entry) => String(entry).trim())
|
|
148
176
|
.filter(Boolean);
|
|
149
177
|
}
|
|
178
|
+
if (Array.isArray(merged.prohibited_domains)) {
|
|
179
|
+
merged.prohibited_domains = merged.prohibited_domains
|
|
180
|
+
.map((entry) => String(entry).trim())
|
|
181
|
+
.filter(Boolean);
|
|
182
|
+
}
|
|
150
183
|
return new BrowserProfile(merged);
|
|
151
184
|
}
|
|
152
185
|
initializeLlmForDirectTools() {
|
|
@@ -154,23 +187,15 @@ export class MCPServer {
|
|
|
154
187
|
return;
|
|
155
188
|
}
|
|
156
189
|
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
190
|
const model = typeof llmConfig.model === 'string' && llmConfig.model.trim()
|
|
166
191
|
? llmConfig.model.trim()
|
|
167
192
|
: 'gpt-4o-mini';
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
193
|
+
try {
|
|
194
|
+
this.llm = this.createLlmFromModelName(model, llmConfig);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
logger.debug(`Skipping MCP direct-tools LLM initialization for model "${model}": ${error instanceof Error ? error.message : String(error)}`);
|
|
198
|
+
}
|
|
174
199
|
}
|
|
175
200
|
initializeFileSystem(profileConfig) {
|
|
176
201
|
if (this.fileSystem) {
|
|
@@ -183,7 +208,8 @@ export class MCPServer {
|
|
|
183
208
|
}
|
|
184
209
|
formatRetryResult(history) {
|
|
185
210
|
const results = [];
|
|
186
|
-
const steps = Array.isArray(history?.history) ||
|
|
211
|
+
const steps = Array.isArray(history?.history) ||
|
|
212
|
+
typeof history?.number_of_steps === 'function'
|
|
187
213
|
? typeof history?.number_of_steps === 'function'
|
|
188
214
|
? history.number_of_steps()
|
|
189
215
|
: history.history.length
|
|
@@ -211,6 +237,126 @@ export class MCPServer {
|
|
|
211
237
|
}
|
|
212
238
|
return results.join('\n');
|
|
213
239
|
}
|
|
240
|
+
trackSession(session) {
|
|
241
|
+
const now = Date.now() / 1000;
|
|
242
|
+
const existing = this.activeSessions.get(session.id);
|
|
243
|
+
if (existing) {
|
|
244
|
+
existing.session = session;
|
|
245
|
+
existing.last_activity = now;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.activeSessions.set(session.id, {
|
|
249
|
+
session,
|
|
250
|
+
created_at: now,
|
|
251
|
+
last_activity: now,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
updateSessionActivity(session) {
|
|
255
|
+
if (!session) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const tracked = this.activeSessions.get(session.id);
|
|
259
|
+
if (!tracked) {
|
|
260
|
+
this.trackSession(session);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
tracked.last_activity = Date.now() / 1000;
|
|
264
|
+
}
|
|
265
|
+
serializeTrackedSessions() {
|
|
266
|
+
const now = Date.now() / 1000;
|
|
267
|
+
return Array.from(this.activeSessions.entries()).map(([session_id, tracked]) => ({
|
|
268
|
+
session_id,
|
|
269
|
+
created_at: tracked.created_at,
|
|
270
|
+
last_activity: tracked.last_activity,
|
|
271
|
+
age_minutes: (now - tracked.created_at) / 60,
|
|
272
|
+
active: Boolean(tracked.session?.initialized),
|
|
273
|
+
current_session: this.browserSession?.id === session_id,
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
async shutdownSession(session) {
|
|
277
|
+
const withKill = session;
|
|
278
|
+
if (typeof withKill.kill === 'function') {
|
|
279
|
+
await withKill.kill();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (typeof session.stop === 'function') {
|
|
283
|
+
await session.stop();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async closeSessionById(sessionId) {
|
|
287
|
+
const tracked = this.activeSessions.get(sessionId);
|
|
288
|
+
if (!tracked) {
|
|
289
|
+
return {
|
|
290
|
+
session_id: sessionId,
|
|
291
|
+
closed: false,
|
|
292
|
+
message: `Session ${sessionId} not found`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
await this.shutdownSession(tracked.session);
|
|
297
|
+
this.activeSessions.delete(sessionId);
|
|
298
|
+
if (this.browserSession?.id === sessionId) {
|
|
299
|
+
this.browserSession = null;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
session_id: sessionId,
|
|
303
|
+
closed: true,
|
|
304
|
+
message: `Closed session ${sessionId}`,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return {
|
|
309
|
+
session_id: sessionId,
|
|
310
|
+
closed: false,
|
|
311
|
+
message: `Failed to close session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async closeAllTrackedSessions() {
|
|
316
|
+
const sessionIds = Array.from(this.activeSessions.keys());
|
|
317
|
+
const results = await Promise.all(sessionIds.map((sessionId) => this.closeSessionById(sessionId)));
|
|
318
|
+
const closedCount = results.filter((result) => result.closed).length;
|
|
319
|
+
return {
|
|
320
|
+
closed_count: closedCount,
|
|
321
|
+
total_count: sessionIds.length,
|
|
322
|
+
results,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
async cleanupExpiredSessions() {
|
|
326
|
+
const now = Date.now() / 1000;
|
|
327
|
+
const timeoutSeconds = this.sessionTimeoutMinutes * 60;
|
|
328
|
+
const expiredSessionIds = [];
|
|
329
|
+
for (const [sessionId, tracked] of this.activeSessions.entries()) {
|
|
330
|
+
if (now - tracked.last_activity > timeoutSeconds) {
|
|
331
|
+
expiredSessionIds.push(sessionId);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const sessionId of expiredSessionIds) {
|
|
335
|
+
const result = await this.closeSessionById(sessionId);
|
|
336
|
+
if (!result.closed) {
|
|
337
|
+
logger.warning(result.message);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
startSessionCleanupLoop() {
|
|
342
|
+
if (this.sessionCleanupInterval) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
this.sessionCleanupInterval = setInterval(() => {
|
|
346
|
+
this.cleanupExpiredSessions().catch((error) => {
|
|
347
|
+
logger.warning(`MCP session cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
348
|
+
});
|
|
349
|
+
}, 120_000);
|
|
350
|
+
// Do not keep the Node process alive solely because of the cleanup loop.
|
|
351
|
+
this.sessionCleanupInterval.unref?.();
|
|
352
|
+
}
|
|
353
|
+
stopSessionCleanupLoop() {
|
|
354
|
+
if (!this.sessionCleanupInterval) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
clearInterval(this.sessionCleanupInterval);
|
|
358
|
+
this.sessionCleanupInterval = null;
|
|
359
|
+
}
|
|
214
360
|
setupHandlers() {
|
|
215
361
|
// List available tools
|
|
216
362
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -314,19 +460,23 @@ export class MCPServer {
|
|
|
314
460
|
const profileConfig = this.getDefaultProfileConfig();
|
|
315
461
|
const profile = this.buildDirectSessionProfile(profileConfig);
|
|
316
462
|
this.browserSession = new BrowserSession({ browser_profile: profile });
|
|
463
|
+
this.trackSession(this.browserSession);
|
|
317
464
|
this.initializeLlmForDirectTools();
|
|
318
465
|
this.initializeFileSystem(profileConfig);
|
|
319
466
|
}
|
|
320
467
|
if (!this.browserSession.initialized) {
|
|
321
468
|
await this.browserSession.start();
|
|
322
469
|
}
|
|
470
|
+
this.trackSession(this.browserSession);
|
|
471
|
+
this.updateSessionActivity(this.browserSession);
|
|
323
472
|
return this.browserSession;
|
|
324
473
|
}
|
|
325
474
|
async executeControllerAction(actionName, args) {
|
|
326
475
|
const controller = await this.ensureController();
|
|
327
476
|
const browserSession = await this.ensureBrowserSession();
|
|
477
|
+
this.updateSessionActivity(browserSession);
|
|
328
478
|
if (actionName === 'extract_structured_data' && !this.llm) {
|
|
329
|
-
throw new Error('LLM not initialized
|
|
479
|
+
throw new Error('LLM not initialized. Set provider API key env vars and configure BROWSER_USE_LLM_MODEL/DEFAULT_LLM to a supported model.');
|
|
330
480
|
}
|
|
331
481
|
return await controller.registry.execute_action(actionName, args, {
|
|
332
482
|
browser_session: browserSession,
|
|
@@ -408,13 +558,26 @@ export class MCPServer {
|
|
|
408
558
|
this.registerTool('browser_get_state', 'Get the current state of the page including interactive elements', z
|
|
409
559
|
.object({
|
|
410
560
|
include_screenshot: z.boolean().default(false),
|
|
561
|
+
include_recent_events: z.boolean().default(false),
|
|
411
562
|
})
|
|
412
|
-
.default({ include_screenshot: false }), async (args) => {
|
|
563
|
+
.default({ include_screenshot: false, include_recent_events: false }), async (args) => {
|
|
413
564
|
const browserSession = await this.ensureBrowserSession();
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
565
|
+
let state = null;
|
|
566
|
+
if (typeof browserSession.dispatch_browser_event === 'function') {
|
|
567
|
+
const dispatchResult = await browserSession.dispatch_browser_event(new BrowserStateRequestEvent({
|
|
568
|
+
include_dom: true,
|
|
569
|
+
include_screenshot: Boolean(args?.include_screenshot),
|
|
570
|
+
include_recent_events: Boolean(args?.include_recent_events),
|
|
571
|
+
}));
|
|
572
|
+
state = dispatchResult?.event?.event_result ?? null;
|
|
573
|
+
}
|
|
574
|
+
if (!state) {
|
|
575
|
+
state = await browserSession.get_browser_state_with_recovery({
|
|
576
|
+
include_screenshot: Boolean(args?.include_screenshot),
|
|
577
|
+
include_recent_events: Boolean(args?.include_recent_events),
|
|
578
|
+
cache_clickable_elements_hashes: true,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
418
581
|
return {
|
|
419
582
|
url: state.url,
|
|
420
583
|
title: state.title,
|
|
@@ -424,6 +587,10 @@ export class MCPServer {
|
|
|
424
587
|
pixels_below: state.pixels_below,
|
|
425
588
|
browser_errors: state.browser_errors,
|
|
426
589
|
loading_status: state.loading_status,
|
|
590
|
+
recent_events: state.recent_events,
|
|
591
|
+
pending_network_requests: state.pending_network_requests,
|
|
592
|
+
pagination_buttons: state.pagination_buttons,
|
|
593
|
+
closed_popup_messages: state.closed_popup_messages,
|
|
427
594
|
screenshot: state.screenshot,
|
|
428
595
|
interactive_elements: state.element_tree.clickable_elements_to_string(),
|
|
429
596
|
interactive_count: Object.keys(state.selector_map ?? {}).length,
|
|
@@ -449,16 +616,57 @@ export class MCPServer {
|
|
|
449
616
|
const browserSession = await this.ensureBrowserSession();
|
|
450
617
|
return browserSession.get_tabs_info();
|
|
451
618
|
});
|
|
452
|
-
this.registerTool('browser_switch_tab', 'Switch to a tab by
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
page_id:
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
619
|
+
this.registerTool('browser_switch_tab', 'Switch to a tab by tab_id (or page_id/tab_index for compatibility)', z
|
|
620
|
+
.object({
|
|
621
|
+
tab_id: z.string().trim().length(4).optional(),
|
|
622
|
+
page_id: z.number().int().optional(),
|
|
623
|
+
tab_index: z.number().int().optional(),
|
|
624
|
+
})
|
|
625
|
+
.refine((value) => value.tab_id != null ||
|
|
626
|
+
value.page_id != null ||
|
|
627
|
+
value.tab_index != null, { message: 'Provide tab_id, page_id, or tab_index' }), async (args) => {
|
|
628
|
+
const tabId = typeof args?.tab_id === 'string' && args.tab_id.trim()
|
|
629
|
+
? args.tab_id.trim()
|
|
630
|
+
: null;
|
|
631
|
+
if (tabId) {
|
|
632
|
+
return this.executeControllerAction('switch_tab', { tab_id: tabId });
|
|
633
|
+
}
|
|
634
|
+
const pageId = typeof args?.page_id === 'number' && Number.isFinite(args.page_id)
|
|
635
|
+
? Number(args.page_id)
|
|
636
|
+
: Number(args?.tab_index);
|
|
637
|
+
return this.executeControllerAction('switch_tab', {
|
|
638
|
+
page_id: pageId,
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
this.registerTool('browser_close_tab', 'Close a tab by tab_id (or page_id/tab_index for compatibility)', z
|
|
642
|
+
.object({
|
|
643
|
+
tab_id: z.string().trim().length(4).optional(),
|
|
644
|
+
page_id: z.number().int().optional(),
|
|
645
|
+
tab_index: z.number().int().optional(),
|
|
646
|
+
})
|
|
647
|
+
.refine((value) => value.tab_id != null ||
|
|
648
|
+
value.page_id != null ||
|
|
649
|
+
value.tab_index != null, { message: 'Provide tab_id, page_id, or tab_index' }), async (args) => {
|
|
650
|
+
const tabId = typeof args?.tab_id === 'string' && args.tab_id.trim()
|
|
651
|
+
? args.tab_id.trim()
|
|
652
|
+
: null;
|
|
653
|
+
if (tabId) {
|
|
654
|
+
return this.executeControllerAction('close_tab', { tab_id: tabId });
|
|
655
|
+
}
|
|
656
|
+
const pageId = typeof args?.page_id === 'number' && Number.isFinite(args.page_id)
|
|
657
|
+
? Number(args.page_id)
|
|
658
|
+
: Number(args?.tab_index);
|
|
659
|
+
return this.executeControllerAction('close_tab', {
|
|
660
|
+
page_id: pageId,
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
this.registerTool('browser_list_sessions', 'List active browser sessions managed by this MCP server', z.object({}).strict(), async () => {
|
|
664
|
+
return this.serializeTrackedSessions();
|
|
665
|
+
});
|
|
666
|
+
this.registerTool('browser_close_session', 'Close a specific browser session by session_id', z.object({
|
|
667
|
+
session_id: z.string().trim().min(1),
|
|
668
|
+
}), async (args) => this.closeSessionById(String(args?.session_id ?? '')));
|
|
669
|
+
this.registerTool('browser_close_all', 'Close all active browser sessions managed by this MCP server', z.object({}).strict(), async () => this.closeAllTrackedSessions());
|
|
462
670
|
this.registerTool('retry_with_browser_use_agent', 'Retry a complex task with the browser-use autonomous agent', z.object({
|
|
463
671
|
task: z.string(),
|
|
464
672
|
max_steps: z.number().int().optional().default(100),
|
|
@@ -479,24 +687,17 @@ export class MCPServer {
|
|
|
479
687
|
.filter(Boolean)
|
|
480
688
|
: [];
|
|
481
689
|
const llmConfig = this.getDefaultLlmConfig();
|
|
482
|
-
const configuredApiKey = typeof llmConfig.api_key === 'string' ? llmConfig.api_key.trim() : '';
|
|
483
|
-
const envApiKey = typeof process.env.OPENAI_API_KEY === 'string'
|
|
484
|
-
? process.env.OPENAI_API_KEY.trim()
|
|
485
|
-
: '';
|
|
486
|
-
const apiKey = configuredApiKey || envApiKey;
|
|
487
|
-
if (!apiKey) {
|
|
488
|
-
return 'Error: OPENAI_API_KEY not set in config or environment';
|
|
489
|
-
}
|
|
490
690
|
const configuredModel = typeof llmConfig.model === 'string' && llmConfig.model.trim()
|
|
491
691
|
? llmConfig.model.trim()
|
|
492
692
|
: 'gpt-4o';
|
|
493
693
|
const llmModel = model || configuredModel;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
694
|
+
let llm;
|
|
695
|
+
try {
|
|
696
|
+
llm = this.createLlmFromModelName(llmModel, llmConfig);
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
return `Error: Failed to initialize LLM "${llmModel}": ${error instanceof Error ? error.message : String(error)}`;
|
|
700
|
+
}
|
|
500
701
|
const profileConfig = this.getDefaultProfileConfig();
|
|
501
702
|
const profile = this.buildRetryProfile(profileConfig, allowedDomains);
|
|
502
703
|
const retryBrowserSession = new BrowserSession({
|
|
@@ -593,7 +794,7 @@ export class MCPServer {
|
|
|
593
794
|
this.tools[name] = {
|
|
594
795
|
description,
|
|
595
796
|
inputSchema: inputSchema instanceof z.ZodType
|
|
596
|
-
?
|
|
797
|
+
? zodSchemaToJsonSchema(inputSchema)
|
|
597
798
|
: inputSchema,
|
|
598
799
|
handler,
|
|
599
800
|
};
|
|
@@ -621,7 +822,10 @@ export class MCPServer {
|
|
|
621
822
|
*/
|
|
622
823
|
async initBrowserSession(browserSession) {
|
|
623
824
|
this.browserSession = browserSession;
|
|
825
|
+
this.trackSession(browserSession);
|
|
624
826
|
await this.browserSession.start();
|
|
827
|
+
this.trackSession(this.browserSession);
|
|
828
|
+
this.updateSessionActivity(this.browserSession);
|
|
625
829
|
logger.info('Browser session initialized');
|
|
626
830
|
}
|
|
627
831
|
/**
|
|
@@ -641,6 +845,7 @@ export class MCPServer {
|
|
|
641
845
|
const transport = new StdioServerTransport();
|
|
642
846
|
await this.server.connect(transport);
|
|
643
847
|
this.isRunning = true;
|
|
848
|
+
this.startSessionCleanupLoop();
|
|
644
849
|
logger.info(`🔌 MCP Server started (${this.getToolCount()} tools, ${this.getPromptCount()} prompts registered)`);
|
|
645
850
|
}
|
|
646
851
|
catch (error) {
|
|
@@ -659,14 +864,15 @@ export class MCPServer {
|
|
|
659
864
|
}
|
|
660
865
|
try {
|
|
661
866
|
this.isRunning = false;
|
|
867
|
+
this.stopSessionCleanupLoop();
|
|
662
868
|
// Cancel any pending operations
|
|
663
869
|
if (this.abortController) {
|
|
664
870
|
this.abortController.abort();
|
|
665
871
|
this.abortController = null;
|
|
666
872
|
}
|
|
667
873
|
// Close browser session if active
|
|
668
|
-
if (this.
|
|
669
|
-
await this.
|
|
874
|
+
if (this.activeSessions.size > 0) {
|
|
875
|
+
await this.closeAllTrackedSessions();
|
|
670
876
|
this.browserSession = null;
|
|
671
877
|
logger.info('Browser session closed');
|
|
672
878
|
}
|
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 };
|