diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 23e0a548d1..eb291ae0df 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -182,6 +182,11 @@ const baseProviderSettingsSchema = z.object({ // Model verbosity. verbosity: verbosityLevelsSchema.optional(), + + // Smart tool selection. + smartToolSelectionEnabled: z.boolean().optional(), + smartToolSelectionMinTools: z.number().min(1).max(20).optional(), + smartToolSelectionMaxTools: z.number().min(1).max(20).optional(), }) // Several of the providers share common model config properties. diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 3cc327c815..426393e703 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -62,6 +62,7 @@ async function generatePrompt( settings?: SystemPromptSettings, todoList?: TodoItem[], modelId?: string, + userQuery?: string, ): Promise { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -108,6 +109,7 @@ ${getToolDescriptionsForMode( settings, enableMcpServerCreation, modelId, + userQuery, )} ${getToolUseGuidelinesSection(codeIndexManager)} @@ -153,6 +155,7 @@ export const SYSTEM_PROMPT = async ( settings?: SystemPromptSettings, todoList?: TodoItem[], modelId?: string, + userQuery?: string, ): Promise => { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -225,5 +228,6 @@ ${customInstructions}` settings, todoList, modelId, + userQuery, ) } diff --git a/src/core/prompts/tools/ToolSelectionAnalyzer.test.ts b/src/core/prompts/tools/ToolSelectionAnalyzer.test.ts new file mode 100644 index 0000000000..e669d57c23 --- /dev/null +++ b/src/core/prompts/tools/ToolSelectionAnalyzer.test.ts @@ -0,0 +1,308 @@ +import { describe, it, expect, beforeEach } from "vitest" +import { ToolSelectionAnalyzer, SmartToolSelectionConfig } from "./ToolSelectionAnalyzer" +import type { ModeConfig, ToolName, ToolGroup } from "@roo-code/types" + +describe("ToolSelectionAnalyzer", () => { + let analyzer: ToolSelectionAnalyzer + + beforeEach(() => { + analyzer = new ToolSelectionAnalyzer() + }) + + const createModeConfig = (groups: ToolGroup[]): ModeConfig => ({ + slug: "test", + name: "Test Mode", + roleDefinition: "Test role", + groups: groups as any, // Groups can be string or tuple format + }) + + const createAvailableTools = (tools: ToolName[]): Set => new Set(tools) + + describe("query analysis", () => { + it("should detect simple queries", () => { + const simpleQueries = [ + "what does this function do?", + "explain this code", + "what is the purpose of this file?", + "can you summarize this?", + ] + + const modeConfig = createModeConfig(["read"]) + const availableTools = createAvailableTools([ + "read_file", + "list_files", + "search_files", + "list_code_definition_names", + "ask_followup_question", + "attempt_completion", + ]) + + simpleQueries.forEach((query) => { + const selected = analyzer.selectTools(query, modeConfig, availableTools) + expect(selected.length).toBeGreaterThanOrEqual(6) + expect(selected.length).toBeLessThanOrEqual(8) + // Should include essential read tools + expect(selected).toContain("read_file") + expect(selected).toContain("list_files") + }) + }) + + it("should detect complex queries", () => { + const complexQueries = [ + "refactor the entire authentication system to use JWT tokens", + "implement a comprehensive logging system across all modules", + "redesign the database schema and migrate all existing data", + "build a complete REST API with authentication and rate limiting", + ] + + const modeConfig = createModeConfig(["read", "edit"]) + const availableTools = createAvailableTools([ + "read_file", + "write_to_file", + "apply_diff", + "list_files", + "search_files", + "execute_command", + "ask_followup_question", + "attempt_completion", + ]) + + complexQueries.forEach((query) => { + const selected = analyzer.selectTools(query, modeConfig, availableTools) + expect(selected.length).toBeGreaterThanOrEqual(8) + expect(selected.length).toBeLessThanOrEqual(12) + // Should include both read and edit tools + expect(selected).toContain("read_file") + expect(selected).toContain("write_to_file") + expect(selected).toContain("apply_diff") + }) + }) + }) + + describe("tool-specific queries", () => { + it("should prioritize mentioned tools", () => { + const query = "run npm test and show me the results" + const modeConfig = createModeConfig(["read", "command"]) + const availableTools = createAvailableTools([ + "read_file", + "list_files", + "execute_command", + "ask_followup_question", + "attempt_completion", + ]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + // execute_command should be included and highly ranked (top 5) due to explicit mention + expect(selected).toContain("execute_command") + const executeIndex = selected.indexOf("execute_command") + expect(executeIndex).toBeGreaterThanOrEqual(0) + expect(executeIndex).toBeLessThan(5) // Within top 5 is still highly ranked + }) + + it("should detect file operations", () => { + const query = "create a new config file with the settings I provided" + const modeConfig = createModeConfig(["read", "edit"]) + const availableTools = createAvailableTools([ + "read_file", + "write_to_file", + "apply_diff", + "list_files", + "search_files", + "ask_followup_question", + "attempt_completion", + ]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + // write_to_file should be highly ranked + expect(selected.slice(0, 3)).toContain("write_to_file") + }) + + it("should detect search operations", () => { + const query = "find all places where the API key is used" + const modeConfig = createModeConfig(["read"]) + const availableTools = createAvailableTools([ + "read_file", + "list_files", + "search_files", + "list_code_definition_names", + "ask_followup_question", + "attempt_completion", + ]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + // search_files should be included and highly ranked (top 5) due to explicit search intent + expect(selected).toContain("search_files") + const searchIndex = selected.indexOf("search_files") + expect(searchIndex).toBeGreaterThanOrEqual(0) + expect(searchIndex).toBeLessThan(5) // Within top 5 is still highly ranked + }) + }) + + describe("settings configuration", () => { + it("should respect minimum tools setting", () => { + const query = "simple task" + const modeConfig = createModeConfig(["read"]) + const availableTools = createAvailableTools([ + "read_file", + "list_files", + "ask_followup_question", + "attempt_completion", + ]) + + const customAnalyzer = new ToolSelectionAnalyzer({ + enabled: true, + minTools: 3, + maxTools: 12, + }) + + const selected = customAnalyzer.selectTools(query, modeConfig, availableTools) + expect(selected.length).toBeGreaterThanOrEqual(3) + }) + + it("should respect maximum tools setting", () => { + const query = "complex refactoring task involving multiple systems" + const modeConfig = createModeConfig(["read", "edit", "command"]) + const availableTools = createAvailableTools([ + "read_file", + "write_to_file", + "apply_diff", + "insert_content", + "list_files", + "search_files", + "list_code_definition_names", + "execute_command", + "ask_followup_question", + "attempt_completion", + "switch_mode", + "new_task", + "update_todo_list", + ]) + + const customAnalyzer = new ToolSelectionAnalyzer({ + enabled: true, + minTools: 6, + maxTools: 8, + }) + + const selected = customAnalyzer.selectTools(query, modeConfig, availableTools) + expect(selected.length).toBeLessThanOrEqual(8) + }) + + it("should return all tools when smart selection is disabled", () => { + const query = "some task" + const modeConfig = createModeConfig(["read"]) + const availableTools = createAvailableTools([ + "read_file", + "list_files", + "search_files", + "ask_followup_question", + "attempt_completion", + ]) + + const disabledAnalyzer = new ToolSelectionAnalyzer({ + enabled: false, + }) + + const selected = disabledAnalyzer.selectTools(query, modeConfig, availableTools) + expect(selected.length).toBe(availableTools.size) + expect(selected.sort()).toEqual(Array.from(availableTools).sort()) + }) + }) + + describe("essential tools", () => { + it("should always include ask_followup_question and attempt_completion", () => { + const queries = ["simple query", "complex refactoring", "create a file", "run tests"] + + const modeConfig = createModeConfig(["read", "edit"]) + const availableTools = createAvailableTools([ + "read_file", + "write_to_file", + "list_files", + "ask_followup_question", + "attempt_completion", + ]) + + queries.forEach((query) => { + const selected = analyzer.selectTools(query, modeConfig, availableTools) + expect(selected).toContain("ask_followup_question") + expect(selected).toContain("attempt_completion") + }) + }) + }) + + describe("empty or invalid inputs", () => { + it("should handle empty query gracefully", () => { + const query = "" + const modeConfig = createModeConfig(["read"]) + const availableTools = createAvailableTools([ + "read_file", + "list_files", + "ask_followup_question", + "attempt_completion", + ]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + expect(selected.length).toBeGreaterThan(0) + expect(selected).toContain("ask_followup_question") + expect(selected).toContain("attempt_completion") + }) + + it("should handle empty available tools", () => { + const query = "do something" + const modeConfig = createModeConfig(["read"]) + const availableTools = createAvailableTools([]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + expect(selected).toEqual([]) + }) + }) + + describe("tool scoring", () => { + it("should score tools based on query relevance", () => { + const query = "write a test file for the authentication module" + const modeConfig = createModeConfig(["read", "edit"]) + const availableTools = createAvailableTools([ + "read_file", + "write_to_file", + "apply_diff", + "list_files", + "search_files", + "execute_command", + "ask_followup_question", + "attempt_completion", + ]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + + // write_to_file should be highly ranked for "write a test file" + const writeIndex = selected.indexOf("write_to_file") + expect(writeIndex).toBeGreaterThanOrEqual(0) + expect(writeIndex).toBeLessThan(4) // Should be in top 4 tools + + // read_file should also be included for context + expect(selected).toContain("read_file") + }) + + it("should handle multiple tool mentions in query", () => { + const query = "read the config file, update it, and run the build command" + const modeConfig = createModeConfig(["read", "edit", "command"]) + const availableTools = createAvailableTools([ + "read_file", + "write_to_file", + "apply_diff", + "execute_command", + "list_files", + "search_files", + "ask_followup_question", + "attempt_completion", + ]) + + const selected = analyzer.selectTools(query, modeConfig, availableTools) + + // Should include all mentioned operations + expect(selected).toContain("read_file") + expect(selected).toContain("apply_diff") // for "update" + expect(selected).toContain("execute_command") // for "run" + }) + }) +}) diff --git a/src/core/prompts/tools/ToolSelectionAnalyzer.ts b/src/core/prompts/tools/ToolSelectionAnalyzer.ts new file mode 100644 index 0000000000..892bec2120 --- /dev/null +++ b/src/core/prompts/tools/ToolSelectionAnalyzer.ts @@ -0,0 +1,394 @@ +import type { ToolName, ToolGroup, ModeConfig } from "@roo-code/types" +import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools" +import { getGroupName } from "../../../shared/modes" + +/** + * Configuration for smart tool selection + */ +export interface SmartToolSelectionConfig { + enabled?: boolean + minTools?: number + maxTools?: number + defaultComplexityThreshold?: number +} + +/** + * Result of query analysis + */ +interface QueryAnalysis { + complexity: "simple" | "moderate" | "complex" + mentionedTools: Set + mentionedGroups: Set + isReadOnly: boolean + needsEditing: boolean + needsCommandExecution: boolean + needsBrowser: boolean + needsMcp: boolean + confidence: number +} + +/** + * Tool relevance score + */ +interface ToolScore { + tool: ToolName + score: number + reason: string +} + +/** + * Patterns for detecting tool mentions in queries + */ +const TOOL_MENTION_PATTERNS: Record = { + mcp: { + groups: ["mcp"], + keywords: /\b(mcp|server|github\s+mcp|database\s+mcp|use\s+mcp)\b/i, + }, + browser: { + groups: ["browser"], + keywords: + /\b(browser|website|web\s+page|localhost|chrome|firefox|test\s+in\s+browser|check\s+the\s+(website|browser))\b/i, + }, + command: { + groups: ["command"], + keywords: /\b(run|execute|npm|yarn|pnpm|test|install|dependencies|cli|command|terminal|shell)\b/i, + }, + edit: { + groups: ["edit"], + keywords: /\b(fix|refactor|modify|change|update|write|create|add|remove|delete|implement|code)\b/i, + }, + read: { + groups: ["read"], + keywords: /\b(explain|what\s+does|understand|analyze|show|describe|look\s+at|read|view|check)\b/i, + }, +} + +/** + * Complexity indicators in queries + */ +const COMPLEXITY_INDICATORS = { + simple: [ + /^(what|how|why|when|where|who)\s+/i, + /\b(explain|describe|show|tell\s+me)\b/i, + /\b(typo|spelling|rename)\b/i, + /\b(single|one|this)\s+\w+/i, + ], + complex: [ + /\b(entire|whole|all|complete|full|system|architecture|refactor)\b/i, + /\b(multiple|several|many|various)\b/i, + /\b(and|also|then|after|before|while)\b.*\b(and|also|then|after|before|while)\b/i, // Multiple conjunctions + /\b(implement|create|build|design|develop)\s+\w+\s+(system|module|feature)/i, + ], +} + +/** + * Analyzer for smart tool selection based on query context + */ +export class ToolSelectionAnalyzer { + private config: SmartToolSelectionConfig + private recentMessages: string[] = [] + private maxMessageHistory = 5 + + constructor(config: SmartToolSelectionConfig = {}) { + this.config = { + enabled: config.enabled ?? true, + minTools: config.minTools ?? 6, + maxTools: config.maxTools ?? 12, + defaultComplexityThreshold: config.defaultComplexityThreshold ?? 0.7, + } + } + + /** + * Add a message to the conversation history + */ + public addMessage(message: string): void { + this.recentMessages.push(message) + if (this.recentMessages.length > this.maxMessageHistory) { + this.recentMessages.shift() + } + } + + /** + * Clear the conversation history + */ + public clearHistory(): void { + this.recentMessages = [] + } + + /** + * Analyze a query to determine its characteristics + */ + private analyzeQuery(query: string): QueryAnalysis { + const lowerQuery = query.toLowerCase() + const analysis: QueryAnalysis = { + complexity: "moderate", + mentionedTools: new Set(), + mentionedGroups: new Set(), + isReadOnly: false, + needsEditing: false, + needsCommandExecution: false, + needsBrowser: false, + needsMcp: false, + confidence: 0.5, + } + + // Check for explicit tool/group mentions + for (const [key, pattern] of Object.entries(TOOL_MENTION_PATTERNS)) { + if (pattern.keywords.test(query)) { + pattern.groups.forEach((group) => analysis.mentionedGroups.add(group)) + if (pattern.tools) { + pattern.tools.forEach((tool) => analysis.mentionedTools.add(tool)) + } + + // Set specific needs based on mentions + switch (key) { + case "mcp": + analysis.needsMcp = true + break + case "browser": + analysis.needsBrowser = true + break + case "command": + analysis.needsCommandExecution = true + break + case "edit": + analysis.needsEditing = true + break + case "read": + analysis.isReadOnly = !analysis.needsEditing // Only read-only if not also editing + break + } + } + } + + // Determine complexity + const simpleCount = COMPLEXITY_INDICATORS.simple.filter((pattern) => pattern.test(query)).length + const complexCount = COMPLEXITY_INDICATORS.complex.filter((pattern) => pattern.test(query)).length + + if (complexCount > 1 || query.length > 500) { + analysis.complexity = "complex" + analysis.confidence = Math.min(0.9, 0.6 + complexCount * 0.1) + } else if (simpleCount > 1 && complexCount === 0 && query.length < 100) { + analysis.complexity = "simple" + analysis.confidence = Math.min(0.9, 0.6 + simpleCount * 0.1) + } else { + analysis.complexity = "moderate" + analysis.confidence = 0.7 + } + + // If no editing keywords found but query seems to be about making changes + if (!analysis.needsEditing && /\b(fix|update|change|modify|add|remove)\b/i.test(query)) { + analysis.needsEditing = true + analysis.isReadOnly = false + } + + // Adjust based on conversation history + if (this.recentMessages.length > 0) { + const recentContext = this.recentMessages.join(" ").toLowerCase() + if (/\b(debug|error|issue|problem|bug)\b/i.test(recentContext)) { + // In debugging context, likely need read and command tools + analysis.needsCommandExecution = true + analysis.confidence = Math.min(0.95, analysis.confidence + 0.1) + } + if (/\b(implement|create|build|write)\b/i.test(recentContext)) { + // In implementation context, likely need editing tools + analysis.needsEditing = true + analysis.isReadOnly = false + } + } + + return analysis + } + + /** + * Score tools based on query analysis + */ + private scoreTools(availableTools: Set, analysis: QueryAnalysis, modeConfig: ModeConfig): ToolScore[] { + const scores: ToolScore[] = [] + + for (const tool of availableTools) { + let score = 0 + let reason = "" + + // Always include essential tools with high score + if (ALWAYS_AVAILABLE_TOOLS.includes(tool)) { + score = 0.9 + reason = "essential tool" + } + // Explicitly mentioned tools get highest score + else if (analysis.mentionedTools.has(tool)) { + score = 1.0 + reason = "explicitly mentioned" + } + // Tools in mentioned groups get high score + else { + // Find which group this tool belongs to + let toolGroup: ToolGroup | undefined + for (const [groupName, groupConfig] of Object.entries(TOOL_GROUPS)) { + if (groupConfig.tools.includes(tool)) { + toolGroup = groupName as ToolGroup + break + } + } + + if (toolGroup && analysis.mentionedGroups.has(toolGroup)) { + score = 0.85 + reason = `part of mentioned ${toolGroup} group` + } else { + // Score based on query characteristics + switch (tool) { + case "read_file": + case "list_files": + case "search_files": + case "list_code_definition_names": + score = analysis.isReadOnly ? 0.8 : 0.6 + reason = analysis.isReadOnly ? "read-only query" : "may need to read files" + break + + case "write_to_file": + case "apply_diff": + case "insert_content": + score = analysis.needsEditing ? 0.8 : analysis.isReadOnly ? 0.1 : 0.3 + reason = analysis.needsEditing ? "editing needed" : "editing tool" + break + + case "execute_command": + score = analysis.needsCommandExecution ? 0.85 : 0.2 + reason = analysis.needsCommandExecution ? "command execution needed" : "command tool" + break + + case "browser_action": + score = analysis.needsBrowser ? 0.9 : 0.1 + reason = analysis.needsBrowser ? "browser interaction needed" : "browser tool" + break + + case "use_mcp_tool": + case "access_mcp_resource": + score = analysis.needsMcp ? 0.9 : 0.1 + reason = analysis.needsMcp ? "MCP needed" : "MCP tool" + break + + case "codebase_search": + score = analysis.complexity === "complex" ? 0.7 : 0.4 + reason = "codebase search" + break + + case "fetch_instructions": + score = 0.3 // Lower priority unless specifically needed + reason = "instruction fetcher" + break + + default: + score = 0.5 + reason = "general tool" + } + } + } + + // Adjust score based on complexity + if (score > 0.1 && score < 0.9) { + if (analysis.complexity === "simple") { + score *= 0.8 // Reduce score for non-essential tools in simple queries + } else if (analysis.complexity === "complex") { + score *= 1.1 // Boost score for complex queries + } + score = Math.min(1.0, score) + } + + scores.push({ tool, score, reason }) + } + + return scores + } + + /** + * Select tools based on query context + */ + public selectTools( + query: string, + modeConfig: ModeConfig, + availableTools: Set, + customModes?: ModeConfig[], + ): ToolName[] { + // If feature is disabled, return all available tools + if (!this.config.enabled) { + return Array.from(availableTools) + } + + // Add query to history + this.addMessage(query) + + // Analyze the query + const analysis = this.analyzeQuery(query) + + // Score all available tools + const scores = this.scoreTools(availableTools, analysis, modeConfig) + + // Sort by score (descending) + scores.sort((a, b) => b.score - a.score) + + // Determine how many tools to include + let targetCount = this.config.minTools! + if (analysis.complexity === "simple") { + targetCount = this.config.minTools! + } else if (analysis.complexity === "moderate") { + targetCount = Math.floor((this.config.minTools! + this.config.maxTools!) / 2) + } else { + targetCount = this.config.maxTools! + } + + // Always include tools with score >= threshold + const threshold = this.config.defaultComplexityThreshold! + const selectedTools = new Set() + const highScoreTools = scores.filter((s) => s.score >= threshold) + + for (const { tool } of highScoreTools) { + selectedTools.add(tool) + } + + // If we have fewer tools than minimum, add more based on score + let i = highScoreTools.length + while (selectedTools.size < targetCount && i < scores.length) { + selectedTools.add(scores[i].tool) + i++ + } + + // Ensure we always have essential tools + for (const tool of ALWAYS_AVAILABLE_TOOLS) { + if (availableTools.has(tool)) { + selectedTools.add(tool) + } + } + + // Log selection for debugging (in development) + if (process.env.NODE_ENV === "development") { + console.log("Smart Tool Selection:", { + query: query.substring(0, 100), + analysis, + selectedCount: selectedTools.size, + totalAvailable: availableTools.size, + topScores: scores + .slice(0, 10) + .map((s) => ({ tool: s.tool, score: s.score.toFixed(2), reason: s.reason })), + }) + } + + return Array.from(selectedTools) + } +} + +// Singleton instance +let analyzerInstance: ToolSelectionAnalyzer | undefined + +/** + * Get or create the singleton ToolSelectionAnalyzer instance + */ +export function getToolSelectionAnalyzer(config?: SmartToolSelectionConfig): ToolSelectionAnalyzer { + if (!analyzerInstance) { + analyzerInstance = new ToolSelectionAnalyzer(config) + } else if (config) { + // Update config if provided + analyzerInstance = new ToolSelectionAnalyzer(config) + } + return analyzerInstance +} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 22588d55d1..2198b8da4c 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -5,6 +5,7 @@ import { McpHub } from "../../../services/mcp/McpHub" import { Mode, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes" import { ToolArgs } from "./types" +import { getToolSelectionAnalyzer, type SmartToolSelectionConfig } from "./ToolSelectionAnalyzer" import { getExecuteCommandDescription } from "./execute-command" import { getReadFileDescription } from "./read-file" import { getSimpleReadFileDescription } from "./simple-read-file" @@ -74,6 +75,7 @@ export function getToolDescriptionsForMode( settings?: Record, enableMcpServerCreation?: boolean, modelId?: string, + userQuery?: string, ): string { const config = getModeConfig(mode, customModes) const args: ToolArgs = { @@ -141,8 +143,24 @@ export function getToolDescriptionsForMode( tools.delete("run_slash_command") } - // Map tool descriptions for allowed tools - const descriptions = Array.from(tools).map((toolName) => { + // Apply smart tool selection if enabled and query is provided + let selectedTools = Array.from(tools) + + if (userQuery && settings?.smartToolSelection !== false) { + const smartSelectionConfig: SmartToolSelectionConfig = { + enabled: settings?.smartToolSelection?.enabled ?? true, + minTools: settings?.smartToolSelection?.minTools ?? 6, + maxTools: settings?.smartToolSelection?.maxTools ?? 12, + defaultComplexityThreshold: settings?.smartToolSelection?.threshold ?? 0.7, + } + + const analyzer = getToolSelectionAnalyzer(smartSelectionConfig) + const modeConfig = getModeConfig(mode, customModes) + selectedTools = analyzer.selectTools(userQuery, modeConfig, tools as Set, customModes) + } + + // Map tool descriptions for selected tools + const descriptions = selectedTools.map((toolName) => { const descriptionFn = toolDescriptionMap[toolName] if (!descriptionFn) { return undefined diff --git a/src/core/prompts/types.ts b/src/core/prompts/types.ts index d90b1b821a..7e0edadba2 100644 --- a/src/core/prompts/types.ts +++ b/src/core/prompts/types.ts @@ -6,4 +6,8 @@ export interface SystemPromptSettings { todoListEnabled: boolean useAgentRules: boolean newTaskRequireTodos: boolean + // Smart tool selection settings + smartToolSelectionEnabled?: boolean + smartToolSelectionMinTools?: number + smartToolSelectionMaxTools?: number } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 7ed317da10..99c81a6f71 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2553,6 +2553,31 @@ export class Task extends EventEmitter implements TaskLike { apiConfiguration, } = state ?? {} + // Extract the most recent user query from the conversation history + let userQuery: string | undefined + if (this.apiConversationHistory.length > 0) { + // Find the last user message in the conversation + const lastUserMessage = [...this.apiConversationHistory].reverse().find((msg) => msg.role === "user") + + if (lastUserMessage && Array.isArray(lastUserMessage.content)) { + // Extract text content from the user message + const textContents = lastUserMessage.content + .filter((block) => block.type === "text") + .map((block) => (block as any).text || "") + + // Combine all text content to form the query + if (textContents.length > 0) { + userQuery = textContents.join(" ").trim() + // Remove task tags if present + userQuery = userQuery.replace(/<\/?task>/g, "").trim() + // Limit length to prevent overly long queries + if (userQuery.length > 500) { + userQuery = userQuery.substring(0, 500) + "..." + } + } + } + } + return await (async () => { const provider = this.providerRef.deref() @@ -2595,9 +2620,14 @@ export class Task extends EventEmitter implements TaskLike { newTaskRequireTodos: vscode.workspace .getConfiguration("roo-cline") .get("newTaskRequireTodos", false), + // Smart tool selection configuration + smartToolSelectionEnabled: apiConfiguration?.smartToolSelectionEnabled ?? true, + smartToolSelectionMinTools: apiConfiguration?.smartToolSelectionMinTools ?? 6, + smartToolSelectionMaxTools: apiConfiguration?.smartToolSelectionMaxTools ?? 12, }, undefined, // todoList this.api.getModel().id, + userQuery, // Pass the extracted user query for smart tool selection ) })() }