diff --git a/legacy/docs/tools/brave-search.mdx b/legacy/docs/tools/brave-search.mdx new file mode 100644 index 00000000..5017972a --- /dev/null +++ b/legacy/docs/tools/brave-search.mdx @@ -0,0 +1,36 @@ +--- +title: Brave Search +description: Brave web search and local places search. +--- + +- package: `@agentic/brave-search` +- exports: `class BraveSearchClient`, `namespace braveSearch` +- env vars: `BRAVE_SEARCH_API_KEY` +- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/brave-search/src/brave-search-client.ts) +- [brave search docs](https://brave.com/search/api) + +## Install + + +```bash npm +npm install @agentic/brave-search +``` + +```bash yarn +yarn add @agentic/brave-search +``` + +```bash pnpm +pnpm add @agentic/brave-search +``` + + + +## Usage + +```ts +import { BraveSearchClient } from '@agentic/brave-search' + +const braveSearch = new BraveSearchClient() +const res = await braveSearch.search('latest news about openai') +``` diff --git a/legacy/packages/brave-search/package.json b/legacy/packages/brave-search/package.json new file mode 100644 index 00000000..cd63962b --- /dev/null +++ b/legacy/packages/brave-search/package.json @@ -0,0 +1,47 @@ +{ + "name": "@agentic/brave-search", + "version": "0.1.0", + "description": "Agentic SDK for the Brave search engine.", + "author": "Travis Fischer ", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/transitive-bullshit/agentic.git", + "directory": "packages/brave-search" + }, + "type": "module", + "source": "./src/index.ts", + "types": "./dist/index.d.ts", + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "clean": "del dist", + "test": "run-s test:*", + "test:lint": "eslint .", + "test:typecheck": "tsc --noEmit" + }, + "dependencies": { + "@agentic/core": "workspace:*", + "ky": "catalog:" + }, + "peerDependencies": { + "zod": "catalog:" + }, + "devDependencies": { + "@agentic/tsconfig": "workspace:*" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/legacy/packages/brave-search/readme.md b/legacy/packages/brave-search/readme.md new file mode 100644 index 00000000..38781f32 --- /dev/null +++ b/legacy/packages/brave-search/readme.md @@ -0,0 +1,24 @@ +

+ + Agentic + +

+ +

+ AI agent stdlib that works with any LLM and TypeScript AI SDK. +

+ +

+ Build Status + NPM + MIT License + Prettier Code Formatting +

+ +# Agentic + +**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.** + +## License + +MIT © [Travis Fischer](https://x.com/transitive_bs) diff --git a/legacy/packages/brave-search/src/brave-search-client.ts b/legacy/packages/brave-search/src/brave-search-client.ts new file mode 100644 index 00000000..5bff613a --- /dev/null +++ b/legacy/packages/brave-search/src/brave-search-client.ts @@ -0,0 +1,154 @@ +import { + aiFunction, + AIFunctionsProvider, + assert, + getEnv, + sanitizeSearchParams +} from '@agentic/core' +import defaultKy, { type KyInstance } from 'ky' + +import { bravesearch } from './brave-search' + +/** + * Agentic client for the Brave search engine. + * + * @see https://brave.com/search/api + */ +export class BraveSearchClient extends AIFunctionsProvider { + protected readonly ky: KyInstance + protected readonly apiKey: string + protected readonly apiBaseUrl: string + + constructor({ + apiKey = getEnv('BRAVE_SEARCH_API_KEY'), + apiBaseUrl = bravesearch.apiBaseUrl, + ky = defaultKy + }: { + apiKey?: string + apiBaseUrl?: string + ky?: KyInstance + } = {}) { + assert( + apiKey, + 'BraveSearchClient missing required "apiKey" (defaults to "BRAVE_SEARCH_API_KEY")' + ) + super() + + this.apiKey = apiKey + this.apiBaseUrl = apiBaseUrl + + this.ky = ky.extend({ + prefixUrl: apiBaseUrl, + headers: { + 'X-Subscription-Token': apiKey + } + }) + } + + /** + * Brave web search. + */ + @aiFunction({ + name: 'brave_search', + description: + 'Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. ' + + 'Use this for broad information gathering, recent events, or when you need diverse web sources. ' + + 'Supports pagination, content filtering, and freshness controls. ' + + 'Maximum 20 results per request, with offset for pagination. ', + inputSchema: bravesearch.SearchParamsSchema + }) + async search( + queryOrParams: string | bravesearch.SearchParams + ): Promise { + const { query: q, ...params } = + typeof queryOrParams === 'string' + ? { query: queryOrParams } + : queryOrParams + + return this.ky + .get('res/v1/web/search', { + searchParams: sanitizeSearchParams({ + ...params, + q + }) + }) + .json() + } + + /** + * Brave local search for businesses and places. + */ + @aiFunction({ + name: 'brave_local_search', + description: + "Searches for local businesses and places using Brave's Local Search API. " + + 'Best for queries related to physical locations, businesses, restaurants, services, etc. ' + + 'Returns detailed information including:\n' + + '- Business names and addresses\n' + + '- Ratings and review counts\n' + + '- Phone numbers and opening hours\n' + + "Use this when the query implies 'near me' or mentions specific locations. " + + 'Automatically falls back to web search if no local results are found.', + inputSchema: bravesearch.LocalSearchParamsSchema + }) + async localSearch( + queryOrParams: string | bravesearch.LocalSearchParams + ): Promise { + const { query: q, ...params } = + typeof queryOrParams === 'string' + ? { query: queryOrParams } + : queryOrParams + + const webData = await this.ky + .get('res/v1/web/search', { + searchParams: sanitizeSearchParams({ + search_lang: 'en', + result_filter: 'locations', + ...params, + q + }) + }) + .json() + + const locationIds = webData.locations?.results + ?.filter((r) => !!r.id) + .map((r) => r.id) + + if (!locationIds?.length) { + return this.search(queryOrParams) + } + + // Get POI details and descriptions in parallel + const [pois, descriptions] = await Promise.all([ + this.getPoisData(locationIds), + this.getDescriptionsData(locationIds) + ]) + + const desc = descriptions.descriptions + + return Object.entries(desc).map(([id, description]) => ({ + description, + ...pois.results.find((r) => r.id === id)! + })) + } + + async getPoisData(ids: string[]): Promise { + return this.ky + .get('res/v1/local/pois', { + searchParams: sanitizeSearchParams({ + ids: ids.filter(Boolean) + }) + }) + .json() + } + + async getDescriptionsData(ids: string[]): Promise { + return this.ky + .get('res/v1/local/descriptions', { + searchParams: sanitizeSearchParams({ + ids: ids.filter(Boolean) + }) + }) + .json() + } +} diff --git a/legacy/packages/brave-search/src/brave-search.ts b/legacy/packages/brave-search/src/brave-search.ts new file mode 100644 index 00000000..891b694c --- /dev/null +++ b/legacy/packages/brave-search/src/brave-search.ts @@ -0,0 +1,90 @@ +import { z } from 'zod' + +export namespace bravesearch { + export const apiBaseUrl = 'https://api.search.brave.com' + + export const SearchParamsSchema = z.object({ + query: z + .string() + .describe('Search query (max 400 chars, 50 words)') + .optional(), + count: z + .number() + .int() + .min(1) + .max(20) + .describe('Number of results (1-20, default 10)') + .optional(), + offset: z + .number() + .describe('Pagination offset (max 9, default 0)') + .optional() + }) + export type SearchParams = z.infer + + export const LocalSearchParamsSchema = z.object({ + query: z + .string() + .describe('Search query (max 400 chars, 50 words)') + .optional(), + count: z + .number() + .describe('Number of results (1-20, default 10)') + .int() + .min(1) + .max(20) + .optional() + }) + export type LocalSearchParams = z.infer + + export interface SearchResponse { + web?: { + results?: Array<{ + title: string + description: string + url: string + language?: string + published?: string + rank?: number + }> + } + locations?: { + results?: Array<{ + id: string // Required by API + title?: string + }> + } + } + + export interface Location { + id: string + name: string + address: { + streetAddress?: string + addressLocality?: string + addressRegion?: string + postalCode?: string + } + coordinates?: { + latitude: number + longitude: number + } + phone?: string + rating?: { + ratingValue?: number + ratingCount?: number + } + openingHours?: string[] + priceRange?: string + } + + export interface PoiResponse { + results: Location[] + } + + export interface Description { + descriptions: { [id: string]: string } + } + + export type LocalSearchResponse = Array +} diff --git a/legacy/packages/brave-search/src/index.ts b/legacy/packages/brave-search/src/index.ts new file mode 100644 index 00000000..cb3880a3 --- /dev/null +++ b/legacy/packages/brave-search/src/index.ts @@ -0,0 +1,2 @@ +export * from './brave-search' +export * from './brave-search-client' diff --git a/legacy/packages/brave-search/tsconfig.json b/legacy/packages/brave-search/tsconfig.json new file mode 100644 index 00000000..6c8d720c --- /dev/null +++ b/legacy/packages/brave-search/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@agentic/tsconfig/base.json", + "include": ["src"], + "exclude": ["node_modules", "dist"] +}