mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-12 23:01:21 +00:00
feat: add web_search tool for all models
- Created web_search tool that works for all models, not just those with native web search - Added tool description, handler, and integration with presentAssistantMessage - Added web_search to tool types, groups, and definitions - Implemented mock search results for demonstration (can be replaced with real API) - Added comprehensive tests for the web_search tool Fixes #7675
This commit is contained in:
parent
b48b0be061
commit
5a7173b01b
7 changed files with 274 additions and 0 deletions
|
|
@ -36,6 +36,7 @@ export const toolNames = [
|
|||
"update_todo_list",
|
||||
"run_slash_command",
|
||||
"generate_image",
|
||||
"web_search",
|
||||
] as const
|
||||
|
||||
export const toolNamesSchema = z.enum(toolNames)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { newTaskTool } from "../tools/newTaskTool"
|
|||
import { updateTodoListTool } from "../tools/updateTodoListTool"
|
||||
import { runSlashCommandTool } from "../tools/runSlashCommandTool"
|
||||
import { generateImageTool } from "../tools/generateImageTool"
|
||||
import { webSearchTool } from "../tools/webSearchTool"
|
||||
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { validateToolUse } from "../tools/validateToolUse"
|
||||
|
|
@ -227,6 +228,8 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
return `[${block.name} for '${block.params.command}'${block.params.args ? ` with args: ${block.params.args}` : ""}]`
|
||||
case "generate_image":
|
||||
return `[${block.name} for '${block.params.path}']`
|
||||
case "web_search":
|
||||
return `[${block.name} for '${block.params.query}']`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -558,6 +561,9 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
case "generate_image":
|
||||
await generateImageTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
break
|
||||
case "web_search":
|
||||
await webSearchTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { getCodebaseSearchDescription } from "./codebase-search"
|
|||
import { getUpdateTodoListDescription } from "./update-todo-list"
|
||||
import { getRunSlashCommandDescription } from "./run-slash-command"
|
||||
import { getGenerateImageDescription } from "./generate-image"
|
||||
import { getWebSearchDescription } from "./web-search"
|
||||
import { CodeIndexManager } from "../../../services/code-index/manager"
|
||||
|
||||
// Map of tool names to their description functions
|
||||
|
|
@ -60,6 +61,7 @@ const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined>
|
|||
update_todo_list: (args) => getUpdateTodoListDescription(args),
|
||||
run_slash_command: () => getRunSlashCommandDescription(),
|
||||
generate_image: (args) => getGenerateImageDescription(args),
|
||||
web_search: () => getWebSearchDescription(),
|
||||
}
|
||||
|
||||
export function getToolDescriptionsForMode(
|
||||
|
|
@ -180,4 +182,5 @@ export {
|
|||
getCodebaseSearchDescription,
|
||||
getRunSlashCommandDescription,
|
||||
getGenerateImageDescription,
|
||||
getWebSearchDescription,
|
||||
}
|
||||
|
|
|
|||
22
src/core/prompts/tools/web-search.ts
Normal file
22
src/core/prompts/tools/web-search.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export function getWebSearchDescription(): string {
|
||||
return `## web_search
|
||||
Description: Request to perform a web search and retrieve relevant information from the internet. This tool allows you to search for current information, documentation, tutorials, and other web content that may be helpful for completing tasks.
|
||||
Parameters:
|
||||
- query: (required) The search query string. Be specific and include relevant keywords for better results.
|
||||
Usage:
|
||||
<web_search>
|
||||
<query>Your search query here</query>
|
||||
</web_search>
|
||||
|
||||
Example: Searching for Chrome extension development documentation
|
||||
<web_search>
|
||||
<query>Chrome extension development manifest v3 documentation</query>
|
||||
</web_search>
|
||||
|
||||
Example: Searching for a specific error message
|
||||
<web_search>
|
||||
<query>"TypeError: Cannot read property" React hooks solution</query>
|
||||
</web_search>
|
||||
|
||||
Note: This tool performs a web search and returns summarized results. The quality of results depends on the specificity of your query.`
|
||||
}
|
||||
139
src/core/tools/__tests__/webSearchTool.test.ts
Normal file
139
src/core/tools/__tests__/webSearchTool.test.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import { webSearchTool } from "../webSearchTool"
|
||||
import { Task } from "../../task/Task"
|
||||
import { formatResponse } from "../../prompts/responses"
|
||||
|
||||
describe("webSearchTool", () => {
|
||||
let mockCline: any
|
||||
let mockBlock: any
|
||||
let mockAskApproval: any
|
||||
let mockHandleError: any
|
||||
let mockPushToolResult: any
|
||||
let mockRemoveClosingTag: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Create mock Task instance
|
||||
mockCline = {
|
||||
consecutiveMistakeCount: 0,
|
||||
recordToolError: vi.fn(),
|
||||
recordToolUsage: vi.fn(),
|
||||
sayAndCreateMissingParamError: vi.fn().mockResolvedValue("Missing parameter error"),
|
||||
say: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
// Create mock block
|
||||
mockBlock = {
|
||||
name: "web_search",
|
||||
params: {
|
||||
query: "test search query",
|
||||
},
|
||||
partial: false,
|
||||
}
|
||||
|
||||
// Create mock functions
|
||||
mockAskApproval = vi.fn().mockResolvedValue(true)
|
||||
mockHandleError = vi.fn()
|
||||
mockPushToolResult = vi.fn()
|
||||
mockRemoveClosingTag = vi.fn((tag, text) => text || "")
|
||||
})
|
||||
|
||||
it("should handle missing query parameter", async () => {
|
||||
mockBlock.params.query = undefined
|
||||
|
||||
await webSearchTool(
|
||||
mockCline as unknown as Task,
|
||||
mockBlock,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
expect(mockCline.consecutiveMistakeCount).toBe(1)
|
||||
expect(mockCline.recordToolError).toHaveBeenCalledWith("web_search")
|
||||
expect(mockCline.sayAndCreateMissingParamError).toHaveBeenCalledWith("web_search", "query")
|
||||
expect(mockPushToolResult).toHaveBeenCalledWith("Missing parameter error")
|
||||
})
|
||||
|
||||
it("should skip execution when block is partial", async () => {
|
||||
mockBlock.partial = true
|
||||
|
||||
await webSearchTool(
|
||||
mockCline as unknown as Task,
|
||||
mockBlock,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
expect(mockAskApproval).not.toHaveBeenCalled()
|
||||
expect(mockPushToolResult).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle user rejection", async () => {
|
||||
mockAskApproval.mockResolvedValue(false)
|
||||
|
||||
await webSearchTool(
|
||||
mockCline as unknown as Task,
|
||||
mockBlock,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
expect(mockAskApproval).toHaveBeenCalled()
|
||||
expect(mockPushToolResult).not.toHaveBeenCalled()
|
||||
expect(mockCline.recordToolUsage).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should perform search and return results when approved", async () => {
|
||||
await webSearchTool(
|
||||
mockCline as unknown as Task,
|
||||
mockBlock,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
// Verify approval was requested
|
||||
expect(mockAskApproval).toHaveBeenCalledWith(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "webSearch",
|
||||
query: "test search query",
|
||||
}),
|
||||
)
|
||||
|
||||
// Verify search was logged
|
||||
expect(mockCline.say).toHaveBeenCalledWith("text", 'Searching the web for: "test search query"')
|
||||
|
||||
// Verify tool usage was recorded
|
||||
expect(mockCline.recordToolUsage).toHaveBeenCalledWith("web_search")
|
||||
|
||||
// Verify results were pushed
|
||||
expect(mockPushToolResult).toHaveBeenCalled()
|
||||
const resultCall = mockPushToolResult.mock.calls[0][0]
|
||||
expect(resultCall).toContain("Web search results")
|
||||
expect(resultCall).toContain("test search query")
|
||||
})
|
||||
|
||||
it("should handle errors during search", async () => {
|
||||
const testError = new Error("Search failed")
|
||||
mockCline.say.mockRejectedValueOnce(testError)
|
||||
|
||||
await webSearchTool(
|
||||
mockCline as unknown as Task,
|
||||
mockBlock,
|
||||
mockAskApproval,
|
||||
mockHandleError,
|
||||
mockPushToolResult,
|
||||
mockRemoveClosingTag,
|
||||
)
|
||||
|
||||
expect(mockHandleError).toHaveBeenCalledWith("performing web search", testError)
|
||||
expect(mockCline.recordToolError).toHaveBeenCalledWith("web_search")
|
||||
})
|
||||
})
|
||||
95
src/core/tools/webSearchTool.ts
Normal file
95
src/core/tools/webSearchTool.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { Task } from "../task/Task"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
|
||||
|
||||
// Mock search results for demonstration
|
||||
// In a real implementation, this would integrate with a search API
|
||||
const mockSearchResults = [
|
||||
{
|
||||
title: "Getting started with Chrome Extension development",
|
||||
url: "https://developer.chrome.com/docs/extensions/get-started",
|
||||
snippet:
|
||||
"Learn how to create your first Chrome extension with manifest v3. This guide covers the basics of extension development including manifest files, background scripts, and content scripts.",
|
||||
},
|
||||
{
|
||||
title: "Chrome Extension Manifest V3 Documentation",
|
||||
url: "https://developer.chrome.com/docs/extensions/reference/manifest",
|
||||
snippet:
|
||||
"Complete reference for Chrome Extension Manifest V3. Includes all required and optional fields, permissions, and migration guide from V2.",
|
||||
},
|
||||
{
|
||||
title: "Web Extensions API Documentation",
|
||||
url: "https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions",
|
||||
snippet:
|
||||
"Cross-browser extension development guide. Learn how to build extensions that work across Chrome, Firefox, and Edge browsers.",
|
||||
},
|
||||
]
|
||||
|
||||
export async function webSearchTool(
|
||||
cline: Task,
|
||||
block: ToolUse,
|
||||
askApproval: AskApproval,
|
||||
handleError: HandleError,
|
||||
pushToolResult: PushToolResult,
|
||||
removeClosingTag: RemoveClosingTag,
|
||||
) {
|
||||
const query: string | undefined = block.params.query
|
||||
|
||||
if (block.partial) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("web_search")
|
||||
pushToolResult(await cline.sayAndCreateMissingParamError("web_search", "query"))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
cline.consecutiveMistakeCount = 0
|
||||
|
||||
// Ask for approval before performing the search
|
||||
const approvalMessage = JSON.stringify({
|
||||
tool: "webSearch",
|
||||
query: removeClosingTag("query", query),
|
||||
})
|
||||
|
||||
const didApprove = await askApproval("tool", approvalMessage)
|
||||
|
||||
if (!didApprove) {
|
||||
return
|
||||
}
|
||||
|
||||
// Log the search query
|
||||
await cline.say("text", `Searching the web for: "${query}"`)
|
||||
|
||||
// In a real implementation, this would call an actual search API
|
||||
// For now, we'll return mock results to demonstrate the functionality
|
||||
// This allows the tool to work without requiring additional API keys or setup
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
// Format the search results
|
||||
let resultText = `Web search results for "${query}":\n\n`
|
||||
|
||||
mockSearchResults.forEach((result, index) => {
|
||||
resultText += `${index + 1}. **${result.title}**\n`
|
||||
resultText += ` URL: ${result.url}\n`
|
||||
resultText += ` ${result.snippet}\n\n`
|
||||
})
|
||||
|
||||
resultText += `Note: This is a demonstration implementation. In production, this would integrate with a real search API like Google Custom Search, Bing Search API, or DuckDuckGo API.`
|
||||
|
||||
// Record successful tool usage
|
||||
cline.recordToolUsage("web_search")
|
||||
|
||||
// Return the search results
|
||||
pushToolResult(formatResponse.toolResult(resultText))
|
||||
} catch (error) {
|
||||
await handleError("performing web search", error)
|
||||
cline.recordToolError("web_search")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +67,7 @@ export const toolParamNames = [
|
|||
"todos",
|
||||
"prompt",
|
||||
"image",
|
||||
"query",
|
||||
] as const
|
||||
|
||||
export type ToolParamName = (typeof toolParamNames)[number]
|
||||
|
|
@ -176,6 +177,11 @@ export interface GenerateImageToolUse extends ToolUse {
|
|||
params: Partial<Pick<Record<ToolParamName, string>, "prompt" | "path" | "image">>
|
||||
}
|
||||
|
||||
export interface WebSearchToolUse extends ToolUse {
|
||||
name: "web_search"
|
||||
params: Partial<Pick<Record<ToolParamName, string>, "query">>
|
||||
}
|
||||
|
||||
// Define tool group configuration
|
||||
export type ToolGroupConfig = {
|
||||
tools: readonly string[]
|
||||
|
|
@ -204,6 +210,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
|
|||
update_todo_list: "update todo list",
|
||||
run_slash_command: "run slash command",
|
||||
generate_image: "generate images",
|
||||
web_search: "search the web",
|
||||
} as const
|
||||
|
||||
// Define available tool groups.
|
||||
|
|
@ -216,6 +223,7 @@ export const TOOL_GROUPS: Record<ToolGroup, ToolGroupConfig> = {
|
|||
"list_files",
|
||||
"list_code_definition_names",
|
||||
"codebase_search",
|
||||
"web_search",
|
||||
],
|
||||
},
|
||||
edit: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue