Roo-Code/src/core/tools/codebaseSearchTool.ts
MuriloFP 616cafc3a1 fix: handle codebase search indexing state properly (#5662)
- Always show codebase_search tool in the tool list
- Add runtime state checking with user-friendly feedback
- Provide clear messages for each indexing state (Standby, Indexing, Error)
- Suggest alternative tools when semantic search is unavailable
- Add comprehensive tests for the new behavior

This ensures Roo doesn't try to use semantic search when indexing
is incomplete and provides clear feedback to users about the current state.
2025-07-23 11:10:48 -03:00

171 lines
4.8 KiB
TypeScript

import * as vscode from "vscode"
import { Task } from "../task/Task"
import { CodeIndexManager } from "../../services/code-index/manager"
import { getWorkspacePath } from "../../utils/path"
import { formatResponse } from "../prompts/responses"
import { VectorStoreSearchResult } from "../../services/code-index/interfaces"
import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools"
import path from "path"
export async function codebaseSearchTool(
cline: Task,
block: ToolUse,
askApproval: AskApproval,
handleError: HandleError,
pushToolResult: PushToolResult,
removeClosingTag: RemoveClosingTag,
) {
const toolName = "codebase_search"
const workspacePath = getWorkspacePath()
if (!workspacePath) {
// This case should ideally not happen if Cline is initialized correctly
await handleError(toolName, new Error("Could not determine workspace path."))
return
}
// --- Parameter Extraction and Validation ---
let query: string | undefined = block.params.query
let directoryPrefix: string | undefined = block.params.path
query = removeClosingTag("query", query)
if (directoryPrefix) {
directoryPrefix = removeClosingTag("path", directoryPrefix)
directoryPrefix = path.normalize(directoryPrefix)
}
const sharedMessageProps = {
tool: "codebaseSearch",
query: query,
path: directoryPrefix,
isOutsideWorkspace: false,
}
if (block.partial) {
await cline.ask("tool", JSON.stringify(sharedMessageProps), block.partial).catch(() => {})
return
}
if (!query) {
cline.consecutiveMistakeCount++
pushToolResult(await cline.sayAndCreateMissingParamError(toolName, "query"))
return
}
const didApprove = await askApproval("tool", JSON.stringify(sharedMessageProps))
if (!didApprove) {
pushToolResult(formatResponse.toolDenied())
return
}
cline.consecutiveMistakeCount = 0
// --- Core Logic ---
try {
const context = cline.providerRef.deref()?.context
if (!context) {
throw new Error("Extension context is not available.")
}
const manager = CodeIndexManager.getInstance(context)
if (!manager) {
throw new Error("CodeIndexManager is not available.")
}
if (!manager.isFeatureEnabled) {
throw new Error("Code Indexing is disabled in the settings.")
}
if (!manager.isFeatureConfigured) {
throw new Error("Code Indexing is not configured (Missing OpenAI Key or Qdrant URL).")
}
// Check indexing state at runtime
const indexingState = manager.state
if (indexingState !== "Indexed") {
let stateMessage = ""
switch (indexingState) {
case "Standby":
stateMessage =
"Code indexing has not started yet. Please wait for the initial indexing to complete."
break
case "Indexing":
stateMessage =
"Code indexing is currently in progress. Semantic search will be available once indexing is complete."
break
case "Error":
stateMessage = "Code indexing encountered an error. Please check your configuration and try again."
break
default:
stateMessage = `Code indexing is in an unexpected state: ${indexingState}`
}
// Return informative message instead of throwing error
pushToolResult(
`Semantic search is not available yet (currently ${indexingState}).\n\n${stateMessage}\n\nPlease use file reading tools (read_file, search_files) for now.`,
)
return
}
const searchResults: VectorStoreSearchResult[] = await manager.searchIndex(query, directoryPrefix)
// 3. Format and push results
if (!searchResults || searchResults.length === 0) {
pushToolResult(`No relevant code snippets found for the query: "${query}"`) // Use simple string for no results
return
}
const jsonResult = {
query,
results: [],
} as {
query: string
results: Array<{
filePath: string
score: number
startLine: number
endLine: number
codeChunk: string
}>
}
searchResults.forEach((result) => {
if (!result.payload) return
if (!("filePath" in result.payload)) return
const relativePath = vscode.workspace.asRelativePath(result.payload.filePath, false)
jsonResult.results.push({
filePath: relativePath,
score: result.score,
startLine: result.payload.startLine,
endLine: result.payload.endLine,
codeChunk: result.payload.codeChunk.trim(),
})
})
// Send results to UI
const payload = { tool: "codebaseSearch", content: jsonResult }
await cline.say("codebase_search_result", JSON.stringify(payload))
// Push results to AI
const output = `Query: ${query}
Results:
${jsonResult.results
.map(
(result) => `File path: ${result.filePath}
Score: ${result.score}
Lines: ${result.startLine}-${result.endLine}
Code Chunk: ${result.codeChunk}
`,
)
.join("\n")}`
pushToolResult(output)
} catch (error: any) {
await handleError(toolName, error) // Use the standard error handler
}
}