From 4800397c67bd1a03b174cd3a850cfe1ae2fe7b0e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 19 Apr 2025 19:04:48 -0700 Subject: [PATCH] feat(tools): added google search and linkup search tools/blocks (#283) --- sim/blocks/blocks/google.ts | 111 ++++++++++++++++++++++++++++++++++++ sim/blocks/blocks/linkup.ts | 65 +++++++++++++++++++++ sim/blocks/index.ts | 7 ++- sim/components/icons.tsx | 14 +++++ sim/tools/google/index.ts | 3 + sim/tools/google/search.ts | 85 +++++++++++++++++++++++++++ sim/tools/google/types.ts | 26 +++++++++ sim/tools/linkup/index.ts | 3 + sim/tools/linkup/search.ts | 74 ++++++++++++++++++++++++ sim/tools/linkup/types.ts | 23 ++++++++ sim/tools/registry.ts | 4 ++ 11 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 sim/blocks/blocks/google.ts create mode 100644 sim/blocks/blocks/linkup.ts create mode 100644 sim/tools/google/index.ts create mode 100644 sim/tools/google/search.ts create mode 100644 sim/tools/google/types.ts create mode 100644 sim/tools/linkup/index.ts create mode 100644 sim/tools/linkup/search.ts create mode 100644 sim/tools/linkup/types.ts diff --git a/sim/blocks/blocks/google.ts b/sim/blocks/blocks/google.ts new file mode 100644 index 0000000000..d9a35d8c98 --- /dev/null +++ b/sim/blocks/blocks/google.ts @@ -0,0 +1,111 @@ +import { GoogleIcon } from '@/components/icons' +import { BlockConfig } from '../types' +import { ToolResponse } from '@/tools/types' + +interface GoogleSearchResponse extends ToolResponse { + output: { + items: Array<{ + title: string + link: string + snippet: string + displayLink?: string + pagemap?: Record + }> + searchInformation: { + totalResults: string + searchTime: number + formattedSearchTime: string + formattedTotalResults: string + } + } +} + +export const GoogleSearchBlock: BlockConfig = { + type: 'google_search', + name: 'Google Search', + description: 'Search the web', + longDescription: 'Searches the web using Google\'s Custom Search API, which provides high-quality search results from the entire internet or a specific site defined by a custom search engine ID.', + category: 'tools', + bgColor: '#E0E0E0', + icon: GoogleIcon, + + subBlocks: [ + { + id: 'query', + title: 'Search Query', + type: 'long-input', + layout: 'full', + placeholder: 'Enter your search query', + }, + { + id: 'searchEngineId', + title: 'Custom Search Engine ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter your Custom Search Engine ID', + description: 'Required Custom Search Engine ID', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + layout: 'full', + placeholder: 'Enter your Google API key', + description: 'Required API Key for Google Search', + password: true, + }, + { + id: 'num', + title: 'Number of Results', + type: 'short-input', + layout: 'half', + placeholder: '10', + description: 'Number of search results to return (max: 10)', + } + ], + + tools: { + access: ['google_search'], + config: { + tool: () => 'google_search', + params: (params) => ({ + query: params.query, + apiKey: params.apiKey, + searchEngineId: params.searchEngineId, + num: params.num || undefined, + }), + }, + }, + + inputs: { + query: { + type: 'string', + required: true, + description: 'The search query to execute', + }, + apiKey: { + type: 'string', + required: true, + description: 'Google API key', + }, + searchEngineId: { + type: 'string', + required: true, + description: 'Custom Search Engine ID', + }, + num: { + type: 'string', + required: false, + description: 'Number of results to return (default: 10, max: 10)', + } + }, + + outputs: { + response: { + type: { + items: 'json', + searchInformation: 'json', + } as any, + }, + }, +} \ No newline at end of file diff --git a/sim/blocks/blocks/linkup.ts b/sim/blocks/blocks/linkup.ts new file mode 100644 index 0000000000..09d90afc32 --- /dev/null +++ b/sim/blocks/blocks/linkup.ts @@ -0,0 +1,65 @@ +import { LinkupIcon } from '@/components/icons' +import { BlockConfig } from '../types' +import { LinkupSearchToolResponse } from '@/tools/linkup/types' + +export const LinkupBlock: BlockConfig = { + type: 'linkup', + name: 'Linkup', + description: 'Search the web with Linkup', + longDescription: 'Linkup Search allows you to search and retrieve up-to-date information from the web with source attribution.', + category: 'tools', + bgColor: '#EAEADC', + icon: LinkupIcon, + + subBlocks: [ + { + id: 'q', + title: 'Search Query', + type: 'long-input', + layout: 'full', + placeholder: 'Enter your search query', + }, + { + id: 'outputType', + title: 'Output Type', + type: 'dropdown', + layout: 'half', + options: [ { label: 'Answer', id: 'sourcedAnswer' }, { label: 'Search', id: 'searchResults' }], + }, + { + id: 'depth', + title: 'Search Depth', + type: 'dropdown', + layout: 'half', + options: [ { label: 'Standard', id: 'standard' }, { label: 'Deep', id: 'deep' }], + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + layout: 'full', + placeholder: 'Enter your Linkup API key', + password: true, + }, + ], + + tools: { + access: ['linkup_search'], + }, + + inputs: { + q: { type: 'string', required: true }, + apiKey: { type: 'string', required: true }, + depth: { type: 'string', required: true }, + outputType: { type: 'string', required: true } + }, + + outputs: { + response: { + type: { + answer: 'string', + sources: 'json', + }, + }, + }, +} diff --git a/sim/blocks/index.ts b/sim/blocks/index.ts index 7b974319ac..60a2d92f38 100644 --- a/sim/blocks/index.ts +++ b/sim/blocks/index.ts @@ -16,10 +16,11 @@ import { FirecrawlBlock } from './blocks/firecrawl' import { FunctionBlock } from './blocks/function' import { GitHubBlock } from './blocks/github' import { GmailBlock } from './blocks/gmail' +import { GoogleSearchBlock } from './blocks/google' // import { GuestyBlock } from './blocks/guesty' import { ImageGeneratorBlock } from './blocks/image-generator' import { JinaBlock } from './blocks/jina' - +import { LinkupBlock } from './blocks/linkup' import { MistralParseBlock } from './blocks/mistral-parse' import { NotionBlock } from './blocks/notion' import { OpenAIBlock } from './blocks/openai' @@ -61,7 +62,9 @@ export { FirecrawlBlock, // GuestyBlock, FileBlock, + GoogleSearchBlock, JinaBlock, + LinkupBlock, TranslateBlock, SlackBlock, GitHubBlock, @@ -113,10 +116,12 @@ const blocks: Record = { gmail: GmailBlock, google_docs: GoogleDocsBlock, google_drive: GoogleDriveBlock, + google_search: GoogleSearchBlock, google_sheets: GoogleSheetsBlock, // guesty: GuestyBlock, image_generator: ImageGeneratorBlock, jina: JinaBlock, + linkup: LinkupBlock, mem0: Mem0Block, mistral_parse: MistralParseBlock, notion: NotionBlock, diff --git a/sim/components/icons.tsx b/sim/components/icons.tsx index 2e70b99ac7..083e5bc8a6 100644 --- a/sim/components/icons.tsx +++ b/sim/components/icons.tsx @@ -2163,4 +2163,18 @@ export function ElevenLabsIcon(props: SVGProps) { ) +} + +export function LinkupIcon(props: SVGProps) { + return ( + + + + + + + + + + ) } \ No newline at end of file diff --git a/sim/tools/google/index.ts b/sim/tools/google/index.ts new file mode 100644 index 0000000000..293effaa65 --- /dev/null +++ b/sim/tools/google/index.ts @@ -0,0 +1,3 @@ +import { searchTool } from './search' + +export { searchTool } \ No newline at end of file diff --git a/sim/tools/google/search.ts b/sim/tools/google/search.ts new file mode 100644 index 0000000000..8e2f1ecd84 --- /dev/null +++ b/sim/tools/google/search.ts @@ -0,0 +1,85 @@ +import { ToolConfig } from '../types' +import { GoogleSearchParams, GoogleSearchResponse } from './types' + +export const searchTool: ToolConfig = { + id: 'google_search', + name: 'Google Search', + description: 'Search the web with the Custom Search API', + version: '1.0.0', + + params: { + query: { + type: 'string', + required: true, + description: 'The search query to execute', + }, + apiKey: { + type: 'string', + required: true, + description: 'Google API key', + requiredForToolCall: true, + }, + searchEngineId: { + type: 'string', + required: true, + description: 'Custom Search Engine ID', + requiredForToolCall: true, + }, + num: { + type: 'string', // Treated as string for compatibility with tool interfaces + required: false, + description: 'Number of results to return (default: 10, max: 10)', + } + }, + + request: { + url: (params: GoogleSearchParams) => { + const baseUrl = 'https://www.googleapis.com/customsearch/v1' + const searchParams = new URLSearchParams() + + // Add required parameters + searchParams.append('key', params.apiKey) + searchParams.append('q', params.query) + searchParams.append('cx', params.searchEngineId) + + // Add optional parameter + if (params.num) { + searchParams.append('num', params.num.toString()) + } + + return `${baseUrl}?${searchParams.toString()}` + }, + method: 'GET', + headers: () => ({ + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error?.message || 'Failed to perform Google search') + } + + const data = await response.json() + + return { + success: true, + output: { + items: data.items || [], + searchInformation: data.searchInformation || { + totalResults: '0', + searchTime: 0, + formattedSearchTime: '0', + formattedTotalResults: '0', + }, + }, + } + }, + + transformError: (error) => { + return error instanceof Error + ? error.message + : 'An error occurred while performing the Google search' + }, +} \ No newline at end of file diff --git a/sim/tools/google/types.ts b/sim/tools/google/types.ts new file mode 100644 index 0000000000..7204218203 --- /dev/null +++ b/sim/tools/google/types.ts @@ -0,0 +1,26 @@ +import { ToolResponse } from "../types" + +export interface GoogleSearchParams { + query: string + apiKey: string + searchEngineId: string + num?: number | string +} + +export interface GoogleSearchResponse extends ToolResponse { + output: { + items: Array<{ + title: string + link: string + snippet: string + displayLink?: string + pagemap?: Record + }> + searchInformation: { + totalResults: string + searchTime: number + formattedSearchTime: string + formattedTotalResults: string + } + } +} \ No newline at end of file diff --git a/sim/tools/linkup/index.ts b/sim/tools/linkup/index.ts new file mode 100644 index 0000000000..e22278ea92 --- /dev/null +++ b/sim/tools/linkup/index.ts @@ -0,0 +1,3 @@ +import { searchTool } from './search' + +export const linkupSearchTool = searchTool \ No newline at end of file diff --git a/sim/tools/linkup/search.ts b/sim/tools/linkup/search.ts new file mode 100644 index 0000000000..a863c8c768 --- /dev/null +++ b/sim/tools/linkup/search.ts @@ -0,0 +1,74 @@ +import { ToolConfig } from '../types' +import { LinkupSearchParams, LinkupSearchResponse, LinkupSearchToolResponse } from './types' + +export const searchTool: ToolConfig = { + id: 'linkup_search', + name: 'Linkup Search', + description: 'Search the web for information using Linkup', + version: '1.0.0', + + params: { + q: { + type: 'string', + required: true, + description: 'The search query', + }, + apiKey: { + type: 'string', + required: true, + description: 'Enter your Linkup API key', + requiredForToolCall: true, + }, + depth: { + type: 'string', + required: true, + description: 'Search depth (has to either be "standard" or "deep")', + }, + outputType: { + type: 'string', + required: true, + description: 'Type of output to return (has to either be "sourcedAnswer" or "searchResults")', + }, + }, + + request: { + url: 'https://api.linkup.so/v1/search', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${params.apiKey}`, + }), + body: (params) => { + const body: Record = { + q: params.q, + } + + if (params.depth) body.depth = params.depth + if (params.outputType) body.outputType = params.outputType + body.includeImages = false + + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Linkup API error: ${response.status} ${errorText}`) + } + + const data: LinkupSearchResponse = await response.json() + + return { + success: true, + output: { + answer: data.answer, + sources: data.sources, + }, + } + }, + + transformError: (error) => { + return `Error searching with Linkup: ${error.message}` + }, +} \ No newline at end of file diff --git a/sim/tools/linkup/types.ts b/sim/tools/linkup/types.ts new file mode 100644 index 0000000000..a2480bd98e --- /dev/null +++ b/sim/tools/linkup/types.ts @@ -0,0 +1,23 @@ +import { ToolResponse } from "../types" + +export interface LinkupSource { + name: string + url: string + snippet: string +} + +export interface LinkupSearchParams { + q: string + apiKey: string + depth?: 'standard' | 'deep' + outputType?: 'sourcedAnswer' | 'searchResults' +} + +export interface LinkupSearchResponse { + answer: string + sources: LinkupSource[] +} + +export interface LinkupSearchToolResponse extends ToolResponse { + output: LinkupSearchResponse + } \ No newline at end of file diff --git a/sim/tools/registry.ts b/sim/tools/registry.ts index 6ae8722484..1196fa183a 100644 --- a/sim/tools/registry.ts +++ b/sim/tools/registry.ts @@ -11,9 +11,11 @@ import { functionExecuteTool } from './function' import { githubCommentTool, githubLatestCommitTool, githubPrTool, githubRepoInfoTool } from './github' import { gmailReadTool, gmailSearchTool, gmailSendTool } from './gmail' import { guestyGuestTool, guestyReservationTool } from './guesty' +import { searchTool as googleSearchTool } from './google' import { requestTool as httpRequest } from './http/request' import { contactsTool as hubspotContacts } from './hubspot/contacts' import { readUrlTool } from './jina/reader' +import { linkupSearchTool } from './linkup' import { mem0AddMemoriesTool, mem0SearchMemoriesTool, mem0GetMemoriesTool } from './mem0' import { mistralParserTool } from './mistral' import { notionReadTool, notionWriteTool } from './notion' @@ -51,7 +53,9 @@ export const tools: Record = { vision_tool: visionTool, file_parser: fileParseTool, firecrawl_scrape: scrapeTool, + google_search: googleSearchTool, jina_readurl: readUrlTool, + linkup_search: linkupSearchTool, slack_message: slackMessageTool, github_repoinfo: githubRepoInfoTool, github_latest_commit: githubLatestCommitTool,