import { LLMToolCall } from "../grok/client.js"; import type { ChatCompletionContentPart } from "openai/resources/chat/completions.js"; import { ToolResult } from "../types/index.js"; import { EventEmitter } from "events"; import { SessionState } from "../utils/chat-history-manager.js"; /** * Represents a single entry in the conversation history. * Supports various message types including user messages, assistant responses, * tool calls, tool results, and system messages. */ export interface ChatEntry { type: "user" | "assistant" | "tool_result" | "tool_call" | "system"; content?: string | ChatCompletionContentPart[]; originalContent?: string | ChatCompletionContentPart[]; timestamp: Date; tool_calls?: LLMToolCall[]; toolCall?: LLMToolCall; toolResult?: { success: boolean; output?: string; error?: string; displayOutput?: string; }; isStreaming?: boolean; preserveFormatting?: boolean; metadata?: { rephrased_note?: string; [key: string]: any; }; } /** * Represents a chunk of data in the streaming response. * Used for real-time communication between the agent and UI components. */ export interface StreamingChunk { type: "content" | "tool_calls" | "tool_result" | "done" | "token_count" | "user_message"; content?: string; tool_calls?: LLMToolCall[]; toolCall?: LLMToolCall; toolResult?: ToolResult; tokenCount?: number; userEntry?: ChatEntry; systemMessages?: ChatEntry[]; } /** * Main LLM Agent class that orchestrates AI conversations with tool execution capabilities. * * ## Architecture Overview * * The LLMAgent serves as the central coordinator for AI-powered conversations, managing: * - **Conversation Flow**: Handles user messages, AI responses, and multi-turn conversations * - **Tool Execution**: Coordinates with various tools (file editing, shell commands, web search, etc.) * - **Context Management**: Tracks conversation history and manages token limits * - **Session State**: Maintains persona, mood, active tasks, and other session data * - **Streaming Support**: Provides real-time response streaming for better UX * * ## Delegation Architecture * * The agent delegates specialized functionality to focused manager classes: * - **ToolExecutor**: Handles all tool execution, validation, and approval workflows * - **HookManager**: Manages persona/mood/task hooks and backend testing * - **SessionManager**: Handles session persistence and state restoration * - **MessageProcessor**: Processes user input, handles rephrasing, and XML parsing * - **ContextManager**: Manages context warnings, compaction, and token tracking * * ## Key Features * * - **Multi-Model Support**: Works with various LLM backends (Grok, OpenAI, etc.) * - **Tool Integration**: Seamlessly integrates with 15+ built-in tools * - **MCP Support**: Extends capabilities via Model Context Protocol servers * - **Vision Support**: Handles image inputs for vision-capable models * - **Streaming Responses**: Real-time response generation with token counting * - **Context Awareness**: Intelligent context management and automatic compaction * - **Hook System**: Extensible hook system for custom behaviors * - **Session Persistence**: Maintains conversation state across restarts * * ## Usage Patterns * * ```typescript * // Initialize agent * const agent = new LLMAgent(apiKey, baseURL, model); * await agent.initialize(); * * // Process messages (non-streaming) * const entries = await agent.processUserMessage("Hello, world!"); * * // Process messages (streaming) * for await (const chunk of agent.processUserMessageStream("Write a file")) { * console.log(chunk); * } * * // Manage session state * await agent.setPersona("helpful assistant"); * await agent.startActiveTask("coding", "writing tests"); * ``` * * @extends EventEmitter Emits 'contextChange' events for token usage updates */ export declare class LLMAgent extends EventEmitter { private llmClient; private textEditor; private morphEditor; private zsh; private confirmationTool; private search; private env; private introspect; private clearCacheTool; private characterTool; private taskTool; private internetTool; private imageTool; private fileConversionTool; private restartTool; private audioTool; private chatHistory; private messages; private tokenCounter; private abortController; private mcpInitialized; private maxToolRounds; private temperature; private maxTokens; private firstMessageProcessed; private persona; private personaColor; private mood; private moodColor; private activeTask; private activeTaskAction; private activeTaskColor; private apiKeyEnvVar; private pendingContextEditSession; private rephraseState; private toolExecutor; private hookManager; private sessionManager; private messageProcessor; private contextManager; private maxContextSize; /** * Cleans up incomplete tool calls in the message history. * Ensures all tool calls have corresponding tool results to prevent API errors. * * This method scans the last assistant message for tool calls and adds * "[Cancelled by user]" results for any tool calls that don't have results. * * @private */ private cleanupIncompleteToolCalls; /** * Executes the instance hook if it hasn't been run yet. * * The instance hook runs once per agent session and can: * - Set prompt variables * - Add system messages * - Provide prefill text for responses * * @private */ private executeInstanceHookIfNeeded; /** * Creates a new LLMAgent instance. * * @param apiKey - API key for the LLM service * @param baseURL - Optional base URL for the API endpoint * @param model - Optional model name (defaults to saved model or "grok-code-fast-1") * @param maxToolRounds - Maximum number of tool execution rounds (default: 400) * @param debugLogFile - Optional path for MCP debug logging * @param startupHookOutput - Optional output from startup hook execution * @param temperature - Optional temperature for API requests (0.0-2.0) * @param maxTokens - Optional maximum tokens for API responses */ constructor(apiKey: string, baseURL?: string, model?: string, maxToolRounds?: number, debugLogFile?: string, startupHookOutput?: string, temperature?: number, maxTokens?: number); private startupHookOutput?; private systemPrompt; private hasRunInstanceHook; /** * Initialize the agent with dynamic system prompt. * * This method must be called after construction to: * - Build the system message with current tool availability * - Set up the initial conversation context * - Execute the instance hook if configured * * @throws {Error} If system message generation fails */ initialize(): Promise; /** * Build/rebuild the system message with current tool availability. * * This method: * - Generates a dynamic tool list using the introspect tool * - Sets the APP:TOOLS variable for template rendering * - Renders the full SYSTEM template with all variables * - Updates messages[0] with the new system prompt * * The system prompt is always at messages[0] and contains the core * instructions, tool descriptions, and current context information. */ buildSystemMessage(): Promise; /** * Render system message with current variable state * Called before LLM API calls and task processing to ensure fresh content */ renderSystemMessage(): void; /** * Load initial conversation history from persistence. * * This method: * - Loads the chat history (excluding system messages) * - Sets or generates the system prompt * - Converts history to API message format * - Handles tool call/result matching * - Updates token counts * * @param history - Array of chat entries to load * @param systemPrompt - Optional system prompt (will generate if not provided) */ loadInitialHistory(history: ChatEntry[], systemPrompt?: string): Promise; /** * Initialize Model Context Protocol (MCP) servers in the background. * * This method loads MCP configuration and initializes any configured * servers without blocking agent construction. Errors are logged but * don't prevent agent operation. * * @param debugLogFile - Optional path for MCP debug output * @private */ private initializeMCP; /** * Checks if the current model is a Grok model. * Used to enable Grok-specific features like web search. * * @returns True if the current model name contains "grok" * @private */ private isGrokModel; /** * Heuristic to determine if web search should be enabled for a message. * * Analyzes the message content for keywords that suggest the user is * asking for current information, news, or time-sensitive data. * * @param message - The user message to analyze * @returns True if web search should be enabled * @private */ private shouldUseSearchFor; /** * Process a user message and return all conversation entries generated. * * This is the main non-streaming message processing method that: * - Handles rephrase commands and message preprocessing * - Manages the agent loop with tool execution * - Processes multiple rounds of AI responses and tool calls * - Handles errors and context management * - Returns all new conversation entries * * ## Processing Flow * * 1. **Setup**: Parse rephrase commands, clean incomplete tool calls * 2. **Message Processing**: Parse images, assemble content, add to history * 3. **Agent Loop**: Continue until no more tool calls or max rounds reached * - Get AI response * - Execute any tool calls * - Add results to conversation * - Get next response if needed * 4. **Cleanup**: Handle errors, update context, return entries * * @param message - The user message to process * @returns Promise resolving to array of new conversation entries * @throws {Error} If message processing fails critically */ processUserMessage(message: string): Promise; /** * Process a user message with real-time streaming response. * * This is the main streaming message processing method that yields * chunks of data as the conversation progresses. Provides real-time * updates for: * - User message processing * - AI response streaming (content as it's generated) * - Tool execution progress * - Token count updates * - System messages from hooks * * ## Streaming Flow * * 1. **Setup**: Process user message, yield user entry * 2. **Agent Loop**: Stream AI responses and execute tools * - Stream AI response content in real-time * - Yield tool calls when detected * - Execute tools and yield results * - Continue until completion * 3. **Completion**: Yield final token counts and done signal * * ## Chunk Types * * - `user_message`: Initial user message entry * - `content`: Streaming AI response content * - `tool_calls`: Tool calls detected in AI response * - `tool_result`: Results from tool execution * - `token_count`: Updated token usage * - `done`: Processing complete * * @param message - The user message to process * @yields StreamingChunk objects with real-time updates * @throws {Error} If streaming fails critically */ processUserMessageStream(message: string): AsyncGenerator; /** * Apply default parameter values for tools * This ensures the approval hook sees the same parameters that will be used during execution */ /** * Validate tool arguments against the tool's schema * Returns null if valid, or an error message if invalid */ /** * Get a copy of the current chat history. * @returns Array of chat entries (defensive copy) */ getChatHistory(): ChatEntry[]; /** * Set the chat history to a new array of entries. * @param history - New chat history entries */ setChatHistory(history: ChatEntry[]): void; /** * Get the current system prompt. * @returns The system prompt string */ getSystemPrompt(): string; /** * Set a new system prompt and update the first message. * @param prompt - Ignored (deprecated) - system prompt is always rendered from variables */ setSystemPrompt(prompt: string): void; /** * Get a copy of the current API messages array. * @returns Array of LLM messages (defensive copy) */ getMessages(): any[]; /** * Get the current token count for the conversation. * @returns Number of tokens in the current message context */ getCurrentTokenCount(): number; /** * Get the maximum context size for the current model. * @returns Maximum number of tokens supported * @todo Make this model-specific for different context windows */ getMaxContextSize(): number; /** * Get the current context usage as a percentage. * @returns Percentage of context window used (0-100) */ getContextUsagePercent(): number; /** * Convert the conversation context to markdown format for viewing. * * Creates a human-readable markdown representation of the conversation * including: * - Header with context file path and token usage * - Numbered messages with timestamps * - Formatted tool calls and results * - Proper attribution (User/Assistant/System) * * Format: (N) Name (role) - timestamp * * @returns Promise resolving to markdown-formatted conversation */ convertContextToMarkdown(): Promise; /** * Get the current persona setting. * @returns Current persona string */ getPersona(): string; /** * Get the current persona display color. * @returns Color name for persona display */ getPersonaColor(): string; /** * Get the current mood setting. * @returns Current mood string */ getMood(): string; /** * Get the current mood display color. * @returns Color name for mood display */ getMoodColor(): string; /** * Get the current active task. * @returns Current active task string */ getActiveTask(): string; /** * Get the current active task action. * @returns Current task action string */ getActiveTaskAction(): string; /** * Get the current active task display color. * @returns Color name for task display */ getActiveTaskColor(): string; /** * Set a pending context edit session for file-based context editing. * @param tmpJsonPath - Path to temporary JSON file * @param contextFilePath - Path to actual context file */ setPendingContextEditSession(tmpJsonPath: string, contextFilePath: string): void; /** * Get the current pending context edit session. * @returns Edit session info or null if none pending */ getPendingContextEditSession(): { tmpJsonPath: string; contextFilePath: string; } | null; /** * Clear the pending context edit session. */ clearPendingContextEditSession(): void; /** * Set the rephrase state for message editing operations. * @param originalAssistantMessageIndex - Index of original assistant message * @param rephraseRequestIndex - Index of rephrase request * @param newResponseIndex - Index of new response (-1 if not yet created) * @param messageType - Type of message being rephrased * @param prefillText - Optional prefill text for the response */ setRephraseState(originalAssistantMessageIndex: number, rephraseRequestIndex: number, newResponseIndex: number, messageType: "user" | "system", prefillText?: string): void; /** * Get the current rephrase state. * @returns Rephrase state info or null if none active */ getRephraseState(): { originalAssistantMessageIndex: number; rephraseRequestIndex: number; newResponseIndex: number; messageType: "user" | "system"; prefillText?: string; } | null; /** * Clear the current rephrase state. */ clearRephraseState(): void; /** * Set the agent's persona with optional color. * * Executes the persona hook if configured and updates the agent's * persona state on success. * * @param persona - The persona description * @param color - Optional display color (defaults to "white") * @returns Promise resolving to success/error result */ setPersona(persona: string, color?: string): Promise<{ success: boolean; error?: string; }>; /** * Set the agent's mood with optional color. * * Executes the mood hook if configured and updates the agent's * mood state on success. * * @param mood - The mood description * @param color - Optional display color (defaults to "white") * @returns Promise resolving to success/error result */ setMood(mood: string, color?: string): Promise<{ success: boolean; error?: string; }>; /** * Start an active task with specified action and color. * * Executes the task start hook if configured and updates the agent's * task state on success. * * @param activeTask - The task description * @param action - The current action within the task * @param color - Optional display color (defaults to "white") * @returns Promise resolving to success/error result */ startActiveTask(activeTask: string, action: string, color?: string): Promise<{ success: boolean; error?: string; }>; /** * Transition the active task to a new action/status. * * Updates the current task action without changing the task itself. * * @param action - The new action description * @param color - Optional display color (defaults to current color) * @returns Promise resolving to success/error result */ transitionActiveTaskStatus(action: string, color?: string): Promise<{ success: boolean; error?: string; }>; /** * Stop the current active task with reason and documentation. * * Executes the task stop hook if configured and clears the agent's * task state on success. * * @param reason - Reason for stopping the task * @param documentationFile - Path to documentation file * @param color - Optional display color (defaults to "white") * @returns Promise resolving to success/error result */ stopActiveTask(reason: string, documentationFile: string, color?: string): Promise<{ success: boolean; error?: string; }>; /** * Delegation method for hook processing (used by ToolExecutor). * * Processes hook results through the HookManager to handle commands, * variable transformations, and other hook-specific logic. * * @param hookResult - Result object from hook execution * @param envKey - Optional environment key for variable transformation * @returns Promise resolving to processing result */ processHookResult(hookResult: { commands?: any[]; }, envKey?: string): Promise<{ success: boolean; transformedValue?: string; }>; /** * Execute a shell command through the ZSH tool. * * @param command - Shell command to execute * @param skipConfirmation - Whether to skip confirmation prompts * @returns Promise resolving to tool execution result */ executeCommand(command: string, skipConfirmation?: boolean): Promise; /** * Get the current LLM model name. * @returns Current model identifier */ getCurrentModel(): string; /** * Set a new LLM model and update related components. * * This method: * - Updates the LLM client model * - Resets vision support flag * - Updates the token counter for the new model * - Handles model name suffixes (e.g., :nothinking) * * @param model - New model identifier */ setModel(model: string): void; /** * Get the backend name (e.g., "grok", "openai"). * @returns Backend identifier string */ getBackend(): string; /** * Abort the current operation if one is in progress. * * This will cancel streaming responses and tool execution. */ abortCurrentOperation(): void; /** * Clear the conversation cache and reinitialize the agent. * * This method: * - Backs up current conversation to timestamped files * - Clears chat history and messages * - Resets context warnings and processing flags * - Re-executes startup and instance hooks * - Saves the cleared state * - Emits context change events * * Used when context becomes too large or user requests a fresh start. */ clearCache(): Promise; /** * Get current session state for persistence. * * Collects all session-related state including: * - Model and backend configuration * - Persona, mood, and task settings * - Context usage statistics * - API key environment variable * * @returns Complete session state object */ getSessionState(): SessionState; /** * Restore session state from persistence. * * Restores all session-related state including: * - Model and backend configuration * - Persona, mood, and task settings * - Token counter and API client setup * * @param state - Session state to restore */ restoreSessionState(state: SessionState): Promise; /** * Compact conversation context by keeping system prompt and last N messages. * * Reduces context size when it grows too large for the backend to handle. * Removes older messages while preserving the system prompt and recent context. * * @param keepLastMessages - Number of recent messages to keep (default: 20) * @returns Number of messages removed */ compactContext(keepLastMessages?: number): number; /** * Get all tool instances and their class names for display purposes. * * Uses reflection to find all tool instances and extract their * class names and handled method names for introspection. * * @returns Array of tool info objects with class names and methods */ getToolClassInfo(): Array<{ className: string; methods: string[]; }>; /** * Get all tool instances via reflection. * * Scans all properties of the agent instance to find objects that * implement the tool interface (have getHandledToolNames method). * * @returns Array of tool instances with their class names * @private */ private getToolInstances; }