diff --git a/.vscode/settings.json b/.vscode/settings.json index b661d8fe..d62464c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,7 @@ "typescript.tsc.autoDetect": "off", "liveServer.settings.port": 5501, "editor.fontSize": 14, - "generativeAi.option": "Gemini", + "generativeAi.option": "Groq", "mcp.servers": { "docker-gateway": { "command": "docker", diff --git a/package.json b/package.json index f91ba616..cc0882ba 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "publisher": "fiatinnovations", "description": "CodeBuddy is a Visual Studio Code extension that enhances developer productivity through AI-powered code assistance. It provides intelligent code review, refactoring suggestions, optimization tips, and interactive chat capabilities powered by multiple AI models including Gemini, Groq, Anthropic, and Deepseek.", - "version": "3.7.31", + "version": "4.0.1", "engines": { "vscode": "^1.78.0" }, @@ -291,7 +291,7 @@ "GLM", "Local" ], - "default": "Gemini", + "default": "Groq", "description": "Select Model" }, "tavily.apiKey": { diff --git a/src/agents/langgraph/tools/debugger.ts b/src/agents/langgraph/tools/debugger.ts new file mode 100644 index 00000000..d1b0fc63 --- /dev/null +++ b/src/agents/langgraph/tools/debugger.ts @@ -0,0 +1,182 @@ +import { z } from "zod"; +import { StructuredTool } from "@langchain/core/tools"; +import { DebuggerService } from "../../../services/debugger.service"; + +export class DebugGetStateTool extends StructuredTool { + name = "debug_get_state"; + description = "Get the current debug session state, including threads."; + schema = z.object({}); + + constructor() { + super(); + } + + async _call(_input: Record): Promise { + try { + const service = DebuggerService.getInstance(); + const threads = await service.getThreads(); + return JSON.stringify({ threads }); + } catch (error: any) { + return `Error: ${error.message}`; + } + } +} + +export class DebugGetStackTraceTool extends StructuredTool { + name = "debug_get_stack_trace"; + description = "Get the stack trace for a specific thread."; + schema = z.object({ + threadId: z + .number() + .describe("The ID of the thread to get stack trace for"), + startFrame: z.number().optional().default(0), + levels: z.number().optional().default(20), + }); + + constructor() { + super(); + } + + async _call(input: { + threadId: number; + startFrame: number; + levels: number; + }): Promise { + try { + const service = DebuggerService.getInstance(); + const stackFrames = await service.getStackTrace( + input.threadId, + input.startFrame, + input.levels, + ); + return JSON.stringify({ stackFrames }); + } catch (error: any) { + return `Error: ${error.message}`; + } + } +} + +export class DebugGetVariablesTool extends StructuredTool { + name = "debug_get_variables"; + description = + "Get variables for a specific stack frame. If frameId is not provided, tries to get variables for the top frame of the first thread (or active thread)."; + schema = z.object({ + frameId: z.number().optional().describe("The ID of the stack frame"), + threadId: z + .number() + .optional() + .describe( + "The ID of the thread (if frameId is not known, will use top frame)", + ), + }); + + constructor() { + super(); + } + + async _call(input: { frameId?: number; threadId?: number }): Promise { + try { + const service = DebuggerService.getInstance(); + let frameId = input.frameId; + + if (frameId === undefined) { + // If no frameId, try to find one + let threadId = input.threadId; + if (threadId === undefined) { + const threads = await service.getThreads(); + if (threads.length > 0) threadId = threads[0].id; + } + if (threadId !== undefined) { + const stack = await service.getStackTrace(threadId, 0, 1); + if (stack.length > 0) frameId = stack[0].id; + } + } + + if (frameId === undefined) { + return "Error: Could not determine stack frame ID. Please provide frameId or threadId."; + } + + const scopes = await service.getScopes(frameId); + const variablesResult: Record = {}; + + for (const scope of scopes) { + const vars = await service.getVariables(scope.variablesReference); + variablesResult[scope.name] = vars; + } + + return JSON.stringify(variablesResult); + } catch (error: any) { + return `Error: ${error.message}`; + } + } +} + +export class DebugEvaluateTool extends StructuredTool { + name = "debug_evaluate"; + description = "Evaluate an expression in the context of a stack frame."; + schema = z.object({ + expression: z.string().describe("The expression to evaluate"), + frameId: z.number().optional().describe("The ID of the stack frame"), + }); + + constructor() { + super(); + } + + async _call(input: { + expression: string; + frameId?: number; + }): Promise { + try { + const service = DebuggerService.getInstance(); + const result = await service.evaluate(input.expression, input.frameId); + return JSON.stringify(result); + } catch (error: any) { + return `Error: ${error.message}`; + } + } +} + +export class DebugControlTool extends StructuredTool { + name = "debug_control"; + description = "Control the debugger execution (step, continue, pause)."; + schema = z.object({ + action: z + .enum(["stepOver", "stepInto", "stepOut", "continue", "pause"]) + .describe("The action to perform"), + threadId: z.number().describe("The ID of the thread to control"), + }); + + constructor() { + super(); + } + + async _call(input: { + action: "stepOver" | "stepInto" | "stepOut" | "continue" | "pause"; + threadId: number; + }): Promise { + try { + const service = DebuggerService.getInstance(); + switch (input.action) { + case "stepOver": + await service.stepOver(input.threadId); + break; + case "stepInto": + await service.stepInto(input.threadId); + break; + case "stepOut": + await service.stepOut(input.threadId); + break; + case "continue": + await service.continue(input.threadId); + break; + case "pause": + await service.pause(input.threadId); + break; + } + return `Action ${input.action} executed on thread ${input.threadId}.`; + } catch (error: any) { + return `Error: ${error.message}`; + } + } +} diff --git a/src/agents/langgraph/tools/provider.ts b/src/agents/langgraph/tools/provider.ts index 1af65d83..0a409da4 100644 --- a/src/agents/langgraph/tools/provider.ts +++ b/src/agents/langgraph/tools/provider.ts @@ -36,6 +36,13 @@ import { LangChainWebPreviewTool } from "./web_preview"; import { LangChainSearchTool } from "./search"; import { LangChainTodoTool } from "./todo"; import { LangChainMemoryTool } from "./memory"; +import { + DebugControlTool, + DebugEvaluateTool, + DebugGetStackTraceTool, + DebugGetStateTool, + DebugGetVariablesTool, +} from "./debugger"; const logger = Logger.initialize("ToolProvider", { minLevel: LogLevel.DEBUG, @@ -150,6 +157,36 @@ class SearchToolFactory implements IToolFactory { } } +class DebugGetStateToolFactory implements IToolFactory { + createTool(): StructuredTool { + return new DebugGetStateTool(); + } +} + +class DebugGetStackTraceToolFactory implements IToolFactory { + createTool(): StructuredTool { + return new DebugGetStackTraceTool(); + } +} + +class DebugGetVariablesToolFactory implements IToolFactory { + createTool(): StructuredTool { + return new DebugGetVariablesTool(); + } +} + +class DebugEvaluateToolFactory implements IToolFactory { + createTool(): StructuredTool { + return new DebugEvaluateTool(); + } +} + +class DebugControlToolFactory implements IToolFactory { + createTool(): StructuredTool { + return new DebugControlTool(); + } +} + class MCPToolFactory implements IToolFactory { constructor( private readonly mcpService: MCPService, @@ -282,6 +319,23 @@ const TOOL_ROLE_MAPPING: Record = { "search_vector_db", "manage_terminal", ], + debugger: [ + "debug_get_state", + "debug_get_stack_trace", + "debug_get_variables", + "debug_evaluate", + "debug_control", + "analyze", + "read", + "search", + "terminal", + "run", + "command", + "list_files", + "edit_file", + "ripgrep_search", + "get_diagnostics", + ], }; export class ToolProvider { @@ -313,6 +367,11 @@ export class ToolProvider { new SearchToolFactory(this.contextRetriever), new TodoToolFactory(), new MemoryToolFactory(), + new DebugGetStateToolFactory(), + new DebugGetStackTraceToolFactory(), + new DebugGetVariablesToolFactory(), + new DebugEvaluateToolFactory(), + new DebugControlToolFactory(), ]; // Deduplicate tools during initialization diff --git a/src/commands/fixError.ts b/src/commands/fixError.ts index 456fdd41..32ad70a8 100644 --- a/src/commands/fixError.ts +++ b/src/commands/fixError.ts @@ -38,20 +38,35 @@ export class FixError extends CodeCommandHandler { try { const agentService = CodebuddyAgentService.getInstance(); + // Check for active debug session + const activeSession = vscode.debug.activeDebugSession; + let debugContext = ""; + if (activeSession) { + debugContext = ` +ACTIVE DEBUG SESSION DETECTED: +Session Name: ${activeSession.name} +ID: ${activeSession.id} + +You have access to Debugger Tools (debug_get_state, debug_get_stack_trace, debug_get_variables, debug_evaluate, debug_control). +Use them to inspect the running application state to better understand the error. +`; + } + const prompt = `Task: Fix the following error in the current file. Error Message: ${errorMsg} - +${debugContext} Code Context: ${selectedCode} Instructions: 1. Analyze the error and the code. -2. If you can fix it confidently, use the 'edit_file' tool to apply the fix directly. -3. If you apply a fix, verify it if possible (or at least ensure syntax is correct). -4. If you cannot fix it, or if it requires user input, provide a detailed explanation. -5. End your response with a summary of what you did. +2. If a debug session is active, use debug tools to inspect variables and stack trace. +3. If you can fix it confidently, use the 'edit_file' tool to apply the fix directly. +4. If you apply a fix, verify it if possible (or at least ensure syntax is correct). +5. If you cannot fix it, or if it requires user input, provide a detailed explanation. +6. End your response with a summary of what you did. `; let toolUsed = false; diff --git a/src/services/debugger.service.ts b/src/services/debugger.service.ts new file mode 100644 index 00000000..be59f946 --- /dev/null +++ b/src/services/debugger.service.ts @@ -0,0 +1,123 @@ +import * as vscode from "vscode"; +import { Logger, LogLevel } from "../infrastructure/logger/logger"; + +export class DebuggerService { + private static instance: DebuggerService; + private logger: Logger; + + private constructor() { + this.logger = Logger.initialize("DebuggerService", { + minLevel: LogLevel.DEBUG, + enableConsole: true, + enableFile: true, + enableTelemetry: true, + }); + } + + public static getInstance(): DebuggerService { + if (!DebuggerService.instance) { + DebuggerService.instance = new DebuggerService(); + } + return DebuggerService.instance; + } + + public get activeSession(): vscode.DebugSession | undefined { + return vscode.debug.activeDebugSession; + } + + public async getThreads(): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + try { + const response = await this.activeSession.customRequest("threads"); + return response.threads; + } catch (error: any) { + this.logger.error("Failed to get threads", error); + throw error; + } + } + + public async getStackTrace( + threadId: number, + startFrame: number = 0, + levels: number = 20, + ): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + try { + const response = await this.activeSession.customRequest("stackTrace", { + threadId, + startFrame, + levels, + }); + return response.stackFrames; + } catch (error: any) { + this.logger.error("Failed to get stack trace", error); + throw error; + } + } + + public async getScopes(frameId: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + try { + const response = await this.activeSession.customRequest("scopes", { + frameId, + }); + return response.scopes; + } catch (error: any) { + this.logger.error("Failed to get scopes", error); + throw error; + } + } + + public async getVariables(variablesReference: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + try { + const response = await this.activeSession.customRequest("variables", { + variablesReference, + }); + return response.variables; + } catch (error: any) { + this.logger.error("Failed to get variables", error); + throw error; + } + } + + public async evaluate(expression: string, frameId?: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + try { + const response = await this.activeSession.customRequest("evaluate", { + expression, + frameId, + context: "repl", + }); + return response; + } catch (error: any) { + this.logger.error("Failed to evaluate expression", error); + throw error; + } + } + + public async stepOver(threadId: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + await this.activeSession.customRequest("next", { threadId }); + } + + public async stepInto(threadId: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + await this.activeSession.customRequest("stepIn", { threadId }); + } + + public async stepOut(threadId: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + await this.activeSession.customRequest("stepOut", { threadId }); + } + + public async continue(threadId: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + await this.activeSession.customRequest("continue", { threadId }); + } + + public async pause(threadId: number): Promise { + if (!this.activeSession) throw new Error("No active debug session"); + await this.activeSession.customRequest("pause", { threadId }); + } +} diff --git a/webviewUi/src/components/settings/SettingsContext.tsx b/webviewUi/src/components/settings/SettingsContext.tsx index 124af474..bfdfb109 100644 --- a/webviewUi/src/components/settings/SettingsContext.tsx +++ b/webviewUi/src/components/settings/SettingsContext.tsx @@ -193,7 +193,7 @@ const defaultContextValue: SettingsContextType = { language: 'en', keymap: 'default', nickname: '', - codeBuddyMode: 'Agent', + codeBuddyMode: 'Ask', enableStreaming: true, fontFamily: 'JetBrains Mono', fontSize: 16, @@ -206,7 +206,7 @@ const defaultContextValue: SettingsContextType = { includeHidden: false, maxFileSize: '1', compactMode: false, - selectedModel: 'Gemini', + selectedModel: 'Groq', username: '', accountType: 'Free', customRules: [], diff --git a/webviewUi/src/components/settings/SettingsPanel.tsx b/webviewUi/src/components/settings/SettingsPanel.tsx index e2371874..af5aa811 100644 --- a/webviewUi/src/components/settings/SettingsPanel.tsx +++ b/webviewUi/src/components/settings/SettingsPanel.tsx @@ -110,7 +110,7 @@ export const SettingsPanel: React.FC = ({ language: 'en', keymap: 'default', nickname: username, - codeBuddyMode: 'Agent', + codeBuddyMode: 'Ask', enableStreaming: true, fontFamily: 'JetBrains Mono', fontSize: 16, @@ -123,7 +123,7 @@ export const SettingsPanel: React.FC = ({ includeHidden: false, maxFileSize: '1', compactMode: false, - selectedModel: 'Gemini', + selectedModel: 'Groq', username: username, accountType: accountType, customRules: [], diff --git a/webviewUi/src/components/webview.tsx b/webviewUi/src/components/webview.tsx index 662f8541..81237d22 100644 --- a/webviewUi/src/components/webview.tsx +++ b/webviewUi/src/components/webview.tsx @@ -85,9 +85,9 @@ interface ConfigData { export const WebviewUI = () => { // State variables const [selectedTheme, setSelectedTheme] = useState("tokyo night"); - const [selectedModel, setSelectedModel] = useState("Gemini"); - // Default to Agent so the streaming pipeline is used during testing - const [selectedCodeBuddyMode, setSelectedCodeBuddyMode] = useState("Agent"); + const [selectedModel, setSelectedModel] = useState("Groq"); + // Default to Ask mode for conversational interactions + const [selectedCodeBuddyMode, setSelectedCodeBuddyMode] = useState("Ask"); const [commandAction, setCommandAction] = useState(""); const [commandDescription, setCommandDescription] = useState(""); const [isCommandExecuting, setIsCommandExecuting] = useState(false);