diff --git a/src/services/serpapi.ts b/src/services/serpapi.ts
index 36a0c804..6904d873 100644
--- a/src/services/serpapi.ts
+++ b/src/services/serpapi.ts
@@ -8,7 +8,7 @@ import defaultKy from 'ky'
export type BaseResponse
> = {
search_metadata: {
id: string
- status: 'Queued' | 'Processing' | 'Success'
+ status: string | 'Queued' | 'Processing' | 'Success'
json_endpoint: string
created_at: string
processed_at: string
@@ -24,9 +24,6 @@ export type BaseResponse
> = {
pagination?: {
next: string
}
- next?: (
- callback?: (json: BaseResponse
) => void
- ) => Promise>
[key: string]: any
}
@@ -333,7 +330,7 @@ export type GoogleParameters = BaseParameters & {
* Parameter defines the maximum number of results to return. (e.g., `10` (default)
* returns 10 results, `40` returns 40 results, and `100` returns 100 results).
*/
- num?: string
+ num?: number
/**
* Page Number (images)
@@ -345,8 +342,270 @@ export type GoogleParameters = BaseParameters & {
ijn?: string
}
+interface SearchResult extends BaseResponse {
+ search_metadata: SearchMetadata
+ search_parameters: SearchParameters
+ search_information: SearchInformation
+ local_map?: LocalMap
+ local_results?: LocalResults
+ answer_box?: AnswerBox
+ knowledge_graph?: KnowledgeGraph
+ inline_images?: InlineImage[]
+ inline_people_also_search_for?: InlinePeopleAlsoSearchFor[]
+ related_questions?: SearchResultRelatedQuestion[]
+ organic_results: OrganicResult[]
+ related_searches?: RelatedSearch[]
+ pagination: Pagination
+ serpapi_pagination: Pagination
+}
+
+interface AnswerBox {
+ type: string
+ title: string
+ link: string
+ displayed_link: string
+ snippet: string
+ snippet_highlighted_words: string[]
+ images: string[]
+ about_this_result: AboutThisResult
+ about_page_link: string
+ cached_page_link: string
+}
+
+interface InlineImage {
+ link: string
+ source: string
+ thumbnail: string
+ original: string
+ source_name: string
+ title?: string
+}
+
+interface InlinePeopleAlsoSearchFor {
+ title: string
+ items: SearchItem[]
+ see_more_link: string
+ see_more_serpapi_link: string
+}
+
+interface SearchItem {
+ name: string
+ image: string
+ link: string
+ serpapi_link: string
+}
+
+interface KnowledgeGraph {
+ type: string
+ kgmid: string
+ knowledge_graph_search_link: string
+ serpapi_knowledge_graph_search_link: string
+ header_images: HeaderImage[]
+ description: string
+ source: Source
+ buttons: Button[]
+ people_also_search_for: SearchItem[]
+ people_also_search_for_link: string
+ people_also_search_for_stick: string
+ list: { [key: string]: string[] }
+}
+
+interface Button {
+ text: string
+ subtitle: string
+ title: string
+ link: string
+ displayed_link: string
+ snippet?: string
+ snippet_highlighted_words?: string[]
+ answer?: string
+ thumbnail: string
+ search_link: string
+ serpapi_search_link: string
+ date?: string
+ list?: string[]
+}
+
+interface HeaderImage {
+ image: string
+ source: string
+}
+
+interface Source {
+ name: string
+ link: string
+}
+
+interface LocalMap {
+ link: string
+ image: string
+ gps_coordinates: LocalMapGpsCoordinates
+}
+
+interface LocalMapGpsCoordinates {
+ latitude: number
+ longitude: number
+ altitude: number
+}
+
+interface LocalResults {
+ places: Place[]
+ more_locations_link: string
+}
+
+interface Place {
+ position: number
+ title: string
+ rating?: number
+ reviews_original?: string
+ reviews?: number
+ place_id: string
+ place_id_search: string
+ lsig: string
+ thumbnail: string
+ gps_coordinates: PlaceGpsCoordinates
+ service_options: ServiceOptions
+ address?: string
+ type?: string
+ hours?: string
+}
+
+interface PlaceGpsCoordinates {
+ latitude: number
+ longitude: number
+}
+
+interface ServiceOptions {
+ dine_in?: boolean
+ takeout: boolean
+ no_delivery?: boolean
+}
+
+interface OrganicResult {
+ position: number
+ title: string
+ link: string
+ displayed_link: string
+ thumbnail?: string
+ favicon?: string
+ snippet: string
+ snippet_highlighted_words: string[]
+ sitelinks?: Sitelinks
+ rich_snippet?: RichSnippet
+ about_this_result: AboutThisResult
+ cached_page_link: string
+ related_pages_link?: string
+ source: string
+ related_results?: RelatedResult[]
+ date?: string
+ related_questions?: OrganicResultRelatedQuestion[]
+}
+
+interface AboutThisResult {
+ keywords: string[]
+ languages: string[]
+ regions: string[]
+}
+
+interface OrganicResultRelatedQuestion {
+ question: string
+ snippet: string
+ snippet_links: SnippetLink[]
+}
+
+interface SnippetLink {
+ text: string
+ link: string
+}
+
+interface RelatedResult {
+ position: number
+ title: string
+ link: string
+ displayed_link: string
+ snippet: string
+ snippet_highlighted_words: string[]
+ about_this_result: AboutThisResult
+ cached_page_link: string
+}
+
+interface RichSnippet {
+ bottom: Bottom
+}
+
+interface Bottom {
+ extensions?: string[]
+ questions?: string[]
+}
+
+interface Sitelinks {
+ inline: Inline[]
+}
+
+interface Inline {
+ title: string
+ link: string
+}
+
+interface Pagination {
+ current: number
+ next: string
+ other_pages: { [key: string]: string }
+ next_link?: string
+}
+
+interface SearchResultRelatedQuestion {
+ question: string
+ snippet: string
+ title: string
+ link: string
+ displayed_link: string
+ thumbnail: string
+ next_page_token: string
+ serpapi_link: string
+ date?: string
+}
+
+interface RelatedSearch {
+ query: string
+ link: string
+}
+
+interface SearchInformation {
+ organic_results_state: string
+ query_displayed: string
+ total_results: number
+ time_taken_displayed: number
+ menu_items: MenuItem[]
+}
+
+interface MenuItem {
+ position: number
+ title: string
+ link: string
+ serpapi_link?: string
+}
+
+interface SearchMetadata {
+ id: string
+ status: string
+ json_endpoint: string
+ created_at: string
+ processed_at: string
+ google_url: string
+ raw_html_file: string
+ total_time_taken: number
+}
+
+interface SearchParameters {
+ engine: string
+ q: string
+ google_domain: string
+ device?: 'desktop' | 'tablet' | 'mobile'
+}
+
export type SerpAPIParams = Omit
-export type SerpAPISearchResponse = BaseResponse
+export type SerpAPISearchResponse = SearchResult
export interface SerpAPIClientOptions extends Partial {
apiKey?: string
@@ -387,9 +646,14 @@ export class SerpAPIClient {
})
}
- async search(queryOrOpts: string | { query: string }) {
- const query =
- typeof queryOrOpts === 'string' ? queryOrOpts : queryOrOpts.query
+ async search(queryOrOpts: string | GoogleParameters) {
+ const defaultGoogleParams: Partial = {
+ num: 10
+ }
+ const options: GoogleParameters =
+ typeof queryOrOpts === 'string'
+ ? { ...defaultGoogleParams, q: queryOrOpts }
+ : queryOrOpts
const { timeout, ...rest } = this.params
return this.api
@@ -398,7 +662,7 @@ export class SerpAPIClient {
...rest,
engine: 'google',
api_key: this.apiKey,
- q: query
+ ...(options as any) // TODO
},
timeout
})
diff --git a/src/tools/calculator.ts b/src/tools/calculator.ts
index 82a0aef6..24a963d3 100644
--- a/src/tools/calculator.ts
+++ b/src/tools/calculator.ts
@@ -7,12 +7,11 @@ import { BaseTask } from '@/task'
export const CalculatorInputSchema = z.object({
expression: z.string().describe('mathematical expression to evaluate')
})
+export type CalculatorInput = z.infer
+
export const CalculatorOutputSchema = z
.number()
.describe('result of calculating the expression')
-
-export type CalculatorInput = z.infer
-
export type CalculatorOutput = z.infer
export class CalculatorTool extends BaseTask<
diff --git a/test/services/serpapi.test.ts b/test/services/serpapi.test.ts
index 36054a69..110cfd85 100644
--- a/test/services/serpapi.test.ts
+++ b/test/services/serpapi.test.ts
@@ -4,7 +4,7 @@ import { SerpAPIClient } from '@/services/serpapi'
import { ky } from '../_utils'
-test('SerpAPIClient.search', async (t) => {
+test('SerpAPIClient.search - coffee', async (t) => {
if (!process.env.SERPAPI_API_KEY) {
return t.pass()
}
@@ -13,6 +13,21 @@ test('SerpAPIClient.search', async (t) => {
const client = new SerpAPIClient({ ky })
const result = await client.search('coffee')
- // console.log(result)
- t.truthy(result)
+ // console.log(JSON.stringify(result, null, 2))
+ t.truthy(result.organic_results)
+})
+
+test('SerpAPIClient.search - answer box', async (t) => {
+ if (!process.env.SERPAPI_API_KEY) {
+ return t.pass()
+ }
+
+ t.timeout(2 * 60 * 1000)
+ const client = new SerpAPIClient({ ky })
+
+ const result = await client.search(
+ 'how many planets are there in the milky way?'
+ )
+ // console.log(JSON.stringify(result, null, 2))
+ t.truthy(result.answer_box)
})