/** * Agent - The main class for running AI agents with tool use */ import type { LLMProvider, Message, ChatOptions, StreamChunk, ContentBlock } from './providers/types.js'; import type { Tool, ToolDefinition, ToolRegistry, ToolExecutionResult, ToolExecutionContext } from './tools/types.js'; import type { ContextStats, VerbosityLevel, SmartCompactionResult } from './context/types.js'; import type { AgentState, Checkpointer, SessionMetadata } from './state/types.js'; import type { Anchor, AnchorInput, AnchorQueryOptions, AnchorClearOptions, AnchorManagerOptions } from './anchors/types.js'; import type { Guardrail, GuardrailInput, GuardrailResult, GuardrailManagerOptions } from './guardrails/types.js'; import type { ToolPermission, PermissionLevel, PermissionManagerOptions } from './permissions/types.js'; import type { ProjectMemoryOptions, ProjectMemory } from './memory/types.js'; import type { UsageTrackerOptions, UsageStats, BudgetStatus, TokenUsage } from './costs/types.js'; import type { HooksConfig } from './hooks/types.js'; import type { DelegationConfig } from './context/delegation-types.js'; import { PermissionManager } from './permissions/manager.js'; import { ContextManager } from './context/manager.js'; import { FileAccessTracker } from './context/file-tracker.js'; import type { ObservationMaskConfig } from './context/observation-masker.js'; import type { WindowingConfig } from './context/windowing.js'; import type { PruneConfig } from './context/dead-message-pruner.js'; import { AnchorManager } from './anchors/manager.js'; import { GuardrailManager } from './guardrails/manager.js'; import { type RetryConfig } from './utils/index.js'; /** * Event types emitted during agent execution */ export type AgentEvent = { type: 'iteration_start'; iteration: number; } | { type: 'llm_start'; } | { type: 'llm_chunk'; chunk: StreamChunk; } | { type: 'llm_end'; text: string; hasToolUses: boolean; } | { type: 'tool_start'; name: string; input: Record; toolUseId: string; } | { type: 'tool_output'; toolUseId: string; toolName: string; output: string; stream?: 'stdout' | 'stderr'; } | { type: 'tool_end'; name: string; result: ToolExecutionResult; toolUseId: string; } | { type: 'iteration_end'; iteration: number; } | { type: 'done'; response: string; } | { type: 'context_warning'; utilization: number; threshold: number; } | { type: 'context_compacted'; tokensBefore: number; tokensAfter: number; } | { type: 'context_summarized'; tokensBefore: number; tokensAfter: number; rounds: number; } | { type: 'context_overflow'; utilization: number; } | { type: 'subagent_start'; name: string; task: string; } | { type: 'subagent_end'; name: string; result: SubAgentResult; } | { type: 'tool_loop_warning'; toolName: string; consecutiveCalls: number; } | { type: 'tool_loop_nudge'; toolName: string; consecutiveCalls: number; nudgeCount: number; } | { type: 'abort_checkpoint_saved'; sessionId: string; reason: 'aborted' | 'error'; } | { type: 'abort_checkpoint_failed'; error: string; } | { type: 'custom'; name: string; data: unknown; metadata?: Record; } | { type: 'anchor_added'; anchor: Anchor; } | { type: 'anchor_removed'; anchorId: string; } | { type: 'guardrail_triggered'; result: GuardrailResult; } | { type: 'guardrail_blocked'; result: GuardrailResult; message: string; } | { type: 'guardrail_warning'; result: GuardrailResult; message: string; } | { type: 'permission_granted'; toolName: string; level: PermissionLevel; } | { type: 'permission_denied'; toolName: string; level: PermissionLevel; reason?: string; } | { type: 'permission_asked'; toolName: string; level: PermissionLevel; } | { type: 'usage_recorded'; tokens: TokenUsage; model: string; } | { type: 'usage_budget_warning'; status: BudgetStatus; threshold: number; } | { type: 'usage_budget_exceeded'; status: BudgetStatus; } | { type: 'suggest'; action: string; reason?: string; } | { type: 'iteration_limit_reached'; iteration: number; maxIterations: number; } | { type: 'iteration_limit_extended'; newMaxIterations: number; addedIterations: number; } | { type: 'model_changed'; previousModel: string; newModel: string; } | { type: 'llm_retry'; attempt: number; maxAttempts: number; error: string; delayMs: number; provider: string; } | { type: 'llm_retry_exhausted'; attempts: number; error: string; provider: string; }; /** * Event handler function type */ export type AgentEventHandler = (event: AgentEvent) => void; /** * Stream writer for emitting custom events during execution. * * Tools and middleware can use this to stream custom events to the client. * Inspired by LangGraph's get_stream_writer() pattern. * * @example * ```typescript * // In a tool executor: * const writer = agent.getStreamWriter(); * writer('Processing step 1...', { step: 1 }); * // ... do work ... * writer('Processing step 2...', { step: 2 }); * ``` */ export type StreamWriter = (data: unknown, metadata?: Record) => void; /** * Custom event configuration */ export interface CustomEventConfig { /** * Event name (used for filtering/routing) */ name: string; /** * Event data payload */ data: unknown; /** * Optional metadata (preserved through the event pipeline) */ metadata?: Record; } /** * Agent configuration options */ export interface AgentConfig { /** * The LLM provider to use */ provider: LLMProvider; /** * System prompt for the agent */ systemPrompt?: string; /** * Maximum iterations for tool use loops (default: 10) */ maxIterations?: number; /** * Maximum consecutive identical tool calls (same name + input + result) before * the loop detector trips (default: 5). Set to 0 to disable loop detection. * * Detection is result-aware: a repeated call only counts when its result is * also identical to the previous one, so legitimate polling that returns * changing output (e.g. `bash_output` while a build runs) does not trip. */ maxConsecutiveToolCalls?: number; /** * On a loop trip, how many times to inject a corrective "self-heal" message * and let the agent continue before throwing ToolLoopError (default: 1). * Set to 0 to throw immediately on the first trip (no self-heal). * * Ignored when `onToolLoopDetected` is provided (that callback takes over). */ maxToolLoopNudges?: number; /** * Behavior when max iterations is reached (default: 'error'). * - 'error': Throw MaxIterationsError immediately * - 'summarize': Generate a final summary response before throwing * - 'continue': Return partial result without throwing (response will be empty) * * Note: If onIterationLimitReached callback is provided, it takes precedence. */ iterationLimitBehavior?: 'error' | 'summarize' | 'continue'; /** * Callback invoked when the agent reaches its iteration limit. * Allows the caller to decide whether to continue or stop. * * @param context - Information about the current state * @returns Promise resolving to: * - `false` to stop the agent gracefully * - A positive number to continue with that many additional iterations * * If this callback is provided and returns a number, the agent will continue * running with the extended iteration limit. This takes precedence over * iterationLimitBehavior. * * @example * ```typescript * onIterationLimitReached: async ({ iteration, toolCallCount }) => { * const answer = await askUser(`Agent used ${iteration} iterations. Continue?`); * return answer === 'yes' ? 50 : false; // Add 50 more or stop * } * ``` */ onIterationLimitReached?: (context: { iteration: number; maxIterations: number; toolCallCount: number; }) => Promise; /** * Callback when tool loop is detected (same tool called N times with identical input). * * When provided, the agent asks the user instead of throwing ToolLoopError. * Return `true` to continue (reset the counter), `false` to stop. * * When not provided, ToolLoopError is thrown (backwards compatible). * * @example * ```typescript * onToolLoopDetected: async ({ toolName, consecutiveCalls }) => { * const answer = await askUser(`${toolName} called ${consecutiveCalls} times. Continue?`); * return answer === 'yes'; * } * ``` */ onToolLoopDetected?: (context: { toolName: string; consecutiveCalls: number; input: Record; }) => Promise; /** * Chat options (model, temperature, etc.) */ chatOptions?: ChatOptions; /** * Custom tool registry (optional, creates new one if not provided) */ toolRegistry?: ToolRegistry; /** * Default timeout for tool execution in milliseconds (default: 30000 = 30s). * Set to 0 to disable timeout. Only used when toolRegistry is not provided. * Sub-agents inherit this timeout from the parent agent. */ toolTimeoutMs?: number; /** * Context manager for tracking and managing context window usage. * If not provided, context management is disabled. */ contextManager?: ContextManager; /** * Enable automatic context management (compaction, summarization). * Requires contextManager to be set. Default: true when contextManager is provided. */ autoContextManagement?: boolean; /** * Observation masking configuration. Masks old tool results in history to reduce tokens. * Enabled by default when contextManager is provided. Set to `false` to disable. */ observationMask?: Partial | false; /** * Smart windowing configuration. Programmatically compacts old messages when * history exceeds a token budget. Three zones: recent (intact), middle * (truncated), old (event log). Zero LLM calls. * Enabled by default when contextManager is provided. Set to `false` to disable. */ windowing?: Partial | false; /** * Use compact text format for tool results in LLM messages. * Strips JSON wrappers and metadata, reducing token usage. * Enabled by default when contextManager is provided. Set to `false` to disable. */ compactToolResults?: boolean; /** * Dead message pruning configuration. Prunes superseded errors and permission exchanges. * Enabled by default when contextManager is provided. Set to `false` to disable. */ deadMessagePruning?: Partial | false; /** * Event handler for monitoring agent execution */ onEvent?: AgentEventHandler; /** * Configuration for automatic retry on transient LLM errors. * Enabled by default with sensible defaults (10 attempts, exponential backoff). * * Retryable errors include: * - Rate limit (429) * - Server errors (5xx) * - Connection/network errors * - Anthropic overloaded (529) * * @example * ```typescript * const agent = new Agent({ * provider, * retry: { * maxAttempts: 5, // Max 5 retries * baseDelayMs: 2000, // Start with 2s delay * maxDelayMs: 60000, // Cap at 60s * } * }); * ``` */ retry?: RetryConfig; /** * Checkpointer for persisting agent state. * If provided, enables checkpoint() and resume() functionality. */ checkpointer?: Checkpointer; /** * Session ID for this agent instance. * If not provided, a new session ID is generated automatically. */ sessionId?: string; /** * Automatically save state after each run() call. * Requires checkpointer to be set. Default: false. */ autoCheckpoint?: boolean; /** * Save a partial checkpoint when run is aborted or encounters an error. * This allows recovery from interrupted runs. * Requires checkpointer to be set. Default: false. * * Inspired by LangGraph issue #5672: Cancellation causes loss of * streamed state not yet persisted as checkpoint. */ checkpointOnAbort?: boolean; /** * Pin manager options for critical information that survives context compaction. * * Pins are pieces of information that: * - Never get compacted - Survive all context management * - Always re-injected - Present in every LLM call * - Scoped - Session, persistent, or temporary * * @example * ```typescript * const agent = new Agent({ * provider, * pins: { * maxAnchors: 20, * maxTokens: 2000, * persistPath: '~/.myapp/anchors.json', * includeDefaults: true, // Include built-in safety anchors * } * }); * ``` */ pins?: AnchorManagerOptions; /** @deprecated Use `pins` instead */ anchors?: AnchorManagerOptions; /** * Guardrail options for pattern-based safety checks. * * Guardrails scan tool inputs before execution and can: * - warn: Log a warning but proceed * - confirm: Require confirmation (via onTriggered handler) * - block: Prevent execution entirely * * @example * ```typescript * const agent = new Agent({ * provider, * guardrails: { * enabled: true, * includeDefaults: true, // Include built-in guardrails * custom: [ * { * id: 'no-prod', * name: 'Production Protection', * description: 'Block operations on production', * patterns: [/prod/i], * action: 'block', * message: 'Production operations are blocked', * } * ], * onTriggered: async (result, context) => { * // Return true to proceed, false to block * return await askUser(result.guardrail.message); * } * } * }); * ``` */ guardrails?: GuardrailManagerOptions; /** * Permission options for tool-level access control. * * Permissions allow fine-grained control over which tools can execute * and when user approval is required. * * @example * ```typescript * const agent = new Agent({ * provider, * permissions: { * defaultLevel: 'always', // Default: allow all tools * rules: [ * { toolName: 'bash', level: 'once', description: 'Shell commands' }, * { toolName: 'write_file', level: 'session', description: 'File writes' }, * { toolName: 'delete_*', level: 'deny' }, // Wildcard: block all delete tools * ], * onPermissionRequest: async (request) => { * // Return true to allow, false to deny * return await showConfirmDialog( * `Allow ${request.toolName}?`, * request.preview * ); * } * } * }); * ``` */ permissions?: PermissionManagerOptions; /** * Pre-existing PermissionManager instance. * * Use this to share a PermissionManager between agents (e.g., parent and sub-agents). * When provided, takes precedence over `permissions` options. * * This is primarily used internally for sub-agent permission inheritance. */ permissionManager?: PermissionManager; /** * Pre-loaded project memory to prepend to system prompt. * * Use `Agent.createWithMemory()` to automatically load from files, * or pass a pre-loaded ProjectMemory object. * * @example * ```typescript * // Option 1: Use the factory method (recommended) * const agent = await Agent.createWithMemory({ * provider, * systemPrompt: 'You are a helpful assistant.', * projectMemoryOptions: { * providers: ['claude', 'gemini'], * includeGeneric: true, * }, * projectMemoryDir: '/path/to/project', * }); * * // Option 2: Pre-load memory manually * const loader = new ProjectMemoryLoader({ providers: 'claude' }); * const memory = await loader.load('/path/to/project'); * * const agent = new Agent({ * provider, * systemPrompt: 'You are a helpful assistant.', * projectMemory: memory, // Pre-loaded memory * }); * ``` */ projectMemory?: ProjectMemory; /** * Usage tracking options for monitoring token usage. * * Note: We track tokens only, not dollar costs. Pricing changes frequently * and providing potentially inaccurate cost information would be misleading. * * @example * ```typescript * const agent = new Agent({ * provider, * usage: { * enabled: true, * budget: { * maxTotalTokens: 100000, // 100k token limit * warningThreshold: 0.8, // Warn at 80% * }, * }, * }); * * // After runs, check usage * console.log(agent.getUsageStats()); * console.log(agent.getTotalTokens()); * * // Listen for budget events * agent.onEvent((event) => { * if (event.type === 'usage_budget_warning') { * console.log('Budget warning!', event.status); * } * }); * ``` */ usage?: UsageTrackerOptions; /** * Hooks for customizing agent behavior at various lifecycle points. * * Hooks provide extension points without subclassing for: * - Logging and tracing * - Input/output transformation * - Custom validation * - Metrics collection * - Integration with external systems * * @example * ```typescript * const agent = new Agent({ * provider, * hooks: { * beforeTool: [ * async (ctx) => { * console.log(`Tool ${ctx.toolName} starting...`); * } * ], * afterTool: [ * async (ctx) => { * console.log(`Tool ${ctx.toolName} completed in ${ctx.durationMs}ms`); * // Optionally modify result * return { result: ctx.result }; * } * ], * beforeLLM: [ * async (ctx) => { * // Optionally inject or transform messages * return { messages: ctx.messages }; * } * ], * onError: [ * async (ctx) => { * console.error(`Error in ${ctx.phase}:`, ctx.error); * } * ] * } * }); * ``` */ hooks?: HooksConfig; /** * Tool result delegation config. When set and enabled, large tool results * are automatically summarized to conserve context tokens. Full results are * stored in-memory for optional recall via `recall_full_result`. * * @example * ```typescript * const agent = new Agent({ * provider, * delegation: { * enabled: true, * delegationThreshold: 8000, // tokens above which to delegate * strategy: 'auto', // try LLM, fall back to extractive * toolOverrides: { * bash: { threshold: 12000 }, * grep: { threshold: 4000 }, * }, * }, * }); * ``` */ delegation?: DelegationConfig; /** * Enable file access tracking for context restoration hints. * * When enabled, the agent tracks which files were read, referenced (grep/glob), * and modified during execution. After context compaction, these hints are * injected to help the LLM understand what files it previously accessed. * * Requires contextManager to be set (hints are injected after compaction). * * @default false * * @example * ```typescript * const agent = new Agent({ * provider, * contextManager: new ContextManager({ provider }), * enableFileTracking: true, // Automatically track file accesses * }); * * // After compaction, the agent will inject hints like: * // [Context compacted. Previously accessed files:] * // Read (3 files): file1.ts (100 lines), file2.ts (50 lines)... * ``` */ enableFileTracking?: boolean; /** * Options for file context restoration after compaction. * * Controls how file contents are re-injected into the context after * compaction. Small files get their content inlined; large files get * reference-only hints. * * Only applies when `enableFileTracking` is true. */ fileRestoration?: { /** Max total tokens for inline file content after compaction (default: 4000) */ maxInlineTokens?: number; }; /** * Logger instance for structured diagnostic logging. * When omitted, no logging is performed. * * @example * ```typescript * import { createLogger } from '@compilr-dev/logger'; * const log = createLogger({ package: 'my-app' }); * const agent = new Agent({ provider, logger: log }); * ``` */ logger?: import('@compilr-dev/logger').Logger; } /** * Options for a single run */ export interface RunOptions { /** * AbortSignal for cancellation */ signal?: AbortSignal; /** * Override max iterations for this run */ maxIterations?: number; /** * Override chat options for this run */ chatOptions?: ChatOptions; /** * Event handler for this run (in addition to config handler) */ onEvent?: AgentEventHandler; /** * Filter tools for this run. * - If provided, only these tool names will be available * - Reduces token usage by not sending unused tool definitions * - Tools must be registered with the agent * * @example * ```typescript * // Only allow file and search tools for this request * await agent.run(message, { * toolFilter: ['read_file', 'write_file', 'grep', 'glob'], * }); * ``` */ toolFilter?: string[]; /** * Callback to provide additional tool execution context. * Called before each tool execution, allowing the caller to inject * abort signals or other context-specific options. * * @example * ```typescript * // Provide abort signal for bash commands (for Ctrl+B backgrounding) * const bashAbortController = new AbortController(); * await agent.stream(message, { * getToolContext: (toolName, toolUseId) => { * if (toolName === 'bash') { * return { * abortSignal: bashAbortController.signal, * onBackground: (shellId, output) => { ... }, * }; * } * return {}; * }, * }); * ``` */ getToolContext?: (toolName: string, toolUseId: string) => Partial>; } /** * Agent run result */ export interface AgentRunResult { /** * Final text response from the agent */ response: string; /** * All messages in the conversation */ messages: Message[]; /** * Number of iterations (tool use loops) executed */ iterations: number; /** * Tool calls made during execution */ toolCalls: Array<{ name: string; input: Record; result: ToolExecutionResult; }>; /** * Whether the run was aborted */ aborted: boolean; /** * Context statistics (if context manager is enabled) */ contextStats?: ContextStats; } /** * Configuration for creating a sub-agent */ export interface SubAgentConfig { /** * Unique name for this sub-agent */ name: string; /** * Description of what this sub-agent specializes in. * Used for automatic delegation decisions. */ description?: string; /** * System prompt for this sub-agent. * If not provided, inherits from parent. */ systemPrompt?: string; /** * Context mode for this sub-agent. * - 'isolated': Fresh context, no parent history (default) * - 'inherited': Receives a summary of parent context * - 'shared': Full access to parent context (not recommended for large contexts) */ contextMode?: 'isolated' | 'inherited' | 'shared'; /** * Tools available to this sub-agent. * - If not specified, inherits parent's tools * - Can be a subset of parent's tools for safety */ tools?: Tool[]; /** * Maximum iterations for this sub-agent (default: 5) */ maxIterations?: number; /** * Chat options override for this sub-agent */ chatOptions?: ChatOptions; /** * Maximum tokens for this sub-agent's context budget. * Default: 25% of parent's context budget. */ contextBudget?: number; /** * Automatically dispose the sub-agent after each execution. * This releases memory but requires re-creation for subsequent runs. * Default: false (sub-agent persists for reuse) */ autoDispose?: boolean; /** * Maximum size (in bytes) for tool result data returned in SubAgentResult. * Larger results are truncated to prevent memory bloat. * Default: 50KB */ maxToolResultSize?: number; /** * Enable state isolation for this sub-agent. * * When true, the sub-agent uses an isolated todo store instead of the * shared default store. This prevents state leakage between parallel * sub-agent executions. * * Automatically enabled when using runParallelSubAgents(). * * Inspired by LangGraph issue #6446: Parallel subgraphs with shared * state keys cause InvalidUpdateError. * * Default: false (uses shared store) */ stateIsolation?: boolean; /** * Inherit parent's permission manager. * * When true (default), the sub-agent uses the parent's PermissionManager, * sharing session grants and permission rules. This ensures: * - Sub-agents follow the same permission rules as parent * - Session grants from parent are available to sub-agents * - User sees permission prompts for sub-agent tool usage * * Set to false to allow sub-agents to bypass permissions (use with caution). * * Default: true */ inheritPermissions?: boolean; } /** * Result from a sub-agent execution */ export interface SubAgentResult { /** * Name of the sub-agent that executed */ name: string; /** * Final response from the sub-agent */ response: string; /** * Whether the execution was successful */ success: boolean; /** * Error message if execution failed */ error?: string; /** * Number of iterations used */ iterations: number; /** * Tool calls made by the sub-agent */ toolCalls: AgentRunResult['toolCalls']; /** * Context stats for the sub-agent's execution */ contextStats?: ContextStats; } /** * Agent class - orchestrates LLM interactions with tool use * * @example * ```typescript * const agent = new Agent({ * provider: new ClaudeProvider({ apiKey: 'sk-...' }), * systemPrompt: 'You are a helpful assistant.', * }); * * agent.registerTool(readFileTool); * agent.registerTool(writeFileTool); * * const result = await agent.run('Read the contents of package.json'); * console.log(result.response); * ``` */ export declare class Agent { private readonly provider; private readonly systemPrompt; private readonly maxIterations; private readonly maxConsecutiveToolCalls; private readonly maxToolLoopNudges; private readonly iterationLimitBehavior; private readonly chatOptions; private readonly toolRegistry; private readonly contextManager?; private readonly autoContextManagement; private readonly onEvent?; private readonly onIterationLimitReached?; private readonly onToolLoopDetected?; private readonly retryConfig; private readonly checkpointer?; private readonly _sessionId; private readonly autoCheckpoint; private readonly checkpointOnAbort; private _createdAt; private _totalTokensUsed; private _currentIteration; /** * Conversation history - persists across run() calls */ private conversationHistory; /** * Registered sub-agents */ private readonly subAgents; /** * Pin manager for critical information that survives context compaction */ private readonly pinManager?; /** * Guardrail manager for pattern-based safety checks */ private readonly guardrailManager?; /** * Permission manager for tool-level access control */ private readonly permissionManager?; /** * Loaded project memory (instructions from CLAUDE.md, etc.) */ private readonly projectMemory?; /** * Usage tracker for token usage monitoring */ private readonly usageTracker?; /** * Hooks manager for lifecycle hooks */ private readonly hooksManager?; /** * File access tracker for context restoration hints */ private readonly fileTracker?; /** * File restoration options for post-compaction content injection */ private readonly fileRestorationConfig?; /** * Observation masker for reducing token usage by masking old tool results */ private readonly observationMasker?; /** * Smart windowing config for programmatic context compaction */ private readonly windowingConfig?; /** * Whether to use compact text format for tool results in LLM messages */ private readonly compactToolResults; /** * Dead message pruner for removing superseded errors and permission exchanges */ private readonly deadMessagePruner?; constructor(config: AgentConfig); /** * Create an agent with project memory loaded from files. * * This factory method automatically discovers and loads project-specific * instructions from files like CLAUDE.md, GEMINI.md, PROJECT.md, etc. * * @param config - Agent configuration * @param memoryOptions - Project memory loading options * @param memoryDir - Directory to search for memory files (defaults to cwd) * @returns Agent instance with loaded project memory * * @example * ```typescript * // Load Claude-specific instructions * const agent = await Agent.createWithMemory( * { * provider, * systemPrompt: 'You are a helpful assistant.', * }, * { providers: 'claude' }, * '/path/to/project' * ); * * // Load instructions for multiple providers * const agent = await Agent.createWithMemory( * { provider }, * { providers: ['claude', 'gemini'], includeGeneric: true } * ); * * // Access loaded memory * const memory = agent.getProjectMemory(); * console.log(`Loaded ${memory?.files.length} memory files`); * ``` */ static createWithMemory(config: Omit, memoryOptions?: ProjectMemoryOptions, memoryDir?: string): Promise; /** * Get the session ID for this agent instance */ get sessionId(): string; /** * Get the loaded project memory (if any). * * Project memory contains instructions loaded from files like * CLAUDE.md, GEMINI.md, PROJECT.md, etc. * * @returns The loaded project memory, or undefined if none was loaded * * @example * ```typescript * const memory = agent.getProjectMemory(); * if (memory) { * console.log(`Loaded ${memory.files.length} instruction files`); * console.log(`Total tokens: ~${memory.estimatedTokens}`); * for (const file of memory.files) { * console.log(` - ${file.relativePath}`); * } * } * ``` */ getProjectMemory(): ProjectMemory | undefined; /** * Check if project memory was loaded */ hasProjectMemory(): boolean; /** * Get usage tracking statistics. * * @returns Usage statistics or undefined if usage tracking is not enabled * * @example * ```typescript * const stats = agent.getUsageStats(); * if (stats) { * console.log(`Total calls: ${stats.totalCalls}`); * console.log(`Total tokens: ${stats.totalTokens}`); * console.log(`Input tokens: ${stats.totalInputTokens}`); * console.log(`Output tokens: ${stats.totalOutputTokens}`); * } * ``` */ getUsageStats(): UsageStats | undefined; /** * Get total tokens used across all LLM calls. */ getTotalTokens(): number; /** * Get total input tokens used across all LLM calls. */ getTotalInputTokens(): number; /** * Get total output tokens used across all LLM calls. */ getTotalOutputTokens(): number; /** * Get budget status. */ getBudgetStatus(): BudgetStatus | undefined; /** * Check if budget is exceeded. */ isBudgetExceeded(): boolean; /** * Get a human-readable usage summary. */ getUsageSummary(): string | undefined; /** * Reset usage tracking data. */ resetUsageTracking(): void; /** * Check if usage tracking is enabled. */ hasUsageTracking(): boolean; /** * Record usage manually (for custom provider integrations). * * @internal */ recordUsage(model: string, provider: string, tokens: TokenUsage): void; /** * Add a pin (critical information that survives context compaction). * * Pins are injected into every LLM call and never get compacted. * Use them for information that must not be forgotten. * * @param input - Pin input (uses AnchorInput type) * @returns The created pin, or undefined if pins are not enabled * * @example * ```typescript * agent.addPin({ * content: 'Team roster: $default, $arch', * priority: 'info', * scope: 'session', * }); * ``` */ addPin(input: AnchorInput): Anchor | undefined; /** * Get a pin by ID */ getPin(id: string): Anchor | undefined; /** * Get all pins, optionally filtered */ getPins(options?: AnchorQueryOptions): Anchor[]; /** * Check if a pin exists */ hasPin(id: string): boolean; /** * Remove a pin by ID * * @returns true if pin was removed, false if not found */ removePin(id: string): boolean; /** * Clear pins based on criteria * * @param options - Clear options for filtering which pins to remove * @returns Number of pins removed */ clearPins(options?: AnchorClearOptions): number; /** * Get the pin manager (if configured) */ getPinManager(): AnchorManager | undefined; /** * Check if pins are enabled */ hasPins(): boolean; /** * Get the current model ID for this agent. * * @returns The model ID string (e.g., 'claude-sonnet-4-20250514') */ getModel(): string; /** * Change the model for subsequent LLM calls (same provider only). * * Takes effect on the next `run()` or `stream()` call — never interrupts * a running turn. Conversation history is preserved (it's provider-agnostic). * Emits a `model_changed` event. * * Use this to switch between models within the same provider, e.g., * Claude Sonnet → Claude Opus for a harder task, then back. * * @param modelId - The new model ID (e.g., 'claude-opus-4-20250514') * @throws If modelId is empty or not a string * * @example * ```typescript * console.log(agent.getModel()); // 'claude-sonnet-4-20250514' * agent.setModel('claude-opus-4-20250514'); * // Next run() uses Opus * const result = await agent.run('Solve this complex problem'); * agent.setModel('claude-sonnet-4-20250514'); // Switch back * ``` * * @since 0.5.8 */ setModel(modelId: string): void; /** @deprecated Use addPin() instead */ addAnchor(input: AnchorInput): Anchor | undefined; /** @deprecated Use getPin() instead */ getAnchor(id: string): Anchor | undefined; /** @deprecated Use getPins() instead */ getAnchors(options?: AnchorQueryOptions): Anchor[]; /** @deprecated Use hasPin() instead */ hasAnchor(id: string): boolean; /** @deprecated Use removePin() instead */ removeAnchor(id: string): boolean; /** @deprecated Use clearPins() instead */ clearAnchors(options?: AnchorClearOptions): number; /** @deprecated Use getPinManager() instead */ getAnchorManager(): AnchorManager | undefined; /** @deprecated Use hasPins() instead */ hasAnchors(): boolean; /** * Add a custom guardrail * * @param input - Guardrail definition * @returns The created guardrail, or undefined if guardrails are not enabled * * @example * ```typescript * agent.addGuardrail({ * id: 'no-delete-important', * name: 'Important Files Protection', * description: 'Prevent deletion of important files', * patterns: [/rm.*important/i, /delete.*important/i], * action: 'block', * message: 'Cannot delete files marked as important', * scope: ['bash'], * }); * ``` */ addGuardrail(input: GuardrailInput): Guardrail | undefined; /** * Get a guardrail by ID */ getGuardrail(id: string): Guardrail | undefined; /** * Get all guardrails */ getGuardrails(): Guardrail[]; /** * Check if a guardrail exists */ hasGuardrail(id: string): boolean; /** * Remove a guardrail by ID */ removeGuardrail(id: string): boolean; /** * Enable a guardrail by ID */ enableGuardrail(id: string): boolean; /** * Disable a guardrail by ID */ disableGuardrail(id: string): boolean; /** * Get the guardrail manager (if configured) */ getGuardrailManager(): GuardrailManager | undefined; /** * Check if guardrails are enabled */ hasGuardrails(): boolean; /** * Add a permission rule for a tool * * @param rule - Permission rule to add * @returns this for chaining * * @example * ```typescript * agent.addPermission({ * toolName: 'bash', * level: 'once', * description: 'Execute shell commands', * }); * ``` */ addPermission(rule: ToolPermission): this; /** * Remove a permission rule by tool name */ removePermission(toolName: string): boolean; /** * Get a permission rule by tool name */ getPermission(toolName: string): ToolPermission | undefined; /** * Get all permission rules */ getPermissions(): ToolPermission[]; /** * Set the permission level for a tool * * @param toolName - Tool name or pattern * @param level - Permission level * @param description - Optional description */ setPermissionLevel(toolName: string, level: PermissionLevel, description?: string): this; /** * Get the effective permission level for a tool */ getPermissionLevel(toolName: string): PermissionLevel; /** * Grant session-level permission for a tool */ grantSessionPermission(toolName: string): this; /** * Revoke session-level permission for a tool */ revokeSessionPermission(toolName: string): boolean; /** * Clear all session-level permissions */ clearSessionPermissions(): this; /** * Get all tools with session-level permission */ getSessionPermissions(): string[]; /** * Get the permission manager (if configured) */ getPermissionManager(): PermissionManager | undefined; /** * Check if permissions are enabled */ hasPermissions(): boolean; /** * Emit a custom event that will be streamed to event handlers. * * This allows tools, middleware, and user code to emit custom events * that are streamed alongside built-in agent events. * * Inspired by LangGraph's get_stream_writer() pattern. * Addresses issues like LangGraph #6330 (preserve event metadata). * * @param config - Custom event configuration * * @example * ```typescript * agent.emitCustomEvent({ * name: 'progress', * data: { step: 1, total: 5, message: 'Processing...' }, * metadata: { toolName: 'myTool' }, * }); * ``` */ emitCustomEvent(config: CustomEventConfig): void; /** * Get a stream writer function for emitting custom events. * * This returns a simple function that can be passed to tools or * middleware for streaming progress updates. * * @param eventName - Name for all events emitted by this writer * @returns A stream writer function * * @example * ```typescript * const writer = agent.getStreamWriter('myTool'); * writer('Starting...', { phase: 'init' }); * writer('Processing...', { phase: 'work', progress: 50 }); * writer('Done!', { phase: 'complete' }); * ``` */ getStreamWriter(eventName?: string): StreamWriter; /** * Clear conversation history to start fresh */ clearHistory(): this; /** * Get the current conversation history */ getHistory(): Message[]; /** * Set the conversation history (for manual compaction/restoration) * Also updates the context manager's token count if configured. * * @param messages - The message history to restore * @param options - Optional restore options * @param options.turnCount - The turn count to restore (important for compaction) */ setHistory(messages: Message[], options?: { turnCount?: number; }): Promise; /** * Get the context manager (if configured) */ getContextManager(): ContextManager | undefined; /** * Get the tool registry instance. * Useful for setting up fallback handlers or inspecting registered tools. */ getToolRegistry(): ToolRegistry; /** * Get context statistics */ getContextStats(): ContextStats | undefined; /** * Get observation masking statistics (tokens saved, observations masked, inputs compacted). */ getObservationMaskStats(): { maskedCount: number; tokensSaved: number; activeStamps: number; inputsCompacted: number; } | undefined; /** * Get dead message pruning statistics (errors pruned, permissions pruned, tokens saved). */ getDeadMessagePruneStats(): { prunedCount: number; tokensSaved: number; errorsPruned: number; permissionsPruned: number; } | undefined; /** * Get current verbosity level based on context pressure */ getVerbosityLevel(): VerbosityLevel; /** * Get the file access tracker (if file tracking is enabled) */ getFileTracker(): FileAccessTracker | undefined; /** * Format context restoration hints based on tracked file accesses. * Returns empty string if no files have been accessed or file tracking is disabled. */ formatRestorationHints(): string; /** * Inject context restoration hints into messages after compaction/summarization. * Uses content-aware format: small files get content inlined, large files get * reference-only notes. Each file is injected as a separate user message. * * @internal */ private injectRestorationHints; /** * Compact the conversation context to reduce token usage. * * This is the recommended way to trigger context compaction externally. * It handles: * 1. Summarizing older messages * 2. Repairing tool use/result pairing (prevents API errors) * 3. Injecting context restoration hints (if file tracking is enabled) * 4. Updating the conversation history * * @param options - Compaction options * @returns Compaction result with statistics * * @example * ```typescript * // Basic compaction * const result = await agent.compact(); * console.log(`Reduced from ${result.originalTokens} to ${result.summaryTokens} tokens`); * * // Compaction without restoration hints * await agent.compact({ injectRestorationHints: false }); * * // Emergency compaction (more aggressive) * await agent.compact({ emergency: true }); * ``` */ compact(options?: { /** * Inject file restoration hints after compaction. * Only applies if file tracking is enabled. * Default: true (if file tracking is enabled) */ injectRestorationHints?: boolean; /** * Use emergency mode (more aggressive summarization). * Default: auto-detect based on context utilization */ emergency?: boolean; /** * Target utilization after compaction (0-1). * Default: from context manager config (typically 0.5) */ targetUtilization?: number; /** * Use smart category-aware compaction instead of simple summarization. * Smart compaction: * - Preserves system and recent messages completely * - Saves large tool results to files * - Summarizes history with LLM * Default: true */ useSmartCompaction?: boolean; }): Promise<{ /** Whether compaction was successful */ success: boolean; /** Original token count */ originalTokens: number; /** Token count after compaction */ summaryTokens: number; /** Number of summarization rounds performed */ rounds: number; /** Number of messages preserved (not summarized) */ messagesPreserved: number; /** Whether restoration hints were injected */ restorationHintsInjected: boolean; /** Number of orphaned tool_results removed */ toolResultsRepaired: number; /** The generated summary (for debugging/display) */ summary: string; /** Files created during smart compaction (tool results saved to files) */ filesCreated?: string[]; /** Category-specific statistics (only for smart compaction) */ categoryStats?: SmartCompactionResult['categoryStats']; }>; /** * Serialize the current agent state to an AgentState object. * This can be used for manual persistence or transferring state. * * @example * ```typescript * const state = agent.serialize(); * const json = JSON.stringify(state); * // Store json somewhere... * ``` */ serialize(): AgentState; /** * Save the current state using the configured checkpointer. * Throws if no checkpointer is configured. * * @param metadata - Optional metadata overrides * @returns The session ID * * @example * ```typescript * const agent = new Agent({ * provider, * checkpointer: new FileCheckpointer('~/.myapp/sessions/'), * }); * * await agent.run('Hello!'); * const sessionId = await agent.checkpoint(); * console.log(`Saved as: ${sessionId}`); * ``` */ checkpoint(metadata?: Partial): Promise; /** * Check if a checkpointer is configured */ hasCheckpointer(): boolean; /** * Save a partial checkpoint on abort or error. * This is called internally when checkpointOnAbort is enabled. * * @param reason - Why the checkpoint was saved ('aborted' or 'error') * @param emit - Event emitter function * @returns Promise that resolves when checkpoint is saved */ private saveAbortCheckpoint; /** * Resume an agent from a saved session. * * @param sessionId - Session ID to resume * @param options - Resume options (provider and checkpointer required) * * @example * ```typescript * const checkpointer = new FileCheckpointer('~/.myapp/sessions/'); * * // Resume a previous session * const agent = await Agent.resume('session_abc123', { * provider: new ClaudeProvider({ apiKey: '...' }), * checkpointer, * }); * * // Continue the conversation * await agent.run('Continue where we left off...'); * ``` */ static resume(sessionId: string, options: { provider: LLMProvider; checkpointer: Checkpointer; systemPrompt?: string; tools?: Tool[]; onEvent?: AgentEventHandler; }): Promise; /** * Create an agent from a serialized AgentState object. * * @param state - The serialized agent state * @param options - Options for the new agent * * @example * ```typescript * // Load state from somewhere * const json = await fs.readFile('session.json', 'utf-8'); * const state = JSON.parse(json); * * // Create agent from state * const agent = Agent.fromState(state, { provider }); * await agent.run('Continue...'); * ``` */ static fromState(state: AgentState, options: { provider: LLMProvider; checkpointer?: Checkpointer; tools?: Tool[]; onEvent?: AgentEventHandler; systemPrompt?: string; }): Agent; /** * Create and register a sub-agent with isolated context. * * Sub-agents are specialized agents that handle discrete tasks independently. * They have their own context window and can have different tools/permissions. * * @example * ```typescript * agent.createSubAgent({ * name: 'code-reviewer', * description: 'Reviews code for security and quality issues', * systemPrompt: 'You are a code review specialist...', * tools: [readFileTool], // Restricted tools * contextMode: 'isolated', * }); * * const result = await agent.runSubAgent('code-reviewer', 'Review src/auth.ts'); * ``` */ createSubAgent(config: SubAgentConfig): this; /** * Run a sub-agent with a specific task. * * The sub-agent executes independently with its own context and returns * the result to the parent agent. * * @param name - Name of the registered sub-agent * @param task - Task description for the sub-agent * @param options - Optional run options */ runSubAgent(name: string, task: string, options?: RunOptions): Promise; /** * Run multiple sub-agents in parallel with proper state isolation. * * This method ensures that parallel sub-agents don't share mutable state, * preventing race conditions and state leakage between concurrent runs. * * Inspired by LangGraph issue #6446: Parallel subgraphs with shared * state keys cause InvalidUpdateError. * * @param tasks - Array of {name, task} objects to run in parallel * @param options - Optional run options applied to all sub-agents * @returns Array of results in the same order as tasks * * @example * ```typescript * // Run code review and security scan in parallel * const results = await agent.runParallelSubAgents([ * { name: 'code-reviewer', task: 'Review src/auth.ts' }, * { name: 'security-scanner', task: 'Scan src/auth.ts for vulnerabilities' }, * ]); * ``` */ runParallelSubAgents(tasks: Array<{ name: string; task: string; }>, options?: RunOptions): Promise; /** * Get a registered sub-agent by name */ getSubAgent(name: string): Agent | undefined; /** * Get all registered sub-agent names */ getSubAgentNames(): string[]; /** * Remove a registered sub-agent (alias for disposeSubAgent) */ removeSubAgent(name: string): boolean; /** * Dispose a sub-agent and release its resources. * * This clears the sub-agent's: * - Conversation history * - Context manager state * - Tool registry * * After disposal, the sub-agent must be re-created to use again. */ disposeSubAgent(name: string): boolean; /** * Dispose all sub-agents and release their resources. * * Useful for cleanup when the parent agent is done or * to free memory during long-running sessions. */ disposeAllSubAgents(): void; /** * Generate a concise summary of context for inherited mode */ private generateContextSummary; /** * Register a tool that the agent can call during conversations. * * Tools are functions the LLM can invoke to perform actions like reading * files, running commands, or querying APIs. The LLM sees the tool's name, * description, and parameter schema, then decides when to call it. * * @param tool - Tool definition created with `defineTool()` * @returns The agent instance (for chaining) * * @example * ```typescript * agent.registerTool(defineTool({ * name: 'get_weather', * description: 'Get current weather for a city', * parameters: { * type: 'object', * properties: { city: { type: 'string' } }, * required: ['city'], * }, * execute: async ({ city }) => { * const data = await fetchWeather(city); * return { content: JSON.stringify(data) }; * }, * })); * ``` */ registerTool(tool: Tool): this; /** * Register multiple tools at once. * * @param tools - Array of tool definitions * @returns The agent instance (for chaining) */ registerTools(tools: Tool[]): this; /** * Get all registered tool definitions */ getToolDefinitions(): ToolDefinition[]; /** * Check if a tool is marked as silent (no spinner or result output) */ isToolSilent(name: string): boolean; /** * Run the agent with a user message and return the result. * * This is the main entry point for agent interaction. The agent will: * 1. Add the user message to conversation history * 2. Send the conversation to the LLM * 3. Execute any tool calls the LLM requests * 4. Repeat steps 2-3 until the LLM responds with text (no tool calls) * 5. Return the final text response and metadata * * Events are emitted throughout the process via the `onEvent` callback * configured at construction time. * * @param userMessage - The user's message (string or content blocks for images) * @param options - Optional run configuration (max iterations, abort signal, etc.) * @returns The agent's response, tool call history, and context stats * * @example * ```typescript * const result = await agent.run('What files are in this directory?'); * console.log(result.response); * console.log(`Used ${result.toolCalls.length} tool calls`); * ``` * * @example * ```typescript * // With abort signal * const controller = new AbortController(); * const result = await agent.run('Refactor this file', { * signal: controller.signal, * }); * ``` */ run(userMessage: string | ContentBlock[], options?: RunOptions): Promise; /** * Stream the agent's response as events. * * Yields `AgentEvent` objects in real time as the agent thinks, calls tools, * and generates text. Use this for building interactive UIs that show * progress as it happens. * * @param userMessage - The user's message * @param options - Optional run configuration * @returns An async iterable of agent events * * @example * ```typescript * for await (const event of agent.stream('Explain this code')) { * if (event.type === 'llm_chunk') { * process.stdout.write(event.chunk.text ?? ''); * } else if (event.type === 'tool_start') { * console.log(`\nCalling tool: ${event.name}`); * } else if (event.type === 'done') { * console.log('\n\nDone!'); * } * } * ``` */ stream(userMessage: string, options?: RunOptions): AsyncIterable; /** * Process stream chunks into text, tool uses, and usage data */ private processChunks; /** * Wrap provider.chat() with automatic retry on transient errors. * * Retries the entire stream on failure with exponential backoff. * Emits llm_retry events before each retry attempt. * * @param messages - Messages to send * @param options - Chat options * @param emit - Event emitter function * @param signal - Optional abort signal */ private chatWithRetry; /** * Generate a summary of messages using the LLM provider. * Used for context summarization when approaching limits. * * If pins are configured, the summary will prioritize keeping * information related to pinned content (pin-weighted summarization). */ private generateSummary; /** * Generate a summary when the agent hits max iterations. * This provides a graceful fallback instead of just throwing an error. */ private generateIterationLimitSummary; }