import z from 'zod'; import type { ResearchAction } from './types.js'; import type { Chunk, SearchResultsResearchBlock } from '../types.js'; import { searchSearxng } from '../searxng.js'; const schema = z.object({ queries: z.array(z.string()).describe('List of social search queries'), }); const socialSearchAction: ResearchAction = { name: 'social_search', schema, getToolDescription: () => 'Use this tool to perform social media searches for relevant posts, discussions, and trends. Provide up to 3 queries at a time.', getDescription: () => 'Use this tool to perform social media searches for posts, discussions, and trends. Provide concise search queries. You can provide up to 3 queries at a time.', enabled: (config) => config.sources.includes('discussions') && config.classification.classification.skipSearch === false && config.classification.classification.discussionSearch === true, execute: async (input, additionalConfig) => { input.queries = input.queries.slice(0, 3); const researchBlock = additionalConfig.session.getBlock(additionalConfig.researchBlockId); if (researchBlock && researchBlock.type === 'research') { researchBlock.data.subSteps.push({ id: crypto.randomUUID(), type: 'searching', searching: input.queries, }); additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [ { op: 'replace', path: '/data/subSteps', value: researchBlock.data.subSteps }, ]); } const searchResultsBlockId = crypto.randomUUID(); let searchResultsEmitted = false; const results: Chunk[] = []; const search = async (q: string) => { const res = await searchSearxng(q, { engines: ['reddit'] }); const resultChunks: Chunk[] = res.results.map((r) => ({ content: r.content || r.title, metadata: { title: r.title, url: r.url }, })); results.push(...resultChunks); if (!searchResultsEmitted && researchBlock && researchBlock.type === 'research') { searchResultsEmitted = true; researchBlock.data.subSteps.push({ id: searchResultsBlockId, type: 'search_results', reading: resultChunks, }); additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [ { op: 'replace', path: '/data/subSteps', value: researchBlock.data.subSteps }, ]); } else if (searchResultsEmitted && researchBlock && researchBlock.type === 'research') { const subStepIndex = researchBlock.data.subSteps.findIndex((s) => s.id === searchResultsBlockId); const subStep = researchBlock.data.subSteps[subStepIndex] as SearchResultsResearchBlock | undefined; if (subStep) { subStep.reading.push(...resultChunks); additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [ { op: 'replace', path: '/data/subSteps', value: researchBlock.data.subSteps }, ]); } } }; await Promise.all(input.queries.map(search)); return { type: 'search_results', results }; }, }; export default socialSearchAction;