browser-use 0.2.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 +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 +99 -41
- package/dist/agent/service.js +2266 -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 +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 +1097 -58
- 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 +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 +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 +1807 -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 +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 +14 -9
- 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 +255 -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 +87 -26
- 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) {
|
|
@@ -212,6 +237,126 @@ export class MCPServer {
|
|
|
212
237
|
}
|
|
213
238
|
return results.join('\n');
|
|
214
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
|
+
}
|
|
215
360
|
setupHandlers() {
|
|
216
361
|
// List available tools
|
|
217
362
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -315,19 +460,23 @@ export class MCPServer {
|
|
|
315
460
|
const profileConfig = this.getDefaultProfileConfig();
|
|
316
461
|
const profile = this.buildDirectSessionProfile(profileConfig);
|
|
317
462
|
this.browserSession = new BrowserSession({ browser_profile: profile });
|
|
463
|
+
this.trackSession(this.browserSession);
|
|
318
464
|
this.initializeLlmForDirectTools();
|
|
319
465
|
this.initializeFileSystem(profileConfig);
|
|
320
466
|
}
|
|
321
467
|
if (!this.browserSession.initialized) {
|
|
322
468
|
await this.browserSession.start();
|
|
323
469
|
}
|
|
470
|
+
this.trackSession(this.browserSession);
|
|
471
|
+
this.updateSessionActivity(this.browserSession);
|
|
324
472
|
return this.browserSession;
|
|
325
473
|
}
|
|
326
474
|
async executeControllerAction(actionName, args) {
|
|
327
475
|
const controller = await this.ensureController();
|
|
328
476
|
const browserSession = await this.ensureBrowserSession();
|
|
477
|
+
this.updateSessionActivity(browserSession);
|
|
329
478
|
if (actionName === 'extract_structured_data' && !this.llm) {
|
|
330
|
-
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.');
|
|
331
480
|
}
|
|
332
481
|
return await controller.registry.execute_action(actionName, args, {
|
|
333
482
|
browser_session: browserSession,
|
|
@@ -409,13 +558,26 @@ export class MCPServer {
|
|
|
409
558
|
this.registerTool('browser_get_state', 'Get the current state of the page including interactive elements', z
|
|
410
559
|
.object({
|
|
411
560
|
include_screenshot: z.boolean().default(false),
|
|
561
|
+
include_recent_events: z.boolean().default(false),
|
|
412
562
|
})
|
|
413
|
-
.default({ include_screenshot: false }), async (args) => {
|
|
563
|
+
.default({ include_screenshot: false, include_recent_events: false }), async (args) => {
|
|
414
564
|
const browserSession = await this.ensureBrowserSession();
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
+
}
|
|
419
581
|
return {
|
|
420
582
|
url: state.url,
|
|
421
583
|
title: state.title,
|
|
@@ -425,6 +587,10 @@ export class MCPServer {
|
|
|
425
587
|
pixels_below: state.pixels_below,
|
|
426
588
|
browser_errors: state.browser_errors,
|
|
427
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,
|
|
428
594
|
screenshot: state.screenshot,
|
|
429
595
|
interactive_elements: state.element_tree.clickable_elements_to_string(),
|
|
430
596
|
interactive_count: Object.keys(state.selector_map ?? {}).length,
|
|
@@ -450,16 +616,57 @@ export class MCPServer {
|
|
|
450
616
|
const browserSession = await this.ensureBrowserSession();
|
|
451
617
|
return browserSession.get_tabs_info();
|
|
452
618
|
});
|
|
453
|
-
this.registerTool('browser_switch_tab', 'Switch to a tab by
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
page_id:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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());
|
|
463
670
|
this.registerTool('retry_with_browser_use_agent', 'Retry a complex task with the browser-use autonomous agent', z.object({
|
|
464
671
|
task: z.string(),
|
|
465
672
|
max_steps: z.number().int().optional().default(100),
|
|
@@ -480,26 +687,17 @@ export class MCPServer {
|
|
|
480
687
|
.filter(Boolean)
|
|
481
688
|
: [];
|
|
482
689
|
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
690
|
const configuredModel = typeof llmConfig.model === 'string' && llmConfig.model.trim()
|
|
492
691
|
? llmConfig.model.trim()
|
|
493
692
|
: 'gpt-4o';
|
|
494
693
|
const llmModel = model || configuredModel;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
});
|
|
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
|
+
}
|
|
503
701
|
const profileConfig = this.getDefaultProfileConfig();
|
|
504
702
|
const profile = this.buildRetryProfile(profileConfig, allowedDomains);
|
|
505
703
|
const retryBrowserSession = new BrowserSession({
|
|
@@ -596,7 +794,7 @@ export class MCPServer {
|
|
|
596
794
|
this.tools[name] = {
|
|
597
795
|
description,
|
|
598
796
|
inputSchema: inputSchema instanceof z.ZodType
|
|
599
|
-
?
|
|
797
|
+
? zodSchemaToJsonSchema(inputSchema)
|
|
600
798
|
: inputSchema,
|
|
601
799
|
handler,
|
|
602
800
|
};
|
|
@@ -624,7 +822,10 @@ export class MCPServer {
|
|
|
624
822
|
*/
|
|
625
823
|
async initBrowserSession(browserSession) {
|
|
626
824
|
this.browserSession = browserSession;
|
|
825
|
+
this.trackSession(browserSession);
|
|
627
826
|
await this.browserSession.start();
|
|
827
|
+
this.trackSession(this.browserSession);
|
|
828
|
+
this.updateSessionActivity(this.browserSession);
|
|
628
829
|
logger.info('Browser session initialized');
|
|
629
830
|
}
|
|
630
831
|
/**
|
|
@@ -644,6 +845,7 @@ export class MCPServer {
|
|
|
644
845
|
const transport = new StdioServerTransport();
|
|
645
846
|
await this.server.connect(transport);
|
|
646
847
|
this.isRunning = true;
|
|
848
|
+
this.startSessionCleanupLoop();
|
|
647
849
|
logger.info(`🔌 MCP Server started (${this.getToolCount()} tools, ${this.getPromptCount()} prompts registered)`);
|
|
648
850
|
}
|
|
649
851
|
catch (error) {
|
|
@@ -662,14 +864,15 @@ export class MCPServer {
|
|
|
662
864
|
}
|
|
663
865
|
try {
|
|
664
866
|
this.isRunning = false;
|
|
867
|
+
this.stopSessionCleanupLoop();
|
|
665
868
|
// Cancel any pending operations
|
|
666
869
|
if (this.abortController) {
|
|
667
870
|
this.abortController.abort();
|
|
668
871
|
this.abortController = null;
|
|
669
872
|
}
|
|
670
873
|
// Close browser session if active
|
|
671
|
-
if (this.
|
|
672
|
-
await this.
|
|
874
|
+
if (this.activeSessions.size > 0) {
|
|
875
|
+
await this.closeAllTrackedSessions();
|
|
673
876
|
this.browserSession = null;
|
|
674
877
|
logger.info('Browser session closed');
|
|
675
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 };
|