",
+ "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 @@
+
+
+
+
+
+
+
+ AI agent stdlib that works with any LLM and TypeScript AI SDK.
+
+
+
+
+
+
+
+
+
+# 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"]
+}