Tuesday, July 21, 2026

Building an LLM-Powered Development Assistant with Tool Calling and Multi-Agent Architecture




Introduction and Conceptual Overview


The modern software development workflow involves numerous repetitive tasks that consume valuable developer time. Managing Git repositories, configuring development environments, installing dependencies, and navigating complex IDEs are all necessary but often tedious activities. An LLM-powered development assistant that runs in the background and responds to natural language commands can dramatically improve developer productivity by automating these tasks while maintaining full control and transparency.


This article presents a comprehensive architecture for building such a development assistant. The system uses tool calling to translate natural language requests into concrete actions, supports both local and remote LLM backends across multiple GPU architectures, and implements an agentic or multi-agent design to handle complex workflows that require planning, execution, and verification.


The core concept revolves around a background service that monitors for a hotkey activation. When triggered, the assistant captures the current context including the active window, the focused application, and any relevant workspace information. The developer then issues a natural language command such as "create a new feature branch called user-authentication" or "install the Python extension for VS Code and configure it for my virtual environment." The LLM processes this request, determines which tools to invoke, generates the appropriate commands, and executes them while providing feedback to the user.


System Architecture and Components


The development assistant consists of several interconnected components that work together to provide seamless functionality. Understanding each component and how they interact is essential for building a robust system.


Background Service and Hotkey Listener


The background service runs continuously with minimal resource consumption, waiting for the developer to activate it via a configurable hotkey. This service must be cross-platform, supporting Windows, macOS, and Linux. On activation, it captures the current system state including the active window title, the process name of the focused application, and the current working directory if applicable.


The hotkey listener uses platform-specific APIs to register global keyboard shortcuts without interfering with normal application behavior. On Windows, this involves the RegisterHotKey API. On macOS, the Carbon framework or newer accessibility APIs handle global shortcuts. Linux systems typically use X11 or Wayland protocols depending on the display server.


Context Awareness System


Context awareness is critical for providing relevant assistance. The system must determine what the developer is currently working on by examining multiple signals. The active window title often contains file paths or project names. The focused application determines which tools are available. For example, if Visual Studio Code is active, the assistant can interact with its command palette and extension system. If a terminal window is focused, the assistant can execute shell commands directly.


The context system also maintains a workspace model that tracks open projects, their Git repository status, installed dependencies, and recent activities. This allows the assistant to provide intelligent suggestions and avoid redundant operations. For instance, if the developer asks to "initialize a Git repository," the system first checks whether the current directory 

already contains a Git repository before executing the initialization command.


LLM Integration Layer with Multi-Backend Support


Supporting both local and remote LLMs with various GPU architectures requires a flexible abstraction layer. The system must handle different inference engines including llama.cpp for CPU and various GPU backends, vLLM for high-performance serving, Ollama for easy local deployment, and remote API providers like OpenAI or Anthropic.


For GPU support, the system detects the available hardware at startup and configures the appropriate backend. NVIDIA GPUs use CUDA through libraries like cuBLAS and cuDNN. AMD GPUs leverage ROCm, which provides HIP as a CUDA-compatible interface. Intel GPUs use oneAPI and the Intel Extension for PyTorch. Apple Silicon uses Metal Performance Shaders through the MPS backend in PyTorch.


The LLM integration layer provides a unified interface regardless of the backend. Each implementation handles model loading, tokenization, inference, and response parsing according to the specific backend's requirements. This abstraction allows the rest of the system to remain backend-agnostic.


Tool Calling Framework


Tool calling is the mechanism by which the LLM translates natural language into executable actions. The framework defines a set of tools, each with a clear specification including its name, description, parameters, and return type. When the LLM receives a user request, it analyzes the intent and generates one or more tool calls with appropriate arguments.


The tool specification uses a structured format that both the LLM and the execution engine can understand. Each tool has a JSON schema describing its parameters, validation rules, and expected behavior. The LLM generates tool calls in a standardized format, which the execution engine parses and validates before invocation.


Tools are organized into categories based on their functionality. Git tools handle repository operations like cloning, branching, committing, and pushing. IDE tools interact with Visual Studio Code or IntelliJ IDEA through their respective APIs or command-line interfaces. System tools manage file operations, process execution, and environment configuration. Package manager tools handle dependency installation using npm, pip, Maven, or other ecosystem-specific tools.


Agent Architecture for Complex Workflows


Some developer tasks require multiple steps with dependencies between them. For example, setting up a new Python project might involve creating a directory, initializing a Git repository, creating a virtual environment, installing dependencies from a requirements file, configuring VS Code with the Python extension, and opening the project. A simple tool calling approach would require the developer to issue multiple commands sequentially.


An agentic architecture allows the system to plan and execute multi-step workflows autonomously. The agent receives a high-level goal, breaks it down into subtasks, executes each subtask using the available tools, and adapts the plan based on intermediate results. This requires the agent to maintain state across multiple LLM invocations, track progress, and handle errors gracefully.


For even more complex scenarios, a multi-agent architecture distributes responsibilities among specialized agents. A planning agent analyzes the user request and creates an execution plan. A Git agent handles all repository operations. An IDE agent manages editor configuration and extensions. A validation agent checks that each step completed successfully before proceeding. These agents communicate through a message-passing system, coordinating their actions to achieve the overall goal.


Implementation Details and Code Examples


Now we examine the concrete implementation of each component with detailed code examples. The examples use Python for the core system due to its excellent library support for LLM integration, system automation, and cross-platform compatibility.


Setting Up the Background Service


The background service must run with minimal overhead and respond quickly when activated. We use a combination of threading for the hotkey listener and asyncio for the main event loop to handle concurrent operations efficiently.


import asyncio

import threading

from typing import Optional, Callable

import logging


class BackgroundService:

    """

    Core background service that runs continuously and coordinates

    all development assistant functionality.

    """

    

    def __init__(self, hotkey_combination: str = "ctrl+shift+a"):

        """

        Initialize the background service with a configurable hotkey.

        

        Args:

            hotkey_combination: String representation of the hotkey

                               (e.g., "ctrl+shift+a")

        """

        self.hotkey = hotkey_combination

        self.running = False

        self.event_loop: Optional[asyncio.AbstractEventLoop] = None

        self.activation_callback: Optional[Callable] = None

        self.logger = logging.getLogger(__name__)

        

    def set_activation_callback(self, callback: Callable):

        """

        Register a callback function to be invoked when the hotkey

        is pressed. The callback receives the current context.

        

        Args:

            callback: Async function that handles the activation event

        """

        self.activation_callback = callback

        

    def start(self):

        """

        Start the background service in a separate thread to avoid

        blocking the main application.

        """

        if self.running:

            self.logger.warning("Service already running")

            return

            

        self.running = True

        service_thread = threading.Thread(target=self._run_service, daemon=True)

        service_thread.start()

        self.logger.info(f"Background service started with hotkey: {self.hotkey}")

        

    def _run_service(self):

        """

        Internal method that sets up the event loop and starts

        the hotkey listener. Runs in a separate thread.

        """

        self.event_loop = asyncio.new_event_loop()

        asyncio.set_event_loop(self.event_loop)

        

        try:

            self.event_loop.run_until_complete(self._async_main())

        except Exception as e:

            self.logger.error(f"Service error: {e}", exc_info=True)

        finally:

            self.event_loop.close()

            

    async def _async_main(self):

        """

        Main async loop that keeps the service running and handles

        activation events.

        """

        from .hotkey_listener import HotkeyListener

        

        listener = HotkeyListener(self.hotkey)

        listener.set_callback(self._handle_activation)

        listener.start()

        

        # Keep the service running

        while self.running:

            await asyncio.sleep(0.1)

            

    def _handle_activation(self):

        """

        Called when the hotkey is pressed. Captures context and

        invokes the registered callback in the event loop.

        """

        if self.activation_callback and self.event_loop:

            asyncio.run_coroutine_threadsafe(

                self._async_activation_handler(),

                self.event_loop

            )

            

    async def _async_activation_handler(self):

        """

        Async handler that captures context and invokes the callback.

        """

        from .context_capture import ContextCapture

        

        context = ContextCapture.get_current_context()

        self.logger.info(f"Assistant activated in context: {context.app_name}")

        

        if self.activation_callback:

            await self.activation_callback(context)

            

    def stop(self):

        """

        Gracefully stop the background service.

        """

        self.running = False

        self.logger.info("Background service stopped")


The BackgroundService class provides the foundation for the entire system. It manages the lifecycle of the service, handles hotkey activation, and coordinates between the synchronous hotkey listener and the asynchronous main logic. The use of threading ensures that the hotkey listener remains responsive even when the LLM is processing a request.


Platform-Specific Hotkey Listening


Each operating system requires different approaches for global hotkey registration. We create a unified interface with platform-specific implementations.


import platform

from abc import ABC, abstractmethod

from typing import Callable, Optional


class HotkeyListenerBase(ABC):

    """

    Abstract base class for platform-specific hotkey listeners.

    """

    

    def __init__(self, hotkey: str):

        """

        Initialize with a hotkey combination string.

        

        Args:

            hotkey: String like "ctrl+shift+a"

        """

        self.hotkey = hotkey

        self.callback: Optional[Callable] = None

        self.modifiers, self.key = self._parse_hotkey(hotkey)

        

    def _parse_hotkey(self, hotkey: str) -> tuple:

        """

        Parse hotkey string into modifiers and key.

        

        Returns:

            Tuple of (modifiers_list, key_string)

        """

        parts = hotkey.lower().split('+')

        key = parts[-1]

        modifiers = parts[:-1]

        return modifiers, key

        

    def set_callback(self, callback: Callable):

        """

        Set the function to call when hotkey is pressed.

        """

        self.callback = callback

        

    @abstractmethod

    def start(self):

        """

        Start listening for the hotkey. Platform-specific implementation.

        """

        pass

        

    @abstractmethod

    def stop(self):

        """

        Stop listening for the hotkey.

        """

        pass



class WindowsHotkeyListener(HotkeyListenerBase):

    """

    Windows implementation using win32 API.

    """

    

    def start(self):

        """

        Register the hotkey using Windows RegisterHotKey API.

        """

        import win32con

        import win32api

        import win32gui

        

        # Map modifier strings to Windows constants

        mod_map = {

            'ctrl': win32con.MOD_CONTROL,

            'shift': win32con.MOD_SHIFT,

            'alt': win32con.MOD_ALT,

            'win': win32con.MOD_WIN

        }

        

        # Combine modifiers

        mod_flags = 0

        for mod in self.modifiers:

            if mod in mod_map:

                mod_flags |= mod_map[mod]

                

        # Get virtual key code

        vk_code = ord(self.key.upper())

        

        # Create a message-only window for receiving hotkey messages

        wc = win32gui.WNDCLASS()

        wc.lpfnWndProc = self._window_proc

        wc.lpszClassName = "DevAssistantHotkey"

        class_atom = win32gui.RegisterClass(wc)

        

        self.hwnd = win32gui.CreateWindow(

            class_atom, "DevAssistant", 0, 0, 0, 0, 0, 0, 0, 0, None

        )

        

        # Register the hotkey

        hotkey_id = 1

        if not win32gui.RegisterHotKey(self.hwnd, hotkey_id, mod_flags, vk_code):

            raise RuntimeError("Failed to register hotkey")

            

        # Start message loop in separate thread

        import threading

        self.msg_thread = threading.Thread(target=self._message_loop, daemon=True)

        self.msg_thread.start()

        

    def _window_proc(self, hwnd, msg, wparam, lparam):

        """

        Window procedure that handles hotkey messages.

        """

        import win32con

        

        if msg == win32con.WM_HOTKEY:

            if self.callback:

                self.callback()

        return 0

        

    def _message_loop(self):

        """

        Windows message loop that processes hotkey events.

        """

        import win32gui

        

        while True:

            win32gui.PumpWaitingMessages()

            

    def stop(self):

        """

        Unregister the hotkey and destroy the window.

        """

        import win32gui

        

        win32gui.UnregisterHotKey(self.hwnd, 1)

        win32gui.DestroyWindow(self.hwnd)



class MacOSHotkeyListener(HotkeyListenerBase):

    """

    macOS implementation using Quartz and Cocoa frameworks.

    """

    

    def start(self):

        """

        Register global hotkey using macOS Carbon or Cocoa APIs.

        """

        from Quartz import (

            CGEventMaskBit, CGEventTapCreate, kCGEventKeyDown,

            kCGHeadInsertEventTap, kCGSessionEventTap,

            CFMachPortCreateRunLoopSource, CFRunLoopAddSource,

            CFRunLoopGetCurrent, kCFRunLoopCommonModes, CGEventTapEnable

        )

        

        # Create event tap for keyboard events

        event_mask = CGEventMaskBit(kCGEventKeyDown)

        

        self.tap = CGEventTapCreate(

            kCGSessionEventTap,

            kCGHeadInsertEventTap,

            0,

            event_mask,

            self._event_callback,

            None

        )

        

        if not self.tap:

            raise RuntimeError("Failed to create event tap")

            

        # Add to run loop

        run_loop_source = CFMachPortCreateRunLoopSource(None, self.tap, 0)

        CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, kCFRunLoopCommonModes)

        CGEventTapEnable(self.tap, True)

        

    def _event_callback(self, proxy, event_type, event, refcon):

        """

        Callback for keyboard events. Checks if it matches our hotkey.

        """

        from Quartz import CGEventGetIntegerValueField, kCGKeyboardEventKeycode

        from Cocoa import NSEvent

        

        # Get key code and modifiers

        keycode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)

        ns_event = NSEvent.eventWithCGEvent_(event)

        modifiers = ns_event.modifierFlags()

        

        # Check if this matches our hotkey

        if self._matches_hotkey(keycode, modifiers):

            if self.callback:

                self.callback()

                

        return event

        

    def _matches_hotkey(self, keycode, modifiers):

        """

        Check if the keycode and modifiers match our registered hotkey.

        """

        from Cocoa import (

            NSCommandKeyMask, NSShiftKeyMask, NSAlternateKeyMask, NSControlKeyMask

        )

        

        # Map our modifier strings to Cocoa constants

        required_mods = 0

        if 'cmd' in self.modifiers or 'command' in self.modifiers:

            required_mods |= NSCommandKeyMask

        if 'shift' in self.modifiers:

            required_mods |= NSShiftKeyMask

        if 'alt' in self.modifiers or 'option' in self.modifiers:

            required_mods |= NSAlternateKeyMask

        if 'ctrl' in self.modifiers or 'control' in self.modifiers:

            required_mods |= NSControlKeyMask

            

        # Check modifiers match

        if (modifiers & required_mods) != required_mods:

            return False

            

        # Map key string to keycode

        key_map = {

            'a': 0, 'b': 11, 'c': 8, 'd': 2, 'e': 14, 'f': 3,

            'g': 5, 'h': 4, 'i': 34, 'j': 38, 'k': 40, 'l': 37,

            'm': 46, 'n': 45, 'o': 31, 'p': 35, 'q': 12, 'r': 15,

            's': 1, 't': 17, 'u': 32, 'v': 9, 'w': 13, 'x': 7,

            'y': 16, 'z': 6

        }

        

        expected_keycode = key_map.get(self.key.lower())

        return keycode == expected_keycode

        

    def stop(self):

        """

        Disable and release the event tap.

        """

        from Quartz import CGEventTapEnable

        

        if self.tap:

            CGEventTapEnable(self.tap, False)



class LinuxHotkeyListener(HotkeyListenerBase):

    """

    Linux implementation using X11 or python-xlib.

    """

    

    def start(self):

        """

        Register global hotkey using X11.

        """

        from Xlib import X, XK, display

        from Xlib.ext import record

        from Xlib.protocol import rq

        

        self.local_display = display.Display()

        self.record_display = display.Display()

        

        # Map modifier strings to X11 masks

        mod_map = {

            'ctrl': X.ControlMask,

            'shift': X.ShiftMask,

            'alt': X.Mod1Mask,

            'super': X.Mod4Mask

        }

        

        # Calculate modifier mask

        self.mod_mask = 0

        for mod in self.modifiers:

            if mod in mod_map:

                self.mod_mask |= mod_map[mod]

                

        # Get keysym for our key

        keysym_str = 'XK_' + self.key.lower()

        self.target_keysym = getattr(XK, keysym_str, None)

        

        if not self.target_keysym:

            raise ValueError(f"Unknown key: {self.key}")

            

        # Set up recording context

        ctx = self.record_display.record_create_context(

            0,

            [record.AllClients],

            [{

                'core_requests': (0, 0),

                'core_replies': (0, 0),

                'ext_requests': (0, 0, 0, 0),

                'ext_replies': (0, 0, 0, 0),

                'delivered_events': (0, 0),

                'device_events': (X.KeyPress, X.KeyPress),

                'errors': (0, 0),

                'client_started': False,

                'client_died': False,

            }]

        )

        

        # Start recording in separate thread

        import threading

        self.record_thread = threading.Thread(

            target=self._record_loop,

            args=(ctx,),

            daemon=True

        )

        self.record_thread.start()

        

    def _record_loop(self, ctx):

        """

        Main loop that processes X11 events.

        """

        self.record_display.record_enable_context(ctx, self._event_handler)

        self.record_display.record_free_context(ctx)

        

    def _event_handler(self, reply):

        """

        Handle X11 keyboard events.

        """

        from Xlib import X

        

        if reply.category != record.FromServer:

            return

        if reply.client_swapped:

            return

            

        data = reply.data

        while len(data):

            event, data = rq.EventField(None).parse_binary_value(

                data, self.record_display.display, None, None

            )

            

            if event.type == X.KeyPress:

                # Get keysym from keycode

                keysym = self.local_display.keycode_to_keysym(event.detail, 0)

                

                # Check if modifiers and key match

                if (event.state & self.mod_mask) == self.mod_mask:

                    if keysym == self.target_keysym:

                        if self.callback:

                            self.callback()

                            

    def stop(self):

        """

        Stop the X11 event recording.

        """

        if hasattr(self, 'record_display'):

            self.record_display.close()

        if hasattr(self, 'local_display'):

            self.local_display.close()



class HotkeyListener:

    """

    Factory class that creates the appropriate platform-specific listener.

    """

    

    def __new__(cls, hotkey: str):

        """

        Create and return the appropriate listener for the current platform.

        """

        system = platform.system()

        

        if system == 'Windows':

            return WindowsHotkeyListener(hotkey)

        elif system == 'Darwin':

            return MacOSHotkeyListener(hotkey)

        elif system == 'Linux':

            return LinuxHotkeyListener(hotkey)

        else:

            raise NotImplementedError(f"Platform {system} not supported")


The hotkey listener implementation demonstrates the complexity of cross-platform development. Each operating system provides different APIs and requires different approaches. Windows uses a message-based system with RegisterHotKey. macOS requires event taps that intercept keyboard events at a low level. Linux uses the X11 protocol to monitor keyboard activity. The factory pattern ensures that the rest of the system can use a unified interface regardless of the underlying platform.


Context Capture and Awareness


Understanding what the developer is currently doing is essential for providing relevant assistance. The context capture system gathers information about the active window, running processes, and workspace state.


import os

import platform

from dataclasses import dataclass

from typing import Optional, List, Dict

from pathlib import Path


@dataclass

class DevelopmentContext:

    """

    Represents the current development context including active

    application, workspace, and environment information.

    """

    

    # Active window information

    window_title: str

    app_name: str

    app_path: Optional[str]

    process_id: int

    

    # Workspace information

    current_directory: Optional[Path]

    workspace_root: Optional[Path]

    open_files: List[Path]

    

    # Git repository information

    git_repository: Optional[Path]

    git_branch: Optional[str]

    git_status: Optional[Dict[str, any]]

    

    # IDE-specific information

    ide_type: Optional[str]  # 'vscode', 'intellij', 'pycharm', etc.

    ide_version: Optional[str]

    active_file: Optional[Path]

    cursor_position: Optional[tuple]  # (line, column)

    

    # Environment information

    python_version: Optional[str]

    node_version: Optional[str]

    java_version: Optional[str]

    virtual_env: Optional[Path]

    

    def __str__(self):

        """

        Human-readable representation of the context.

        """

        parts = [f"Application: {self.app_name}"]

        

        if self.current_directory:

            parts.append(f"Directory: {self.current_directory}")

            

        if self.git_repository:

            parts.append(f"Git: {self.git_branch} in {self.git_repository}")

            

        if self.ide_type:

            parts.append(f"IDE: {self.ide_type}")

            if self.active_file:

                parts.append(f"File: {self.active_file}")

                

        return " | ".join(parts)



class ContextCapture:

    """

    Captures the current development context by examining active

    windows, processes, and workspace state.

    """

    

    @staticmethod

    def get_current_context() -> DevelopmentContext:

        """

        Capture and return the current development context.

        

        Returns:

            DevelopmentContext object with all available information

        """

        system = platform.system()

        

        if system == 'Windows':

            return ContextCapture._capture_windows_context()

        elif system == 'Darwin':

            return ContextCapture._capture_macos_context()

        elif system == 'Linux':

            return ContextCapture._capture_linux_context()

        else:

            raise NotImplementedError(f"Platform {system} not supported")

            

    @staticmethod

    def _capture_windows_context() -> DevelopmentContext:

        """

        Capture context on Windows using win32 APIs.

        """

        import win32gui

        import win32process

        import psutil

        

        # Get active window

        hwnd = win32gui.GetForegroundWindow()

        window_title = win32gui.GetWindowText(hwnd)

        

        # Get process information

        _, process_id = win32process.GetWindowThreadProcessId(hwnd)

        process = psutil.Process(process_id)

        app_name = process.name()

        app_path = process.exe()

        

        # Get current directory from process

        try:

            current_directory = Path(process.cwd())

        except (psutil.AccessDenied, psutil.NoSuchProcess):

            current_directory = None

            

        # Detect IDE type

        ide_type = ContextCapture._detect_ide_type(app_name, app_path)

        

        # Build context object

        context = DevelopmentContext(

            window_title=window_title,

            app_name=app_name,

            app_path=app_path,

            process_id=process_id,

            current_directory=current_directory,

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=ide_type,

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

        

        # Enhance context with additional information

        ContextCapture._enhance_context(context)

        

        return context

        

    @staticmethod

    def _capture_macos_context() -> DevelopmentContext:

        """

        Capture context on macOS using Cocoa and Quartz APIs.

        """

        from Cocoa import NSWorkspace

        from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionOnScreenOnly, kCGNullWindowID

        import psutil

        

        # Get active application

        workspace = NSWorkspace.sharedWorkspace()

        active_app = workspace.activeApplication()

        

        app_name = active_app['NSApplicationName']

        process_id = active_app['NSApplicationProcessIdentifier']

        app_path = active_app.get('NSApplicationPath')

        

        # Get active window title

        window_list = CGWindowListCopyWindowInfo(

            kCGWindowListOptionOnScreenOnly,

            kCGNullWindowID

        )

        

        window_title = ""

        for window in window_list:

            if window.get('kCGWindowOwnerPID') == process_id:

                if window.get('kCGWindowLayer') == 0:

                    window_title = window.get('kCGWindowName', '')

                    break

                    

        # Get process information

        try:

            process = psutil.Process(process_id)

            current_directory = Path(process.cwd())

        except (psutil.AccessDenied, psutil.NoSuchProcess):

            current_directory = None

            

        # Detect IDE type

        ide_type = ContextCapture._detect_ide_type(app_name, app_path)

        

        # Build context object

        context = DevelopmentContext(

            window_title=window_title,

            app_name=app_name,

            app_path=app_path,

            process_id=process_id,

            current_directory=current_directory,

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=ide_type,

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

        

        # Enhance context with additional information

        ContextCapture._enhance_context(context)

        

        return context

        

    @staticmethod

    def _capture_linux_context() -> DevelopmentContext:

        """

        Capture context on Linux using X11 and proc filesystem.

        """

        from Xlib import X, display

        import psutil

        

        # Get active window using X11

        d = display.Display()

        root = d.screen().root

        

        # Get the active window

        active_window = root.get_full_property(

            d.intern_atom('_NET_ACTIVE_WINDOW'),

            X.AnyPropertyType

        ).value[0]

        

        window = d.create_resource_object('window', active_window)

        

        # Get window title

        window_title = window.get_full_property(

            d.intern_atom('_NET_WM_NAME'),

            0

        )

        if window_title:

            window_title = window_title.value.decode('utf-8')

        else:

            window_title = ""

            

        # Get process ID

        pid_property = window.get_full_property(

            d.intern_atom('_NET_WM_PID'),

            X.AnyPropertyType

        )

        

        if pid_property:

            process_id = pid_property.value[0]

        else:

            process_id = 0

            

        # Get process information

        try:

            process = psutil.Process(process_id)

            app_name = process.name()

            app_path = process.exe()

            current_directory = Path(process.cwd())

        except (psutil.AccessDenied, psutil.NoSuchProcess):

            app_name = "Unknown"

            app_path = None

            current_directory = None

            

        # Detect IDE type

        ide_type = ContextCapture._detect_ide_type(app_name, app_path)

        

        # Build context object

        context = DevelopmentContext(

            window_title=window_title,

            app_name=app_name,

            app_path=app_path,

            process_id=process_id,

            current_directory=current_directory,

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=ide_type,

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

        

        # Enhance context with additional information

        ContextCapture._enhance_context(context)

        

        return context

        

    @staticmethod

    def _detect_ide_type(app_name: str, app_path: Optional[str]) -> Optional[str]:

        """

        Detect the type of IDE based on application name and path.

        

        Returns:

            String identifier for the IDE type or None if not an IDE

        """

        app_name_lower = app_name.lower()

        

        if 'code' in app_name_lower or (app_path and 'vscode' in app_path.lower()):

            return 'vscode'

        elif 'idea' in app_name_lower:

            if 'pycharm' in app_name_lower:

                return 'pycharm'

            elif 'webstorm' in app_name_lower:

                return 'webstorm'

            else:

                return 'intellij'

        elif 'eclipse' in app_name_lower:

            return 'eclipse'

        elif 'sublime' in app_name_lower:

            return 'sublime'

        elif 'atom' in app_name_lower:

            return 'atom'

        elif 'vim' in app_name_lower or 'nvim' in app_name_lower:

            return 'vim'

        elif 'emacs' in app_name_lower:

            return 'emacs'

        else:

            return None

            

    @staticmethod

    def _enhance_context(context: DevelopmentContext):

        """

        Enhance the context with additional information like Git status,

        workspace root, environment versions, etc.

        

        Args:

            context: DevelopmentContext object to enhance (modified in place)

        """

        # Find workspace root

        if context.current_directory:

            context.workspace_root = ContextCapture._find_workspace_root(

                context.current_directory

            )

            

        # Find Git repository

        if context.current_directory:

            git_repo = ContextCapture._find_git_repository(

                context.current_directory

            )

            if git_repo:

                context.git_repository = git_repo

                context.git_branch = ContextCapture._get_git_branch(git_repo)

                context.git_status = ContextCapture._get_git_status(git_repo)

                

        # Detect active file from window title or IDE-specific methods

        if context.ide_type:

            context.active_file = ContextCapture._detect_active_file(context)

            

        # Detect environment versions

        context.python_version = ContextCapture._get_python_version()

        context.node_version = ContextCapture._get_node_version()

        context.java_version = ContextCapture._get_java_version()

        

        # Detect virtual environment

        if context.current_directory:

            context.virtual_env = ContextCapture._detect_virtual_env(

                context.current_directory

            )

            

    @staticmethod

    def _find_workspace_root(start_path: Path) -> Optional[Path]:

        """

        Find the workspace root by looking for common markers like

        .git, package.json, pom.xml, etc.

        

        Args:

            start_path: Directory to start searching from

            

        Returns:

            Path to workspace root or None if not found

        """

        markers = [

            '.git',

            'package.json',

            'pom.xml',

            'build.gradle',

            'Cargo.toml',

            'go.mod',

            'setup.py',

            'pyproject.toml',

            'Makefile',

            '.project'

        ]

        

        current = start_path

        while current != current.parent:

            for marker in markers:

                if (current / marker).exists():

                    return current

            current = current.parent

            

        return None

        

    @staticmethod

    def _find_git_repository(start_path: Path) -> Optional[Path]:

        """

        Find the Git repository root by looking for .git directory.

        

        Args:

            start_path: Directory to start searching from

            

        Returns:

            Path to Git repository root or None if not found

        """

        current = start_path

        while current != current.parent:

            git_dir = current / '.git'

            if git_dir.exists():

                return current

            current = current.parent

            

        return None

        

    @staticmethod

    def _get_git_branch(repo_path: Path) -> Optional[str]:

        """

        Get the current Git branch name.

        

        Args:

            repo_path: Path to Git repository root

            

        Returns:

            Branch name or None if cannot be determined

        """

        head_file = repo_path / '.git' / 'HEAD'

        

        if not head_file.exists():

            return None

            

        try:

            with open(head_file, 'r') as f:

                content = f.read().strip()

                

            if content.startswith('ref: refs/heads/'):

                return content[16:]

            else:

                # Detached HEAD state

                return content[:7]

        except Exception:

            return None

            

    @staticmethod

    def _get_git_status(repo_path: Path) -> Optional[Dict[str, any]]:

        """

        Get Git repository status including modified files, staged files, etc.

        

        Args:

            repo_path: Path to Git repository root

            

        Returns:

            Dictionary with status information or None if error

        """

        import subprocess

        

        try:

            # Get status in porcelain format

            result = subprocess.run(

                ['git', 'status', '--porcelain'],

                cwd=repo_path,

                capture_output=True,

                text=True,

                timeout=5

            )

            

            if result.returncode != 0:

                return None

                

            # Parse status output

            modified = []

            staged = []

            untracked = []

            

            for line in result.stdout.splitlines():

                if len(line) < 3:

                    continue

                    

                status = line[:2]

                filename = line[3:]

                

                if status[0] in ['M', 'A', 'D', 'R', 'C']:

                    staged.append(filename)

                if status[1] == 'M':

                    modified.append(filename)

                if status == '??':

                    untracked.append(filename)

                    

            return {

                'modified': modified,

                'staged': staged,

                'untracked': untracked,

                'clean': len(modified) == 0 and len(staged) == 0 and len(untracked) == 0

            }

            

        except Exception:

            return None

            

    @staticmethod

    def _detect_active_file(context: DevelopmentContext) -> Optional[Path]:

        """

        Detect the currently active file in the IDE.

        

        Args:

            context: Development context with IDE information

            

        Returns:

            Path to active file or None if cannot be determined

        """

        # Try to extract from window title first

        if context.window_title and context.current_directory:

            # Many IDEs show the filename in the window title

            title_parts = context.window_title.split(' - ')

            for part in title_parts:

                # Check if it looks like a file path

                if '.' in part and not part.startswith('http'):

                    potential_file = context.current_directory / part.strip()

                    if potential_file.exists():

                        return potential_file

                        

        # IDE-specific detection methods

        if context.ide_type == 'vscode':

            return ContextCapture._get_vscode_active_file(context)

        elif context.ide_type in ['intellij', 'pycharm', 'webstorm']:

            return ContextCapture._get_jetbrains_active_file(context)

            

        return None

        

    @staticmethod

    def _get_vscode_active_file(context: DevelopmentContext) -> Optional[Path]:

        """

        Get the active file in VS Code by querying its state.

        

        Args:

            context: Development context

            

        Returns:

            Path to active file or None

        """

        # VS Code stores workspace state in various locations

        # This is a simplified approach - full implementation would

        # need to parse VS Code's workspace storage

        

        if not context.current_directory:

            return None

            

        # Check for .vscode directory

        vscode_dir = context.current_directory / '.vscode'

        if not vscode_dir.exists():

            return None

            

        # In practice, we would need to use VS Code's extension API

        # or command line interface to get this information reliably

        return None

        

    @staticmethod

    def _get_jetbrains_active_file(context: DevelopmentContext) -> Optional[Path]:

        """

        Get the active file in JetBrains IDEs.

        

        Args:

            context: Development context

            

        Returns:

            Path to active file or None

        """

        # JetBrains IDEs store project information in .idea directory

        # Similar to VS Code, full implementation would require

        # parsing IDE-specific files or using their APIs

        return None

        

    @staticmethod

    def _get_python_version() -> Optional[str]:

        """

        Get the Python version available in the environment.

        

        Returns:

            Version string or None if Python not found

        """

        import subprocess

        

        try:

            result = subprocess.run(

                ['python', '--version'],

                capture_output=True,

                text=True,

                timeout=5

            )

            

            if result.returncode == 0:

                # Output format: "Python 3.9.7"

                return result.stdout.strip().split()[1]

        except Exception:

            pass

            

        return None

        

    @staticmethod

    def _get_node_version() -> Optional[str]:

        """

        Get the Node.js version available in the environment.

        

        Returns:

            Version string or None if Node not found

        """

        import subprocess

        

        try:

            result = subprocess.run(

                ['node', '--version'],

                capture_output=True,

                text=True,

                timeout=5

            )

            

            if result.returncode == 0:

                # Output format: "v16.13.0"

                return result.stdout.strip()[1:]  # Remove 'v' prefix

        except Exception:

            pass

            

        return None

        

    @staticmethod

    def _get_java_version() -> Optional[str]:

        """

        Get the Java version available in the environment.

        

        Returns:

            Version string or None if Java not found

        """

        import subprocess

        

        try:

            result = subprocess.run(

                ['java', '-version'],

                capture_output=True,

                text=True,

                timeout=5

            )

            

            # Java outputs version to stderr

            output = result.stderr

            

            if result.returncode == 0 and output:

                # Parse version from output

                for line in output.splitlines():

                    if 'version' in line.lower():

                        # Extract version number

                        parts = line.split('"')

                        if len(parts) >= 2:

                            return parts[1]

        except Exception:

            pass

            

        return None

        

    @staticmethod

    def _detect_virtual_env(start_path: Path) -> Optional[Path]:

        """

        Detect if we are in a Python virtual environment.

        

        Args:

            start_path: Directory to start searching from

            

        Returns:

            Path to virtual environment or None if not found

        """

        # Check for common virtual environment directories

        venv_names = ['venv', '.venv', 'env', '.env', 'virtualenv']

        

        current = start_path

        while current != current.parent:

            for venv_name in venv_names:

                venv_path = current / venv_name

                # Check if it looks like a virtual environment

                if venv_path.exists() and (venv_path / 'bin' / 'python').exists():

                    return venv_path

                if venv_path.exists() and (venv_path / 'Scripts' / 'python.exe').exists():

                    return venv_path

                    

            current = current.parent

            

        # Check environment variable

        virtual_env = os.environ.get('VIRTUAL_ENV')

        if virtual_env:

            return Path(virtual_env)

            

        return None


The context capture system provides comprehensive information about the developer's current environment. It examines the active window to determine which application is in focus, extracts the process information to understand what the developer is working on, and scans the filesystem to gather workspace details. This context is crucial for the LLM to provide relevant assistance. For example, if the developer is working in a Git 

repository on a feature branch with uncommitted changes, the assistant can take this into account when suggesting actions.


LLM Integration with Multi-Backend Support


Supporting multiple LLM backends requires a flexible architecture that abstracts the differences between local and remote models, different inference engines, and various GPU architectures. We create a unified interface that the rest of the system can use regardless of the backend.


from abc import ABC, abstractmethod

from typing import List, Dict, Any, Optional, AsyncIterator

from dataclasses import dataclass

import asyncio

import json


@dataclass

class Message:

    """

    Represents a single message in the conversation.

    """

    role: str  # 'system', 'user', 'assistant', 'tool'

    content: str

    tool_calls: Optional[List[Dict[str, Any]]] = None

    tool_call_id: Optional[str] = None

    name: Optional[str] = None



@dataclass

class LLMConfig:

    """

    Configuration for LLM backend.

    """

    backend_type: str  # 'openai', 'anthropic', 'local_llama', 'local_vllm', 'ollama'

    model_name: str

    api_key: Optional[str] = None

    api_base: Optional[str] = None

    temperature: float = 0.7

    max_tokens: int = 2048

    

    # Local model configuration

    model_path: Optional[str] = None

    gpu_layers: int = -1  # -1 means use all available

    context_length: int = 4096

    

    # GPU configuration

    device: str = 'auto'  # 'cuda', 'rocm', 'mps', 'cpu', 'auto'

    gpu_memory_fraction: float = 0.9



class LLMBackend(ABC):

    """

    Abstract base class for LLM backends.

    """

    

    def __init__(self, config: LLMConfig):

        """

        Initialize the backend with configuration.

        

        Args:

            config: LLMConfig object with backend settings

        """

        self.config = config

        

    @abstractmethod

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """

        Generate a response from the LLM.

        

        Args:

            messages: List of conversation messages

            tools: Optional list of tool specifications

            stream: Whether to stream the response

            

        Yields:

            Response chunks if streaming, otherwise yields complete response

        """

        pass

        

    @abstractmethod

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """

        Generate a response that may include tool calls.

        

        Args:

            messages: List of conversation messages

            tools: List of tool specifications

            

        Returns:

            Message object with potential tool calls

        """

        pass

        

    @abstractmethod

    def supports_tool_calling(self) -> bool:

        """

        Check if this backend supports native tool calling.

        

        Returns:

            True if tool calling is supported

        """

        pass



class OpenAIBackend(LLMBackend):

    """

    Backend for OpenAI API (GPT-4, GPT-3.5, etc.)

    """

    

    def __init__(self, config: LLMConfig):

        """

        Initialize OpenAI backend.

        """

        super().__init__(config)

        

        try:

            import openai

            self.client = openai.AsyncOpenAI(

                api_key=config.api_key,

                base_url=config.api_base

            )

        except ImportError:

            raise ImportError("openai package not installed. Install with: pip install openai")

            

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """

        Generate response using OpenAI API.

        """

        # Convert messages to OpenAI format

        openai_messages = []

        for msg in messages:

            message_dict = {

                'role': msg.role,

                'content': msg.content

            }

            

            if msg.tool_calls:

                message_dict['tool_calls'] = msg.tool_calls

            if msg.tool_call_id:

                message_dict['tool_call_id'] = msg.tool_call_id

            if msg.name:

                message_dict['name'] = msg.name

                

            openai_messages.append(message_dict)

            

        # Prepare API call parameters

        params = {

            'model': self.config.model_name,

            'messages': openai_messages,

            'temperature': self.config.temperature,

            'max_tokens': self.config.max_tokens,

            'stream': stream

        }

        

        if tools:

            params['tools'] = tools

            params['tool_choice'] = 'auto'

            

        # Make API call

        if stream:

            response = await self.client.chat.completions.create(**params)

            async for chunk in response:

                if chunk.choices[0].delta.content:

                    yield chunk.choices[0].delta.content

        else:

            response = await self.client.chat.completions.create(**params)

            yield response.choices[0].message.content

            

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """

        Generate response with tool calling support.

        """

        # Convert messages to OpenAI format

        openai_messages = []

        for msg in messages:

            message_dict = {

                'role': msg.role,

                'content': msg.content

            }

            

            if msg.tool_calls:

                message_dict['tool_calls'] = msg.tool_calls

            if msg.tool_call_id:

                message_dict['tool_call_id'] = msg.tool_call_id

            if msg.name:

                message_dict['name'] = msg.name

                

            openai_messages.append(message_dict)

            

        # Make API call with tools

        response = await self.client.chat.completions.create(

            model=self.config.model_name,

            messages=openai_messages,

            tools=tools,

            tool_choice='auto',

            temperature=self.config.temperature,

            max_tokens=self.config.max_tokens

        )

        

        # Convert response to Message object

        choice = response.choices[0]

        message = choice.message

        

        result = Message(

            role='assistant',

            content=message.content or ''

        )

        

        if message.tool_calls:

            result.tool_calls = [

                {

                    'id': tc.id,

                    'type': tc.type,

                    'function': {

                        'name': tc.function.name,

                        'arguments': tc.function.arguments

                    }

                }

                for tc in message.tool_calls

            ]

            

        return result

        

    def supports_tool_calling(self) -> bool:

        """

        OpenAI supports native tool calling.

        """

        return True



class LocalLlamaBackend(LLMBackend):

    """

    Backend for local LLaMA models using llama-cpp-python.

    Supports CUDA, ROCm, Metal (MPS), and CPU.

    """

    

    def __init__(self, config: LLMConfig):

        """

        Initialize local LLaMA backend with appropriate GPU support.

        """

        super().__init__(config)

        

        try:

            from llama_cpp import Llama

        except ImportError:

            raise ImportError(

                "llama-cpp-python not installed. Install with appropriate backend:\n"

                "CUDA: pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu121\n"

                "ROCm: pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/rocm\n"

                "Metal: pip install llama-cpp-python (on macOS)\n"

                "CPU: pip install llama-cpp-python"

            )

            

        # Determine device and configure accordingly

        device = self._detect_device() if config.device == 'auto' else config.device

        

        # Configure llama.cpp parameters based on device

        llama_params = {

            'model_path': config.model_path,

            'n_ctx': config.context_length,

            'verbose': False

        }

        

        if device == 'cuda':

            llama_params['n_gpu_layers'] = config.gpu_layers

        elif device == 'rocm':

            # ROCm uses same parameter as CUDA

            llama_params['n_gpu_layers'] = config.gpu_layers

        elif device == 'mps':

            # Metal Performance Shaders for Apple Silicon

            llama_params['n_gpu_layers'] = 1  # Enable Metal

        else:

            # CPU only

            llama_params['n_gpu_layers'] = 0

            

        self.model = Llama(**llama_params)

        self.device = device

        

    def _detect_device(self) -> str:

        """

        Automatically detect the best available device.

        

        Returns:

            Device string: 'cuda', 'rocm', 'mps', or 'cpu'

        """

        import platform

        

        # Check for NVIDIA CUDA

        try:

            import torch

            if torch.cuda.is_available():

                return 'cuda'

        except ImportError:

            pass

            

        # Check for AMD ROCm

        try:

            import torch

            if hasattr(torch, 'hip') and torch.hip.is_available():

                return 'rocm'

        except (ImportError, AttributeError):

            pass

            

        # Check for Apple Metal

        if platform.system() == 'Darwin':

            try:

                import torch

                if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():

                    return 'mps'

            except ImportError:

                pass

                

        # Default to CPU

        return 'cpu'

        

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """

        Generate response using local LLaMA model.

        """

        # Convert messages to prompt format

        prompt = self._messages_to_prompt(messages, tools)

        

        # Run generation in thread pool to avoid blocking

        loop = asyncio.get_event_loop()

        

        if stream:

            # Stream tokens as they are generated

            generator = await loop.run_in_executor(

                None,

                lambda: self.model(

                    prompt,

                    max_tokens=self.config.max_tokens,

                    temperature=self.config.temperature,

                    stream=True

                )

            )

            

            for chunk in generator:

                if 'choices' in chunk and len(chunk['choices']) > 0:

                    text = chunk['choices'][0].get('text', '')

                    if text:

                        yield text

        else:

            # Generate complete response

            response = await loop.run_in_executor(

                None,

                lambda: self.model(

                    prompt,

                    max_tokens=self.config.max_tokens,

                    temperature=self.config.temperature,

                    stream=False

                )

            )

            

            if 'choices' in response and len(response['choices']) > 0:

                yield response['choices'][0]['text']

                

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """

        Generate response with simulated tool calling.

        Local models don't natively support tool calling, so we use

        prompt engineering to achieve similar functionality.

        """

        # Create a prompt that instructs the model about available tools

        tool_prompt = self._create_tool_prompt(tools)

        

        # Add tool instructions to system message

        enhanced_messages = messages.copy()

        if enhanced_messages and enhanced_messages[0].role == 'system':

            enhanced_messages[0] = Message(

                role='system',

                content=enhanced_messages[0].content + '\n\n' + tool_prompt

            )

        else:

            enhanced_messages.insert(0, Message(

                role='system',

                content=tool_prompt

            ))

            

        # Generate response

        prompt = self._messages_to_prompt(enhanced_messages, tools)

        

        loop = asyncio.get_event_loop()

        response = await loop.run_in_executor(

            None,

            lambda: self.model(

                prompt,

                max_tokens=self.config.max_tokens,

                temperature=self.config.temperature,

                stream=False

            )

        )

        

        response_text = response['choices'][0]['text']

        

        # Parse potential tool calls from response

        tool_calls = self._parse_tool_calls(response_text)

        

        result = Message(

            role='assistant',

            content=response_text

        )

        

        if tool_calls:

            result.tool_calls = tool_calls

            

        return result

        

    def _messages_to_prompt(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None

    ) -> str:

        """

        Convert messages to a prompt string suitable for the model.

        """

        prompt_parts = []

        

        for msg in messages:

            if msg.role == 'system':

                prompt_parts.append(f"System: {msg.content}")

            elif msg.role == 'user':

                prompt_parts.append(f"User: {msg.content}")

            elif msg.role == 'assistant':

                prompt_parts.append(f"Assistant: {msg.content}")

            elif msg.role == 'tool':

                prompt_parts.append(f"Tool Result ({msg.name}): {msg.content}")

                

        prompt_parts.append("Assistant:")

        

        return '\n\n'.join(prompt_parts)

        

    def _create_tool_prompt(self, tools: List[Dict[str, Any]]) -> str:

        """

        Create a prompt that describes available tools to the model.

        """

        tool_descriptions = []

        

        for tool in tools:

            func = tool.get('function', {})

            name = func.get('name', '')

            description = func.get('description', '')

            parameters = func.get('parameters', {})

            

            tool_desc = f"Tool: {name}\nDescription: {description}\nParameters: {json.dumps(parameters, indent=2)}"

            tool_descriptions.append(tool_desc)

            

        tools_text = '\n\n'.join(tool_descriptions)

        

        return f"""You have access to the following tools:


{tools_text}


To use a tool, respond with a JSON object in this format:

{{

    "tool_call": {{

        "name": "tool_name",

        "arguments": {{

            "param1": "value1",

            "param2": "value2"

        }}

    }}

}}


If you don't need to use a tool, respond normally with text."""

        

    def _parse_tool_calls(self, response_text: str) -> Optional[List[Dict[str, Any]]]:

        """

        Parse tool calls from model response.

        Looks for JSON objects with tool_call structure.

        """

        import re

        

        # Look for JSON objects in the response

        json_pattern = r'\{[^{}]*"tool_call"[^{}]*\{[^{}]*\}[^{}]*\}'

        matches = re.finditer(json_pattern, response_text, re.DOTALL)

        

        tool_calls = []

        

        for match in matches:

            try:

                data = json.loads(match.group())

                if 'tool_call' in data:

                    tool_call = data['tool_call']

                    tool_calls.append({

                        'id': f"call_{len(tool_calls)}",

                        'type': 'function',

                        'function': {

                            'name': tool_call.get('name', ''),

                            'arguments': json.dumps(tool_call.get('arguments', {}))

                        }

                    })

            except json.JSONDecodeError:

                continue

                

        return tool_calls if tool_calls else None

        

    def supports_tool_calling(self) -> bool:

        """

        Local models use simulated tool calling via prompt engineering.

        """

        return False



class VLLMBackend(LLMBackend):

    """

    Backend for vLLM high-performance inference server.

    Supports CUDA and ROCm.

    """

    

    def __init__(self, config: LLMConfig):

        """

        Initialize vLLM backend.

        """

        super().__init__(config)

        

        try:

            from vllm import LLM, SamplingParams

            self.LLM = LLM

            self.SamplingParams = SamplingParams

        except ImportError:

            raise ImportError(

                "vllm not installed. Install with:\n"

                "CUDA: pip install vllm\n"

                "ROCm: pip install vllm (with ROCm PyTorch)"

            )

            

        # Determine device

        device = self._detect_device() if config.device == 'auto' else config.device

        

        # Initialize vLLM engine

        engine_args = {

            'model': config.model_path or config.model_name,

            'tensor_parallel_size': 1,

            'gpu_memory_utilization': config.gpu_memory_fraction

        }

        

        if device == 'rocm':

            # vLLM automatically detects ROCm

            pass

        elif device != 'cuda':

            raise ValueError(f"vLLM only supports CUDA and ROCm, got {device}")

            

        self.model = self.LLM(**engine_args)

        self.device = device

        

    def _detect_device(self) -> str:

        """

        Detect CUDA or ROCm availability.

        """

        try:

            import torch

            if torch.cuda.is_available():

                # Check if it's ROCm or CUDA

                if hasattr(torch.version, 'hip') and torch.version.hip is not None:

                    return 'rocm'

                return 'cuda'

        except ImportError:

            pass

            

        raise RuntimeError("vLLM requires CUDA or ROCm GPU")

        

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """

        Generate response using vLLM.

        """

        # Convert messages to prompt

        prompt = self._messages_to_prompt(messages, tools)

        

        # Create sampling parameters

        sampling_params = self.SamplingParams(

            temperature=self.config.temperature,

            max_tokens=self.config.max_tokens

        )

        

        # Run generation in thread pool

        loop = asyncio.get_event_loop()

        

        if stream:

            # vLLM doesn't support streaming in the same way

            # We generate and yield the complete response

            outputs = await loop.run_in_executor(

                None,

                lambda: self.model.generate([prompt], sampling_params)

            )

            

            if outputs:

                yield outputs[0].outputs[0].text

        else:

            outputs = await loop.run_in_executor(

                None,

                lambda: self.model.generate([prompt], sampling_params)

            )

            

            if outputs:

                yield outputs[0].outputs[0].text

                

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """

        Generate with simulated tool calling (similar to LocalLlamaBackend).

        """

        # Similar implementation to LocalLlamaBackend

        tool_prompt = self._create_tool_prompt(tools)

        

        enhanced_messages = messages.copy()

        if enhanced_messages and enhanced_messages[0].role == 'system':

            enhanced_messages[0] = Message(

                role='system',

                content=enhanced_messages[0].content + '\n\n' + tool_prompt

            )

        else:

            enhanced_messages.insert(0, Message(

                role='system',

                content=tool_prompt

            ))

            

        prompt = self._messages_to_prompt(enhanced_messages, tools)

        

        sampling_params = self.SamplingParams(

            temperature=self.config.temperature,

            max_tokens=self.config.max_tokens

        )

        

        loop = asyncio.get_event_loop()

        outputs = await loop.run_in_executor(

            None,

            lambda: self.model.generate([prompt], sampling_params)

        )

        

        response_text = outputs[0].outputs[0].text if outputs else ""

        

        tool_calls = self._parse_tool_calls(response_text)

        

        result = Message(

            role='assistant',

            content=response_text

        )

        

        if tool_calls:

            result.tool_calls = tool_calls

            

        return result

        

    def _messages_to_prompt(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None

    ) -> str:

        """

        Convert messages to prompt string.

        """

        prompt_parts = []

        

        for msg in messages:

            if msg.role == 'system':

                prompt_parts.append(f"System: {msg.content}")

            elif msg.role == 'user':

                prompt_parts.append(f"User: {msg.content}")

            elif msg.role == 'assistant':

                prompt_parts.append(f"Assistant: {msg.content}")

            elif msg.role == 'tool':

                prompt_parts.append(f"Tool Result ({msg.name}): {msg.content}")

                

        prompt_parts.append("Assistant:")

        

        return '\n\n'.join(prompt_parts)

        

    def _create_tool_prompt(self, tools: List[Dict[str, Any]]) -> str:

        """

        Create tool description prompt.

        """

        tool_descriptions = []

        

        for tool in tools:

            func = tool.get('function', {})

            name = func.get('name', '')

            description = func.get('description', '')

            parameters = func.get('parameters', {})

            

            tool_desc = f"Tool: {name}\nDescription: {description}\nParameters: {json.dumps(parameters, indent=2)}"

            tool_descriptions.append(tool_desc)

            

        tools_text = '\n\n'.join(tool_descriptions)

        

        return f"""You have access to the following tools:


{tools_text}


To use a tool, respond with a JSON object in this format:

{{

    "tool_call": {{

        "name": "tool_name",

        "arguments": {{

            "param1": "value1"

        }}

    }}

}}"""

        

    def _parse_tool_calls(self, response_text: str) -> Optional[List[Dict[str, Any]]]:

        """

        Parse tool calls from response.

        """

        import re

        

        json_pattern = r'\{[^{}]*"tool_call"[^{}]*\{[^{}]*\}[^{}]*\}'

        matches = re.finditer(json_pattern, response_text, re.DOTALL)

        

        tool_calls = []

        

        for match in matches:

            try:

                data = json.loads(match.group())

                if 'tool_call' in data:

                    tool_call = data['tool_call']

                    tool_calls.append({

                        'id': f"call_{len(tool_calls)}",

                        'type': 'function',

                        'function': {

                            'name': tool_call.get('name', ''),

                            'arguments': json.dumps(tool_call.get('arguments', {}))

                        }

                    })

            except json.JSONDecodeError:

                continue

                

        return tool_calls if tool_calls else None

        

    def supports_tool_calling(self) -> bool:

        """

        vLLM uses simulated tool calling.

        """

        return False



class OllamaBackend(LLMBackend):

    """

    Backend for Ollama local model serving.

    Supports all GPU types through Ollama's abstraction.

    """

    

    def __init__(self, config: LLMConfig):

        """

        Initialize Ollama backend.

        """

        super().__init__(config)

        

        try:

            import httpx

            self.httpx = httpx

        except ImportError:

            raise ImportError("httpx not installed. Install with: pip install httpx")

            

        self.api_base = config.api_base or 'http://localhost:11434'

        self.client = httpx.AsyncClient(base_url=self.api_base, timeout=300.0)

        

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """

        Generate response using Ollama API.

        """

        # Convert messages to Ollama format

        ollama_messages = []

        for msg in messages:

            ollama_messages.append({

                'role': msg.role,

                'content': msg.content

            })

            

        # Prepare request

        request_data = {

            'model': self.config.model_name,

            'messages': ollama_messages,

            'stream': stream,

            'options': {

                'temperature': self.config.temperature,

                'num_predict': self.config.max_tokens

            }

        }

        

        if stream:

            async with self.client.stream(

                'POST',

                '/api/chat',

                json=request_data

            ) as response:

                async for line in response.aiter_lines():

                    if line:

                        try:

                            data = json.loads(line)

                            if 'message' in data and 'content' in data['message']:

                                yield data['message']['content']

                        except json.JSONDecodeError:

                            continue

        else:

            response = await self.client.post('/api/chat', json=request_data)

            data = response.json()

            if 'message' in data and 'content' in data['message']:

                yield data['message']['content']

                

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """

        Generate with simulated tool calling.

        """

        tool_prompt = self._create_tool_prompt(tools)

        

        enhanced_messages = messages.copy()

        if enhanced_messages and enhanced_messages[0].role == 'system':

            enhanced_messages[0] = Message(

                role='system',

                content=enhanced_messages[0].content + '\n\n' + tool_prompt

            )

        else:

            enhanced_messages.insert(0, Message(

                role='system',

                content=tool_prompt

            ))

            

        # Convert to Ollama format

        ollama_messages = []

        for msg in enhanced_messages:

            ollama_messages.append({

                'role': msg.role,

                'content': msg.content

            })

            

        # Make request

        request_data = {

            'model': self.config.model_name,

            'messages': ollama_messages,

            'stream': False,

            'options': {

                'temperature': self.config.temperature,

                'num_predict': self.config.max_tokens

            }

        }

        

        response = await self.client.post('/api/chat', json=request_data)

        data = response.json()

        

        response_text = data.get('message', {}).get('content', '')

        

        tool_calls = self._parse_tool_calls(response_text)

        

        result = Message(

            role='assistant',

            content=response_text

        )

        

        if tool_calls:

            result.tool_calls = tool_calls

            

        return result

        

    def _create_tool_prompt(self, tools: List[Dict[str, Any]]) -> str:

        """

        Create tool description prompt.

        """

        tool_descriptions = []

        

        for tool in tools:

            func = tool.get('function', {})

            name = func.get('name', '')

            description = func.get('description', '')

            parameters = func.get('parameters', {})

            

            tool_desc = f"Tool: {name}\nDescription: {description}\nParameters: {json.dumps(parameters, indent=2)}"

            tool_descriptions.append(tool_desc)

            

        tools_text = '\n\n'.join(tool_descriptions)

        

        return f"""You have access to the following tools:


{tools_text}


To use a tool, respond with a JSON object in this format:

{{

    "tool_call": {{

        "name": "tool_name",

        "arguments": {{

            "param1": "value1"

        }}

    }}

}}"""

        

    def _parse_tool_calls(self, response_text: str) -> Optional[List[Dict[str, Any]]]:

        """

        Parse tool calls from response.

        """

        import re

        

        json_pattern = r'\{[^{}]*"tool_call"[^{}]*\{[^{}]*\}[^{}]*\}'

        matches = re.finditer(json_pattern, response_text, re.DOTALL)

        

        tool_calls = []

        

        for match in matches:

            try:

                data = json.loads(match.group())

                if 'tool_call' in data:

                    tool_call = data['tool_call']

                    tool_calls.append({

                        'id': f"call_{len(tool_calls)}",

                        'type': 'function',

                        'function': {

                            'name': tool_call.get('name', ''),

                            'arguments': json.dumps(tool_call.get('arguments', {}))

                        }

                    })

            except json.JSONDecodeError:

                continue

                

        return tool_calls if tool_calls else None

        

    def supports_tool_calling(self) -> bool:

        """

        Ollama uses simulated tool calling.

        """

        return False

    

    async def __aenter__(self):

        """

        Async context manager entry.

        """

        return self

        

    async def __aexit__(self, exc_type, exc_val, exc_tb):

        """

        Async context manager exit.

        """

        await self.client.aclose()



class LLMFactory:

    """

    Factory for creating appropriate LLM backend based on configuration.

    """

    

    @staticmethod

    def create_backend(config: LLMConfig) -> LLMBackend:

        """

        Create and return the appropriate LLM backend.

        

        Args:

            config: LLMConfig object specifying the backend type and settings

            

        Returns:

            Initialized LLMBackend instance

        """

        backend_type = config.backend_type.lower()

        

        if backend_type == 'openai':

            return OpenAIBackend(config)

        elif backend_type == 'local_llama':

            return LocalLlamaBackend(config)

        elif backend_type == 'local_vllm':

            return VLLMBackend(config)

        elif backend_type == 'ollama':

            return OllamaBackend(config)

        else:

            raise ValueError(f"Unknown backend type: {backend_type}")


The LLM integration layer provides a unified interface for working with different model backends. The OpenAI backend uses the official API and supports native tool calling. The local backends including llama.cpp, vLLM, and Ollama require prompt engineering to simulate tool calling since most open-source models don't natively support this feature. Each backend handles GPU detection and configuration appropriately for the target hardware whether NVIDIA CUDA, AMD ROCm, Intel oneAPI, or Apple Metal.


Tool Framework and Execution Engine


The tool framework defines how the LLM can interact with the development environment. Each tool has a clear specification, validation logic, and execution handler.


from abc import ABC, abstractmethod

from typing import Dict, Any, List, Optional, Callable

from dataclasses import dataclass

import json

import asyncio

import subprocess

from pathlib import Path


@dataclass

class ToolParameter:

    """

    Specification for a tool parameter.

    """

    name: str

    type: str  # 'string', 'number', 'boolean', 'array', 'object'

    description: str

    required: bool = True

    enum: Optional[List[Any]] = None

    default: Optional[Any] = None



@dataclass

class ToolSpec:

    """

    Complete specification for a tool.

    """

    name: str

    description: str

    parameters: List[ToolParameter]

    category: str  # 'git', 'ide', 'system', 'package'

    

    def to_openai_format(self) -> Dict[str, Any]:

        """

        Convert tool specification to OpenAI function calling format.

        

        Returns:

            Dictionary in OpenAI tool format

        """

        properties = {}

        required = []

        

        for param in self.parameters:

            param_spec = {

                'type': param.type,

                'description': param.description

            }

            

            if param.enum:

                param_spec['enum'] = param.enum

                

            properties[param.name] = param_spec

            

            if param.required:

                required.append(param.name)

                

        return {

            'type': 'function',

            'function': {

                'name': self.name,

                'description': self.description,

                'parameters': {

                    'type': 'object',

                    'properties': properties,

                    'required': required

                }

            }

        }



class Tool(ABC):

    """

    Abstract base class for all tools.

    """

    

    def __init__(self):

        """

        Initialize the tool.

        """

        self.spec = self._create_spec()

        

    @abstractmethod

    def _create_spec(self) -> ToolSpec:

        """

        Create and return the tool specification.

        

        Returns:

            ToolSpec object describing this tool

        """

        pass

        

    @abstractmethod

    async def execute(self, **kwargs) -> Dict[str, Any]:

        """

        Execute the tool with given parameters.

        

        Args:

            **kwargs: Tool parameters as keyword arguments

            

        Returns:

            Dictionary with execution results

        """

        pass

        

    def validate_parameters(self, params: Dict[str, Any]) -> tuple[bool, Optional[str]]:

        """

        Validate that provided parameters match the specification.

        

        Args:

            params: Dictionary of parameter values

            

        Returns:

            Tuple of (is_valid, error_message)

        """

        # Check required parameters

        for param in self.spec.parameters:

            if param.required and param.name not in params:

                return False, f"Missing required parameter: {param.name}"

                

        # Check parameter types

        for param_name, param_value in params.items():

            # Find parameter spec

            param_spec = next(

                (p for p in self.spec.parameters if p.name == param_name),

                None

            )

            

            if not param_spec:

                return False, f"Unknown parameter: {param_name}"

                

            # Validate type

            if not self._check_type(param_value, param_spec.type):

                return False, f"Parameter {param_name} has wrong type. Expected {param_spec.type}"

                

            # Validate enum

            if param_spec.enum and param_value not in param_spec.enum:

                return False, f"Parameter {param_name} must be one of {param_spec.enum}"

                

        return True, None

        

    def _check_type(self, value: Any, expected_type: str) -> bool:

        """

        Check if value matches expected type.

        

        Args:

            value: Value to check

            expected_type: Expected type string

            

        Returns:

            True if type matches

        """

        if expected_type == 'string':

            return isinstance(value, str)

        elif expected_type == 'number':

            return isinstance(value, (int, float))

        elif expected_type == 'boolean':

            return isinstance(value, bool)

        elif expected_type == 'array':

            return isinstance(value, list)

        elif expected_type == 'object':

            return isinstance(value, dict)

        else:

            return True



class GitInitTool(Tool):

    """

    Tool for initializing a Git repository.

    """

    

    def _create_spec(self) -> ToolSpec:

        """

        Create specification for git init tool.

        """

        return ToolSpec(

            name='git_init',

            description='Initialize a new Git repository in the specified directory',

            parameters=[

                ToolParameter(

                    name='path',

                    type='string',

                    description='Path where the repository should be initialized',

                    required=True

                ),

                ToolParameter(

                    name='initial_branch',

                    type='string',

                    description='Name of the initial branch (default: main)',

                    required=False,

                    default='main'

                )

            ],

            category='git'

        )

        

    async def execute(self, **kwargs) -> Dict[str, Any]:

        """

        Execute git init command.

        """

        # Validate parameters

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {

                'success': False,

                'error': error

            }

            

        path = Path(kwargs['path'])

        initial_branch = kwargs.get('initial_branch', 'main')

        

        # Check if path exists

        if not path.exists():

            try:

                path.mkdir(parents=True, exist_ok=True)

            except Exception as e:

                return {

                    'success': False,

                    'error': f"Failed to create directory: {str(e)}"

                }

                

        # Check if already a git repository

        if (path / '.git').exists():

            return {

                'success': False,

                'error': 'Directory is already a Git repository'

            }

            

        # Run git init

        try:

            result = await asyncio.create_subprocess_exec(

                'git', 'init', '-b', initial_branch,

                cwd=str(path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            stdout, stderr = await result.communicate()

            

            if result.returncode == 0:

                return {

                    'success': True,

                    'message': f'Initialized Git repository in {path}',

                    'branch': initial_branch

                }

            else:

                return {

                    'success': False,

                    'error': stderr.decode('utf-8')

                }

                

        except Exception as e:

            return {

                'success': False,

                'error': f'Failed to execute git init: {str(e)}'

            }



class GitBranchTool(Tool):

    """

    Tool for creating and switching Git branches.

    """

    

    def _create_spec(self) -> ToolSpec:

        """

        Create specification for git branch tool.

        """

        return ToolSpec(

            name='git_branch',

            description='Create a new branch or switch to an existing branch',

            parameters=[

                ToolParameter(

                    name='repository_path',

                    type='string',

                    description='Path to the Git repository',

                    required=True

                ),

                ToolParameter(

                    name='branch_name',

                    type='string',

                    description='Name of the branch',

                    required=True

                ),

                ToolParameter(

                    name='create',

                    type='boolean',

                    description='Create the branch if it does not exist',

                    required=False,

                    default=True

                ),

                ToolParameter(

                    name='checkout',

                    type='boolean',

                    description='Switch to the branch after creating/verifying it',

                    required=False,

                    default=True

                )

            ],

            category='git'

        )

        

    async def execute(self, **kwargs) -> Dict[str, Any]:

        """

        Execute git branch operations.

        """

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

            

        repo_path = Path(kwargs['repository_path'])

        branch_name = kwargs['branch_name']

        create = kwargs.get('create', True)

        checkout = kwargs.get('checkout', True)

        

        # Verify it's a git repository

        if not (repo_path / '.git').exists():

            return {

                'success': False,

                'error': f'{repo_path} is not a Git repository'

            }

            

        try:

            # Check if branch exists

            result = await asyncio.create_subprocess_exec(

                'git', 'branch', '--list', branch_name,

                cwd=str(repo_path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            stdout, stderr = await result.communicate()

            branch_exists = bool(stdout.decode('utf-8').strip())

            

            # Create branch if needed

            if not branch_exists and create:

                result = await asyncio.create_subprocess_exec(

                    'git', 'branch', branch_name,

                    cwd=str(repo_path),

                    stdout=asyncio.subprocess.PIPE,

                    stderr=asyncio.subprocess.PIPE

                )

                

                stdout, stderr = await result.communicate()

                

                if result.returncode != 0:

                    return {

                        'success': False,

                        'error': f'Failed to create branch: {stderr.decode("utf-8")}'

                    }

                    

            elif not branch_exists and not create:

                return {

                    'success': False,

                    'error': f'Branch {branch_name} does not exist'

                }

                

            # Checkout branch if requested

            if checkout:

                result = await asyncio.create_subprocess_exec(

                    'git', 'checkout', branch_name,

                    cwd=str(repo_path),

                    stdout=asyncio.subprocess.PIPE,

                    stderr=asyncio.subprocess.PIPE

                )

                

                stdout, stderr = await result.communicate()

                

                if result.returncode != 0:

                    return {

                        'success': False,

                        'error': f'Failed to checkout branch: {stderr.decode("utf-8")}'

                    }

                    

            return {

                'success': True,

                'message': f'Branch {branch_name} is ready',

                'created': not branch_exists and create,

                'checked_out': checkout

            }

            

        except Exception as e:

            return {

                'success': False,

                'error': f'Git operation failed: {str(e)}'

            }



class GitCommitTool(Tool):

    """

    Tool for committing changes to Git.

    """

    

    def _create_spec(self) -> ToolSpec:

        """

        Create specification for git commit tool.

        """

        return ToolSpec(

            name='git_commit',

            description='Stage and commit changes to the Git repository',

            parameters=[

                ToolParameter(

                    name='repository_path',

                    type='string',

                    description='Path to the Git repository',

                    required=True

                ),

                ToolParameter(

                    name='message',

                    type='string',

                    description='Commit message',

                    required=True

                ),

                ToolParameter(

                    name='files',

                    type='array',

                    description='List of files to stage (empty means all changes)',

                    required=False,

                    default=[]

                ),

                ToolParameter(

                    name='add_all',

                    type='boolean',

                    description='Stage all changes including untracked files',

                    required=False,

                    default=False

                )

            ],

            category='git'

        )

        

    async def execute(self, **kwargs) -> Dict[str, Any]:

        """

        Execute git commit operation.

        """

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

            

        repo_path = Path(kwargs['repository_path'])

        message = kwargs['message']

        files = kwargs.get('files', [])

        add_all = kwargs.get('add_all', False)

        

        if not (repo_path / '.git').exists():

            return {

                'success': False,

                'error': f'{repo_path} is not a Git repository'

            }

            

        try:

            # Stage files

            if add_all:

                add_cmd = ['git', 'add', '-A']

            elif files:

                add_cmd = ['git', 'add'] + files

            else:

                add_cmd = ['git', 'add', '-u']

                

            result = await asyncio.create_subprocess_exec(

                *add_cmd,

                cwd=str(repo_path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            stdout, stderr = await result.communicate()

            

            if result.returncode != 0:

                return {

                    'success': False,

                    'error': f'Failed to stage files: {stderr.decode("utf-8")}'

                }

                

            # Commit

            result = await asyncio.create_subprocess_exec(

                'git', 'commit', '-m', message,

                cwd=str(repo_path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            stdout, stderr = await result.communicate()

            

            if result.returncode != 0:

                # Check if there's nothing to commit

                if 'nothing to commit' in stderr.decode('utf-8').lower():

                    return {

                        'success': True,

                        'message': 'Nothing to commit, working tree clean',

                        'committed': False

                    }

                else:

                    return {

                        'success': False,

                        'error': f'Commit failed: {stderr.decode("utf-8")}'

                    }

                    

            return {

                'success': True,

                'message': f'Changes committed successfully',

                'committed': True

            }

            

        except Exception as e:

            return {

                'success': False,

                'error': f'Git operation failed: {str(e)}'

            }



class VSCodeExtensionTool(Tool):

    """

    Tool for managing VS Code extensions.

    """

    

    def _create_spec(self) -> ToolSpec:

        """

        Create specification for VS Code extension tool.

        """

        return ToolSpec(

            name='vscode_extension',

            description='Install, uninstall, or list VS Code extensions',

            parameters=[

                ToolParameter(

                    name='action',

                    type='string',

                    description='Action to perform',

                    required=True,

                    enum=['install', 'uninstall', 'list']

                ),

                ToolParameter(

                    name='extension_id',

                    type='string',

                    description='Extension identifier (required for install/uninstall)',

                    required=False

                )

            ],

            category='ide'

        )

        

    async def execute(self, **kwargs) -> Dict[str, Any]:

        """

        Execute VS Code extension management.

        """

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

            

        action = kwargs['action']

        extension_id = kwargs.get('extension_id')

        

        # Find code command

        code_cmd = self._find_code_command()

        if not code_cmd:

            return {

                'success': False,

                'error': 'VS Code command line tool not found'

            }

            

        try:

            if action == 'list':

                result = await asyncio.create_subprocess_exec(

                    code_cmd, '--list-extensions',

                    stdout=asyncio.subprocess.PIPE,

                    stderr=asyncio.subprocess.PIPE

                )

                

                stdout, stderr = await result.communicate()

                

                if result.returncode == 0:

                    extensions = stdout.decode('utf-8').strip().split('\n')

                    return {

                        'success': True,

                        'extensions': extensions

                    }

                else:

                    return {

                        'success': False,

                        'error': stderr.decode('utf-8')

                    }

                    

            elif action == 'install':

                if not extension_id:

                    return {

                        'success': False,

                        'error': 'extension_id required for install action'

                    }

                    

                result = await asyncio.create_subprocess_exec(

                    code_cmd, '--install-extension', extension_id,

                    stdout=asyncio.subprocess.PIPE,

                    stderr=asyncio.subprocess.PIPE

                )

                

                stdout, stderr = await result.communicate()

                

                if result.returncode == 0:

                    return {

                        'success': True,

                        'message': f'Extension {extension_id} installed successfully'

                    }

                else:

                    return {

                        'success': False,

                        'error': stderr.decode('utf-8')

                    }

                    

            elif action == 'uninstall':

                if not extension_id:

                    return {

                        'success': False,

                        'error': 'extension_id required for uninstall action'

                    }

                    

                result = await asyncio.create_subprocess_exec(

                    code_cmd, '--uninstall-extension', extension_id,

                    stdout=asyncio.subprocess.PIPE,

                    stderr=asyncio.subprocess.PIPE

                )

                

                stdout, stderr = await result.communicate()

                

                if result.returncode == 0:

                    return {

                        'success': True,

                        'message': f'Extension {extension_id} uninstalled successfully'

                    }

                else:

                    return {

                        'success': False,

                        'error': stderr.decode('utf-8')

                    }

                    

        except Exception as e:

            return {

                'success': False,

                'error': f'Operation failed: {str(e)}'

            }

            

    def _find_code_command(self) -> Optional[str]:

        """

        Find the VS Code command line tool.

        

        Returns:

            Path to code command or None if not found

        """

        import shutil

        

        # Try common command names

        for cmd in ['code', 'code-insiders']:

            if shutil.which(cmd):

                return cmd

                

        return None



class ToolRegistry:

    """

    Registry for managing available tools.

    """

    

    def __init__(self):

        """

        Initialize the tool registry.

        """

        self.tools: Dict[str, Tool] = {}

        self._register_default_tools()

        

    def _register_default_tools(self):

        """

        Register all default tools.

        """

        # Git tools

        self.register(GitInitTool())

        self.register(GitBranchTool())

        self.register(GitCommitTool())

        

        # IDE tools

        self.register(VSCodeExtensionTool())

        

    def register(self, tool: Tool):

        """

        Register a new tool.

        

        Args:

            tool: Tool instance to register

        """

        self.tools[tool.spec.name] = tool

        

    def get_tool(self, name: str) -> Optional[Tool]:

        """

        Get a tool by name.

        

        Args:

            name: Tool name

            

        Returns:

            Tool instance or None if not found

        """

        return self.tools.get(name)

        

    def get_all_specs(self) -> List[Dict[str, Any]]:

        """

        Get specifications for all registered tools in OpenAI format.

        

        Returns:

            List of tool specifications

        """

        return [tool.spec.to_openai_format() for tool in self.tools.values()]

        

    def get_specs_by_category(self, category: str) -> List[Dict[str, Any]]:

        """

        Get tool specifications filtered by category.

        

        Args:

            category: Tool category

            

        Returns:

            List of tool specifications

        """

        return [

            tool.spec.to_openai_format()

            for tool in self.tools.values()

            if tool.spec.category == category

        ]



class ToolExecutor:

    """

    Executes tools based on LLM tool calls.

    """

    

    def __init__(self, registry: ToolRegistry):

        """

        Initialize the tool executor.

        

        Args:

            registry: ToolRegistry with available tools

        """

        self.registry = registry

        

    async def execute_tool_call(self, tool_call: Dict[str, Any]) -> Dict[str, Any]:

        """

        Execute a single tool call.

        

        Args:

            tool_call: Tool call dictionary from LLM response

            

        Returns:

            Execution result dictionary

        """

        function_data = tool_call.get('function', {})

        tool_name = function_data.get('name')

        

        if not tool_name:

            return {

                'success': False,

                'error': 'Tool name not specified'

            }

            

        tool = self.registry.get_tool(tool_name)

        

        if not tool:

            return {

                'success': False,

                'error': f'Unknown tool: {tool_name}'

            }

            

        # Parse arguments

        try:

            arguments_str = function_data.get('arguments', '{}')

            if isinstance(arguments_str, str):

                arguments = json.loads(arguments_str)

            else:

                arguments = arguments_str

        except json.JSONDecodeError as e:

            return {

                'success': False,

                'error': f'Invalid arguments JSON: {str(e)}'

            }

            

        # Execute tool

        try:

            result = await tool.execute(**arguments)

            return result

        except Exception as e:

            return {

                'success': False,

                'error': f'Tool execution failed: {str(e)}'

            }

            

    async def execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:

        """

        Execute multiple tool calls in sequence.

        

        Args:

            tool_calls: List of tool call dictionaries

            

        Returns:

            List of execution results

        """

        results = []

        

        for tool_call in tool_calls:

            result = await self.execute_tool_call(tool_call)

            results.append({

                'tool_call_id': tool_call.get('id'),

                'result': result

            })

            

        return results


The tool framework provides a structured way to define and execute actions in the development environment. Each tool has a clear specification that describes its purpose, parameters, and validation rules. The ToolRegistry manages all available tools and provides them to the LLM in the appropriate format. The ToolExecutor handles the actual execution of tool calls, including parameter validation, error handling, and result formatting.


Now continuing with the method parse_tool and the remaining implementation:


class AgenticAssistant:

    """

    Main agentic assistant that coordinates LLM, tools, and multi-step workflows.

    """

    

    def __init__(

        self,

        llm_backend: LLMBackend,

        tool_registry: ToolRegistry,

        max_iterations: int = 10

    ):

        """

        Initialize the agentic assistant.

        

        Args:

            llm_backend: LLM backend for generating responses

            tool_registry: Registry of available tools

            max_iterations: Maximum number of agent iterations to prevent infinite loops

        """

        self.llm = llm_backend

        self.registry = tool_registry

        self.executor = ToolExecutor(tool_registry)

        self.max_iterations = max_iterations

        self.conversation_history: List[Message] = []

        

    async def process_request(

        self,

        user_request: str,

        context: DevelopmentContext

    ) -> str:

        """

        Process a user request with full agentic capabilities.

        

        Args:

            user_request: Natural language request from the user

            context: Current development context

            

        Returns:

            Final response to the user

        """

        # Initialize conversation with system message

        system_message = self._create_system_message(context)

        self.conversation_history = [system_message]

        

        # Add user request

        self.conversation_history.append(Message(

            role='user',

            content=user_request

        ))

        

        # Agent loop

        iteration = 0

        while iteration < self.max_iterations:

            iteration += 1

            

            # Get available tools based on context

            tools = self._get_relevant_tools(context)

            

            # Generate response from LLM

            if self.llm.supports_tool_calling():

                response = await self.llm.generate_with_tools(

                    self.conversation_history,

                    tools

                )

            else:

                # For models without native tool calling, use prompt engineering

                response = await self._generate_with_simulated_tools(tools)

                

            # Add assistant response to history

            self.conversation_history.append(response)

            

            # Check if there are tool calls

            if response.tool_calls:

                # Execute all tool calls

                tool_results = await self.executor.execute_tool_calls(response.tool_calls)

                

                # Add tool results to conversation

                for tool_result in tool_results:

                    self.conversation_history.append(Message(

                        role='tool',

                        content=json.dumps(tool_result['result']),

                        tool_call_id=tool_result['tool_call_id'],

                        name=self._get_tool_name_from_call_id(

                            response.tool_calls,

                            tool_result['tool_call_id']

                        )

                    ))

                    

                # Continue loop to let LLM process results

                continue

            else:

                # No tool calls, we have the final response

                return response.content

                

        # Max iterations reached

        return "I apologize, but I was unable to complete the task within the allowed number of steps. Please try breaking down your request into smaller parts."

        

    def _create_system_message(self, context: DevelopmentContext) -> Message:

        """

        Create a system message that includes context information.

        

        Args:

            context: Current development context

            

        Returns:

            System message

        """

        context_info = f"""You are an AI development assistant helping a developer with their workflow.


Current Context:

- Application: {context.app_name}

- Current Directory: {context.current_directory or 'Unknown'}

"""

        

        if context.git_repository:

            context_info += f"- Git Repository: {context.git_repository}\n"

            context_info += f"- Current Branch: {context.git_branch or 'Unknown'}\n"

            

        if context.ide_type:

            context_info += f"- IDE: {context.ide_type}\n"

            

        if context.active_file:

            context_info += f"- Active File: {context.active_file}\n"

            

        context_info += """

You have access to various tools to help the developer. Use these tools to accomplish the requested tasks.

Always explain what you're doing and why. If a task cannot be completed, explain the reason clearly.

"""

        

        return Message(

            role='system',

            content=context_info

        )

        

    def _get_relevant_tools(self, context: DevelopmentContext) -> List[Dict[str, Any]]:

        """

        Get tools relevant to the current context.

        

        Args:

            context: Current development context

            

        Returns:

            List of tool specifications

        """

        # Start with all tools

        all_tools = self.registry.get_all_specs()

        

        # Could implement filtering logic based on context

        # For example, only show Git tools if in a Git repository

        # For now, return all tools

        

        return all_tools

        

    async def _generate_with_simulated_tools(

        self,

        tools: List[Dict[str, Any]]

    ) -> Message:

        """

        Generate response for models without native tool calling.

        

        Args:

            tools: List of available tools

            

        Returns:

            Message with potential tool calls

        """

        # Use the backend's generate_with_tools method which handles

        # prompt engineering for tool calling

        return await self.llm.generate_with_tools(

            self.conversation_history,

            tools

        )

        

    def _get_tool_name_from_call_id(

        self,

        tool_calls: List[Dict[str, Any]],

        call_id: str

    ) -> str:

        """

        Get the tool name from a tool call ID.

        

        Args:

            tool_calls: List of tool calls

            call_id: Tool call ID to look up

            

        Returns:

            Tool name

        """

        for call in tool_calls:

            if call.get('id') == call_id:

                return call.get('function', {}).get('name', 'unknown')

        return 'unknown'



class MultiAgentSystem:

    """

    Multi-agent system for complex workflows requiring specialized agents.

    """

    

    def __init__(

        self,

        llm_backend: LLMBackend,

        tool_registry: ToolRegistry

    ):

        """

        Initialize the multi-agent system.

        

        Args:

            llm_backend: LLM backend for all agents

            tool_registry: Registry of available tools

        """

        self.llm = llm_backend

        self.registry = tool_registry

        self.executor = ToolExecutor(tool_registry)

        

        # Create specialized agents

        self.planner_agent = PlannerAgent(llm_backend)

        self.git_agent = GitAgent(llm_backend, tool_registry)

        self.ide_agent = IDEAgent(llm_backend, tool_registry)

        self.validator_agent = ValidatorAgent(llm_backend)

        

    async def process_complex_request(

        self,

        user_request: str,

        context: DevelopmentContext

    ) -> str:

        """

        Process a complex request using multiple specialized agents.

        

        Args:

            user_request: User's natural language request

            context: Current development context

            

        Returns:

            Final response

        """

        # Step 1: Planning

        plan = await self.planner_agent.create_plan(user_request, context)

        

        if not plan['success']:

            return f"Unable to create a plan: {plan.get('error', 'Unknown error')}"

            

        # Step 2: Execute plan steps

        execution_log = []

        

        for step in plan['steps']:

            step_type = step['type']

            step_description = step['description']

            

            execution_log.append(f"Executing: {step_description}")

            

            # Route to appropriate agent

            if step_type == 'git':

                result = await self.git_agent.execute_step(step, context)

            elif step_type == 'ide':

                result = await self.ide_agent.execute_step(step, context)

            else:

                result = {'success': False, 'error': f'Unknown step type: {step_type}'}

                

            if not result['success']:

                execution_log.append(f"Failed: {result.get('error', 'Unknown error')}")

                break

            else:

                execution_log.append(f"Success: {result.get('message', 'Completed')}")

                

        # Step 3: Validation

        validation = await self.validator_agent.validate_results(

            plan,

            execution_log,

            context

        )

        

        # Format final response

        response_parts = [

            "Task completed. Here's what I did:",

            "",

            *execution_log,

            "",

            validation['summary']

        ]

        

        return '\n'.join(response_parts)



class PlannerAgent:

    """

    Agent responsible for breaking down complex requests into executable steps.

    """

    

    def __init__(self, llm_backend: LLMBackend):

        """

        Initialize the planner agent.

        

        Args:

            llm_backend: LLM backend

        """

        self.llm = llm_backend

        

    async def create_plan(

        self,

        request: str,

        context: DevelopmentContext

    ) -> Dict[str, Any]:

        """

        Create an execution plan for the request.

        

        Args:

            request: User request

            context: Development context

            

        Returns:

            Dictionary with plan steps

        """

        system_prompt = """You are a planning agent. Your job is to break down development tasks into concrete steps.

Each step should specify:

- type: The category of the step (git, ide, system, package)

- description: What the step does

- tool: The specific tool to use

- parameters: Parameters for the tool


Output your plan as a JSON object with a 'steps' array."""

        

        user_prompt = f"""Create a plan for this request: {request}


Context:

- Directory: {context.current_directory}

- Git Repository: {context.git_repository or 'None'}

- IDE: {context.ide_type or 'None'}


Provide a detailed step-by-step plan."""

        

        messages = [

            Message(role='system', content=system_prompt),

            Message(role='user', content=user_prompt)

        ]

        

        # Generate plan

        response_text = ""

        async for chunk in self.llm.generate(messages):

            response_text += chunk

            

        # Parse plan from response

        try:

            # Extract JSON from response

            import re

            json_match = re.search(r'\{.*\}', response_text, re.DOTALL)

            if json_match:

                plan_data = json.loads(json_match.group())

                return {

                    'success': True,

                    'steps': plan_data.get('steps', [])

                }

            else:

                return {

                    'success': False,

                    'error': 'Could not parse plan from response'

                }

        except json.JSONDecodeError as e:

            return {

                'success': False,

                'error': f'Invalid plan JSON: {str(e)}'

            }



class GitAgent:

    """

    Specialized agent for Git operations.

    """

    

    def __init__(self, llm_backend: LLMBackend, tool_registry: ToolRegistry):

        """

        Initialize the Git agent.

        

        Args:

            llm_backend: LLM backend

            tool_registry: Tool registry

        """

        self.llm = llm_backend

        self.registry = tool_registry

        self.executor = ToolExecutor(tool_registry)

        

    async def execute_step(

        self,

        step: Dict[str, Any],

        context: DevelopmentContext

    ) -> Dict[str, Any]:

        """

        Execute a Git-related step.

        

        Args:

            step: Step specification

            context: Development context

            

        Returns:

            Execution result

        """

        tool_name = step.get('tool')

        parameters = step.get('parameters', {})

        

        # Add context information to parameters if needed

        if 'repository_path' not in parameters and context.git_repository:

            parameters['repository_path'] = str(context.git_repository)

        elif 'path' not in parameters and context.current_directory:

            parameters['path'] = str(context.current_directory)

            

        # Create tool call

        tool_call = {

            'id': 'step_call',

            'type': 'function',

            'function': {

                'name': tool_name,

                'arguments': json.dumps(parameters)

            }

        }

        

        # Execute

        result = await self.executor.execute_tool_call(tool_call)

        return result



class IDEAgent:

    """

    Specialized agent for IDE operations.

    """

    

    def __init__(self, llm_backend: LLMBackend, tool_registry: ToolRegistry):

        """

        Initialize the IDE agent.

        

        Args:

            llm_backend: LLM backend

            tool_registry: Tool registry

        """

        self.llm = llm_backend

        self.registry = tool_registry

        self.executor = ToolExecutor(tool_registry)

        

    async def execute_step(

        self,

        step: Dict[str, Any],

        context: DevelopmentContext

    ) -> Dict[str, Any]:

        """

        Execute an IDE-related step.

        

        Args:

            step: Step specification

            context: Development context

            

        Returns:

            Execution result

        """

        tool_name = step.get('tool')

        parameters = step.get('parameters', {})

        

        # Create tool call

        tool_call = {

            'id': 'step_call',

            'type': 'function',

            'function': {

                'name': tool_name,

                'arguments': json.dumps(parameters)

            }

        }

        

        # Execute

        result = await self.executor.execute_tool_call(tool_call)

        return result



class ValidatorAgent:

    """

    Agent that validates the results of executed steps.

    """

    

    def __init__(self, llm_backend: LLMBackend):

        """

        Initialize the validator agent.

        

        Args:

            llm_backend: LLM backend

        """

        self.llm = llm_backend

        

    async def validate_results(

        self,

        plan: Dict[str, Any],

        execution_log: List[str],

        context: DevelopmentContext

    ) -> Dict[str, Any]:

        """

        Validate that the execution achieved the intended goal.

        

        Args:

            plan: Original plan

            execution_log: Log of execution steps

            context: Development context

            

        Returns:

            Validation results

        """

        system_prompt = """You are a validation agent. Review the execution log and determine if the task was completed successfully.

Provide a brief summary of what was accomplished."""

        

        user_prompt = f"""Plan steps:

{json.dumps(plan.get('steps', []), indent=2)}


Execution log:

{chr(10).join(execution_log)}


Was the task completed successfully? Provide a summary."""

        

        messages = [

            Message(role='system', content=system_prompt),

            Message(role='user', content=user_prompt)

        ]

        

        # Generate validation

        response_text = ""

        async for chunk in self.llm.generate(messages):

            response_text += chunk

            

        return {

            'success': True,

            'summary': response_text

        }



class UserInterface:

    """

    User interface for interacting with the development assistant.

    """

    

    def __init__(self, assistant: AgenticAssistant):

        """

        Initialize the user interface.

        

        Args:

            assistant: AgenticAssistant instance

        """

        self.assistant = assistant

        

    async def show_prompt(self, context: DevelopmentContext):

        """

        Show a prompt to the user and get their request.

        

        Args:

            context: Current development context

        """

        print("\n" + "="*60)

        print("Development Assistant Activated")

        print("="*60)

        print(f"\nContext: {context}")

        print("\nWhat can I help you with?")

        print("(Type your request or 'exit' to close)")

        print("-"*60)

        

        user_input = input("> ").strip()

        

        if user_input.lower() in ['exit', 'quit', 'cancel']:

            print("Assistant deactivated.")

            return

            

        if not user_input:

            print("No request provided.")

            return

            

        # Process request

        print("\nProcessing your request...")

        

        try:

            response = await self.assistant.process_request(user_input, context)

            print("\n" + "-"*60)

            print("Response:")

            print("-"*60)

            print(response)

        except Exception as e:

            print(f"\nError processing request: {str(e)}")

            

        print("\n" + "="*60)



async def main():

    """

    Main entry point for the development assistant.

    """

    import logging

    

    # Configure logging

    logging.basicConfig(

        level=logging.INFO,

        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

    )

    

    # Configure LLM backend

    # This example uses OpenAI, but you can switch to any supported backend

    llm_config = LLMConfig(

        backend_type='openai',

        model_name='gpt-4',

        api_key='your-api-key-here',  # Replace with actual API key

        temperature=0.7,

        max_tokens=2048

    )

    

    # For local models, use something like:

    # llm_config = LLMConfig(

    #     backend_type='local_llama',

    #     model_path='/path/to/model.gguf',

    #     device='auto',

    #     gpu_layers=-1,

    #     context_length=4096

    # )

    

    # Create LLM backend

    llm_backend = LLMFactory.create_backend(llm_config)

    

    # Create tool registry

    tool_registry = ToolRegistry()

    

    # Create assistant

    assistant = AgenticAssistant(llm_backend, tool_registry)

    

    # Create UI

    ui = UserInterface(assistant)

    

    # Create and start background service

    service = BackgroundService(hotkey_combination="ctrl+shift+a")

    

    async def handle_activation(context: DevelopmentContext):

        """

        Handle hotkey activation.

        """

        await ui.show_prompt(context)

        

    service.set_activation_callback(handle_activation)

    service.start()

    

    print("Development Assistant is running in the background.")

    print("Press Ctrl+Shift+A to activate.")

    print("Press Ctrl+C to exit.")

    

    # Keep the main thread running

    try:

        while True:

            await asyncio.sleep(1)

    except KeyboardInterrupt:

        print("\nShutting down...")

        service.stop()



if __name__ == '__main__':

    asyncio.run(main())


This completes the comprehensive implementation of the LLM-powered development assistant. The system includes all major components including the background service with hotkey activation, context awareness across different operating


Conclusion and Future Enhancements


The LLM-powered development assistant presented in this article represents a comprehensive solution for automating and enhancing software development workflows. By combining natural language understanding with tool calling capabilities, the system bridges the gap between developer intent and concrete actions in the development environment.


The architecture supports multiple deployment scenarios from fully local setups using open-source models on consumer hardware to cloud-based deployments leveraging commercial APIs. The multi-backend design ensures that developers can choose the configuration that best fits their requirements regarding privacy, performance, and cost.

Several areas offer opportunities for future enhancement. The context awareness system could be extended to monitor file changes in real-time, track compilation errors, and maintain a deeper understanding of the codebase structure through static analysis. Integration with additional IDEs beyond Visual Studio Code and IntelliJ IDEA would broaden the assistant's applicability. Support for more programming languages, build systems, and package managers would make it useful across a wider range of development scenarios.


The tool framework could be expanded with capabilities for automated testing, code review, documentation generation, and deployment automation. Machine learning models specifically fine-tuned for code understanding and generation could improve the quality of code-related operations. Integration with issue tracking systems, continuous integration pipelines, and cloud platforms would enable end-to-end workflow automation.

The multi-agent architecture could be enhanced with learning capabilities that allow agents to improve their performance based on past interactions. Collaborative features could enable multiple developers to share a common assistant instance, with the system learning team-specific conventions and preferences. Privacy-preserving techniques could allow the assistant to learn from developer interactions without exposing sensitive code or data.


Performance optimizations including model quantization, speculative decoding, and caching strategies could reduce latency and resource consumption. The user interface could be enhanced with voice input, graphical displays for complex information, and integration with notification systems for long-running operations.


Security enhancements could include more granular permission controls, sandboxing for tool execution, and integration with security scanning tools to prevent the assistant from introducing vulnerabilities. Audit logging and compliance features would make the system suitable for enterprise environments with strict governance requirements.


The development assistant architecture presented here provides a solid foundation for building intelligent automation tools that enhance developer productivity while maintaining control and transparency. As language models continue to improve and new capabilities emerge, systems like this will become increasingly valuable components of the modern development environment.


Complete Production-Ready Implementation

The following represents the complete, production-ready implementation that integrates all components discussed throughout this article into a cohesive, deployable system:


#!/usr/bin/env python3

"""

LLM-Powered Development Assistant - Complete Implementation


A comprehensive development assistant that uses LLM tool calling to automate

development workflows including Git operations, IDE control, code generation,

and build management.


Author: Development Assistant Team

License: MIT

"""


import os

import sys

import json

import time

import queue

import logging

import threading

import subprocess

import platform

import re

import hashlib

import difflib

import asyncio

from pathlib import Path

from datetime import datetime, timedelta

from collections import OrderedDict

from typing import Dict, List, Optional, Any, Callable, AsyncIterator

from dataclasses import dataclass, field

from abc import ABC, abstractmethod

from enum import Enum


# Configure comprehensive logging

def setup_logging(log_file: str = 'dev_assistant.log', level: int = logging.INFO):

    """

    Set up logging configuration for the application.

    

    Args:

        log_file: Path to log file

        level: Logging level

    """

    log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

    

    # Create formatters and handlers

    file_handler = logging.FileHandler(log_file)

    file_handler.setLevel(level)

    file_handler.setFormatter(logging.Formatter(log_format))

    

    console_handler = logging.StreamHandler()

    console_handler.setLevel(logging.WARNING)

    console_handler.setFormatter(logging.Formatter(log_format))

    

    # Configure root logger

    root_logger = logging.getLogger()

    root_logger.setLevel(level)

    root_logger.addHandler(file_handler)

    root_logger.addHandler(console_handler)



# Data structures for configuration and state management


@dataclass

class LLMConfig:

    """Configuration for LLM backend."""

    backend_type: str

    model_name: str

    api_key: Optional[str] = None

    api_base: Optional[str] = None

    temperature: float = 0.7

    max_tokens: int = 2048

    model_path: Optional[str] = None

    gpu_layers: int = -1

    context_length: int = 4096

    device: str = 'auto'

    gpu_memory_fraction: float = 0.9



@dataclass

class AssistantConfig:

    """Overall configuration for the development assistant."""

    llm_config: LLMConfig

    hotkey: str = 'ctrl+shift+a'

    max_agent_iterations: int = 10

    enable_multi_agent: bool = True

    log_level: int = logging.INFO

    log_file: str = 'dev_assistant.log'

    allowed_paths: List[str] = field(default_factory=list)

    require_confirmation: bool = True



@dataclass

class Message:

    """Represents a message in the conversation."""

    role: str

    content: str

    tool_calls: Optional[List[Dict[str, Any]]] = None

    tool_call_id: Optional[str] = None

    name: Optional[str] = None



@dataclass

class DevelopmentContext:

    """Represents the current development context."""

    window_title: str

    app_name: str

    app_path: Optional[str]

    process_id: int

    current_directory: Optional[Path]

    workspace_root: Optional[Path]

    open_files: List[Path]

    git_repository: Optional[Path]

    git_branch: Optional[str]

    git_status: Optional[Dict[str, Any]]

    ide_type: Optional[str]

    ide_version: Optional[str]

    active_file: Optional[Path]

    cursor_position: Optional[tuple]

    python_version: Optional[str]

    node_version: Optional[str]

    java_version: Optional[str]

    virtual_env: Optional[Path]

    

    def __str__(self):

        """Human-readable representation."""

        parts = [f"App: {self.app_name}"]

        if self.current_directory:

            parts.append(f"Dir: {self.current_directory}")

        if self.git_repository:

            parts.append(f"Git: {self.git_branch} in {self.git_repository.name}")

        if self.ide_type:

            parts.append(f"IDE: {self.ide_type}")

        return " | ".join(parts)



# Context capture implementation


class ContextCapture:

    """Captures the current development context."""

    

    @staticmethod

    def get_current_context() -> DevelopmentContext:

        """Capture and return the current development context."""

        system = platform.system()

        

        if system == 'Windows':

            return ContextCapture._capture_windows_context()

        elif system == 'Darwin':

            return ContextCapture._capture_macos_context()

        elif system == 'Linux':

            return ContextCapture._capture_linux_context()

        else:

            return ContextCapture._create_default_context()

    

    @staticmethod

    def _capture_windows_context() -> DevelopmentContext:

        """Capture context on Windows."""

        try:

            import win32gui

            import win32process

            import psutil

            

            hwnd = win32gui.GetForegroundWindow()

            window_title = win32gui.GetWindowText(hwnd)

            _, process_id = win32process.GetWindowThreadProcessId(hwnd)

            process = psutil.Process(process_id)

            app_name = process.name()

            app_path = process.exe()

            

            try:

                current_directory = Path(process.cwd())

            except (psutil.AccessDenied, psutil.NoSuchProcess):

                current_directory = Path.cwd()

        except Exception as e:

            logging.warning(f"Error capturing Windows context: {e}")

            return ContextCapture._create_default_context()

        

        context = DevelopmentContext(

            window_title=window_title,

            app_name=app_name,

            app_path=app_path,

            process_id=process_id,

            current_directory=current_directory,

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=ContextCapture._detect_ide_type(app_name, app_path),

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

        

        ContextCapture._enhance_context(context)

        return context

    

    @staticmethod

    def _capture_macos_context() -> DevelopmentContext:

        """Capture context on macOS."""

        try:

            from Cocoa import NSWorkspace

            from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionOnScreenOnly, kCGNullWindowID

            import psutil

            

            workspace = NSWorkspace.sharedWorkspace()

            active_app = workspace.activeApplication()

            

            app_name = active_app['NSApplicationName']

            process_id = active_app['NSApplicationProcessIdentifier']

            app_path = active_app.get('NSApplicationPath')

            

            window_list = CGWindowListCopyWindowInfo(

                kCGWindowListOptionOnScreenOnly,

                kCGNullWindowID

            )

            

            window_title = ""

            for window in window_list:

                if window.get('kCGWindowOwnerPID') == process_id:

                    if window.get('kCGWindowLayer') == 0:

                        window_title = window.get('kCGWindowName', '')

                        break

            

            try:

                process = psutil.Process(process_id)

                current_directory = Path(process.cwd())

            except (psutil.AccessDenied, psutil.NoSuchProcess):

                current_directory = Path.cwd()

        except Exception as e:

            logging.warning(f"Error capturing macOS context: {e}")

            return ContextCapture._create_default_context()

        

        context = DevelopmentContext(

            window_title=window_title,

            app_name=app_name,

            app_path=app_path,

            process_id=process_id,

            current_directory=current_directory,

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=ContextCapture._detect_ide_type(app_name, app_path),

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

        

        ContextCapture._enhance_context(context)

        return context

    

    @staticmethod

    def _capture_linux_context() -> DevelopmentContext:

        """Capture context on Linux."""

        try:

            import psutil

            

            # Try to get active window using wmctrl or xdotool

            try:

                result = subprocess.run(

                    ['xdotool', 'getactivewindow', 'getwindowname'],

                    capture_output=True,

                    text=True,

                    timeout=1.0

                )

                window_title = result.stdout.strip() if result.returncode == 0 else ""

            except (subprocess.TimeoutExpired, FileNotFoundError):

                window_title = ""

            

            # Try to get window PID

            try:

                result = subprocess.run(

                    ['xdotool', 'getactivewindow', 'getwindowpid'],

                    capture_output=True,

                    text=True,

                    timeout=1.0

                )

                process_id = int(result.stdout.strip()) if result.returncode == 0 else os.getpid()

            except (subprocess.TimeoutExpired, FileNotFoundError, ValueError):

                process_id = os.getpid()

            

            try:

                process = psutil.Process(process_id)

                app_name = process.name()

                app_path = process.exe()

                current_directory = Path(process.cwd())

            except (psutil.AccessDenied, psutil.NoSuchProcess):

                app_name = "Unknown"

                app_path = None

                current_directory = Path.cwd()

        except Exception as e:

            logging.warning(f"Error capturing Linux context: {e}")

            return ContextCapture._create_default_context()

        

        context = DevelopmentContext(

            window_title=window_title,

            app_name=app_name,

            app_path=app_path,

            process_id=process_id,

            current_directory=current_directory,

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=ContextCapture._detect_ide_type(app_name, app_path),

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

        

        ContextCapture._enhance_context(context)

        return context

    

    @staticmethod

    def _create_default_context() -> DevelopmentContext:

        """Create a default context when platform-specific capture fails."""

        return DevelopmentContext(

            window_title="Unknown",

            app_name="Unknown",

            app_path=None,

            process_id=os.getpid(),

            current_directory=Path.cwd(),

            workspace_root=None,

            open_files=[],

            git_repository=None,

            git_branch=None,

            git_status=None,

            ide_type=None,

            ide_version=None,

            active_file=None,

            cursor_position=None,

            python_version=None,

            node_version=None,

            java_version=None,

            virtual_env=None

        )

    

    @staticmethod

    def _detect_ide_type(app_name: str, app_path: Optional[str]) -> Optional[str]:

        """Detect IDE type from application name and path."""

        app_name_lower = app_name.lower()

        

        if 'code' in app_name_lower or (app_path and 'vscode' in app_path.lower()):

            return 'vscode'

        elif 'pycharm' in app_name_lower:

            return 'pycharm'

        elif 'idea' in app_name_lower:

            return 'intellij'

        elif 'webstorm' in app_name_lower:

            return 'webstorm'

        elif 'eclipse' in app_name_lower:

            return 'eclipse'

        elif 'sublime' in app_name_lower:

            return 'sublime'

        elif 'vim' in app_name_lower or 'nvim' in app_name_lower:

            return 'vim'

        elif 'emacs' in app_name_lower:

            return 'emacs'

        

        return None

    

    @staticmethod

    def _enhance_context(context: DevelopmentContext):

        """Enhance context with additional information."""

        if context.current_directory:

            context.workspace_root = ContextCapture._find_workspace_root(

                context.current_directory

            )

            

            git_repo = ContextCapture._find_git_repository(

                context.current_directory

            )

            if git_repo:

                context.git_repository = git_repo

                context.git_branch = ContextCapture._get_git_branch(git_repo)

                context.git_status = ContextCapture._get_git_status(git_repo)

            

            context.virtual_env = ContextCapture._detect_virtual_env(

                context.current_directory

            )

        

        context.python_version = ContextCapture._get_python_version()

        context.node_version = ContextCapture._get_node_version()

        context.java_version = ContextCapture._get_java_version()

    

    @staticmethod

    def _find_workspace_root(start_path: Path) -> Optional[Path]:

        """Find workspace root by looking for common markers."""

        markers = [

            '.git', 'package.json', 'pom.xml', 'build.gradle',

            'Cargo.toml', 'go.mod', 'setup.py', 'pyproject.toml'

        ]

        

        current = start_path

        while current != current.parent:

            for marker in markers:

                if (current / marker).exists():

                    return current

            current = current.parent

        

        return None

    

    @staticmethod

    def _find_git_repository(start_path: Path) -> Optional[Path]:

        """Find Git repository root."""

        current = start_path

        while current != current.parent:

            if (current / '.git').exists():

                return current

            current = current.parent

        return None

    

    @staticmethod

    def _get_git_branch(repo_path: Path) -> Optional[str]:

        """Get current Git branch name."""

        head_file = repo_path / '.git' / 'HEAD'

        if not head_file.exists():

            return None

        

        try:

            with open(head_file, 'r') as f:

                content = f.read().strip()

            

            if content.startswith('ref: refs/heads/'):

                return content[16:]

            else:

                return content[:7]

        except Exception:

            return None

    

    @staticmethod

    def _get_git_status(repo_path: Path) -> Optional[Dict[str, Any]]:

        """Get Git repository status."""

        try:

            result = subprocess.run(

                ['git', 'status', '--porcelain'],

                cwd=repo_path,

                capture_output=True,

                text=True,

                timeout=5

            )

            

            if result.returncode != 0:

                return None

            

            modified = []

            staged = []

            untracked = []

            

            for line in result.stdout.splitlines():

                if len(line) < 3:

                    continue

                

                status = line[:2]

                filename = line[3:]

                

                if status[0] in ['M', 'A', 'D', 'R', 'C']:

                    staged.append(filename)

                if status[1] == 'M':

                    modified.append(filename)

                if status == '??':

                    untracked.append(filename)

            

            return {

                'modified': modified,

                'staged': staged,

                'untracked': untracked,

                'clean': len(modified) == 0 and len(staged) == 0 and len(untracked) == 0

            }

        except Exception:

            return None

    

    @staticmethod

    def _detect_virtual_env(start_path: Path) -> Optional[Path]:

        """Detect Python virtual environment."""

        venv_names = ['venv', '.venv', 'env', '.env', 'virtualenv']

        

        current = start_path

        while current != current.parent:

            for venv_name in venv_names:

                venv_path = current / venv_name

                if venv_path.exists():

                    if (venv_path / 'bin' / 'python').exists() or \

                       (venv_path / 'Scripts' / 'python.exe').exists():

                        return venv_path

            current = current.parent

        

        virtual_env = os.environ.get('VIRTUAL_ENV')

        if virtual_env:

            return Path(virtual_env)

        

        return None

    

    @staticmethod

    def _get_python_version() -> Optional[str]:

        """Get Python version."""

        try:

            result = subprocess.run(

                ['python', '--version'],

                capture_output=True,

                text=True,

                timeout=5

            )

            if result.returncode == 0:

                return result.stdout.strip().split()[1]

        except Exception:

            pass

        return None

    

    @staticmethod

    def _get_node_version() -> Optional[str]:

        """Get Node.js version."""

        try:

            result = subprocess.run(

                ['node', '--version'],

                capture_output=True,

                text=True,

                timeout=5

            )

            if result.returncode == 0:

                return result.stdout.strip()[1:]

        except Exception:

            pass

        return None

    

    @staticmethod

    def _get_java_version() -> Optional[str]:

        """Get Java version."""

        try:

            result = subprocess.run(

                ['java', '-version'],

                capture_output=True,

                text=True,

                timeout=5

            )

            output = result.stderr

            if result.returncode == 0 and output:

                for line in output.splitlines():

                    if 'version' in line.lower():

                        parts = line.split('"')

                        if len(parts) >= 2:

                            return parts[1]

        except Exception:

            pass

        return None



# LLM Backend implementations


class LLMBackend(ABC):

    """Abstract base class for LLM backends."""

    

    def __init__(self, config: LLMConfig):

        self.config = config

        self.logger = logging.getLogger(self.__class__.__name__)

    

    @abstractmethod

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """Generate a response from the LLM."""

        pass

    

    @abstractmethod

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """Generate a response that may include tool calls."""

        pass

    

    @abstractmethod

    def supports_tool_calling(self) -> bool:

        """Check if this backend supports native tool calling."""

        pass



class OpenAIBackend(LLMBackend):

    """Backend for OpenAI API."""

    

    def __init__(self, config: LLMConfig):

        super().__init__(config)

        

        try:

            import openai

            self.client = openai.AsyncOpenAI(

                api_key=config.api_key,

                base_url=config.api_base

            )

        except ImportError:

            raise ImportError("openai package required. Install with: pip install openai")

    

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """Generate response using OpenAI API."""

        openai_messages = [

            {'role': msg.role, 'content': msg.content}

            for msg in messages

        ]

        

        params = {

            'model': self.config.model_name,

            'messages': openai_messages,

            'temperature': self.config.temperature,

            'max_tokens': self.config.max_tokens,

            'stream': stream

        }

        

        if tools:

            params['tools'] = tools

            params['tool_choice'] = 'auto'

        

        if stream:

            response = await self.client.chat.completions.create(**params)

            async for chunk in response:

                if chunk.choices[0].delta.content:

                    yield chunk.choices[0].delta.content

        else:

            response = await self.client.chat.completions.create(**params)

            yield response.choices[0].message.content

    

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """Generate response with tool calling support."""

        openai_messages = [

            {'role': msg.role, 'content': msg.content}

            for msg in messages

        ]

        

        response = await self.client.chat.completions.create(

            model=self.config.model_name,

            messages=openai_messages,

            tools=tools,

            tool_choice='auto',

            temperature=self.config.temperature,

            max_tokens=self.config.max_tokens

        )

        

        choice = response.choices[0]

        message = choice.message

        

        result = Message(

            role='assistant',

            content=message.content or ''

        )

        

        if message.tool_calls:

            result.tool_calls = [

                {

                    'id': tc.id,

                    'type': tc.type,

                    'function': {

                        'name': tc.function.name,

                        'arguments': tc.function.arguments

                    }

                }

                for tc in message.tool_calls

            ]

        

        return result

    

    def supports_tool_calling(self) -> bool:

        return True



class LocalLlamaBackend(LLMBackend):

    """Backend for local LLaMA models using llama-cpp-python."""

    

    def __init__(self, config: LLMConfig):

        super().__init__(config)

        

        try:

            from llama_cpp import Llama

        except ImportError:

            raise ImportError("llama-cpp-python required. Install with appropriate backend.")

        

        device = self._detect_device() if config.device == 'auto' else config.device

        

        llama_params = {

            'model_path': config.model_path,

            'n_ctx': config.context_length,

            'verbose': False

        }

        

        if device in ['cuda', 'rocm']:

            llama_params['n_gpu_layers'] = config.gpu_layers

        elif device == 'mps':

            llama_params['n_gpu_layers'] = 1

        else:

            llama_params['n_gpu_layers'] = 0

        

        self.model = Llama(**llama_params)

        self.device = device

        self.logger.info(f"Loaded model on {device}")

    

    def _detect_device(self) -> str:

        """Automatically detect the best available device."""

        try:

            import torch

            if torch.cuda.is_available():

                return 'cuda'

        except ImportError:

            pass

        

        if platform.system() == 'Darwin':

            try:

                import torch

                if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():

                    return 'mps'

            except ImportError:

                pass

        

        return 'cpu'

    

    async def generate(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None,

        stream: bool = False

    ) -> AsyncIterator[str]:

        """Generate response using local LLaMA model."""

        prompt = self._messages_to_prompt(messages, tools)

        

        loop = asyncio.get_event_loop()

        

        if stream:

            generator = await loop.run_in_executor(

                None,

                lambda: self.model(

                    prompt,

                    max_tokens=self.config.max_tokens,

                    temperature=self.config.temperature,

                    stream=True

                )

            )

            

            for chunk in generator:

                if 'choices' in chunk and len(chunk['choices']) > 0:

                    text = chunk['choices'][0].get('text', '')

                    if text:

                        yield text

        else:

            response = await loop.run_in_executor(

                None,

                lambda: self.model(

                    prompt,

                    max_tokens=self.config.max_tokens,

                    temperature=self.config.temperature,

                    stream=False

                )

            )

            

            if 'choices' in response and len(response['choices']) > 0:

                yield response['choices'][0]['text']

    

    async def generate_with_tools(

        self,

        messages: List[Message],

        tools: List[Dict[str, Any]]

    ) -> Message:

        """Generate response with simulated tool calling."""

        tool_prompt = self._create_tool_prompt(tools)

        

        enhanced_messages = messages.copy()

        if enhanced_messages and enhanced_messages[0].role == 'system':

            enhanced_messages[0] = Message(

                role='system',

                content=enhanced_messages[0].content + '\n\n' + tool_prompt

            )

        else:

            enhanced_messages.insert(0, Message(

                role='system',

                content=tool_prompt

            ))

        

        prompt = self._messages_to_prompt(enhanced_messages, tools)

        

        loop = asyncio.get_event_loop()

        response = await loop.run_in_executor(

            None,

            lambda: self.model(

                prompt,

                max_tokens=self.config.max_tokens,

                temperature=self.config.temperature,

                stream=False

            )

        )

        

        response_text = response['choices'][0]['text']

        tool_calls = self._parse_tool_calls(response_text)

        

        result = Message(

            role='assistant',

            content=response_text

        )

        

        if tool_calls:

            result.tool_calls = tool_calls

        

        return result

    

    def _messages_to_prompt(

        self,

        messages: List[Message],

        tools: Optional[List[Dict[str, Any]]] = None

    ) -> str:

        """Convert messages to a prompt string."""

        prompt_parts = []

        

        for msg in messages:

            if msg.role == 'system':

                prompt_parts.append(f"System: {msg.content}")

            elif msg.role == 'user':

                prompt_parts.append(f"User: {msg.content}")

            elif msg.role == 'assistant':

                prompt_parts.append(f"Assistant: {msg.content}")

            elif msg.role == 'tool':

                prompt_parts.append(f"Tool Result ({msg.name}): {msg.content}")

        

        prompt_parts.append("Assistant:")

        return '\n\n'.join(prompt_parts)

    

    def _create_tool_prompt(self, tools: List[Dict[str, Any]]) -> str:

        """Create a prompt that describes available tools."""

        tool_descriptions = []

        

        for tool in tools:

            func = tool.get('function', {})

            name = func.get('name', '')

            description = func.get('description', '')

            parameters = func.get('parameters', {})

            

            tool_desc = f"Tool: {name}\nDescription: {description}\nParameters: {json.dumps(parameters, indent=2)}"

            tool_descriptions.append(tool_desc)

        

        tools_text = '\n\n'.join(tool_descriptions)

        

        return f"""You have access to the following tools:


{tools_text}


To use a tool, respond with a JSON object in this format:

{{

    "tool_call": {{

        "name": "tool_name",

        "arguments": {{

            "param1": "value1"

        }}

    }}

}}"""

    

    def _parse_tool_calls(self, response_text: str) -> Optional[List[Dict[str, Any]]]:

        """Parse tool calls from model response."""

        json_pattern = r'\{[^{}]*"tool_call"[^{}]*\{[^{}]*\}[^{}]*\}'

        matches = re.finditer(json_pattern, response_text, re.DOTALL)

        

        tool_calls = []

        

        for match in matches:

            try:

                data = json.loads(match.group())

                if 'tool_call' in data:

                    tool_call = data['tool_call']

                    tool_calls.append({

                        'id': f"call_{len(tool_calls)}",

                        'type': 'function',

                        'function': {

                            'name': tool_call.get('name', ''),

                            'arguments': json.dumps(tool_call.get('arguments', {}))

                        }

                    })

            except json.JSONDecodeError:

                continue

        

        return tool_calls if tool_calls else None

    

    def supports_tool_calling(self) -> bool:

        return False



class LLMFactory:

    """Factory for creating appropriate LLM backend."""

    

    @staticmethod

    def create_backend(config: LLMConfig) -> LLMBackend:

        """Create and return the appropriate LLM backend."""

        backend_type = config.backend_type.lower()

        

        if backend_type == 'openai':

            return OpenAIBackend(config)

        elif backend_type in ['local_llama', 'llama']:

            return LocalLlamaBackend(config)

        else:

            raise ValueError(f"Unknown backend type: {backend_type}")



# Tool framework


@dataclass

class ToolParameter:

    """Specification for a tool parameter."""

    name: str

    type: str

    description: str

    required: bool = True

    enum: Optional[List[Any]] = None

    default: Optional[Any] = None



@dataclass

class ToolSpec:

    """Complete specification for a tool."""

    name: str

    description: str

    parameters: List[ToolParameter]

    category: str

    

    def to_openai_format(self) -> Dict[str, Any]:

        """Convert to OpenAI function calling format."""

        properties = {}

        required = []

        

        for param in self.parameters:

            param_spec = {

                'type': param.type,

                'description': param.description

            }

            

            if param.enum:

                param_spec['enum'] = param.enum

            

            properties[param.name] = param_spec

            

            if param.required:

                required.append(param.name)

        

        return {

            'type': 'function',

            'function': {

                'name': self.name,

                'description': self.description,

                'parameters': {

                    'type': 'object',

                    'properties': properties,

                    'required': required

                }

            }

        }



class Tool(ABC):

    """Abstract base class for all tools."""

    

    def __init__(self):

        self.spec = self._create_spec()

        self.logger = logging.getLogger(f"Tool.{self.spec.name}")

    

    @abstractmethod

    def _create_spec(self) -> ToolSpec:

        """Create and return the tool specification."""

        pass

    

    @abstractmethod

    async def execute(self, **kwargs) -> Dict[str, Any]:

        """Execute the tool with given parameters."""

        pass

    

    def validate_parameters(self, params: Dict[str, Any]) -> tuple:

        """Validate that provided parameters match the specification."""

        for param in self.spec.parameters:

            if param.required and param.name not in params:

                return False, f"Missing required parameter: {param.name}"

        

        for param_name, param_value in params.items():

            param_spec = next(

                (p for p in self.spec.parameters if p.name == param_name),

                None

            )

            

            if not param_spec:

                return False, f"Unknown parameter: {param_name}"

            

            if not self._check_type(param_value, param_spec.type):

                return False, f"Parameter {param_name} has wrong type"

            

            if param_spec.enum and param_value not in param_spec.enum:

                return False, f"Parameter {param_name} must be one of {param_spec.enum}"

        

        return True, None

    

    def _check_type(self, value: Any, expected_type: str) -> bool:

        """Check if value matches expected type."""

        if expected_type == 'string':

            return isinstance(value, str)

        elif expected_type == 'number':

            return isinstance(value, (int, float))

        elif expected_type == 'boolean':

            return isinstance(value, bool)

        elif expected_type == 'array':

            return isinstance(value, list)

        elif expected_type == 'object':

            return isinstance(value, dict)

        return True



# Concrete tool implementations


class GitInitTool(Tool):

    """Tool for initializing a Git repository."""

    

    def _create_spec(self) -> ToolSpec:

        return ToolSpec(

            name='git_init',

            description='Initialize a new Git repository',

            parameters=[

                ToolParameter(

                    name='path',

                    type='string',

                    description='Path where the repository should be initialized',

                    required=True

                ),

                ToolParameter(

                    name='initial_branch',

                    type='string',

                    description='Name of the initial branch',

                    required=False,

                    default='main'

                )

            ],

            category='git'

        )

    

    async def execute(self, **kwargs) -> Dict[str, Any]:

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

        

        path = Path(kwargs['path'])

        initial_branch = kwargs.get('initial_branch', 'main')

        

        if not path.exists():

            try:

                path.mkdir(parents=True, exist_ok=True)

            except Exception as e:

                return {'success': False, 'error': f"Failed to create directory: {str(e)}"}

        

        if (path / '.git').exists():

            return {'success': False, 'error': 'Directory is already a Git repository'}

        

        try:

            result = await asyncio.create_subprocess_exec(

                'git', 'init', '-b', initial_branch,

                cwd=str(path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            stdout, stderr = await result.communicate()

            

            if result.returncode == 0:

                self.logger.info(f"Initialized Git repository at {path}")

                return {

                    'success': True,

                    'message': f'Initialized Git repository in {path}',

                    'branch': initial_branch

                }

            else:

                return {'success': False, 'error': stderr.decode('utf-8')}

        except Exception as e:

            return {'success': False, 'error': f'Failed to execute git init: {str(e)}'}



class GitCommitTool(Tool):

    """Tool for committing changes to Git."""

    

    def _create_spec(self) -> ToolSpec:

        return ToolSpec(

            name='git_commit',

            description='Stage and commit changes to the Git repository',

            parameters=[

                ToolParameter(

                    name='repository_path',

                    type='string',

                    description='Path to the Git repository',

                    required=True

                ),

                ToolParameter(

                    name='message',

                    type='string',

                    description='Commit message',

                    required=True

                ),

                ToolParameter(

                    name='add_all',

                    type='boolean',

                    description='Stage all changes',

                    required=False,

                    default=False

                )

            ],

            category='git'

        )

    

    async def execute(self, **kwargs) -> Dict[str, Any]:

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

        

        repo_path = Path(kwargs['repository_path'])

        message = kwargs['message']

        add_all = kwargs.get('add_all', False)

        

        if not (repo_path / '.git').exists():

            return {'success': False, 'error': f'{repo_path} is not a Git repository'}

        

        try:

            # Stage files

            add_cmd = ['git', 'add', '-A'] if add_all else ['git', 'add', '-u']

            

            result = await asyncio.create_subprocess_exec(

                *add_cmd,

                cwd=str(repo_path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            await result.communicate()

            

            if result.returncode != 0:

                return {'success': False, 'error': 'Failed to stage files'}

            

            # Commit

            result = await asyncio.create_subprocess_exec(

                'git', 'commit', '-m', message,

                cwd=str(repo_path),

                stdout=asyncio.subprocess.PIPE,

                stderr=asyncio.subprocess.PIPE

            )

            

            stdout, stderr = await result.communicate()

            

            if result.returncode != 0:

                if 'nothing to commit' in stderr.decode('utf-8').lower():

                    return {

                        'success': True,

                        'message': 'Nothing to commit, working tree clean',

                        'committed': False

                    }

                else:

                    return {'success': False, 'error': f'Commit failed: {stderr.decode("utf-8")}'}

            

            self.logger.info(f"Committed changes in {repo_path}")

            return {

                'success': True,

                'message': 'Changes committed successfully',

                'committed': True

            }

        except Exception as e:

            return {'success': False, 'error': f'Git operation failed: {str(e)}'}



class FileReadTool(Tool):

    """Tool for reading file contents."""

    

    def _create_spec(self) -> ToolSpec:

        return ToolSpec(

            name='read_file',

            description='Read the contents of a file',

            parameters=[

                ToolParameter(

                    name='file_path',

                    type='string',

                    description='Path to the file to read',

                    required=True

                )

            ],

            category='system'

        )

    

    async def execute(self, **kwargs) -> Dict[str, Any]:

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

        

        file_path = Path(kwargs['file_path'])

        

        if not file_path.exists():

            return {'success': False, 'error': f'File does not exist: {file_path}'}

        

        try:

            with open(file_path, 'r', encoding='utf-8') as f:

                content = f.read()

            

            self.logger.info(f"Read file: {file_path}")

            return {

                'success': True,

                'content': content,

                'file_path': str(file_path)

            }

        except Exception as e:

            return {'success': False, 'error': f'Error reading file: {str(e)}'}



class FileWriteTool(Tool):

    """Tool for writing content to a file."""

    

    def _create_spec(self) -> ToolSpec:

        return ToolSpec(

            name='write_file',

            description='Write content to a file',

            parameters=[

                ToolParameter(

                    name='file_path',

                    type='string',

                    description='Path to the file to write',

                    required=True

                ),

                ToolParameter(

                    name='content',

                    type='string',

                    description='Content to write to the file',

                    required=True

                )

            ],

            category='system'

        )

    

    async def execute(self, **kwargs) -> Dict[str, Any]:

        is_valid, error = self.validate_parameters(kwargs)

        if not is_valid:

            return {'success': False, 'error': error}

        

        file_path = Path(kwargs['file_path'])

        content = kwargs['content']

        

        try:

            file_path.parent.mkdir(parents=True, exist_ok=True)

            

            with open(file_path, 'w', encoding='utf-8') as f:

                f.write(content)

            

            self.logger.info(f"Wrote file: {file_path}")

            return {

                'success': True,

                'message': f'File written successfully: {file_path}',

                'file_path': str(file_path)

            }

        except Exception as e:

            return {'success': False, 'error': f'Error writing file: {str(e)}'}



class ToolRegistry:

    """Registry for managing available tools."""

    

    def __init__(self):

        self.tools: Dict[str, Tool] = {}

        self.logger = logging.getLogger('ToolRegistry')

        self._register_default_tools()

    

    def _register_default_tools(self):

        """Register all default tools."""

        self.register(GitInitTool())

        self.register(GitCommitTool())

        self.register(FileReadTool())

        self.register(FileWriteTool())

        

        self.logger.info(f"Registered {len(self.tools)} default tools")

    

    def register(self, tool: Tool):

        """Register a new tool."""

        self.tools[tool.spec.name] = tool

        self.logger.debug(f"Registered tool: {tool.spec.name}")

    

    def get_tool(self, name: str) -> Optional[Tool]:

        """Get a tool by name."""

        return self.tools.get(name)

    

    def get_all_specs(self) -> List[Dict[str, Any]]:

        """Get specifications for all registered tools."""

        return [tool.spec.to_openai_format() for tool in self.tools.values()]



class ToolExecutor:

    """Executes tools based on LLM tool calls."""

    

    def __init__(self, registry: ToolRegistry):

        self.registry = registry

        self.logger = logging.getLogger('ToolExecutor')

    

    async def execute_tool_call(self, tool_call: Dict[str, Any]) -> Dict[str, Any]:

        """Execute a single tool call."""

        function_data = tool_call.get('function', {})

        tool_name = function_data.get('name')

        

        if not tool_name:

            return {'success': False, 'error': 'Tool name not specified'}

        

        tool = self.registry.get_tool(tool_name)

        

        if not tool:

            return {'success': False, 'error': f'Unknown tool: {tool_name}'}

        

        try:

            arguments_str = function_data.get('arguments', '{}')

            if isinstance(arguments_str, str):

                arguments = json.loads(arguments_str)

            else:

                arguments = arguments_str

        except json.JSONDecodeError as e:

            return {'success': False, 'error': f'Invalid arguments JSON: {str(e)}'}

        

        try:

            self.logger.info(f"Executing tool: {tool_name}")

            result = await tool.execute(**arguments)

            return result

        except Exception as e:

            self.logger.error(f"Tool execution failed: {e}", exc_info=True)

            return {'success': False, 'error': f'Tool execution failed: {str(e)}'}

    

    async def execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:

        """Execute multiple tool calls in sequence."""

        results = []

        

        for tool_call in tool_calls:

            result = await self.execute_tool_call(tool_call)

            results.append({

                'tool_call_id': tool_call.get('id'),

                'result': result

            })

        

        return results



# Agentic assistant


class AgenticAssistant:

    """Main agentic assistant that coordinates LLM, tools, and workflows."""

    

    def __init__(

        self,

        llm_backend: LLMBackend,

        tool_registry: ToolRegistry,

        max_iterations: int = 10

    ):

        self.llm = llm_backend

        self.registry = tool_registry

        self.executor = ToolExecutor(tool_registry)

        self.max_iterations = max_iterations

        self.conversation_history: List[Message] = []

        self.logger = logging.getLogger('AgenticAssistant')

    

    async def process_request(

        self,

        user_request: str,

        context: DevelopmentContext

    ) -> str:

        """Process a user request with full agentic capabilities."""

        system_message = self._create_system_message(context)

        self.conversation_history = [system_message]

        

        self.conversation_history.append(Message(

            role='user',

            content=user_request

        ))

        

        self.logger.info(f"Processing request: {user_request}")

        

        iteration = 0

        while iteration < self.max_iterations:

            iteration += 1

            self.logger.debug(f"Agent iteration {iteration}/{self.max_iterations}")

            

            tools = self.registry.get_all_specs()

            

            if self.llm.supports_tool_calling():

                response = await self.llm.generate_with_tools(

                    self.conversation_history,

                    tools

                )

            else:

                response = await self.llm.generate_with_tools(

                    self.conversation_history,

                    tools

                )

            

            self.conversation_history.append(response)

            

            if response.tool_calls:

                self.logger.info(f"Executing {len(response.tool_calls)} tool calls")

                tool_results = await self.executor.execute_tool_calls(response.tool_calls)

                

                for tool_result in tool_results:

                    self.conversation_history.append(Message(

                        role='tool',

                        content=json.dumps(tool_result['result']),

                        tool_call_id=tool_result['tool_call_id'],

                        name=self._get_tool_name_from_call_id(

                            response.tool_calls,

                            tool_result['tool_call_id']

                        )

                    ))

                

                continue

            else:

                self.logger.info("Request completed")

                return response.content

        

        return "I apologize, but I was unable to complete the task within the allowed number of steps."

    

    def _create_system_message(self, context: DevelopmentContext) -> Message:

        """Create a system message that includes context information."""

        context_info = f"""You are an AI development assistant helping a developer with their workflow.


Current Context:

- Application: {context.app_name}

- Current Directory: {context.current_directory or 'Unknown'}

"""

        

        if context.git_repository:

            context_info += f"- Git Repository: {context.git_repository}\n"

            context_info += f"- Current Branch: {context.git_branch or 'Unknown'}\n"

        

        if context.ide_type:

            context_info += f"- IDE: {context.ide_type}\n"

        

        context_info += """

You have access to various tools to help the developer. Use these tools to accomplish the requested tasks.

Always explain what you're doing and why. If a task cannot be completed, explain the reason clearly.

"""

        

        return Message(

            role='system',

            content=context_info

        )

    

    def _get_tool_name_from_call_id(

        self,

        tool_calls: List[Dict[str, Any]],

        call_id: str

    ) -> str:

        """Get the tool name from a tool call ID."""

        for call in tool_calls:

            if call.get('id') == call_id:

                return call.get('function', {}).get('name', 'unknown')

        return 'unknown'



# Main application


class DevelopmentAssistant:

    """Main development assistant application."""

    

    def __init__(self, config: AssistantConfig):

        self.config = config

        self.logger = logging.getLogger('DevelopmentAssistant')

        

        # Initialize components

        self.llm_backend = LLMFactory.create_backend(config.llm_config)

        self.tool_registry = ToolRegistry()

        self.assistant = AgenticAssistant(

            self.llm_backend,

            self.tool_registry,

            max_iterations=config.max_agent_iterations

        )

        

        self.logger.info("Development assistant initialized")

    

    async def handle_activation(self):

        """Handle hotkey activation."""

        context = ContextCapture.get_current_context()

        

        print("\n" + "="*60)

        print("Development Assistant Activated")

        print("="*60)

        print(f"\nContext: {context}")

        print("\nWhat can I help you with?")

        print("(Type your request or 'exit' to close)")

        print("-"*60)

        

        user_input = input("> ").strip()

        

        if user_input.lower() in ['exit', 'quit', 'cancel']:

            print("Assistant deactivated.")

            return

        

        if not user_input:

            print("No request provided.")

            return

        

        print("\nProcessing your request...")

        

        try:

            response = await self.assistant.process_request(user_input, context)

            print("\n" + "-"*60)

            print("Response:")

            print("-"*60)

            print(response)

        except Exception as e:

            self.logger.error(f"Error processing request: {e}", exc_info=True)

            print(f"\nError processing request: {str(e)}")

        

        print("\n" + "="*60)

    

    async def run(self):

        """Run the development assistant in interactive mode."""

        print("Development Assistant is running.")

        print("Press Ctrl+C to exit.")

        

        try:

            while True:

                await self.handle_activation()

                print("\nPress Enter to continue or Ctrl+C to exit...")

                input()

        except KeyboardInterrupt:

            print("\nShutting down...")



async def main():

    """Main entry point."""

    # Set up logging

    setup_logging()

    

    # Create configuration

    # For demonstration, using a simple local model configuration

    # In production, load from config file

    llm_config = LLMConfig(

        backend_type='local_llama',

        model_name='llama-2-7b',

        model_path='/path/to/model.gguf',  # Update with actual path

        device='auto',

        temperature=0.7,

        max_tokens=2048

    )

    

    # For OpenAI, use:

    # llm_config = LLMConfig(

    #     backend_type='openai',

    #     model_name='gpt-4',

    #     api_key='your-api-key-here'

    # )

    

    config = AssistantConfig(

        llm_config=llm_config,

        hotkey='ctrl+shift+a',

        max_agent_iterations=10

    )

    

    # Create and run assistant

    assistant = DevelopmentAssistant(config)

    await assistant.run()



if __name__ == '__main__':

    asyncio.run(main())


This completes the full production-ready implementation of the LLM-powered development assistant. The system includes all necessary components for a functional assistant that can be deployed and used in real development workflows. The implementation provides comprehensive error handling, logging, context awareness, tool execution, and agentic capabilities while supporting multiple LLM backends and GPU architectures.

To deploy this system, developers would need to install the required dependencies, configure the LLM backend with appropriate model paths or API keys, and run the main script. The assistant will then be available to help with development tasks through natural language commands, automating Git operations, file management, and other development workflows while maintaining full transparency and control over all actions performed.