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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { CONFIG } from '../config.js';
|
|
2
|
+
import { createLogger } from '../logging-config.js';
|
|
3
|
+
import { build_skill_parameters_schema, get_skill_slug } from './utils.js';
|
|
4
|
+
import { MissingCookieException, } from './views.js';
|
|
5
|
+
const logger = createLogger('browser_use.skills');
|
|
6
|
+
const toSkillParameter = (raw) => {
|
|
7
|
+
if (!raw || typeof raw !== 'object') {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const record = raw;
|
|
11
|
+
const name = typeof record.name === 'string' ? record.name.trim() : '';
|
|
12
|
+
const type = typeof record.type === 'string' ? record.type.trim() : '';
|
|
13
|
+
if (!name || !type) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (type !== 'string' &&
|
|
17
|
+
type !== 'number' &&
|
|
18
|
+
type !== 'boolean' &&
|
|
19
|
+
type !== 'object' &&
|
|
20
|
+
type !== 'array' &&
|
|
21
|
+
type !== 'cookie') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
type,
|
|
27
|
+
required: typeof record.required === 'boolean' ? record.required : undefined,
|
|
28
|
+
description: typeof record.description === 'string' ? record.description : undefined,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const toSkillDefinition = (raw) => {
|
|
32
|
+
if (!raw || typeof raw !== 'object') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const record = raw;
|
|
36
|
+
const id = typeof record.id === 'string' ? record.id.trim() : '';
|
|
37
|
+
const title = typeof record.title === 'string' ? record.title.trim() : '';
|
|
38
|
+
const description = typeof record.description === 'string' ? record.description.trim() : '';
|
|
39
|
+
if (!id || !title) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const parametersRaw = Array.isArray(record.parameters)
|
|
43
|
+
? record.parameters
|
|
44
|
+
: [];
|
|
45
|
+
const parameters = parametersRaw
|
|
46
|
+
.map((entry) => toSkillParameter(entry))
|
|
47
|
+
.filter((entry) => entry != null);
|
|
48
|
+
const output_schema = record.output_schema && typeof record.output_schema === 'object'
|
|
49
|
+
? record.output_schema
|
|
50
|
+
: null;
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
title,
|
|
54
|
+
description,
|
|
55
|
+
parameters,
|
|
56
|
+
output_schema,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export class CloudSkillService {
|
|
60
|
+
skill_ids;
|
|
61
|
+
api_key;
|
|
62
|
+
base_url;
|
|
63
|
+
fetch_impl;
|
|
64
|
+
initialized = false;
|
|
65
|
+
skills = new Map();
|
|
66
|
+
constructor(options) {
|
|
67
|
+
this.skill_ids = options.skill_ids;
|
|
68
|
+
this.api_key = options.api_key ?? process.env.BROWSER_USE_API_KEY ?? '';
|
|
69
|
+
this.base_url = options.base_url ?? CONFIG.BROWSER_USE_CLOUD_API_URL;
|
|
70
|
+
this.fetch_impl = options.fetch_impl ?? fetch;
|
|
71
|
+
if (!this.api_key) {
|
|
72
|
+
throw new Error('BROWSER_USE_API_KEY environment variable is not set');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async requestJson(path, init = {}) {
|
|
76
|
+
const response = await this.fetch_impl(`${this.base_url}${path}`, {
|
|
77
|
+
...init,
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${this.api_key}`,
|
|
80
|
+
...(init.body ? { 'Content-Type': 'application/json' } : {}),
|
|
81
|
+
...(init.headers ?? {}),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
let payload;
|
|
85
|
+
try {
|
|
86
|
+
payload = await response.json();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
payload = null;
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const details = payload && typeof payload === 'object'
|
|
93
|
+
? JSON.stringify(payload)
|
|
94
|
+
: String(payload ?? '');
|
|
95
|
+
throw new Error(`Skill API request failed (${response.status}): ${details}`);
|
|
96
|
+
}
|
|
97
|
+
return payload;
|
|
98
|
+
}
|
|
99
|
+
async listSkillsPage(page_number, page_size) {
|
|
100
|
+
const query = new URLSearchParams({
|
|
101
|
+
page_size: String(page_size),
|
|
102
|
+
page_number: String(page_number),
|
|
103
|
+
is_enabled: 'true',
|
|
104
|
+
});
|
|
105
|
+
const payload = (await this.requestJson(`/api/v1/skills?${query.toString()}`));
|
|
106
|
+
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
107
|
+
const skills = [];
|
|
108
|
+
for (const item of items) {
|
|
109
|
+
const status = item && typeof item === 'object'
|
|
110
|
+
? item.status
|
|
111
|
+
: undefined;
|
|
112
|
+
if (typeof status === 'string' && status !== 'finished') {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const skill = toSkillDefinition(item);
|
|
116
|
+
if (skill) {
|
|
117
|
+
skills.push(skill);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return skills;
|
|
121
|
+
}
|
|
122
|
+
async ensureInitialized() {
|
|
123
|
+
if (this.initialized) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const useWildcard = this.skill_ids.includes('*');
|
|
128
|
+
const requestedIds = new Set(this.skill_ids.filter((entry) => entry !== '*'));
|
|
129
|
+
const page_size = 100;
|
|
130
|
+
const max_pages = useWildcard ? 1 : 5;
|
|
131
|
+
const loaded = [];
|
|
132
|
+
let reachedPaginationLimit = true;
|
|
133
|
+
for (let page = 1; page <= max_pages; page += 1) {
|
|
134
|
+
const pageSkills = await this.listSkillsPage(page, page_size);
|
|
135
|
+
loaded.push(...pageSkills);
|
|
136
|
+
if (pageSkills.length < page_size) {
|
|
137
|
+
reachedPaginationLimit = false;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
if (!useWildcard && requestedIds.size > 0) {
|
|
141
|
+
const found = new Set(loaded.map((entry) => entry.id).filter((id) => requestedIds.has(id)));
|
|
142
|
+
if (found.size === requestedIds.size) {
|
|
143
|
+
reachedPaginationLimit = false;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (useWildcard && loaded.length >= page_size) {
|
|
149
|
+
logger.warning('Wildcard "*" limited to first 100 skills. Specify explicit skill IDs if you need specific skills beyond this limit.');
|
|
150
|
+
}
|
|
151
|
+
if (!useWildcard && reachedPaginationLimit) {
|
|
152
|
+
logger.warning('Reached pagination limit (5 pages) before finding all requested skills');
|
|
153
|
+
}
|
|
154
|
+
const selected = useWildcard
|
|
155
|
+
? loaded
|
|
156
|
+
: loaded.filter((entry) => requestedIds.has(entry.id));
|
|
157
|
+
if (!useWildcard && requestedIds.size > 0) {
|
|
158
|
+
const foundIds = new Set(selected.map((entry) => entry.id));
|
|
159
|
+
const missingIds = Array.from(requestedIds).filter((id) => !foundIds.has(id));
|
|
160
|
+
if (missingIds.length > 0) {
|
|
161
|
+
logger.warning(`Requested skills not found or not available: ${missingIds.join(', ')}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (const skill of selected) {
|
|
165
|
+
this.skills.set(skill.id, skill);
|
|
166
|
+
}
|
|
167
|
+
this.initialized = true;
|
|
168
|
+
logger.info(`Loaded ${this.skills.size} skills${useWildcard ? ' (wildcard mode)' : ''}`);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// Match Python semantics: avoid retry loops after an initialization failure.
|
|
172
|
+
this.initialized = true;
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async get_skill(skill_id) {
|
|
177
|
+
await this.ensureInitialized();
|
|
178
|
+
return this.skills.get(skill_id) ?? null;
|
|
179
|
+
}
|
|
180
|
+
async get_all_skills() {
|
|
181
|
+
await this.ensureInitialized();
|
|
182
|
+
return Array.from(this.skills.values());
|
|
183
|
+
}
|
|
184
|
+
async execute_skill(input) {
|
|
185
|
+
await this.ensureInitialized();
|
|
186
|
+
const skill = this.skills.get(input.skill_id);
|
|
187
|
+
if (!skill) {
|
|
188
|
+
throw new Error(`Skill ${input.skill_id} not found in cache. Available skills: ${Array.from(this.skills.keys()).join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
const cookieMap = new Map();
|
|
191
|
+
for (const cookie of input.cookies ?? []) {
|
|
192
|
+
if (!cookie?.name) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
cookieMap.set(cookie.name, cookie.value ?? '');
|
|
196
|
+
}
|
|
197
|
+
const payload = {
|
|
198
|
+
...(input.parameters ?? {}),
|
|
199
|
+
};
|
|
200
|
+
for (const param of skill.parameters) {
|
|
201
|
+
if (param.type !== 'cookie') {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const required = param.required !== false;
|
|
205
|
+
if (required && !cookieMap.has(param.name)) {
|
|
206
|
+
throw new MissingCookieException(param.name, param.description || 'No description provided');
|
|
207
|
+
}
|
|
208
|
+
if (cookieMap.has(param.name)) {
|
|
209
|
+
payload[param.name] = cookieMap.get(param.name) ?? '';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const validator = build_skill_parameters_schema(skill.parameters, {
|
|
213
|
+
exclude_cookies: false,
|
|
214
|
+
});
|
|
215
|
+
const validated = validator.safeParse(payload);
|
|
216
|
+
if (!validated.success) {
|
|
217
|
+
throw new Error(`Parameter validation failed for skill ${skill.title}: ${validated.error.message}`);
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const response = (await this.requestJson(`/api/v1/skills/${encodeURIComponent(input.skill_id)}/execute`, {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
body: JSON.stringify({ parameters: validated.data }),
|
|
223
|
+
}));
|
|
224
|
+
const success = response.success === true;
|
|
225
|
+
const result = response.result ?? response.output ?? response.data ?? null;
|
|
226
|
+
const error = typeof response.error === 'string' ? response.error : null;
|
|
227
|
+
const latency_ms = typeof response.latency_ms === 'number' ? response.latency_ms : null;
|
|
228
|
+
return {
|
|
229
|
+
success,
|
|
230
|
+
result,
|
|
231
|
+
error,
|
|
232
|
+
latency_ms,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const errorText = error instanceof Error
|
|
237
|
+
? `${error.name}: ${error.message}`
|
|
238
|
+
: String(error);
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: `Failed to execute skill: ${errorText}`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async close() {
|
|
246
|
+
this.skills.clear();
|
|
247
|
+
this.initialized = false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
export const register_skills_as_actions = async (skills, registerAction) => {
|
|
251
|
+
for (const skill of skills) {
|
|
252
|
+
const slug = get_skill_slug(skill, skills);
|
|
253
|
+
const paramSchema = build_skill_parameters_schema(skill.parameters, {
|
|
254
|
+
exclude_cookies: true,
|
|
255
|
+
});
|
|
256
|
+
const description = `${skill.description} (Skill: "${skill.title}")`;
|
|
257
|
+
registerAction(slug, description, paramSchema, skill);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
export const cookies_to_map = (cookies) => {
|
|
261
|
+
const map = new Map();
|
|
262
|
+
for (const cookie of cookies) {
|
|
263
|
+
map.set(cookie.name, cookie.value);
|
|
264
|
+
}
|
|
265
|
+
return map;
|
|
266
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { SkillDefinition, SkillParameterSchema } from './views.js';
|
|
3
|
+
export declare const get_skill_slug: (skill: SkillDefinition, all_skills: SkillDefinition[]) => string;
|
|
4
|
+
export declare const build_skill_parameters_schema: (parameters: SkillParameterSchema[], options?: {
|
|
5
|
+
exclude_cookies?: boolean;
|
|
6
|
+
}) => z.ZodObject<Record<string, z.ZodTypeAny>>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const normalizeSlug = (title) => {
|
|
3
|
+
const cleaned = title
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/[^\w\s-]/g, '')
|
|
6
|
+
.replace(/[\s-]+/g, '_')
|
|
7
|
+
.replace(/^_+|_+$/g, '');
|
|
8
|
+
return cleaned || 'skill';
|
|
9
|
+
};
|
|
10
|
+
const normalizeTypeSchema = (param) => {
|
|
11
|
+
switch (param.type) {
|
|
12
|
+
case 'string':
|
|
13
|
+
case 'cookie':
|
|
14
|
+
return z.string();
|
|
15
|
+
case 'number':
|
|
16
|
+
return z.number();
|
|
17
|
+
case 'boolean':
|
|
18
|
+
return z.boolean();
|
|
19
|
+
case 'array':
|
|
20
|
+
return z.array(z.unknown());
|
|
21
|
+
case 'object':
|
|
22
|
+
return z.record(z.string(), z.unknown());
|
|
23
|
+
default:
|
|
24
|
+
return z.unknown();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export const get_skill_slug = (skill, all_skills) => {
|
|
28
|
+
const slug = normalizeSlug(skill.title);
|
|
29
|
+
const duplicateCount = all_skills.filter((entry) => {
|
|
30
|
+
return normalizeSlug(entry.title) === slug;
|
|
31
|
+
}).length;
|
|
32
|
+
if (duplicateCount > 1) {
|
|
33
|
+
return `${slug}_${skill.id.slice(0, 4)}`;
|
|
34
|
+
}
|
|
35
|
+
return slug;
|
|
36
|
+
};
|
|
37
|
+
export const build_skill_parameters_schema = (parameters, options = {}) => {
|
|
38
|
+
const { exclude_cookies = false } = options;
|
|
39
|
+
const shape = {};
|
|
40
|
+
for (const param of parameters) {
|
|
41
|
+
if (exclude_cookies && param.type === 'cookie') {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const description = typeof param.description === 'string' ? param.description.trim() : '';
|
|
45
|
+
const required = param.required !== false;
|
|
46
|
+
let schema = normalizeTypeSchema(param);
|
|
47
|
+
if (description) {
|
|
48
|
+
schema = schema.describe(description);
|
|
49
|
+
}
|
|
50
|
+
shape[param.name] = required ? schema : schema.optional();
|
|
51
|
+
}
|
|
52
|
+
return z.object(shape);
|
|
53
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type SkillParameterType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'cookie';
|
|
2
|
+
export interface SkillParameterSchema {
|
|
3
|
+
name: string;
|
|
4
|
+
type: SkillParameterType;
|
|
5
|
+
required?: boolean | null;
|
|
6
|
+
description?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface SkillDefinition {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
parameters: SkillParameterSchema[];
|
|
13
|
+
output_schema?: Record<string, unknown> | null;
|
|
14
|
+
}
|
|
15
|
+
export interface SkillExecutionResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
result?: unknown;
|
|
18
|
+
error?: string | null;
|
|
19
|
+
latency_ms?: number | null;
|
|
20
|
+
}
|
|
21
|
+
export interface BrowserCookie {
|
|
22
|
+
name: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ExecuteSkillInput {
|
|
26
|
+
skill_id: string;
|
|
27
|
+
parameters: Record<string, unknown>;
|
|
28
|
+
cookies: BrowserCookie[];
|
|
29
|
+
}
|
|
30
|
+
export interface SkillService {
|
|
31
|
+
get_skill?(skill_id: string): Promise<SkillDefinition | null>;
|
|
32
|
+
get_all_skills(): Promise<SkillDefinition[]>;
|
|
33
|
+
execute_skill(input: ExecuteSkillInput): Promise<SkillExecutionResult>;
|
|
34
|
+
close?(): Promise<void> | void;
|
|
35
|
+
}
|
|
36
|
+
export declare class MissingCookieException extends Error {
|
|
37
|
+
cookie_name: string;
|
|
38
|
+
cookie_description: string;
|
|
39
|
+
constructor(cookie_name: string, cookie_description: string);
|
|
40
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export class MissingCookieException extends Error {
|
|
2
|
+
cookie_name;
|
|
3
|
+
cookie_description;
|
|
4
|
+
constructor(cookie_name, cookie_description) {
|
|
5
|
+
super(`Missing required cookie '${cookie_name}': ${cookie_description}`);
|
|
6
|
+
this.name = 'MissingCookieException';
|
|
7
|
+
this.cookie_name = cookie_name;
|
|
8
|
+
this.cookie_description = cookie_description;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/sync/auth.js
CHANGED
|
@@ -100,7 +100,7 @@ export class DeviceAuthClient {
|
|
|
100
100
|
const response = await this.postForm('/api/v1/oauth/device/authorize', {
|
|
101
101
|
client_id: this.clientId,
|
|
102
102
|
scope: this.scope,
|
|
103
|
-
agent_session_id: agent_session_id ??
|
|
103
|
+
agent_session_id: agent_session_id ?? '',
|
|
104
104
|
device_id: this.device_id,
|
|
105
105
|
});
|
|
106
106
|
return response.data;
|
|
@@ -160,7 +160,7 @@ export class DeviceAuthClient {
|
|
|
160
160
|
const replaceHost = (value) => value?.replace(this.baseUrl, frontendBase);
|
|
161
161
|
const verificationUri = replaceHost(deviceAuth.verification_uri);
|
|
162
162
|
const verificationUriComplete = replaceHost(deviceAuth.verification_uri_complete);
|
|
163
|
-
if (show_instructions) {
|
|
163
|
+
if (show_instructions && CONFIG.BROWSER_USE_CLOUD_SYNC) {
|
|
164
164
|
const divider = '─'.repeat(terminalWidth());
|
|
165
165
|
logger.info(divider);
|
|
166
166
|
logger.info('🌐 View the details of this run in Browser Use Cloud:');
|
|
@@ -200,6 +200,11 @@ export class DeviceAuthClient {
|
|
|
200
200
|
}
|
|
201
201
|
clear_auth() {
|
|
202
202
|
this.authConfig = { api_token: null, user_id: null, authorized_at: null };
|
|
203
|
-
|
|
203
|
+
try {
|
|
204
|
+
fs.unlinkSync(CLOUD_AUTH_PATH());
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
/* noop */
|
|
208
|
+
}
|
|
204
209
|
}
|
|
205
210
|
}
|
package/dist/sync/service.d.ts
CHANGED
|
@@ -3,19 +3,19 @@ import { DeviceAuthClient } from './auth.js';
|
|
|
3
3
|
export interface CloudSyncOptions {
|
|
4
4
|
baseUrl?: string;
|
|
5
5
|
enableAuth?: boolean;
|
|
6
|
+
allowSessionEventsForAuth?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export declare class CloudSync {
|
|
8
9
|
private readonly baseUrl;
|
|
9
|
-
private readonly
|
|
10
|
-
readonly auth_client
|
|
11
|
-
private pendingEvents;
|
|
12
|
-
private authTask;
|
|
10
|
+
private readonly enabled;
|
|
11
|
+
readonly auth_client: DeviceAuthClient;
|
|
13
12
|
private sessionId;
|
|
13
|
+
private allowSessionEventsForAuth;
|
|
14
|
+
private authFlowActive;
|
|
14
15
|
constructor(options?: CloudSyncOptions);
|
|
15
16
|
handle_event(event: BaseEvent): Promise<void>;
|
|
16
17
|
private sendEvent;
|
|
17
|
-
|
|
18
|
-
private resendPendingEvents;
|
|
18
|
+
set_auth_flow_active(): void;
|
|
19
19
|
wait_for_auth(): Promise<void>;
|
|
20
20
|
authenticate(showInstructions?: boolean): Promise<boolean>;
|
|
21
21
|
}
|
package/dist/sync/service.js
CHANGED
|
@@ -7,40 +7,39 @@ const stripTrailingSlash = (input) => input.replace(/\/+$/, '');
|
|
|
7
7
|
const ensureArray = (value) => Array.isArray(value) ? value : [value];
|
|
8
8
|
export class CloudSync {
|
|
9
9
|
baseUrl;
|
|
10
|
-
|
|
10
|
+
enabled;
|
|
11
11
|
auth_client;
|
|
12
|
-
pendingEvents = [];
|
|
13
|
-
authTask = null;
|
|
14
12
|
sessionId = null;
|
|
13
|
+
allowSessionEventsForAuth;
|
|
14
|
+
authFlowActive = false;
|
|
15
15
|
constructor(options = {}) {
|
|
16
|
+
const enableAuth = options.enableAuth ?? true;
|
|
16
17
|
this.baseUrl = stripTrailingSlash(options.baseUrl ?? CONFIG.BROWSER_USE_CLOUD_API_URL);
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
: undefined;
|
|
18
|
+
this.enabled = CONFIG.BROWSER_USE_CLOUD_SYNC && enableAuth;
|
|
19
|
+
this.allowSessionEventsForAuth = options.allowSessionEventsForAuth ?? false;
|
|
20
|
+
this.auth_client = new DeviceAuthClient(this.baseUrl);
|
|
21
21
|
}
|
|
22
22
|
async handle_event(event) {
|
|
23
23
|
try {
|
|
24
|
+
if (!this.enabled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
24
27
|
if (event.event_type === 'CreateAgentSessionEvent' &&
|
|
25
|
-
event?.id) {
|
|
28
|
+
event?.id != null) {
|
|
26
29
|
this.sessionId = String(event.id);
|
|
27
30
|
}
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.authTask = this.backgroundAuth(this.sessionId);
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
logger.warning('Cannot start cloud auth, session_id missing');
|
|
40
|
-
}
|
|
31
|
+
if (this.auth_client.is_authenticated) {
|
|
32
|
+
await this.sendEvent(event);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (this.allowSessionEventsForAuth || this.authFlowActive) {
|
|
36
|
+
await this.sendEvent(event);
|
|
37
|
+
if (event.event_type === 'CreateAgentSessionEvent') {
|
|
38
|
+
this.authFlowActive = true;
|
|
41
39
|
}
|
|
40
|
+
return;
|
|
42
41
|
}
|
|
43
|
-
|
|
42
|
+
logger.debug(`Skipping event ${event.event_type} - user not authenticated`);
|
|
44
43
|
}
|
|
45
44
|
catch (error) {
|
|
46
45
|
logger.error(`Failed to handle ${event.event_type}: ${error.message}`);
|
|
@@ -49,13 +48,18 @@ export class CloudSync {
|
|
|
49
48
|
async sendEvent(event) {
|
|
50
49
|
try {
|
|
51
50
|
const headers = {};
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
event.device_id = authClient.device_id;
|
|
51
|
+
const currentUserId = event.user_id ?? null;
|
|
52
|
+
if (this.auth_client.is_authenticated) {
|
|
53
|
+
if (currentUserId !== TEMP_USER_ID) {
|
|
54
|
+
event.user_id = this.auth_client.user_id;
|
|
55
|
+
}
|
|
58
56
|
}
|
|
57
|
+
else if (!currentUserId) {
|
|
58
|
+
event.user_id = TEMP_USER_ID;
|
|
59
|
+
}
|
|
60
|
+
Object.assign(headers, this.auth_client.get_headers());
|
|
61
|
+
event.device_id =
|
|
62
|
+
event.device_id ?? this.auth_client.device_id;
|
|
59
63
|
const payload = typeof event?.toJSON === 'function'
|
|
60
64
|
? event.toJSON()
|
|
61
65
|
: { ...event };
|
|
@@ -63,84 +67,45 @@ export class CloudSync {
|
|
|
63
67
|
await axios.post(`${this.baseUrl}/api/v1/events`, {
|
|
64
68
|
events: events.map((entry) => ({
|
|
65
69
|
...entry,
|
|
66
|
-
device_id: entry.device_id ??
|
|
70
|
+
device_id: entry.device_id ?? this.auth_client.device_id,
|
|
67
71
|
})),
|
|
68
72
|
}, { headers, timeout: 10_000 });
|
|
69
73
|
}
|
|
70
74
|
catch (error) {
|
|
71
75
|
const status = error?.response?.status;
|
|
72
|
-
if (status === 401 &&
|
|
73
|
-
this.auth_client &&
|
|
74
|
-
!this.auth_client.is_authenticated) {
|
|
75
|
-
this.pendingEvents.push(event);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
76
|
if (status) {
|
|
79
|
-
logger.debug(`
|
|
77
|
+
logger.debug(`Failed to send sync event: POST ${this.baseUrl}/api/v1/events ${status} - ${String(error?.response?.data ?? '')}`);
|
|
80
78
|
}
|
|
81
79
|
else if (error?.code === 'ECONNABORTED') {
|
|
82
|
-
logger.
|
|
80
|
+
logger.debug(`Event send timed out after 10 seconds: ${event}`);
|
|
83
81
|
}
|
|
84
82
|
else {
|
|
85
|
-
logger.
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
async backgroundAuth(agentSessionId) {
|
|
90
|
-
const authClient = this.auth_client;
|
|
91
|
-
if (!authClient) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
if (authClient.is_authenticated) {
|
|
96
|
-
const frontend = CONFIG.BROWSER_USE_CLOUD_UI_URL ||
|
|
97
|
-
this.baseUrl.replace('//api.', '//cloud.');
|
|
98
|
-
const sessionUrl = `${stripTrailingSlash(frontend)}/agent/${agentSessionId}`;
|
|
99
|
-
const divider = '─'.repeat(Math.max((process.stdout?.columns ?? 80) - 40, 20));
|
|
100
|
-
logger.info(divider);
|
|
101
|
-
logger.info('🌐 View the details of this run in Browser Use Cloud:');
|
|
102
|
-
logger.info(` 👉 ${sessionUrl}`);
|
|
103
|
-
logger.info(divider + '\n');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const success = await authClient.authenticate(agentSessionId, true);
|
|
107
|
-
if (success) {
|
|
108
|
-
await this.resendPendingEvents();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
logger.debug(`Cloud sync auth error: ${error.message}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async resendPendingEvents() {
|
|
116
|
-
if (!this.pendingEvents.length) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const events = [...this.pendingEvents];
|
|
120
|
-
this.pendingEvents = [];
|
|
121
|
-
for (const event of events) {
|
|
122
|
-
try {
|
|
123
|
-
await this.sendEvent(event);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
logger.warning(`Failed to resend event ${event.event_type}: ${error.message}`);
|
|
83
|
+
logger.debug(`Unexpected error sending event ${event}: ${typeOfError(error)}`);
|
|
127
84
|
}
|
|
128
85
|
}
|
|
129
86
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
await this.authTask;
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
/* ignore */
|
|
137
|
-
}
|
|
138
|
-
}
|
|
87
|
+
set_auth_flow_active() {
|
|
88
|
+
this.authFlowActive = true;
|
|
89
|
+
this.allowSessionEventsForAuth = true;
|
|
139
90
|
}
|
|
91
|
+
// Backward-compatible no-op; auth is no longer background-scheduled here.
|
|
92
|
+
async wait_for_auth() { }
|
|
140
93
|
async authenticate(showInstructions = true) {
|
|
141
|
-
if (!this.
|
|
94
|
+
if (!this.enabled) {
|
|
142
95
|
return false;
|
|
143
96
|
}
|
|
97
|
+
if (this.auth_client.is_authenticated) {
|
|
98
|
+
if (showInstructions) {
|
|
99
|
+
logger.info('✅ Already authenticated! Skipping OAuth flow.');
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
144
103
|
return this.auth_client.authenticate(this.sessionId, showInstructions);
|
|
145
104
|
}
|
|
146
105
|
}
|
|
106
|
+
const typeOfError = (error) => {
|
|
107
|
+
if (error instanceof Error) {
|
|
108
|
+
return `${error.name}: ${error.message}`;
|
|
109
|
+
}
|
|
110
|
+
return String(error);
|
|
111
|
+
};
|