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.
Files changed (183) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +2 -0
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +33 -21
  6. package/dist/actions.js.map +1 -1
  7. package/dist/agent-comms.d.ts.map +1 -1
  8. package/dist/agent-comms.js +36 -25
  9. package/dist/agent-comms.js.map +1 -1
  10. package/dist/approve.d.ts +40 -8
  11. package/dist/approve.d.ts.map +1 -1
  12. package/dist/approve.js +86 -20
  13. package/dist/approve.js.map +1 -1
  14. package/dist/ask.d.ts +38 -7
  15. package/dist/ask.d.ts.map +1 -1
  16. package/dist/ask.js +85 -25
  17. package/dist/ask.js.map +1 -1
  18. package/dist/browse.d.ts +223 -0
  19. package/dist/browse.d.ts.map +1 -0
  20. package/dist/browse.js +392 -0
  21. package/dist/browse.js.map +1 -0
  22. package/dist/capability-tiers.js +3 -3
  23. package/dist/capability-tiers.js.map +1 -1
  24. package/dist/cascade-context.d.ts +28 -28
  25. package/dist/client.d.ts +162 -0
  26. package/dist/client.d.ts.map +1 -0
  27. package/dist/client.js +64 -0
  28. package/dist/client.js.map +1 -0
  29. package/dist/decide.d.ts +42 -6
  30. package/dist/decide.d.ts.map +1 -1
  31. package/dist/decide.js +54 -11
  32. package/dist/decide.js.map +1 -1
  33. package/dist/do.d.ts +36 -7
  34. package/dist/do.d.ts.map +1 -1
  35. package/dist/do.js +82 -39
  36. package/dist/do.js.map +1 -1
  37. package/dist/error-escalation.d.ts.map +1 -1
  38. package/dist/error-escalation.js +38 -38
  39. package/dist/error-escalation.js.map +1 -1
  40. package/dist/generate.d.ts +48 -7
  41. package/dist/generate.d.ts.map +1 -1
  42. package/dist/generate.js +49 -8
  43. package/dist/generate.js.map +1 -1
  44. package/dist/goals.d.ts +10 -9
  45. package/dist/goals.d.ts.map +1 -1
  46. package/dist/goals.js +30 -24
  47. package/dist/goals.js.map +1 -1
  48. package/dist/image.d.ts +189 -0
  49. package/dist/image.d.ts.map +1 -0
  50. package/dist/image.js +528 -0
  51. package/dist/image.js.map +1 -0
  52. package/dist/index.d.ts +49 -2
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +58 -2
  55. package/dist/index.js.map +1 -1
  56. package/dist/is.d.ts +45 -10
  57. package/dist/is.d.ts.map +1 -1
  58. package/dist/is.js +56 -21
  59. package/dist/is.js.map +1 -1
  60. package/dist/kpis.d.ts +24 -15
  61. package/dist/kpis.d.ts.map +1 -1
  62. package/dist/kpis.js +16 -14
  63. package/dist/kpis.js.map +1 -1
  64. package/dist/load-balancing.d.ts.map +1 -1
  65. package/dist/load-balancing.js +124 -38
  66. package/dist/load-balancing.js.map +1 -1
  67. package/dist/logger.d.ts +76 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +39 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/notify.d.ts +38 -9
  72. package/dist/notify.d.ts.map +1 -1
  73. package/dist/notify.js +72 -17
  74. package/dist/notify.js.map +1 -1
  75. package/dist/role.d.ts +5 -4
  76. package/dist/role.d.ts.map +1 -1
  77. package/dist/role.js +13 -10
  78. package/dist/role.js.map +1 -1
  79. package/dist/runtime.d.ts +310 -0
  80. package/dist/runtime.d.ts.map +1 -0
  81. package/dist/runtime.js +510 -0
  82. package/dist/runtime.js.map +1 -0
  83. package/dist/team.d.ts +11 -6
  84. package/dist/team.d.ts.map +1 -1
  85. package/dist/team.js +22 -15
  86. package/dist/team.js.map +1 -1
  87. package/dist/transports/email.d.ts +318 -0
  88. package/dist/transports/email.d.ts.map +1 -0
  89. package/dist/transports/email.js +779 -0
  90. package/dist/transports/email.js.map +1 -0
  91. package/dist/transports/slack.d.ts +515 -0
  92. package/dist/transports/slack.d.ts.map +1 -0
  93. package/dist/transports/slack.js +844 -0
  94. package/dist/transports/slack.js.map +1 -0
  95. package/dist/transports.d.ts.map +1 -1
  96. package/dist/transports.js +44 -25
  97. package/dist/transports.js.map +1 -1
  98. package/dist/types.d.ts +141 -19
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/types.js +5 -0
  101. package/dist/types.js.map +1 -1
  102. package/dist/utils/id.d.ts +19 -0
  103. package/dist/utils/id.d.ts.map +1 -0
  104. package/dist/utils/id.js +21 -0
  105. package/dist/utils/id.js.map +1 -0
  106. package/dist/video.d.ts +203 -0
  107. package/dist/video.d.ts.map +1 -0
  108. package/dist/video.js +528 -0
  109. package/dist/video.js.map +1 -0
  110. package/dist/worker.d.ts +343 -0
  111. package/dist/worker.d.ts.map +1 -0
  112. package/dist/worker.js +698 -0
  113. package/dist/worker.js.map +1 -0
  114. package/package.json +32 -14
  115. package/src/actions.ts +39 -30
  116. package/src/agent-comms.ts +54 -92
  117. package/src/approve.ts +91 -20
  118. package/src/ask.ts +99 -25
  119. package/src/browse.ts +627 -0
  120. package/src/capability-tiers.ts +5 -5
  121. package/src/client.ts +221 -0
  122. package/src/decide.ts +81 -35
  123. package/src/do.ts +98 -52
  124. package/src/error-escalation.ts +55 -67
  125. package/src/generate.ts +52 -18
  126. package/src/goals.ts +36 -27
  127. package/src/image.ts +816 -0
  128. package/src/index.ts +187 -2
  129. package/src/is.ts +59 -25
  130. package/src/kpis.ts +41 -36
  131. package/src/load-balancing.ts +132 -46
  132. package/src/logger.ts +93 -0
  133. package/src/notify.ts +78 -17
  134. package/src/role.ts +30 -20
  135. package/src/runtime.ts +796 -0
  136. package/src/team.ts +24 -19
  137. package/src/transports/email.ts +1160 -0
  138. package/src/transports/slack.ts +1320 -0
  139. package/src/transports.ts +58 -43
  140. package/src/types.ts +174 -46
  141. package/src/utils/id.ts +21 -0
  142. package/src/video.ts +906 -0
  143. package/src/worker.ts +1007 -0
  144. package/test/approve.test.ts +305 -0
  145. package/test/ask.test.ts +274 -0
  146. package/test/browse.test.ts +361 -0
  147. package/test/decide.test.ts +252 -0
  148. package/test/do.test.ts +144 -0
  149. package/test/error-logging.test.ts +357 -0
  150. package/test/generate.test.ts +319 -0
  151. package/test/image.test.ts +398 -0
  152. package/test/is.test.ts +287 -0
  153. package/test/load-balancing-safety.test.ts +404 -0
  154. package/test/notify.test.ts +434 -0
  155. package/test/primitives.test.ts +320 -0
  156. package/test/runtime-integration.test.ts +892 -0
  157. package/test/transports/crypto.test.ts +230 -0
  158. package/test/transports/email.test.ts +866 -0
  159. package/test/transports/id-generation.test.ts +91 -0
  160. package/test/transports/slack.test.ts +760 -0
  161. package/test/type-safety.test.ts +834 -0
  162. package/test/types.test.ts +60 -2
  163. package/test/video.test.ts +530 -0
  164. package/test/worker.test.ts +1433 -0
  165. package/tsconfig.json +4 -1
  166. package/vitest.config.ts +42 -0
  167. package/wrangler.jsonc +36 -0
  168. package/LICENSE +0 -21
  169. package/src/actions.js +0 -436
  170. package/src/approve.js +0 -234
  171. package/src/ask.js +0 -226
  172. package/src/decide.js +0 -244
  173. package/src/do.js +0 -227
  174. package/src/generate.js +0 -298
  175. package/src/goals.js +0 -205
  176. package/src/index.js +0 -68
  177. package/src/is.js +0 -317
  178. package/src/kpis.js +0 -270
  179. package/src/notify.js +0 -219
  180. package/src/role.js +0 -110
  181. package/src/team.js +0 -130
  182. package/src/transports.js +0 -357
  183. 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
@@ -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
- `(${config.minComplexity}-${config.maxComplexity})`
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
- `Allowed tools: ${config.allowedTools.join(', ')}`
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
  }