diff --git a/src/agents/atlas.ts b/src/agents/atlas.ts index adf2d73..41cb895 100644 --- a/src/agents/atlas.ts +++ b/src/agents/atlas.ts @@ -5,5 +5,5 @@ registerAgent({ description: 'PRD agent — guides users through structured product discovery and produces a comprehensive requirements document', model: 'A', // Gemini Flash — fast, conversational, cost-effective for dialogue promptId: 'atlas', - tools: pick(['finalize_prd']) + tools: pick(['web_search', 'finalize_prd']) }); diff --git a/src/tools/index.ts b/src/tools/index.ts index c91eedd..806db43 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -9,6 +9,7 @@ import './agent'; import './memory'; import './skills'; import './prd'; +import './search'; // Re-export the public API — identical surface to the old tools.ts export { ALL_TOOLS, executeTool, ToolDefinition } from './registry'; diff --git a/src/tools/search.ts b/src/tools/search.ts new file mode 100644 index 0000000..3f1862a --- /dev/null +++ b/src/tools/search.ts @@ -0,0 +1,54 @@ +import { registerTool } from './registry'; + +/** + * Web search via Jina AI's free search endpoint (s.jina.ai). + * No API key required. Returns clean, AI-readable markdown results. + * Atlas uses this for competitor research, market context, pricing models, etc. + */ +registerTool({ + name: 'web_search', + description: 'Search the web for current information. Use this to research competitors, market trends, pricing models, existing solutions, technology choices, or any topic the user mentions that would benefit from real-world context. Returns a summary of top search results.', + parameters: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'The search query. Be specific — e.g. "SaaS project management tools pricing 2024" rather than just "project management".' + } + }, + required: ['query'] + }, + async handler(args) { + const query = String(args.query).trim(); + if (!query) return { error: 'No query provided' }; + + const url = `https://s.jina.ai/${encodeURIComponent(query)}`; + + try { + const res = await fetch(url, { + headers: { + 'Accept': 'text/plain', + 'X-Return-Format': 'markdown', + }, + signal: AbortSignal.timeout(15_000), + }); + + if (!res.ok) { + return { error: `Search failed with status ${res.status}` }; + } + + const text = await res.text(); + + // Jina returns verbose results — truncate to avoid flooding the context window + const truncated = text.length > 6000 ? text.slice(0, 6000) + '\n\n[...results truncated]' : text; + + return { + query, + results: truncated, + }; + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + return { error: `Search request failed: ${message}` }; + } + } +});