@brightdata/brightdata-plugin 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bright Data
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # Bright Data Plugin for OpenClaw
2
+
3
+ Web search, scraping, browser automation, and 50+ structured data tools — powered by [Bright Data](https://brightdata.com).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ openclaw plugins install @brightdata/brightdata-plugin
9
+ ```
10
+
11
+ ## Configure
12
+
13
+ Set your Bright Data API token (get one at [brightdata.com](https://brightdata.com)):
14
+
15
+ ```bash
16
+ # Option A: environment variable
17
+ export BRIGHTDATA_API_TOKEN=your_token
18
+
19
+ # Option B: OpenClaw config
20
+ openclaw config set plugins.entries.brightdata.config.webSearch.apiKey your_token
21
+ ```
22
+
23
+ The plugin auto-creates two proxy zones on first use: `mcp_unlocker` (Web Unlocker) and `mcp_browser` (Browser API). To use existing zones:
24
+
25
+ ```bash
26
+ export BRIGHTDATA_UNLOCKER_ZONE=my_unlocker_zone
27
+ export BRIGHTDATA_BROWSER_ZONE=my_browser_zone
28
+ ```
29
+
30
+ ## What's Included
31
+
32
+ **66 tools** across five categories:
33
+
34
+ | Category | Tools | Description |
35
+ |----------|-------|-------------|
36
+ | [Search](#search) | 2 | Web search via Google, Bing, Yandex with geo-targeting |
37
+ | [Scrape](#scrape) | 1 | Page extraction as markdown, text, or HTML — handles bot protection |
38
+ | [Batch](#batch) | 2 | Parallel search and scrape (up to 5 items) |
39
+ | [Browser](#browser-automation) | 14 | Full browser automation via Playwright CDP |
40
+ | [Web Data](#web-data) | 47 | Structured data from Amazon, LinkedIn, Instagram, TikTok, and more |
41
+
42
+ ---
43
+
44
+ ### Search
45
+
46
+ The plugin registers as an OpenClaw **web search provider** — it appears in provider selection automatically.
47
+
48
+ | Tool | Description |
49
+ |------|-------------|
50
+ | `brightdata_search` | Search Google, Bing, or Yandex with pagination, geo-targeting, and result count control |
51
+
52
+ ```
53
+ Parameters: query (required), engine ("google"|"bing"|"yandex"), count (1-10),
54
+ cursor, geo_location (2-letter ISO), timeoutSeconds
55
+ ```
56
+
57
+ ### Scrape
58
+
59
+ | Tool | Description |
60
+ |------|-------------|
61
+ | `brightdata_scrape` | Fetch and extract a page through Bright Data Web Unlocker, including bot-protected pages |
62
+
63
+ ```
64
+ Parameters: url (required), extractMode ("markdown"|"text"|"html"), maxChars (≥100), timeoutSeconds
65
+ ```
66
+
67
+ ### Batch
68
+
69
+ | Tool | Description |
70
+ |------|-------------|
71
+ | `brightdata_search_batch` | Run up to 5 search queries in parallel |
72
+ | `brightdata_scrape_batch` | Scrape up to 5 URLs in parallel |
73
+
74
+ ### Browser Automation
75
+
76
+ Full browser control via Bright Data's residential proxy network. Sessions are scoped per user context and idle-timeout after 10 minutes.
77
+
78
+ | Tool | Description |
79
+ |------|-------------|
80
+ | `brightdata_browser_navigate` | Navigate to a URL (optional country routing) |
81
+ | `brightdata_browser_go_back` | Go back |
82
+ | `brightdata_browser_go_forward` | Go forward |
83
+ | `brightdata_browser_snapshot` | Capture ARIA snapshot with interactive element refs |
84
+ | `brightdata_browser_click` | Click an element by ref |
85
+ | `brightdata_browser_type` | Type into an element by ref (optional submit) |
86
+ | `brightdata_browser_screenshot` | Take a screenshot (viewport or full page) |
87
+ | `brightdata_browser_get_html` | Get page HTML |
88
+ | `brightdata_browser_get_text` | Get page text content |
89
+ | `brightdata_browser_scroll` | Scroll to bottom |
90
+ | `brightdata_browser_scroll_to` | Scroll to a specific element by ref |
91
+ | `brightdata_browser_wait_for` | Wait for an element to be visible |
92
+ | `brightdata_browser_network_requests` | List network requests since page load |
93
+ | `brightdata_browser_fill_form` | Fill multiple form fields in one operation |
94
+
95
+ ### Web Data
96
+
97
+ Structured data extraction from 47 platforms via Bright Data datasets. Each tool accepts a `url` or `keyword` input and returns structured JSON.
98
+
99
+ <details>
100
+ <summary>All 47 dataset tools</summary>
101
+
102
+ **E-commerce**
103
+ | Tool | Platform |
104
+ |------|----------|
105
+ | `brightdata_amazon_product` | Amazon product details |
106
+ | `brightdata_amazon_product_reviews` | Amazon reviews |
107
+ | `brightdata_amazon_product_search` | Amazon search results |
108
+ | `brightdata_walmart_product` | Walmart product details |
109
+ | `brightdata_walmart_seller` | Walmart seller info |
110
+ | `brightdata_ebay_product` | eBay product details |
111
+ | `brightdata_homedepot_products` | Home Depot products |
112
+ | `brightdata_zara_products` | Zara products |
113
+ | `brightdata_etsy_products` | Etsy products |
114
+ | `brightdata_bestbuy_products` | Best Buy products |
115
+
116
+ **Professional Networks**
117
+ | Tool | Platform |
118
+ |------|----------|
119
+ | `brightdata_linkedin_person_profile` | LinkedIn person profile |
120
+ | `brightdata_linkedin_company_profile` | LinkedIn company profile |
121
+ | `brightdata_linkedin_job_listings` | LinkedIn jobs |
122
+ | `brightdata_linkedin_posts` | LinkedIn posts |
123
+ | `brightdata_linkedin_people_search` | LinkedIn people search |
124
+ | `brightdata_crunchbase_company` | Crunchbase company data |
125
+ | `brightdata_zoominfo_company_profile` | ZoomInfo company profile |
126
+
127
+ **Social Media — Instagram**
128
+ | Tool | Platform |
129
+ |------|----------|
130
+ | `brightdata_instagram_profiles` | Instagram profiles |
131
+ | `brightdata_instagram_posts` | Instagram posts |
132
+ | `brightdata_instagram_reels` | Instagram reels |
133
+ | `brightdata_instagram_comments` | Instagram comments |
134
+
135
+ **Social Media — Facebook**
136
+ | Tool | Platform |
137
+ |------|----------|
138
+ | `brightdata_facebook_posts` | Facebook posts |
139
+ | `brightdata_facebook_marketplace_listings` | Facebook Marketplace |
140
+ | `brightdata_facebook_company_reviews` | Facebook company reviews |
141
+ | `brightdata_facebook_events` | Facebook events |
142
+
143
+ **Social Media — TikTok**
144
+ | Tool | Platform |
145
+ |------|----------|
146
+ | `brightdata_tiktok_profiles` | TikTok profiles |
147
+ | `brightdata_tiktok_posts` | TikTok posts |
148
+ | `brightdata_tiktok_shop` | TikTok Shop |
149
+ | `brightdata_tiktok_comments` | TikTok comments |
150
+
151
+ **Social Media — X (Twitter)**
152
+ | Tool | Platform |
153
+ |------|----------|
154
+ | `brightdata_x_posts` | X posts |
155
+ | `brightdata_x_profile_posts` | X profile posts |
156
+
157
+ **Social Media — YouTube & Reddit**
158
+ | Tool | Platform |
159
+ |------|----------|
160
+ | `brightdata_youtube_profiles` | YouTube profiles |
161
+ | `brightdata_youtube_videos` | YouTube videos |
162
+ | `brightdata_youtube_comments` | YouTube comments |
163
+ | `brightdata_reddit_posts` | Reddit posts |
164
+
165
+ **Maps, Shopping & Apps**
166
+ | Tool | Platform |
167
+ |------|----------|
168
+ | `brightdata_google_maps_reviews` | Google Maps reviews |
169
+ | `brightdata_google_shopping` | Google Shopping |
170
+ | `brightdata_google_play_store` | Google Play Store |
171
+ | `brightdata_apple_app_store` | Apple App Store |
172
+
173
+ **Finance, News & Code**
174
+ | Tool | Platform |
175
+ |------|----------|
176
+ | `brightdata_reuter_news` | Reuters news |
177
+ | `brightdata_yahoo_finance_business` | Yahoo Finance |
178
+ | `brightdata_github_repository_file` | GitHub repository files |
179
+
180
+ **Real Estate & Travel**
181
+ | Tool | Platform |
182
+ |------|----------|
183
+ | `brightdata_zillow_properties_listing` | Zillow listings |
184
+ | `brightdata_booking_hotel_listings` | Booking.com hotels |
185
+
186
+ **AI Insights**
187
+ | Tool | Platform |
188
+ |------|----------|
189
+ | `brightdata_chatgpt_ai_insights` | ChatGPT responses |
190
+ | `brightdata_grok_ai_insights` | Grok responses |
191
+ | `brightdata_perplexity_ai_insights` | Perplexity responses |
192
+
193
+ </details>
194
+
195
+ ---
196
+
197
+ ## Configuration Reference
198
+
199
+ | Setting | Env Var | Config Path | Default |
200
+ |---------|---------|-------------|---------|
201
+ | API Token | `BRIGHTDATA_API_TOKEN` | `...webSearch.apiKey` | *required* |
202
+ | Base URL | `BRIGHTDATA_BASE_URL` | `...webSearch.baseUrl` | `https://api.brightdata.com` |
203
+ | Unlocker Zone | `BRIGHTDATA_UNLOCKER_ZONE` | `...webSearch.unlockerZone` | `mcp_unlocker` |
204
+ | Browser Zone | `BRIGHTDATA_BROWSER_ZONE` | `...webSearch.browserZone` | `mcp_browser` |
205
+ | Timeout | — | `...webSearch.timeoutSeconds` | `30` (search) / `60` (scrape) |
206
+ | Polling Timeout | — | `...webSearch.pollingTimeoutSeconds` | `600` |
207
+
208
+ Config paths are prefixed with `plugins.entries.brightdata.config`.
209
+
210
+ Environment variables take priority over config file values.
211
+
212
+ ## Plugin Management
213
+
214
+ ```bash
215
+ # Install
216
+ openclaw plugins install @brightdata/brightdata-plugin
217
+
218
+ # Verify
219
+ openclaw plugins inspect brightdata
220
+
221
+ # Disable / re-enable
222
+ openclaw plugins disable brightdata
223
+ openclaw plugins enable brightdata
224
+
225
+ # Update
226
+ openclaw plugins update brightdata
227
+
228
+ # Uninstall
229
+ openclaw plugins uninstall brightdata
230
+ ```
231
+
232
+ ## License
233
+
234
+ MIT
package/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { definePluginEntry, type AnyAgentTool } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { createBrightDataBatchTools } from "./src/brightdata-batch-tools.js";
3
+ import {
4
+ BRIGHTDATA_BROWSER_TOOL_NAMES,
5
+ createBrightDataBrowserTools,
6
+ } from "./src/brightdata-browser-tools.js";
7
+ import { createBrightDataScrapeTool } from "./src/brightdata-scrape-tool.js";
8
+ import { createBrightDataWebSearchProvider } from "./src/brightdata-search-provider.js";
9
+ import { createBrightDataSearchTool } from "./src/brightdata-search-tool.js";
10
+ import { createBrightDataWebDataTools } from "./src/brightdata-web-data-tools.js";
11
+
12
+ export default definePluginEntry({
13
+ id: "brightdata",
14
+ name: "Bright Data Plugin",
15
+ description: "Standalone Bright Data plugin for search, direct scraping, structured data, and browser automation",
16
+ register(api) {
17
+ api.registerWebSearchProvider(createBrightDataWebSearchProvider());
18
+ api.registerTool(createBrightDataSearchTool(api) as AnyAgentTool);
19
+ api.registerTool(createBrightDataScrapeTool(api) as AnyAgentTool);
20
+ for (const tool of createBrightDataBatchTools(api)) {
21
+ api.registerTool(tool as AnyAgentTool);
22
+ }
23
+ api.registerTool((ctx) => createBrightDataBrowserTools(api, ctx) as AnyAgentTool[], {
24
+ names: [...BRIGHTDATA_BROWSER_TOOL_NAMES],
25
+ });
26
+ for (const tool of createBrightDataWebDataTools(api)) {
27
+ api.registerTool(tool as AnyAgentTool);
28
+ }
29
+ },
30
+ });
@@ -0,0 +1,63 @@
1
+ {
2
+ "id": "brightdata",
3
+ "uiHints": {
4
+ "webSearch.apiKey": {
5
+ "label": "Bright Data API Token",
6
+ "help": "Bright Data API token for search and scraping (fallback: BRIGHTDATA_API_TOKEN env var).",
7
+ "sensitive": true,
8
+ "placeholder": "..."
9
+ },
10
+ "webSearch.baseUrl": {
11
+ "label": "Bright Data API Base URL",
12
+ "help": "Bright Data API base URL override."
13
+ },
14
+ "webSearch.unlockerZone": {
15
+ "label": "Bright Data Unlocker Zone",
16
+ "help": "Bright Data Web Unlocker zone name (default: mcp_unlocker)."
17
+ },
18
+ "webSearch.browserZone": {
19
+ "label": "Bright Data Browser Zone",
20
+ "help": "Bright Data Browser API zone name (default: mcp_browser)."
21
+ },
22
+ "webSearch.timeoutSeconds": {
23
+ "label": "Bright Data Timeout (sec)",
24
+ "help": "Default timeout in seconds for Bright Data search, scrape, and browser credential requests."
25
+ },
26
+ "webSearch.pollingTimeoutSeconds": {
27
+ "label": "Bright Data Polling Timeout (sec)",
28
+ "help": "Default polling timeout in seconds for dataset-backed Bright Data tools."
29
+ }
30
+ },
31
+ "configSchema": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "properties": {
35
+ "webSearch": {
36
+ "type": "object",
37
+ "additionalProperties": false,
38
+ "properties": {
39
+ "apiKey": {
40
+ "type": ["string", "object"]
41
+ },
42
+ "baseUrl": {
43
+ "type": "string"
44
+ },
45
+ "unlockerZone": {
46
+ "type": "string"
47
+ },
48
+ "browserZone": {
49
+ "type": "string"
50
+ },
51
+ "timeoutSeconds": {
52
+ "type": "integer",
53
+ "minimum": 1
54
+ },
55
+ "pollingTimeoutSeconds": {
56
+ "type": "integer",
57
+ "minimum": 1
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@brightdata/brightdata-plugin",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw Bright Data plugin",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "openclaw": {
8
+ "extensions": [
9
+ "./index.ts"
10
+ ],
11
+ "bundle": {
12
+ "stageRuntimeDependencies": true
13
+ },
14
+ "compat": {
15
+ "pluginApi": "^1.0.0"
16
+ }
17
+ },
18
+ "keywords": [
19
+ "openclaw",
20
+ "openclaw-plugin",
21
+ "brightdata",
22
+ "web-scraping",
23
+ "browser-automation",
24
+ "web-search"
25
+ ],
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/brightdata/openclaw-plugin.git"
30
+ },
31
+ "dependencies": {
32
+ "@sinclair/typebox": "0.34.48",
33
+ "playwright": "1.58.2"
34
+ },
35
+ "devDependencies": {
36
+ "openclaw": "latest",
37
+ "typescript": "^5.8.0",
38
+ "vitest": "^3.0.0"
39
+ },
40
+ "scripts": {
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "typecheck": "tsc --noEmit"
44
+ }
45
+ }
@@ -0,0 +1,274 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import {
3
+ ToolInputError,
4
+ jsonResult,
5
+ readNumberParam,
6
+ readStringArrayParam,
7
+ readStringParam,
8
+ } from "openclaw/plugin-sdk/agent-runtime";
9
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-runtime";
10
+ import { runBrightDataScrape, runBrightDataSearch } from "./brightdata-client.js";
11
+
12
+ function optionalStringEnum<const T extends readonly string[]>(
13
+ values: T,
14
+ options: { description?: string } = {},
15
+ ) {
16
+ return Type.Optional(
17
+ Type.Unsafe<T[number]>({
18
+ type: "string",
19
+ enum: [...values],
20
+ ...options,
21
+ }),
22
+ );
23
+ }
24
+
25
+ const MAX_BATCH_ITEMS = 5;
26
+
27
+ const BrightDataBatchSearchQuerySchema = Type.Object(
28
+ {
29
+ query: Type.String({ description: "Search query string." }),
30
+ engine: optionalStringEnum(["google", "bing", "yandex"] as const, {
31
+ description: 'Search engine ("google", "bing", or "yandex"). Default: google.',
32
+ }),
33
+ count: Type.Optional(
34
+ Type.Number({
35
+ description: "Number of results to return (1-10).",
36
+ minimum: 1,
37
+ maximum: 10,
38
+ }),
39
+ ),
40
+ cursor: Type.Optional(
41
+ Type.String({
42
+ description: "Pagination cursor for the next page.",
43
+ }),
44
+ ),
45
+ geo_location: Type.Optional(
46
+ Type.String({
47
+ description: '2-letter country code for geo-targeted results, for example "us" or "uk".',
48
+ minLength: 2,
49
+ maxLength: 2,
50
+ }),
51
+ ),
52
+ },
53
+ { additionalProperties: false },
54
+ );
55
+
56
+ const BrightDataSearchBatchToolSchema = Type.Object(
57
+ {
58
+ queries: Type.Array(BrightDataBatchSearchQuerySchema, {
59
+ description: "Array of search requests to run in parallel (1-5).",
60
+ minItems: 1,
61
+ maxItems: MAX_BATCH_ITEMS,
62
+ }),
63
+ timeoutSeconds: Type.Optional(
64
+ Type.Number({
65
+ description: "Timeout in seconds for each Bright Data search request.",
66
+ minimum: 1,
67
+ }),
68
+ ),
69
+ },
70
+ { additionalProperties: false },
71
+ );
72
+
73
+ const BrightDataScrapeBatchToolSchema = Type.Object(
74
+ {
75
+ urls: Type.Array(Type.String({ description: "HTTP or HTTPS URL to scrape via Bright Data." }), {
76
+ description: "Array of URLs to scrape in parallel (1-5).",
77
+ minItems: 1,
78
+ maxItems: MAX_BATCH_ITEMS,
79
+ }),
80
+ extractMode: optionalStringEnum(["markdown", "text", "html"] as const, {
81
+ description: 'Extraction mode ("markdown", "text", or "html"). Default: markdown.',
82
+ }),
83
+ maxChars: Type.Optional(
84
+ Type.Number({
85
+ description: "Maximum characters to return for each result.",
86
+ minimum: 100,
87
+ }),
88
+ ),
89
+ timeoutSeconds: Type.Optional(
90
+ Type.Number({
91
+ description: "Timeout in seconds for each Bright Data scrape request.",
92
+ minimum: 1,
93
+ }),
94
+ ),
95
+ },
96
+ { additionalProperties: false },
97
+ );
98
+
99
+ type SearchBatchQuery = {
100
+ query: string;
101
+ engine?: "google" | "bing" | "yandex";
102
+ count?: number;
103
+ cursor?: string;
104
+ geoLocation?: string;
105
+ };
106
+
107
+ function readSearchBatchQueries(rawParams: Record<string, unknown>): SearchBatchQuery[] {
108
+ const rawQueries = rawParams.queries;
109
+ if (!Array.isArray(rawQueries) || rawQueries.length === 0) {
110
+ throw new ToolInputError("queries required");
111
+ }
112
+ if (rawQueries.length > MAX_BATCH_ITEMS) {
113
+ throw new ToolInputError(`queries must contain at most ${MAX_BATCH_ITEMS} items`);
114
+ }
115
+ return rawQueries.map((entry, index) => {
116
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
117
+ throw new ToolInputError(`queries[${index}] must be an object`);
118
+ }
119
+ const params = entry as Record<string, unknown>;
120
+ const query = readStringParam(params, "query", {
121
+ required: true,
122
+ label: `queries[${index}].query`,
123
+ });
124
+ const engineRaw = readStringParam(params, "engine", {
125
+ label: `queries[${index}].engine`,
126
+ });
127
+ const engine =
128
+ engineRaw === "google" || engineRaw === "bing" || engineRaw === "yandex"
129
+ ? engineRaw
130
+ : undefined;
131
+ const count = readNumberParam(params, "count", {
132
+ integer: true,
133
+ label: `queries[${index}].count`,
134
+ });
135
+ const cursor = readStringParam(params, "cursor", {
136
+ label: `queries[${index}].cursor`,
137
+ });
138
+ const geoLocation = readStringParam(params, "geo_location", {
139
+ label: `queries[${index}].geo_location`,
140
+ });
141
+
142
+ return {
143
+ query,
144
+ engine,
145
+ count,
146
+ cursor,
147
+ geoLocation,
148
+ };
149
+ });
150
+ }
151
+
152
+ function readScrapeBatchUrls(rawParams: Record<string, unknown>): string[] {
153
+ const urls = readStringArrayParam(rawParams, "urls", { required: true });
154
+ if (urls.length > MAX_BATCH_ITEMS) {
155
+ throw new ToolInputError(`urls must contain at most ${MAX_BATCH_ITEMS} items`);
156
+ }
157
+ return urls;
158
+ }
159
+
160
+ function readErrorMessage(error: unknown): string {
161
+ if (error instanceof Error && error.message) {
162
+ return error.message;
163
+ }
164
+ return String(error);
165
+ }
166
+
167
+ export function createBrightDataBatchTools(api: OpenClawPluginApi) {
168
+ return [
169
+ {
170
+ name: "brightdata_search_batch",
171
+ label: "Bright Data Search Batch",
172
+ description:
173
+ "Run up to 5 Bright Data search requests in parallel. Returns per-query results and preserves item-level failures.",
174
+ parameters: BrightDataSearchBatchToolSchema,
175
+ execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {
176
+ const queries = readSearchBatchQueries(rawParams);
177
+ const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {
178
+ integer: true,
179
+ });
180
+
181
+ const settled = await Promise.allSettled(
182
+ queries.map((query) =>
183
+ runBrightDataSearch({
184
+ pluginConfig: api.pluginConfig,
185
+ query: query.query,
186
+ engine: query.engine,
187
+ count: query.count,
188
+ cursor: query.cursor,
189
+ geoLocation: query.geoLocation,
190
+ timeoutSeconds,
191
+ }),
192
+ ),
193
+ );
194
+
195
+ const results = settled.map((entry, index) =>
196
+ entry.status === "fulfilled"
197
+ ? {
198
+ index,
199
+ query: queries[index]?.query ?? "",
200
+ engine: queries[index]?.engine ?? "google",
201
+ ok: true,
202
+ result: entry.value,
203
+ }
204
+ : {
205
+ index,
206
+ query: queries[index]?.query ?? "",
207
+ engine: queries[index]?.engine ?? "google",
208
+ ok: false,
209
+ error: readErrorMessage(entry.reason),
210
+ },
211
+ );
212
+
213
+ return jsonResult({
214
+ total: queries.length,
215
+ succeeded: results.filter((entry) => entry.ok).length,
216
+ failed: results.filter((entry) => !entry.ok).length,
217
+ results,
218
+ });
219
+ },
220
+ },
221
+ {
222
+ name: "brightdata_scrape_batch",
223
+ label: "Bright Data Scrape Batch",
224
+ description:
225
+ "Fetch and extract up to 5 URLs directly through Bright Data in parallel. Returns per-URL results and preserves item-level failures.",
226
+ parameters: BrightDataScrapeBatchToolSchema,
227
+ execute: async (_toolCallId: string, rawParams: Record<string, unknown>) => {
228
+ const urls = readScrapeBatchUrls(rawParams);
229
+ const extractModeRaw = readStringParam(rawParams, "extractMode");
230
+ const extractMode =
231
+ extractModeRaw === "text" || extractModeRaw === "html" ? extractModeRaw : "markdown";
232
+ const maxChars = readNumberParam(rawParams, "maxChars", { integer: true });
233
+ const timeoutSeconds = readNumberParam(rawParams, "timeoutSeconds", {
234
+ integer: true,
235
+ });
236
+
237
+ const settled = await Promise.allSettled(
238
+ urls.map((url) =>
239
+ runBrightDataScrape({
240
+ pluginConfig: api.pluginConfig,
241
+ url,
242
+ extractMode,
243
+ maxChars,
244
+ timeoutSeconds,
245
+ }),
246
+ ),
247
+ );
248
+
249
+ const results = settled.map((entry, index) =>
250
+ entry.status === "fulfilled"
251
+ ? {
252
+ index,
253
+ url: urls[index] ?? "",
254
+ ok: true,
255
+ result: entry.value,
256
+ }
257
+ : {
258
+ index,
259
+ url: urls[index] ?? "",
260
+ ok: false,
261
+ error: readErrorMessage(entry.reason),
262
+ },
263
+ );
264
+
265
+ return jsonResult({
266
+ total: urls.length,
267
+ succeeded: results.filter((entry) => entry.ok).length,
268
+ failed: results.filter((entry) => !entry.ok).length,
269
+ results,
270
+ });
271
+ },
272
+ },
273
+ ];
274
+ }