digital-workers 2.1.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +2 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +33 -21
- package/dist/actions.js.map +1 -1
- package/dist/agent-comms.d.ts.map +1 -1
- package/dist/agent-comms.js +36 -25
- package/dist/agent-comms.js.map +1 -1
- package/dist/approve.d.ts +40 -8
- package/dist/approve.d.ts.map +1 -1
- package/dist/approve.js +86 -20
- package/dist/approve.js.map +1 -1
- package/dist/ask.d.ts +38 -7
- package/dist/ask.d.ts.map +1 -1
- package/dist/ask.js +85 -25
- package/dist/ask.js.map +1 -1
- package/dist/browse.d.ts +223 -0
- package/dist/browse.d.ts.map +1 -0
- package/dist/browse.js +392 -0
- package/dist/browse.js.map +1 -0
- package/dist/capability-tiers.js +3 -3
- package/dist/capability-tiers.js.map +1 -1
- package/dist/cascade-context.d.ts +28 -28
- package/dist/client.d.ts +162 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +64 -0
- package/dist/client.js.map +1 -0
- package/dist/decide.d.ts +42 -6
- package/dist/decide.d.ts.map +1 -1
- package/dist/decide.js +54 -11
- package/dist/decide.js.map +1 -1
- package/dist/do.d.ts +36 -7
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +82 -39
- package/dist/do.js.map +1 -1
- package/dist/error-escalation.d.ts.map +1 -1
- package/dist/error-escalation.js +38 -38
- package/dist/error-escalation.js.map +1 -1
- package/dist/generate.d.ts +48 -7
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +49 -8
- package/dist/generate.js.map +1 -1
- package/dist/goals.d.ts +10 -9
- package/dist/goals.d.ts.map +1 -1
- package/dist/goals.js +30 -24
- package/dist/goals.js.map +1 -1
- package/dist/image.d.ts +189 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +528 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +49 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -2
- package/dist/index.js.map +1 -1
- package/dist/is.d.ts +45 -10
- package/dist/is.d.ts.map +1 -1
- package/dist/is.js +56 -21
- package/dist/is.js.map +1 -1
- package/dist/kpis.d.ts +24 -15
- package/dist/kpis.d.ts.map +1 -1
- package/dist/kpis.js +16 -14
- package/dist/kpis.js.map +1 -1
- package/dist/load-balancing.d.ts.map +1 -1
- package/dist/load-balancing.js +124 -38
- package/dist/load-balancing.js.map +1 -1
- package/dist/logger.d.ts +76 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/notify.d.ts +38 -9
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +72 -17
- package/dist/notify.js.map +1 -1
- package/dist/role.d.ts +5 -4
- package/dist/role.d.ts.map +1 -1
- package/dist/role.js +13 -10
- package/dist/role.js.map +1 -1
- package/dist/runtime.d.ts +310 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +510 -0
- package/dist/runtime.js.map +1 -0
- package/dist/team.d.ts +11 -6
- package/dist/team.d.ts.map +1 -1
- package/dist/team.js +22 -15
- package/dist/team.js.map +1 -1
- package/dist/transports/email.d.ts +318 -0
- package/dist/transports/email.d.ts.map +1 -0
- package/dist/transports/email.js +779 -0
- package/dist/transports/email.js.map +1 -0
- package/dist/transports/slack.d.ts +515 -0
- package/dist/transports/slack.d.ts.map +1 -0
- package/dist/transports/slack.js +844 -0
- package/dist/transports/slack.js.map +1 -0
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +44 -25
- package/dist/transports.js.map +1 -1
- package/dist/types.d.ts +141 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/id.d.ts +19 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +21 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/video.d.ts +203 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +528 -0
- package/dist/video.js.map +1 -0
- package/dist/worker.d.ts +343 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +698 -0
- package/dist/worker.js.map +1 -0
- package/package.json +32 -14
- package/src/actions.ts +39 -30
- package/src/agent-comms.ts +54 -92
- package/src/approve.ts +91 -20
- package/src/ask.ts +99 -25
- package/src/browse.ts +627 -0
- package/src/capability-tiers.ts +5 -5
- package/src/client.ts +221 -0
- package/src/decide.ts +81 -35
- package/src/do.ts +98 -52
- package/src/error-escalation.ts +55 -67
- package/src/generate.ts +52 -18
- package/src/goals.ts +36 -27
- package/src/image.ts +816 -0
- package/src/index.ts +187 -2
- package/src/is.ts +59 -25
- package/src/kpis.ts +41 -36
- package/src/load-balancing.ts +132 -46
- package/src/logger.ts +93 -0
- package/src/notify.ts +78 -17
- package/src/role.ts +30 -20
- package/src/runtime.ts +796 -0
- package/src/team.ts +24 -19
- package/src/transports/email.ts +1160 -0
- package/src/transports/slack.ts +1320 -0
- package/src/transports.ts +58 -43
- package/src/types.ts +174 -46
- package/src/utils/id.ts +21 -0
- package/src/video.ts +906 -0
- package/src/worker.ts +1007 -0
- package/test/approve.test.ts +305 -0
- package/test/ask.test.ts +274 -0
- package/test/browse.test.ts +361 -0
- package/test/decide.test.ts +252 -0
- package/test/do.test.ts +144 -0
- package/test/error-logging.test.ts +357 -0
- package/test/generate.test.ts +319 -0
- package/test/image.test.ts +398 -0
- package/test/is.test.ts +287 -0
- package/test/load-balancing-safety.test.ts +404 -0
- package/test/notify.test.ts +434 -0
- package/test/primitives.test.ts +320 -0
- package/test/runtime-integration.test.ts +892 -0
- package/test/transports/crypto.test.ts +230 -0
- package/test/transports/email.test.ts +866 -0
- package/test/transports/id-generation.test.ts +91 -0
- package/test/transports/slack.test.ts +760 -0
- package/test/type-safety.test.ts +834 -0
- package/test/types.test.ts +60 -2
- package/test/video.test.ts +530 -0
- package/test/worker.test.ts +1433 -0
- package/tsconfig.json +4 -1
- package/vitest.config.ts +42 -0
- package/wrangler.jsonc +36 -0
- package/LICENSE +0 -21
- package/src/actions.js +0 -436
- package/src/approve.js +0 -234
- package/src/ask.js +0 -226
- package/src/decide.js +0 -244
- package/src/do.js +0 -227
- package/src/generate.js +0 -298
- package/src/goals.js +0 -205
- package/src/index.js +0 -68
- package/src/is.js +0 -317
- package/src/kpis.js +0 -270
- package/src/notify.js +0 -219
- package/src/role.js +0 -110
- package/src/team.js +0 -130
- package/src/transports.js +0 -357
- package/src/types.js +0 -71
package/src/browse.ts
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser automation functionality for digital workers
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: Worker Routing vs Direct Browser Calls
|
|
5
|
+
* -------------------------------------------------
|
|
6
|
+
* This module provides worker-routed browser automation, enabling AI-assisted
|
|
7
|
+
* browser control with human fallback capabilities.
|
|
8
|
+
*
|
|
9
|
+
* - `digital-workers.browse()` - Routes browser automation tasks to Workers
|
|
10
|
+
* (AI Agents or Humans) based on capability matching and complexity.
|
|
11
|
+
*
|
|
12
|
+
* Use digital-workers.browse when you need:
|
|
13
|
+
* - AI-planned browser automation
|
|
14
|
+
* - Human-in-the-loop for complex interactions
|
|
15
|
+
* - Capability-based worker selection
|
|
16
|
+
* - Screenshot and data extraction with fallback
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { define } from 'ai-functions'
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Viewport dimensions for browser automation
|
|
29
|
+
*/
|
|
30
|
+
export interface Viewport {
|
|
31
|
+
width: number
|
|
32
|
+
height: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Options for browser automation
|
|
37
|
+
*/
|
|
38
|
+
export interface BrowseOptions {
|
|
39
|
+
/** URL to navigate to */
|
|
40
|
+
url: string
|
|
41
|
+
/** Task or goal to accomplish in the browser */
|
|
42
|
+
task: string
|
|
43
|
+
/** Maximum time to wait for task completion (ms) */
|
|
44
|
+
timeout?: number
|
|
45
|
+
/** Run browser in headless mode */
|
|
46
|
+
headless?: boolean
|
|
47
|
+
/** Browser viewport dimensions */
|
|
48
|
+
viewport?: Viewport
|
|
49
|
+
/** Additional context for the task */
|
|
50
|
+
context?: Record<string, unknown>
|
|
51
|
+
/** Wait for specific selector before starting */
|
|
52
|
+
waitFor?: string
|
|
53
|
+
/** Cookies to set before navigation */
|
|
54
|
+
cookies?: Array<{ name: string; value: string; domain?: string }>
|
|
55
|
+
/** User agent string */
|
|
56
|
+
userAgent?: string
|
|
57
|
+
/** Enable human fallback for complex interactions */
|
|
58
|
+
humanFallback?: boolean
|
|
59
|
+
/** Worker to route to (defaults to automatic selection) */
|
|
60
|
+
worker?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Browser action types
|
|
65
|
+
*/
|
|
66
|
+
export type BrowseActionType =
|
|
67
|
+
| 'navigate'
|
|
68
|
+
| 'click'
|
|
69
|
+
| 'type'
|
|
70
|
+
| 'scroll'
|
|
71
|
+
| 'wait'
|
|
72
|
+
| 'screenshot'
|
|
73
|
+
| 'extract'
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Individual browser action record
|
|
77
|
+
*/
|
|
78
|
+
export interface BrowseAction {
|
|
79
|
+
/** Type of action performed */
|
|
80
|
+
type: BrowseActionType
|
|
81
|
+
/** Target selector or URL */
|
|
82
|
+
target?: string
|
|
83
|
+
/** Value used in the action (text typed, scroll amount, etc.) */
|
|
84
|
+
value?: string
|
|
85
|
+
/** Timestamp when action was performed */
|
|
86
|
+
timestamp?: Date
|
|
87
|
+
/** Whether action succeeded */
|
|
88
|
+
success?: boolean
|
|
89
|
+
/** Error message if action failed */
|
|
90
|
+
error?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Result of browser automation
|
|
95
|
+
*/
|
|
96
|
+
export interface BrowseResult {
|
|
97
|
+
/** Whether the overall task succeeded */
|
|
98
|
+
success: boolean
|
|
99
|
+
/** Extracted data or task result */
|
|
100
|
+
data?: unknown
|
|
101
|
+
/** Screenshot as base64 string (if requested) */
|
|
102
|
+
screenshot?: string
|
|
103
|
+
/** List of actions performed */
|
|
104
|
+
actions: BrowseAction[]
|
|
105
|
+
/** Error message if task failed */
|
|
106
|
+
error?: string
|
|
107
|
+
/** Duration of task execution (ms) */
|
|
108
|
+
duration?: number
|
|
109
|
+
/** Final URL after navigation */
|
|
110
|
+
finalUrl?: string
|
|
111
|
+
/** Page title */
|
|
112
|
+
title?: string
|
|
113
|
+
/** Worker that executed the task */
|
|
114
|
+
executedBy?: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Options for click action
|
|
119
|
+
*/
|
|
120
|
+
export interface ClickOptions {
|
|
121
|
+
/** Wait for navigation after click */
|
|
122
|
+
waitForNavigation?: boolean
|
|
123
|
+
/** Click position offset from element center */
|
|
124
|
+
offset?: { x: number; y: number }
|
|
125
|
+
/** Number of clicks */
|
|
126
|
+
clickCount?: number
|
|
127
|
+
/** Mouse button */
|
|
128
|
+
button?: 'left' | 'right' | 'middle'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Options for type action
|
|
133
|
+
*/
|
|
134
|
+
export interface TypeOptions {
|
|
135
|
+
/** Clear existing text before typing */
|
|
136
|
+
clear?: boolean
|
|
137
|
+
/** Delay between keystrokes (ms) */
|
|
138
|
+
delay?: number
|
|
139
|
+
/** Press Enter after typing */
|
|
140
|
+
pressEnter?: boolean
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Options for scroll action
|
|
145
|
+
*/
|
|
146
|
+
export interface ScrollOptions {
|
|
147
|
+
/** Scroll direction */
|
|
148
|
+
direction: 'up' | 'down' | 'left' | 'right'
|
|
149
|
+
/** Amount to scroll in pixels */
|
|
150
|
+
amount?: number
|
|
151
|
+
/** Scroll to specific element */
|
|
152
|
+
toElement?: string
|
|
153
|
+
/** Scroll smoothly */
|
|
154
|
+
smooth?: boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Options for screenshot action
|
|
159
|
+
*/
|
|
160
|
+
export interface ScreenshotOptions {
|
|
161
|
+
/** Capture full page or viewport only */
|
|
162
|
+
fullPage?: boolean
|
|
163
|
+
/** Specific element to capture */
|
|
164
|
+
selector?: string
|
|
165
|
+
/** Image format */
|
|
166
|
+
format?: 'png' | 'jpeg' | 'webp'
|
|
167
|
+
/** Image quality (0-100) for jpeg/webp */
|
|
168
|
+
quality?: number
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Options for extract action
|
|
173
|
+
*/
|
|
174
|
+
export interface ExtractOptions {
|
|
175
|
+
/** Schema describing the data to extract */
|
|
176
|
+
schema?: Record<string, unknown>
|
|
177
|
+
/** CSS selector to scope extraction */
|
|
178
|
+
selector?: string
|
|
179
|
+
/** Extract multiple items */
|
|
180
|
+
multiple?: boolean
|
|
181
|
+
/** Include raw HTML */
|
|
182
|
+
includeHtml?: boolean
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Main Browse Function
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Execute browser automation by routing to an appropriate Worker (AI Agent or Human).
|
|
191
|
+
*
|
|
192
|
+
* This function uses AI to plan and execute browser actions to accomplish
|
|
193
|
+
* a given task. It supports human fallback for complex interactions that
|
|
194
|
+
* require human judgment.
|
|
195
|
+
*
|
|
196
|
+
* @param url - URL to navigate to
|
|
197
|
+
* @param task - Description of the task to accomplish
|
|
198
|
+
* @param options - Additional options for browser automation
|
|
199
|
+
* @returns Promise resolving to browse result with execution details
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* // Simple navigation and extraction
|
|
204
|
+
* const result = await browse('https://example.com', 'Find the contact email')
|
|
205
|
+
*
|
|
206
|
+
* if (result.success) {
|
|
207
|
+
* console.log('Email:', result.data)
|
|
208
|
+
* }
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* // Form submission
|
|
214
|
+
* const result = await browse('https://example.com/login', 'Log in with test credentials', {
|
|
215
|
+
* context: {
|
|
216
|
+
* username: 'testuser',
|
|
217
|
+
* password: 'testpass123',
|
|
218
|
+
* },
|
|
219
|
+
* timeout: 30000,
|
|
220
|
+
* })
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* // Complex interaction with human fallback
|
|
226
|
+
* const result = await browse('https://example.com/checkout', 'Complete the purchase', {
|
|
227
|
+
* humanFallback: true,
|
|
228
|
+
* timeout: 60000,
|
|
229
|
+
* })
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export async function browse(
|
|
233
|
+
url: string,
|
|
234
|
+
task: string,
|
|
235
|
+
options: Partial<BrowseOptions> = {}
|
|
236
|
+
): Promise<BrowseResult> {
|
|
237
|
+
const {
|
|
238
|
+
timeout = 30000,
|
|
239
|
+
headless = true,
|
|
240
|
+
viewport = { width: 1280, height: 720 },
|
|
241
|
+
context,
|
|
242
|
+
waitFor,
|
|
243
|
+
humanFallback = false,
|
|
244
|
+
} = options
|
|
245
|
+
|
|
246
|
+
const startTime = Date.now()
|
|
247
|
+
const actions: BrowseAction[] = []
|
|
248
|
+
|
|
249
|
+
// Record initial navigation
|
|
250
|
+
actions.push({
|
|
251
|
+
type: 'navigate',
|
|
252
|
+
target: url,
|
|
253
|
+
timestamp: new Date(),
|
|
254
|
+
success: true,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// Use agentic function for browser automation planning
|
|
258
|
+
const browserFn = define.agentic({
|
|
259
|
+
name: 'browserAutomation',
|
|
260
|
+
description: 'Plan and execute browser actions to accomplish a task',
|
|
261
|
+
args: {
|
|
262
|
+
url: 'The URL to navigate to',
|
|
263
|
+
task: 'The task to accomplish',
|
|
264
|
+
contextInfo: 'Additional context and parameters',
|
|
265
|
+
viewportSize: 'Browser viewport dimensions',
|
|
266
|
+
},
|
|
267
|
+
returnType: {
|
|
268
|
+
success: 'Whether the task was completed successfully',
|
|
269
|
+
data: 'Extracted data or task result',
|
|
270
|
+
actions: ['List of browser actions taken'],
|
|
271
|
+
finalUrl: 'The final URL after all actions',
|
|
272
|
+
title: 'The page title',
|
|
273
|
+
error: 'Error message if task failed',
|
|
274
|
+
},
|
|
275
|
+
instructions: `You are a browser automation agent. Plan and execute browser actions to accomplish the task.
|
|
276
|
+
|
|
277
|
+
URL: ${url}
|
|
278
|
+
Task: ${task}
|
|
279
|
+
Viewport: ${viewport.width}x${viewport.height}
|
|
280
|
+
${context ? `Context: ${JSON.stringify(context, null, 2)}` : ''}
|
|
281
|
+
${waitFor ? `Wait for selector: ${waitFor}` : ''}
|
|
282
|
+
|
|
283
|
+
Available actions:
|
|
284
|
+
- navigate(url): Go to a URL
|
|
285
|
+
- click(selector): Click an element
|
|
286
|
+
- type(selector, text): Type text into an input
|
|
287
|
+
- scroll(direction, amount): Scroll the page
|
|
288
|
+
- wait(ms): Wait for a duration
|
|
289
|
+
- waitForSelector(selector): Wait for element to appear
|
|
290
|
+
- screenshot(): Take a screenshot
|
|
291
|
+
- extract(schema): Extract data from the page
|
|
292
|
+
|
|
293
|
+
Plan your actions step by step to accomplish the task efficiently.`,
|
|
294
|
+
maxIterations: 10,
|
|
295
|
+
tools: [], // Browser tools would be provided by the execution environment
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const response = (await Promise.race([
|
|
300
|
+
browserFn.call({
|
|
301
|
+
url,
|
|
302
|
+
task,
|
|
303
|
+
contextInfo: context ? JSON.stringify(context) : '',
|
|
304
|
+
viewportSize: `${viewport.width}x${viewport.height}`,
|
|
305
|
+
}),
|
|
306
|
+
timeout
|
|
307
|
+
? new Promise((_, reject) =>
|
|
308
|
+
setTimeout(() => reject(new Error('Browser automation timeout')), timeout)
|
|
309
|
+
)
|
|
310
|
+
: new Promise(() => {}),
|
|
311
|
+
])) as {
|
|
312
|
+
success: boolean
|
|
313
|
+
data?: unknown
|
|
314
|
+
actions?: Array<{ type: string; target?: string; value?: string }>
|
|
315
|
+
finalUrl?: string
|
|
316
|
+
title?: string
|
|
317
|
+
error?: string
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Record actions from response
|
|
321
|
+
if (response.actions) {
|
|
322
|
+
actions.push(
|
|
323
|
+
...response.actions.map((action) => {
|
|
324
|
+
const browseAction: BrowseAction = {
|
|
325
|
+
type: action.type as BrowseActionType,
|
|
326
|
+
timestamp: new Date(),
|
|
327
|
+
success: true,
|
|
328
|
+
}
|
|
329
|
+
if (action.target !== undefined) {
|
|
330
|
+
browseAction.target = action.target
|
|
331
|
+
}
|
|
332
|
+
if (action.value !== undefined) {
|
|
333
|
+
browseAction.value = action.value
|
|
334
|
+
}
|
|
335
|
+
return browseAction
|
|
336
|
+
})
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const duration = Date.now() - startTime
|
|
341
|
+
|
|
342
|
+
const result: BrowseResult = {
|
|
343
|
+
success: response.success,
|
|
344
|
+
actions,
|
|
345
|
+
duration,
|
|
346
|
+
finalUrl: response.finalUrl || url,
|
|
347
|
+
}
|
|
348
|
+
if (response.data !== undefined) {
|
|
349
|
+
result.data = response.data
|
|
350
|
+
}
|
|
351
|
+
if (response.error !== undefined) {
|
|
352
|
+
result.error = response.error
|
|
353
|
+
}
|
|
354
|
+
if (response.title !== undefined) {
|
|
355
|
+
result.title = response.title
|
|
356
|
+
}
|
|
357
|
+
return result
|
|
358
|
+
} catch (error) {
|
|
359
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
360
|
+
|
|
361
|
+
// If human fallback is enabled and AI fails, route to human
|
|
362
|
+
if (humanFallback) {
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
actions,
|
|
366
|
+
error: `AI automation failed, human fallback requested: ${errorMessage}`,
|
|
367
|
+
duration: Date.now() - startTime,
|
|
368
|
+
executedBy: 'pending-human-fallback',
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
actions,
|
|
375
|
+
error: errorMessage,
|
|
376
|
+
duration: Date.now() - startTime,
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Helper Methods
|
|
383
|
+
// ============================================================================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Click an element on a page
|
|
387
|
+
*
|
|
388
|
+
* @param url - URL of the page
|
|
389
|
+
* @param selector - CSS selector of the element to click
|
|
390
|
+
* @param options - Click options
|
|
391
|
+
* @returns Promise resolving to browse result
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```ts
|
|
395
|
+
* const result = await browse.click('https://example.com', '#submit-button')
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
browse.click = async (
|
|
399
|
+
url: string,
|
|
400
|
+
selector: string,
|
|
401
|
+
options: ClickOptions = {}
|
|
402
|
+
): Promise<BrowseResult> => {
|
|
403
|
+
const { waitForNavigation = false, clickCount = 1 } = options
|
|
404
|
+
|
|
405
|
+
return browse(url, `Click the element matching selector "${selector}"`, {
|
|
406
|
+
context: {
|
|
407
|
+
action: 'click',
|
|
408
|
+
selector,
|
|
409
|
+
waitForNavigation,
|
|
410
|
+
clickCount,
|
|
411
|
+
},
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Type text into an input element
|
|
417
|
+
*
|
|
418
|
+
* @param url - URL of the page
|
|
419
|
+
* @param selector - CSS selector of the input element
|
|
420
|
+
* @param text - Text to type
|
|
421
|
+
* @param options - Type options
|
|
422
|
+
* @returns Promise resolving to browse result
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```ts
|
|
426
|
+
* const result = await browse.type('https://example.com', '#search-input', 'hello world')
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
browse.type = async (
|
|
430
|
+
url: string,
|
|
431
|
+
selector: string,
|
|
432
|
+
text: string,
|
|
433
|
+
options: TypeOptions = {}
|
|
434
|
+
): Promise<BrowseResult> => {
|
|
435
|
+
const { clear = false, delay = 0, pressEnter = false } = options
|
|
436
|
+
|
|
437
|
+
return browse(url, `Type "${text}" into the element matching selector "${selector}"`, {
|
|
438
|
+
context: {
|
|
439
|
+
action: 'type',
|
|
440
|
+
selector,
|
|
441
|
+
text,
|
|
442
|
+
clear,
|
|
443
|
+
delay,
|
|
444
|
+
pressEnter,
|
|
445
|
+
},
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Scroll the page
|
|
451
|
+
*
|
|
452
|
+
* @param url - URL of the page
|
|
453
|
+
* @param direction - Scroll direction
|
|
454
|
+
* @param amount - Amount to scroll in pixels (default: 500)
|
|
455
|
+
* @param options - Additional scroll options
|
|
456
|
+
* @returns Promise resolving to browse result
|
|
457
|
+
*
|
|
458
|
+
* @example
|
|
459
|
+
* ```ts
|
|
460
|
+
* const result = await browse.scroll('https://example.com', 'down', 500)
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
browse.scroll = async (
|
|
464
|
+
url: string,
|
|
465
|
+
direction: 'up' | 'down' | 'left' | 'right',
|
|
466
|
+
amount: number = 500,
|
|
467
|
+
options: Partial<ScrollOptions> = {}
|
|
468
|
+
): Promise<BrowseResult> => {
|
|
469
|
+
const { toElement, smooth = true } = options
|
|
470
|
+
|
|
471
|
+
return browse(url, `Scroll ${direction} by ${amount} pixels`, {
|
|
472
|
+
context: {
|
|
473
|
+
action: 'scroll',
|
|
474
|
+
direction,
|
|
475
|
+
amount,
|
|
476
|
+
toElement,
|
|
477
|
+
smooth,
|
|
478
|
+
},
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Take a screenshot of the page
|
|
484
|
+
*
|
|
485
|
+
* @param url - URL of the page
|
|
486
|
+
* @param options - Screenshot options
|
|
487
|
+
* @returns Promise resolving to browse result with screenshot
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```ts
|
|
491
|
+
* const result = await browse.screenshot('https://example.com')
|
|
492
|
+
* if (result.screenshot) {
|
|
493
|
+
* // Save or process the base64 screenshot
|
|
494
|
+
* }
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
browse.screenshot = async (url: string, options: ScreenshotOptions = {}): Promise<BrowseResult> => {
|
|
498
|
+
const { fullPage = false, selector, format = 'png', quality } = options
|
|
499
|
+
|
|
500
|
+
return browse(url, 'Take a screenshot of the page', {
|
|
501
|
+
context: {
|
|
502
|
+
action: 'screenshot',
|
|
503
|
+
fullPage,
|
|
504
|
+
selector,
|
|
505
|
+
format,
|
|
506
|
+
quality,
|
|
507
|
+
},
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Extract structured data from a page
|
|
513
|
+
*
|
|
514
|
+
* @param url - URL of the page
|
|
515
|
+
* @param schema - Schema describing the data to extract
|
|
516
|
+
* @param options - Extract options
|
|
517
|
+
* @returns Promise resolving to browse result with extracted data
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* const result = await browse.extract('https://example.com/products', {
|
|
522
|
+
* products: [{
|
|
523
|
+
* name: 'Product name',
|
|
524
|
+
* price: 'Product price as number',
|
|
525
|
+
* description: 'Product description',
|
|
526
|
+
* }],
|
|
527
|
+
* })
|
|
528
|
+
*
|
|
529
|
+
* if (result.success) {
|
|
530
|
+
* console.log('Products:', result.data)
|
|
531
|
+
* }
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
browse.extract = async (
|
|
535
|
+
url: string,
|
|
536
|
+
schema: Record<string, unknown>,
|
|
537
|
+
options: ExtractOptions = {}
|
|
538
|
+
): Promise<BrowseResult> => {
|
|
539
|
+
const { selector, multiple = false, includeHtml = false } = options
|
|
540
|
+
|
|
541
|
+
return browse(url, 'Extract structured data from the page according to the schema', {
|
|
542
|
+
context: {
|
|
543
|
+
action: 'extract',
|
|
544
|
+
schema,
|
|
545
|
+
selector,
|
|
546
|
+
multiple,
|
|
547
|
+
includeHtml,
|
|
548
|
+
},
|
|
549
|
+
})
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Wait for an element to appear on the page
|
|
554
|
+
*
|
|
555
|
+
* @param url - URL of the page
|
|
556
|
+
* @param selector - CSS selector to wait for
|
|
557
|
+
* @param timeout - Maximum time to wait (ms)
|
|
558
|
+
* @returns Promise resolving to browse result
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* const result = await browse.waitFor('https://example.com', '.loaded-content', 5000)
|
|
563
|
+
* ```
|
|
564
|
+
*/
|
|
565
|
+
browse.waitFor = async (
|
|
566
|
+
url: string,
|
|
567
|
+
selector: string,
|
|
568
|
+
timeout: number = 30000
|
|
569
|
+
): Promise<BrowseResult> => {
|
|
570
|
+
return browse(url, `Wait for element matching selector "${selector}" to appear`, {
|
|
571
|
+
waitFor: selector,
|
|
572
|
+
timeout,
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Fill out a form with multiple fields
|
|
578
|
+
*
|
|
579
|
+
* @param url - URL of the page with the form
|
|
580
|
+
* @param formData - Object mapping selectors to values
|
|
581
|
+
* @param submitSelector - Optional selector for the submit button
|
|
582
|
+
* @returns Promise resolving to browse result
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* const result = await browse.fill('https://example.com/contact', {
|
|
587
|
+
* '#name': 'John Doe',
|
|
588
|
+
* '#email': 'john@example.com',
|
|
589
|
+
* '#message': 'Hello!',
|
|
590
|
+
* }, '#submit')
|
|
591
|
+
* ```
|
|
592
|
+
*/
|
|
593
|
+
browse.fill = async (
|
|
594
|
+
url: string,
|
|
595
|
+
formData: Record<string, string>,
|
|
596
|
+
submitSelector?: string
|
|
597
|
+
): Promise<BrowseResult> => {
|
|
598
|
+
return browse(url, 'Fill out the form with the provided data and submit if requested', {
|
|
599
|
+
context: {
|
|
600
|
+
action: 'fill',
|
|
601
|
+
formData,
|
|
602
|
+
submitSelector,
|
|
603
|
+
},
|
|
604
|
+
})
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Navigate through multiple pages
|
|
609
|
+
*
|
|
610
|
+
* @param urls - Array of URLs to visit
|
|
611
|
+
* @param taskPerPage - Task to perform on each page
|
|
612
|
+
* @returns Promise resolving to array of browse results
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* const results = await browse.crawl(
|
|
617
|
+
* ['https://example.com/page1', 'https://example.com/page2'],
|
|
618
|
+
* 'Extract the page title'
|
|
619
|
+
* )
|
|
620
|
+
* ```
|
|
621
|
+
*/
|
|
622
|
+
browse.crawl = async (urls: string[], taskPerPage: string): Promise<BrowseResult[]> => {
|
|
623
|
+
return Promise.all(urls.map((url) => browse(url, taskPerPage)))
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Export as default as well
|
|
627
|
+
export default browse
|
package/src/capability-tiers.ts
CHANGED
|
@@ -399,7 +399,7 @@ export function createCapabilityProfile(input: {
|
|
|
399
399
|
if (complexityRating < config.minComplexity || complexityRating > config.maxComplexity) {
|
|
400
400
|
throw new Error(
|
|
401
401
|
`Complexity rating ${complexityRating} is outside bounds for ${tier} tier ` +
|
|
402
|
-
|
|
402
|
+
`(${config.minComplexity}-${config.maxComplexity})`
|
|
403
403
|
)
|
|
404
404
|
}
|
|
405
405
|
|
|
@@ -409,7 +409,7 @@ export function createCapabilityProfile(input: {
|
|
|
409
409
|
if (!allowedTools.has(tool)) {
|
|
410
410
|
throw new Error(
|
|
411
411
|
`Tool '${tool}' is not allowed at ${tier} tier. ` +
|
|
412
|
-
|
|
412
|
+
`Allowed tools: ${config.allowedTools.join(', ')}`
|
|
413
413
|
)
|
|
414
414
|
}
|
|
415
415
|
}
|
|
@@ -419,8 +419,8 @@ export function createCapabilityProfile(input: {
|
|
|
419
419
|
tier,
|
|
420
420
|
complexityRating,
|
|
421
421
|
tools,
|
|
422
|
-
description,
|
|
423
|
-
constraints,
|
|
422
|
+
...(description !== undefined && { description }),
|
|
423
|
+
...(constraints !== undefined && { constraints }),
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
@@ -500,7 +500,7 @@ export class TierRegistry {
|
|
|
500
500
|
const result: CapabilityProfile[] = []
|
|
501
501
|
for (const profile of this.profiles.values()) {
|
|
502
502
|
const profileTools = new Set(profile.tools)
|
|
503
|
-
const hasAllTools = requiredTools.every(tool => profileTools.has(tool))
|
|
503
|
+
const hasAllTools = requiredTools.every((tool) => profileTools.has(tool))
|
|
504
504
|
if (hasAllTools) {
|
|
505
505
|
result.push(profile)
|
|
506
506
|
}
|