feat(COM-464): Add domain filtering support to web_search tool

- Add allowed_domains and blocked_domains parameters to WebSearchParams
- Implement domain filtering logic in WebSearchTool
- Add domain parameter handling in NativeToolCallParser
- Update tool prompt with domain filtering documentation
- Add parseDomainsArray helper function
- Enhance handlePartial to show domain filters in UI

This brings full parity with Cline web search domain filtering capabilities.
This commit is contained in:
Roo Code 2026-01-13 04:11:11 +00:00
parent fbc6a32f65
commit ba6b76a7ee
2 changed files with 92 additions and 6 deletions

View file

@ -424,6 +424,20 @@ export class NativeToolCallParser {
}
break
case "web_search":
if (partialArgs.query !== undefined) {
nativeArgs = {
query: partialArgs.query,
allowed_domains: Array.isArray(partialArgs.allowed_domains)
? partialArgs.allowed_domains
: undefined,
blocked_domains: Array.isArray(partialArgs.blocked_domains)
? partialArgs.blocked_domains
: undefined,
}
}
break
case "codebase_search":
if (partialArgs.query !== undefined) {
nativeArgs = {
@ -697,6 +711,16 @@ export class NativeToolCallParser {
}
break
case "web_search":
if (args.query !== undefined) {
nativeArgs = {
query: args.query,
allowed_domains: Array.isArray(args.allowed_domains) ? args.allowed_domains : undefined,
blocked_domains: Array.isArray(args.blocked_domains) ? args.blocked_domains : undefined,
} as any as NativeArgsFor<TName>
}
break
case "codebase_search":
if (args.query !== undefined) {
nativeArgs = {

View file

@ -5,6 +5,23 @@ import { t } from "../../i18n"
export interface WebSearchParams {
query: string
allowed_domains?: string[]
blocked_domains?: string[]
}
/**
* Parse JSON array string safely, returning empty array on parse errors
*/
function parseDomainsArray(domainsStr: string | undefined): string[] {
if (!domainsStr || domainsStr.trim() === "") {
return []
}
try {
const parsed = JSON.parse(domainsStr)
return Array.isArray(parsed) ? parsed.filter((d) => typeof d === "string") : []
} catch {
return []
}
}
// Mock search results for demonstration
@ -39,19 +56,37 @@ export class WebSearchTool extends BaseTool<"web_search"> {
readonly name = "web_search" as const
parseLegacy(params: Partial<Record<string, string>>): WebSearchParams {
const query = params.query || ""
const allowed_domains = parseDomainsArray(params.allowed_domains)
const blocked_domains = parseDomainsArray(params.blocked_domains)
return {
query: params.query || "",
query,
...(allowed_domains.length > 0 ? { allowed_domains } : {}),
...(blocked_domains.length > 0 ? { blocked_domains } : {}),
}
}
async execute(params: WebSearchParams, task: Task, callbacks: ToolCallbacks): Promise<void> {
const { query } = params
const { query, allowed_domains, blocked_domains } = params
const { handleError, pushToolResult, askApproval, removeClosingTag } = callbacks
if (!query) {
if (!query || query.trim().length < 2) {
task.consecutiveMistakeCount++
task.recordToolError("web_search")
pushToolResult(await task.sayAndCreateMissingParamError("web_search", "query"))
pushToolResult(
await task.sayAndCreateMissingParamError("web_search", "query", "Query must be at least 2 characters"),
)
return
}
// Validate mutual exclusivity of domain filters
if (allowed_domains && allowed_domains.length > 0 && blocked_domains && blocked_domains.length > 0) {
task.consecutiveMistakeCount++
task.didToolFailInCurrentTurn = true
pushToolResult(
formatResponse.toolError("Cannot specify both allowed_domains and blocked_domains at the same time"),
)
return
}
@ -62,6 +97,9 @@ export class WebSearchTool extends BaseTool<"web_search"> {
const approvalMessage = JSON.stringify({
tool: "webSearch",
query: removeClosingTag("query", query),
...(allowed_domains && allowed_domains.length > 0 ? { allowed_domains } : {}),
...(blocked_domains && blocked_domains.length > 0 ? { blocked_domains } : {}),
isOutsideWorkspace: true,
})
const didApprove = await askApproval("tool", approvalMessage)
@ -70,6 +108,14 @@ export class WebSearchTool extends BaseTool<"web_search"> {
return
}
// Construct domain filter description for response
let domainInfo = ""
if (allowed_domains && allowed_domains.length > 0) {
domainInfo = `\nDomain filter: Only results from ${allowed_domains.join(", ")}`
} else if (blocked_domains && blocked_domains.length > 0) {
domainInfo = `\nExcluding results from: ${blocked_domains.join(", ")}`
}
// Log the search query
await task.say("text", t("tools:webSearch.searching", { query }))
@ -81,7 +127,11 @@ export class WebSearchTool extends BaseTool<"web_search"> {
await new Promise((resolve) => setTimeout(resolve, 500))
// Format the search results
let resultText = t("tools:webSearch.results", { query }) + "\n\n"
let resultText = t("tools:webSearch.results", { query })
if (domainInfo) {
resultText += domainInfo
}
resultText += "\n\n"
mockSearchResults.forEach((result, index) => {
resultText += `${index + 1}. **${result.title}**\n`
@ -104,7 +154,19 @@ export class WebSearchTool extends BaseTool<"web_search"> {
}
override async handlePartial(task: Task, block: any): Promise<void> {
return
const query: string | undefined = block.params.query
const allowed_domains = parseDomainsArray(block.params.allowed_domains)
const blocked_domains = parseDomainsArray(block.params.blocked_domains)
const sharedMessageProps = {
tool: "webSearch",
query: query,
...(allowed_domains.length > 0 ? { allowed_domains } : {}),
...(blocked_domains.length > 0 ? { blocked_domains } : {}),
isOutsideWorkspace: true,
}
await task.ask("tool", JSON.stringify(sharedMessageProps), block.partial).catch(() => {})
}
}