/** * Note Search Tool * Wraps semanticSearchService.searchAsUser() */ import { tool } from 'ai' import { z } from 'zod' import { toolRegistry } from './registry' import { prisma } from '@/lib/prisma' toolRegistry.register({ name: 'note_search', description: 'Search the user\'s notes using semantic search. Returns matching notes with titles and content excerpts.', isInternal: true, buildTool: (ctx) => tool({ description: 'Search the user\'s notes by keyword or semantic meaning. Returns matching notes with titles and content excerpts. Optionally restrict to a specific notebook.', inputSchema: z.object({ query: z.string().describe('The search query'), limit: z.number().optional().describe('Max results to return (default 5)').default(5), notebookId: z.string().optional().describe('Optional notebook ID to restrict search to a specific notebook'), }), execute: async ({ query, limit = 5, notebookId }) => { try { // Keyword fallback search using Prisma const keywords = query.toLowerCase().split(/\s+/).filter(w => w.length > 2) const conditions = keywords.flatMap(term => [ { title: { contains: term } }, { content: { contains: term } } ]) const notes = await prisma.note.findMany({ where: { userId: ctx.userId, ...(notebookId ? { notebookId } : {}), ...(conditions.length > 0 ? { OR: conditions } : {}), isArchived: false, trashedAt: null, }, select: { id: true, title: true, content: true, createdAt: true }, take: limit, orderBy: { createdAt: 'desc' }, }) return notes.map(n => ({ id: n.id, title: n.title || 'Untitled', excerpt: n.content.substring(0, 300), createdAt: n.createdAt.toISOString(), })) } catch (e: any) { return { error: `Note search failed: ${e.message}` } } }, }), })