import { z } from 'zod'; import { BaseTool, type ToolContext, type ToolOutput } from '../../tools/index.js'; import type { WikiSearchLaneSummary, WikiSearchMatchReason } from '../types.js'; const WikiSearchInputSchema = z.object({ query: z.string().describe('Natural language search query to find relevant knowledge blocks.'), limit: z.number().optional().default(10).describe('Maximum number of results to return (default 10).'), }); type WikiSearchInput = z.infer; interface WikiSearchResult { blockKey: string; path: string; summary: string; score: number; matchReasons?: WikiSearchMatchReason[]; lanes?: WikiSearchLaneSummary[]; } interface WikiSearchStructured { results: WikiSearchResult[]; totalFound: number; } export interface WikiSearchAdapterPort { search(input: { userId: string; query: string; limit: number }): Promise<{ results: Array<{ key: string; path: string; summary: string; score: number; matchReasons?: WikiSearchMatchReason[]; lanes?: WikiSearchLaneSummary[]; }>; totalFound: number; }>; } export class WikiSearchTool extends BaseTool { readonly name = 'wiki_search'; constructor(private readonly searchAdapter: WikiSearchAdapterPort) { super(); } get description(): string { return ( 'Search knowledge blocks by hybrid lexical, semantic, and token matching. ' + 'Use this when you need to find knowledge on a topic not visible in the discovery index. ' + 'Returns ranked summaries — use wiki_read to load the full content of specific results.' ); } get inputSchema() { return WikiSearchInputSchema; } async call(input: WikiSearchInput, context: ToolContext): Promise> { const response = await this.searchAdapter.search({ userId: context.userId, query: input.query, limit: input.limit, }); if (response.results.length === 0) { return { markdown: `No knowledge blocks found matching "${input.query}".`, structured: { results: [], totalFound: 0 }, }; } const lines = response.results.map((r, i) => `${i + 1}. **${r.key}**: ${r.summary}`); const structured: WikiSearchStructured = { results: response.results.map((r) => ({ blockKey: r.key, path: r.path, summary: r.summary, score: r.score, matchReasons: r.matchReasons, lanes: r.lanes, })), totalFound: response.totalFound, }; return { markdown: `Found ${response.results.length} knowledge block(s):\n\n${lines.join('\n')}`, structured, }; } }