/** * Copyright 2026 Reto Meier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { MultiAgentTool } from '../multiAgentTool.js'; import { findInFiles } from '../../utils/fileAnalysis.js'; import { MultiAgentToolContext, MultiAgentToolResult, ToolParsingResult } from '../../momoa_core/types.js'; /** * Implements the File Reader Tool, providing functionality to read file content * from an in-memory collection of files (Map). This tool does NOT read from disk. */ export const fileSearchTool: MultiAgentTool = { displayName: "File Search", name: 'FILESEARCH{query: "', /** * Executes the file reader tool. * @param params The parameters for the tool's execution, expecting a 'filename' property. * @param context The ToolContext object containing necessary runtime information. * @returns A promise that resolves to the file's content or an error message. */ async execute(params: Record, context: MultiAgentToolContext): Promise { const query = params.query; context.sendMessage({ type: 'PROGRESS_UPDATE', message: `Searching for \`${query}\``, }); // 1. Search text file content. const contentMatches = findInFiles(query, context.fileMap) || []; // Use a Set to store unique results to avoid duplicates. const searchResults = new Set(contentMatches); // 2. Search all filenames (both text and binary). const allFilenames = [...context.fileMap.keys(), ...context.binaryFileMap.keys()]; for (const filename of allFilenames) { if (filename.includes(query)) { if (context.binaryFileMap.has(filename)) { searchResults.add(`Binary file found: ${filename}`); } else { // If found by content search, it's already in the set as just the filename. // This logic ensures we don't add it twice. searchResults.add(filename); } } } const finalResultArray = Array.from(searchResults); const replacementString = `---FILE SEARCH RESULTS INTENTIONALLY REMOVED---`; const result = finalResultArray.length > 0 ? finalResultArray.join('\n') : `No matches found for your query.`; context.sendMessage({ type: 'PROGRESS_UPDATE', message: `\`\`\`\n${result.trim()}\n\`\`\``, }); return { result: result, transcriptReplacementID: query, transcriptReplacementString: replacementString }; }, /** * Extract parameters from the tool invocation string. * @param invocation The string used to invoke the tool. * @returns The parameter names and corresponding values. */ async extractParameters(invocation: string, _context: MultiAgentToolContext): Promise { const toolCallEndMarker = '" END_QUERY}'; const endQueryMarkerIndex = invocation.indexOf(toolCallEndMarker); if (endQueryMarkerIndex === -1) { return { success: false, error: `Unable to search the files because you provided invalid syntax. Please pay close attention to the required syntax before trying again.` } } const extractedQuery = invocation.substring(0, endQueryMarkerIndex); if (!extractedQuery.trim()) { return { success: false, error: `Unable to search the files because the provided query string was empty, which is invalid. Please pay close attention to the required syntax before trying again.` } } return { success: true, params: { query: extractedQuery.trim() } } } };