import { tool, type Tool } from 'ai'
import { z } from 'zod'
import { bdclient } from '@brightdata/sdk'
type BrightDataTools = 'scrape' | 'search' | 'amazonProduct'|'linkedinCollectProfiles'
interface BrightDataToolsConfig {
apiKey: string
excludeTools?: BrightDataTools[]
}
export const brightDataTools = (
config: BrightDataToolsConfig
): Partial<Record<BrightDataTools, Tool>> => {
const client = new bdclient({
apiKey: config.apiKey,
autoCreateZones: true
})
const tools: Partial<Record<BrightDataTools, Tool>> = {
scrape: tool({
description:
'Scrape website content and return it in clean markdown format. Bypasses anti-bot protection and CAPTCHAs.',
inputSchema: z.object({
url: z
.string()
.url()
.describe('The URL of the website to scrape'),
country: z
.string()
.length(2)
.optional()
.describe('Two-letter country code for proxy location (e.g., "us", "gb", "de")'),
}),
execute: async ({ url, country }) => {
try {
const result = await client.scrape(url, {
dataFormat: 'markdown',
format: 'raw',
country: country?.toLowerCase(),
})
return result
} catch (error) {
return `Error scraping ${url}: ${String(error)}`
}
},
}),
search: tool({
description:
'Search the web using Google, Bing, or Yandex. Returns search results with anti-bot protection bypass.',
inputSchema: z.object({
query: z
.string()
.describe('The search query'),
searchEngine: z
.enum(['google', 'bing', 'yandex'])
.optional()
.default('google')
.describe('Search engine to use'),
country: z
.string()
.length(2)
.optional()
.describe('Two-letter country code for localized results'),
dataFormat: z
.enum(['html', 'markdown'])
.optional()
.default('markdown')
.describe('Format of returned search results'),
}),
execute: async ({ query, searchEngine, country, dataFormat }) => {
try {
const result = await client.search(query, {
searchEngine,
dataFormat,
format: 'raw',
country: country?.toLowerCase(),
})
return result
} catch (error) {
return `Error searching for "${query}": ${String(error)}`
}
},
}),
amazonProduct: tool({
description:
'Get detailed Amazon product information including price, ratings, reviews, and specifications. Requires a valid Amazon product URL.',
inputSchema: z.object({
url: z
.string()
.url()
.describe('Amazon product URL (must contain /dp/ or /gp/product/)'),
zipcode: z
.string()
.optional()
.describe('ZIP code for location-specific pricing and availability'),
}),
execute: async ({ url, zipcode }) => {
try {
const result = await client.datasets.amazon.collectProducts(
[{ url, zipcode }],
{
format: 'json',
async: false
}
)
return JSON.stringify(result, null, 2)
} catch (error) {
return `Error fetching Amazon product data: ${String(error)}`
}
},
}),
linkedinCollectProfiles: tool({
description:
'Fetch LinkedIn profile data for one or more LinkedIn profile URLs. Returns detailed information including work experience, education, skills, and contact information.',
inputSchema: z.object({
urls: z
.array(z.string().url())
.min(1)
.describe('Array of LinkedIn profile URLs to collect data from (e.g., ["https://www.linkedin.com/in/example"])'),
format: z
.enum(['json', 'jsonl'])
.optional()
.default('json')
.describe('Output format for the results'),
}),
execute: async ({ urls, format }) => {
try {
const result = await client.datasets.linkedin.collectProfiles(
urls,
{
format: format || 'json',
async: false
}
)
return JSON.stringify(result, null, 2)
} catch (error) {
return `Error fetching LinkedIn profiles: ${String(error)}`
}
},
}),
}
// Remove excluded tools
for (const toolName in tools) {
if (config.excludeTools?.includes(toolName as BrightDataTools)) {
delete tools[toolName as BrightDataTools]
}
}
return tools
}