import * as zod from 'zod'; import { ZodType, ZodTypeAny } from 'zod'; export { z } from 'zod'; import { Logger, ILogObj } from 'tslog'; export { ILogObj, Logger } from 'tslog'; import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { MessageCreateParamsStreaming, MessageStreamEvent } from '@anthropic-ai/sdk/resources/messages'; import OpenAI from 'openai'; import { ChatCompletionMessageParam, ChatCompletionContentPart, ChatCompletionChunk } from 'openai/resources/chat/completions'; /** * Public types for the MCP integration. * * @module mcp/types */ /** * User-supplied spec describing an MCP server to consume. */ type McpServerSpec = StdioMcpServerSpec | HttpMcpServerSpec; interface StdioMcpServerSpec { /** Stable server name used for namespacing tools and surfacing in logs. */ name: string; /** Stdio transport — server is spawned as a child process. */ transport: "stdio"; /** Executable to spawn. The basename is checked against the allowlist unless trust=true. */ command: string; /** Arguments to pass to the executable. */ args?: string[]; /** Optional environment overrides for the spawned child process. */ env?: Record; /** Skip the allowlist check for this server. Default false. */ trust?: boolean; /** Per-call timeout in milliseconds for tools/call. Default: no timeout. */ timeoutMs?: number; } interface HttpMcpServerSpec { /** Stable server name. */ name: string; /** Streamable HTTP transport (the modern, non-deprecated remote transport). */ transport: "http"; /** Server URL (must include scheme — http:// or https://). */ url: string; /** Optional fixed headers (e.g. Authorization, X-API-Key). */ headers?: Record; /** Per-call timeout in milliseconds for tools/call. Default: no timeout. */ timeoutMs?: number; } /** * Minimal subset of an MCP tool descriptor that this integration cares about. */ interface McpToolDescriptor { name: string; description?: string; inputSchema?: unknown; } /** * Canonical MCP content block shape used by the SDK. * * The SDK uses larger union types; this is the subset the adapter handles. */ type McpContentBlock = { type: "text"; text: string; } | { type: "image"; data: string; mimeType: string; } | { type: "audio"; data: string; mimeType: string; } | { type: string; [k: string]: unknown; }; /** * Result shape returned from a tools/call. */ interface McpToolResult { content: McpContentBlock[]; isError?: boolean; } interface McpPromptArgument { name: string; description?: string; required?: boolean; } interface McpPromptDescriptor { name: string; description?: string; arguments?: McpPromptArgument[]; } interface McpPromptMessage { role: "user" | "assistant"; content: McpContentBlock; } interface McpPromptResult { description?: string; messages: McpPromptMessage[]; } /** * Server capabilities advertised on initialize. Only the fields used by plan 1 * are typed; richer capabilities arrive in plan 2. */ interface McpServerCapabilities { tools?: { listChanged?: boolean; }; prompts?: { listChanged?: boolean; }; resources?: { listChanged?: boolean; subscribe?: boolean; }; [k: string]: unknown; } /** * Types and interfaces for multimodal input content. * * These types define the structure for sending images, audio, and other * media alongside text in LLM messages. They complement the output types * in media-types.ts. */ /** * Supported image MIME types for input. * All major providers support these formats. */ type ImageMimeType = "image/jpeg" | "image/png" | "image/gif" | "image/webp"; /** * Supported audio MIME types for input. * Currently only Gemini supports audio input. */ type AudioMimeType = "audio/mp3" | "audio/mpeg" | "audio/wav" | "audio/webm" | "audio/ogg" | "audio/flac"; /** * Base interface for all content parts. */ interface BaseContentPart { type: string; } /** * Text content part. */ interface TextContentPart extends BaseContentPart { type: "text"; text: string; } /** * Image content part. */ interface ImageContentPart extends BaseContentPart { type: "image"; source: ImageSource; } /** * Audio content part. * Currently only supported by Gemini. */ interface AudioContentPart extends BaseContentPart { type: "audio"; source: AudioSource; } /** * Union of all supported content part types. */ type ContentPart = TextContentPart | ImageContentPart | AudioContentPart; /** * Image can come from base64 data or a URL. */ type ImageSource = ImageBase64Source | ImageUrlSource; /** * Base64-encoded image data. * Supported by all providers. */ interface ImageBase64Source { type: "base64"; mediaType: ImageMimeType; data: string; } /** * Image URL reference. * Only supported by OpenAI. */ interface ImageUrlSource { type: "url"; url: string; } /** * Audio source (base64 only). * URL sources are not currently supported for audio. */ interface AudioSource { type: "base64"; mediaType: AudioMimeType; data: string; } /** * Check if a content part is a text part. */ declare function isTextPart(part: ContentPart): part is TextContentPart; /** * Check if a content part is an image part. */ declare function isImagePart(part: ContentPart): part is ImageContentPart; /** * Check if a content part is an audio part. */ declare function isAudioPart(part: ContentPart): part is AudioContentPart; /** * Create a text content part. * * @example * ```typescript * const part = text("What's in this image?"); * ``` */ declare function text(content: string): TextContentPart; /** * Create an image content part from base64-encoded data. * * @param data - Base64-encoded image data * @param mediaType - MIME type of the image * * @example * ```typescript * const part = imageFromBase64(base64Data, "image/jpeg"); * ``` */ declare function imageFromBase64(data: string, mediaType: ImageMimeType): ImageContentPart; /** * Create an image content part from a URL. * Note: Only supported by OpenAI. * * @param url - URL to the image (must be accessible) * * @example * ```typescript * const part = imageFromUrl("https://example.com/image.jpg"); * ``` */ declare function imageFromUrl(url: string): ImageContentPart; /** * Detect the MIME type of image data from magic bytes. * * @param data - Raw image data * @returns Detected MIME type or null if unknown */ declare function detectImageMimeType(data: Buffer | Uint8Array): ImageMimeType | null; /** * Detect the MIME type of audio data from magic bytes. * * @param data - Raw audio data * @returns Detected MIME type or null if unknown */ declare function detectAudioMimeType(data: Buffer | Uint8Array): AudioMimeType | null; /** * Convert data to base64 string. * * @param data - Data to encode (Buffer, Uint8Array, or already base64 string) * @returns Base64-encoded string */ declare function toBase64(data: Buffer | Uint8Array | string): string; /** * Create an image content part from a Buffer or Uint8Array. * Automatically detects the MIME type if not provided. * * @param buffer - Image data * @param mediaType - Optional MIME type (auto-detected if not provided) * * @example * ```typescript * const imageData = await fs.readFile("photo.jpg"); * const part = imageFromBuffer(imageData); // Auto-detects JPEG * ``` */ declare function imageFromBuffer(buffer: Buffer | Uint8Array, mediaType?: ImageMimeType): ImageContentPart; /** * Create an audio content part from base64-encoded data. * * @param data - Base64-encoded audio data * @param mediaType - MIME type of the audio * * @example * ```typescript * const part = audioFromBase64(base64Audio, "audio/mp3"); * ``` */ declare function audioFromBase64(data: string, mediaType: AudioMimeType): AudioContentPart; /** * Create an audio content part from a Buffer or Uint8Array. * Automatically detects the MIME type if not provided. * * @param buffer - Audio data * @param mediaType - Optional MIME type (auto-detected if not provided) * * @example * ```typescript * const audioData = await fs.readFile("audio.mp3"); * const part = audioFromBuffer(audioData); // Auto-detects MP3 * ``` */ declare function audioFromBuffer(buffer: Buffer | Uint8Array, mediaType?: AudioMimeType): AudioContentPart; /** * Check if a string is a data URL. * * @param input - String to check * @returns True if it's a data URL */ declare function isDataUrl(input: string): boolean; /** * Parse a data URL into its components. * * @param url - Data URL to parse * @returns Parsed components or null if invalid * * @example * ```typescript * const result = parseDataUrl("data:image/jpeg;base64,/9j/4AAQ..."); * // { mimeType: "image/jpeg", data: "/9j/4AAQ..." } * ``` */ declare function parseDataUrl(url: string): { mimeType: string; data: string; } | null; /** * Unified event types for the Execution Tree. * * All events carry full tree context (nodeId, parentId, depth, path). * No special SubagentEvent wrapper needed - subagent events are regular * events with depth > 0. * * @module core/execution-events */ /** * Base properties shared by all execution events. * Every event carries full tree context. */ interface BaseExecutionEvent { /** Monotonically increasing event ID */ eventId: number; /** Event timestamp */ timestamp: number; /** Node that emitted this event */ nodeId: string; /** Parent node ID (null for root events) */ parentId: string | null; /** Nesting depth (0 = root, 1 = child, etc.) */ depth: number; /** Full path from root to this node */ path: string[]; } /** * Emitted when an LLM call starts. */ interface LLMCallStartEvent extends BaseExecutionEvent { type: "llm_call_start"; /** Iteration number within agent loop (1-indexed) */ iteration: number; /** Model identifier */ model: string; /** Request messages */ request?: LLMMessage[]; } /** * Emitted for each streaming chunk from LLM. */ interface LLMCallStreamEvent extends BaseExecutionEvent { type: "llm_call_stream"; /** Text chunk */ chunk: string; } /** * Emitted when the LLM finishes generating tokens (before gadget execution completes). * * This event fires when the LLM stream ends, allowing consumers to track * "LLM thinking time" separately from gadget execution time. * * Event order: llm_call_start → llm_response_end → llm_call_complete */ interface LLMResponseEndEvent extends BaseExecutionEvent { type: "llm_response_end"; /** Iteration number within agent loop */ iteration: number; /** Model identifier */ model: string; /** Finish reason from LLM */ finishReason: string | null; /** Token usage (may be partial, final usage in llm_call_complete) */ usage?: TokenUsage; } /** * Emitted when an LLM call completes successfully (after all gadgets finish). */ interface LLMCallCompleteEvent extends BaseExecutionEvent { type: "llm_call_complete"; /** Complete response text */ response: string; /** Token usage */ usage?: TokenUsage; /** Finish reason from LLM */ finishReason?: string | null; /** Cost in USD */ cost?: number; /** Accumulated thinking/reasoning content from reasoning models */ thinkingContent?: string; } /** * Emitted when an LLM call fails. */ interface LLMCallErrorEvent extends BaseExecutionEvent { type: "llm_call_error"; /** The error that occurred */ error: Error; /** Whether the error was recovered by a controller */ recovered: boolean; } /** * Emitted when a gadget call is parsed from LLM output (before execution). */ interface GadgetCallEvent extends BaseExecutionEvent { type: "gadget_call"; /** Invocation ID */ invocationId: string; /** Gadget name */ name: string; /** Parameters */ parameters: Record; /** Dependencies (other invocation IDs) */ dependencies: string[]; } /** * Emitted when gadget execution starts. */ interface GadgetStartEvent extends BaseExecutionEvent { type: "gadget_start"; /** Invocation ID */ invocationId: string; /** Gadget name */ name: string; } /** * Emitted when gadget execution completes successfully. */ interface GadgetCompleteEvent extends BaseExecutionEvent { type: "gadget_complete"; /** Invocation ID */ invocationId: string; /** Gadget name */ name: string; /** Result string */ result: string; /** Execution time in ms */ executionTimeMs: number; /** Cost in USD */ cost?: number; /** Media outputs */ media?: GadgetMediaOutput[]; /** Stored media with file paths */ storedMedia?: StoredMedia[]; } /** * Emitted when gadget execution fails. */ interface GadgetErrorEvent extends BaseExecutionEvent { type: "gadget_error"; /** Invocation ID */ invocationId: string; /** Gadget name */ name: string; /** Error message */ error: string; /** Execution time in ms */ executionTimeMs: number; } /** * Emitted when a gadget is skipped. */ interface GadgetSkippedEvent$1 extends BaseExecutionEvent { type: "gadget_skipped"; /** Invocation ID */ invocationId: string; /** Gadget name */ name: string; /** Reason for skipping */ reason: "dependency_failed" | "controller_skip" | "limit_exceeded"; /** Error message (combines reason and failedDependencyError for consistency with GadgetErrorEvent) */ error: string; /** Failed dependency invocation ID (if dependency_failed) */ failedDependency?: string; /** Error message from failed dependency */ failedDependencyError?: string; } /** * Emitted for text output from LLM (pure notification, not a tree node). */ interface TextEvent extends BaseExecutionEvent { type: "text"; /** Text content */ content: string; } /** * Emitted when a reasoning model produces thinking content during streaming. * This gives consumers a dedicated event type to listen for reasoning output. */ interface ThinkingEvent extends BaseExecutionEvent { type: "thinking"; /** Thinking text content */ content: string; /** Whether this is actual thinking or redacted content */ thinkingType: "thinking" | "redacted"; } /** * Emitted when context compaction occurs. */ interface CompactionEvent$1 extends BaseExecutionEvent { type: "compaction"; /** Tokens before compaction */ tokensBefore: number; /** Tokens after compaction */ tokensAfter: number; /** Compaction strategy used */ strategy: string; /** Messages removed */ messagesRemoved: number; } /** * Emitted when human input is required. */ interface HumanInputRequiredEvent extends BaseExecutionEvent { type: "human_input_required"; /** Question for the user */ question: string; /** Gadget name requesting input */ gadgetName: string; /** Invocation ID */ invocationId: string; } /** * Emitted when the execution stream completes. */ interface StreamCompleteEvent extends BaseExecutionEvent { type: "stream_complete"; /** Whether any gadgets were executed */ didExecuteGadgets: boolean; /** Whether the agent loop should break */ shouldBreakLoop: boolean; /** Total cost for this iteration */ iterationCost?: number; } /** * All LLM-related events. */ type LLMEvent = LLMCallStartEvent | LLMCallStreamEvent | LLMResponseEndEvent | LLMCallCompleteEvent | LLMCallErrorEvent; /** * All gadget-related events. */ type GadgetEvent = GadgetCallEvent | GadgetStartEvent | GadgetCompleteEvent | GadgetErrorEvent | GadgetSkippedEvent$1; /** * Union of all execution events. */ type ExecutionEvent = LLMCallStartEvent | LLMCallStreamEvent | LLMResponseEndEvent | LLMCallCompleteEvent | LLMCallErrorEvent | GadgetCallEvent | GadgetStartEvent | GadgetCompleteEvent | GadgetErrorEvent | GadgetSkippedEvent$1 | TextEvent | ThinkingEvent | CompactionEvent$1 | HumanInputRequiredEvent | StreamCompleteEvent; /** * Event type discriminator. */ type ExecutionEventType = ExecutionEvent["type"] | "*"; /** * Check if an event is an LLM event. */ declare function isLLMEvent(event: ExecutionEvent): event is LLMEvent; /** * Check if an event is a gadget event. */ declare function isGadgetEvent(event: ExecutionEvent): event is GadgetEvent; /** * Check if an event is from a subagent (nested execution). */ declare function isSubagentEvent(event: ExecutionEvent): boolean; /** * Check if an event is from the root agent. */ declare function isRootEvent(event: ExecutionEvent): boolean; /** * Filter events by depth. */ declare function filterByDepth(events: ExecutionEvent[], depth: number): ExecutionEvent[]; /** * Filter events by parent node. */ declare function filterByParent(events: ExecutionEvent[], parentId: string): ExecutionEvent[]; /** * Filter events to only root-level events. */ declare function filterRootEvents(events: ExecutionEvent[]): ExecutionEvent[]; /** * Group events by their parent node. */ declare function groupByParent(events: ExecutionEvent[]): Map; /** * Unique identifier for any execution node. * Format examples: "llm_1", "gadget_abc123", "llm_1_2" (nested) */ type NodeId = string; /** * Node type discriminator. */ type ExecutionNodeType = "llm_call" | "gadget"; /** * Base properties shared by all execution nodes. */ interface BaseExecutionNode { /** Unique identifier for this node */ id: NodeId; /** Node type discriminator */ type: ExecutionNodeType; /** Parent node ID (null for root nodes) */ parentId: NodeId | null; /** Nesting depth (0 = root, 1 = child of gadget, etc.) */ depth: number; /** Path from root to this node: ["llm_1", "gadget_abc", "llm_1_1"] */ path: NodeId[]; /** Creation timestamp */ createdAt: number; /** Completion timestamp (null if in progress) */ completedAt: number | null; } /** * LLM call execution node. */ interface LLMCallNode extends BaseExecutionNode { type: "llm_call"; /** Iteration number within the agent loop (1-indexed for display) */ iteration: number; /** Model identifier */ model: string; /** Request messages (set when call starts) */ request?: LLMMessage[]; /** Accumulated response text */ response: string; /** Token usage (set on completion) */ usage?: TokenUsage; /** Finish reason from LLM */ finishReason?: string | null; /** Cost in USD */ cost?: number; /** Child node IDs (gadgets spawned by this LLM call) */ children: NodeId[]; } /** * Gadget execution state. */ type GadgetState = "pending" | "running" | "completed" | "failed" | "skipped"; /** * Gadget execution node. */ interface GadgetNode extends BaseExecutionNode { type: "gadget"; /** Invocation ID (LLM-generated or auto) */ invocationId: string; /** Gadget name */ name: string; /** Parameters passed to the gadget */ parameters: Record; /** Dependencies (other invocation IDs this gadget waits for) */ dependencies: string[]; /** Execution state */ state: GadgetState; /** Result string (if completed successfully) */ result?: string; /** Error message (if failed or skipped) */ error?: string; /** Failed dependency invocation ID (if skipped due to dependency) */ failedDependency?: string; /** Execution time in milliseconds */ executionTimeMs?: number; /** Cost in USD */ cost?: number; /** Media outputs from this gadget */ media?: GadgetMediaOutput[]; /** Child node IDs (nested LLM calls for subagent gadgets) */ children: NodeId[]; /** Whether this gadget is a subagent (has nested LLM calls) */ isSubagent: boolean; } /** * Union of all execution node types. */ type ExecutionNode = LLMCallNode | GadgetNode; interface AddLLMCallParams { /** Iteration number (1-indexed) */ iteration: number; /** Model identifier */ model: string; /** Request messages */ request?: LLMMessage[]; /** Parent node ID (for subagent LLM calls) */ parentId?: NodeId | null; } interface AddGadgetParams { /** Invocation ID */ invocationId: string; /** Gadget name */ name: string; /** Parameters */ parameters: Record; /** Dependencies */ dependencies?: string[]; /** Parent LLM call node ID */ parentId?: NodeId | null; } interface CompleteLLMCallParams { /** Accumulated response text */ response?: string; /** Token usage */ usage?: TokenUsage; /** Finish reason */ finishReason?: string | null; /** Cost in USD */ cost?: number; /** Accumulated thinking/reasoning content from reasoning models */ thinkingContent?: string; } interface CompleteGadgetParams { /** Result string */ result?: string; /** Error message */ error?: string; /** Failed dependency (for skipped gadgets) */ failedDependency?: string; /** Execution time in ms */ executionTimeMs?: number; /** Cost in USD */ cost?: number; /** Media outputs */ media?: GadgetMediaOutput[]; /** Stored media with file paths */ storedMedia?: StoredMedia[]; } /** * The Execution Tree - single source of truth for all execution state. * * Features: * - Stores all nodes (LLM calls, gadgets) in a hierarchical structure * - Emits events on mutations * - Provides query methods for aggregation (costs, media, descendants) * - Supports single shared tree model for nested subagents * * @example * ```typescript * const tree = new ExecutionTree(); * * // Add root LLM call * const llmNode = tree.addLLMCall({ iteration: 1, model: "sonnet" }); * * // Add gadget under the LLM call * const gadgetNode = tree.addGadget({ * invocationId: "gc_1", * name: "ReadFile", * parameters: { path: "/foo.txt" }, * parentId: llmNode.id, * }); * * // Complete the gadget * tree.completeGadget(gadgetNode.id, { result: "file contents", executionTimeMs: 50 }); * * // Query total cost * console.log(tree.getTotalCost()); * ``` */ declare class ExecutionTree { private nodes; private rootIds; private invocationIdToNodeId; private emitter; private aggregator; /** * Base depth for all nodes in this tree. * Used when this tree is a subagent's view into a parent tree. */ readonly baseDepth: number; /** * Parent node ID for subagent trees. * All root nodes in this tree will have this as their parentId. */ readonly parentNodeId: NodeId | null; constructor(options?: { baseDepth?: number; parentNodeId?: NodeId | null; }); private generateLLMCallId; private gadgetIdCounter; private generateGadgetId; private emit; private createBaseEventProps; /** * Add a new LLM call node to the tree. */ addLLMCall(params: AddLLMCallParams): LLMCallNode; /** * Add text to an LLM call's response (for streaming). */ appendLLMResponse(nodeId: NodeId, chunk: string): void; /** * Mark an LLM call's response as ended (tokens stopped). * * Called when the LLM stream ends, before gadget execution completes. * Use this event to track "LLM thinking time" separately from gadget execution. * * @param nodeId - The LLM call node ID * @param params - Response end parameters (finishReason, usage) */ endLLMResponse(nodeId: NodeId, params: { finishReason: string | null; usage?: TokenUsage; }): void; /** * Complete an LLM call node (after all gadgets finish). */ completeLLMCall(nodeId: NodeId, params: CompleteLLMCallParams): void; /** * Mark an LLM call as failed. */ failLLMCall(nodeId: NodeId, error: Error, recovered: boolean): void; /** * Add a new gadget node to the tree. */ addGadget(params: AddGadgetParams): GadgetNode; /** * Update a gadget's parameters (e.g., after interceptor modifies them). * This is called after the gadget is added to the tree but before execution. */ updateGadgetParameters(invocationId: string, parameters: Record): void; /** * Mark a gadget as started (running). */ startGadget(nodeId: NodeId): void; /** * Complete a gadget node successfully. */ completeGadget(nodeId: NodeId, params: CompleteGadgetParams): void; /** * Mark a gadget as skipped due to dependency failure. */ skipGadget(nodeId: NodeId, failedDependency: string, failedDependencyError: string, reason: "dependency_failed" | "controller_skip" | "limit_exceeded"): void; /** * Emit a text event (notification only, not stored in tree). */ emitText(content: string, llmCallNodeId: NodeId): void; /** * Get a node by ID. */ getNode(id: NodeId): ExecutionNode | undefined; /** * Get a gadget node by invocation ID. */ getNodeByInvocationId(invocationId: string): GadgetNode | undefined; /** * Get all root nodes (depth 0 for this tree). */ getRoots(): ExecutionNode[]; /** * Get children of a node. */ getChildren(id: NodeId): ExecutionNode[]; /** * Get ancestors of a node (from root to parent). */ getAncestors(id: NodeId): ExecutionNode[]; /** * Get all descendants of a node. */ getDescendants(id: NodeId, type?: ExecutionNodeType): ExecutionNode[]; /** * Get the current (most recent incomplete) LLM call node. */ getCurrentLLMCallId(): NodeId | undefined; /** * Get total cost for entire tree. */ getTotalCost(): number; /** * Get total cost for a subtree (node and all descendants). */ getSubtreeCost(nodeId: NodeId): number; /** * Get token usage for entire tree. */ getTotalTokens(): { input: number; output: number; cached: number; }; /** * Get token usage for a subtree. */ getSubtreeTokens(nodeId: NodeId): { input: number; output: number; cached: number; }; /** * Collect all media from a subtree. */ getSubtreeMedia(nodeId: NodeId): GadgetMediaOutput[]; /** * Check if a subtree is complete (all nodes finished). */ isSubtreeComplete(nodeId: NodeId): boolean; /** * Get node counts. */ getNodeCount(): { llmCalls: number; gadgets: number; }; /** * Subscribe to events of a specific type. * Returns unsubscribe function. * * @param type - Event type to subscribe to (use "*" for all events) * @param listener - Callback function that receives matching events * @returns Unsubscribe function * * @example * ```typescript * const unsubscribe = tree.on("gadget_complete", (event) => { * if (event.type === "gadget_complete") { * console.log(`Gadget ${event.name} completed`); * } * }); * ``` */ on(type: ExecutionEventType, listener: (event: ExecutionEvent) => void): () => void; /** * Subscribe to all events. */ onAll(listener: (event: ExecutionEvent) => void): () => void; /** * Get async iterable of all events. * Events are yielded as they occur. */ events(): AsyncGenerator; /** * Mark the tree as complete (no more events will be emitted). * Wakes up any consumers waiting in the events() async generator. */ complete(): void; /** * Check if the tree is complete. */ isComplete(): boolean; } /** * Function-based gadget creation helper. * * For simple gadgets, use createGadget() instead of defining a class. * Parameters are automatically typed from the Zod schema. * * @example * ```typescript * const calculator = createGadget({ * description: "Performs arithmetic operations", * schema: z.object({ * operation: z.enum(["add", "subtract"]), * a: z.number(), * b: z.number(), * }), * execute: ({ operation, a, b }) => { * // Automatically typed! * return operation === "add" ? String(a + b) : String(a - b); * }, * }); * ``` */ /** * Infer the TypeScript type from a Zod schema. */ type InferSchema$1 = T extends ZodType ? U : never; /** * Configuration for creating a function-based gadget. */ interface CreateGadgetConfig { /** Optional custom name (defaults to "FunctionGadget") */ name?: string; /** Human-readable description of what the gadget does */ description: string; /** Zod schema for parameter validation */ schema: TSchema; /** * Execution function with typed parameters. * Can return string or { result, cost? }. * Optionally receives ExecutionContext for callback-based cost reporting. */ execute: (params: InferSchema$1, ctx?: ExecutionContext) => GadgetExecuteReturn | Promise; /** Optional timeout in milliseconds */ timeoutMs?: number; /** Optional usage examples to help LLMs understand proper invocation */ examples?: GadgetExample>[]; /** * Maximum concurrent executions. Use to prevent race conditions. * - `1` = Sequential (one at a time) * - `0` or `undefined` = Unlimited (default) * - `N > 1` = At most N concurrent */ maxConcurrent?: number; /** * If true, this gadget's results are marked sticky and survive compaction. * See `AbstractGadget.stickyResult` for the full contract. */ stickyResult?: boolean; /** * If true, the consuming agent loop should treat this gadget as a per- * iteration barrier — when it appears in a tool batch, no sibling gadgets * in the same batch execute. See `AbstractGadget.iterationBarrier` for the * full contract (enforcement is consumer-side; this flag is declarative). */ iterationBarrier?: boolean; } /** * Creates a gadget from a function (simpler than class-based approach). * * This is perfect for simple gadgets where you don't need the full * power of a class. Parameters are automatically typed from the schema. * * @param config - Configuration with execute function and schema * @returns Gadget instance ready to be registered * * @example * ```typescript * import { z } from 'zod'; * import { createGadget } from 'llmist'; * * // Simple calculator gadget * const calculator = createGadget({ * description: "Performs arithmetic operations", * schema: z.object({ * operation: z.enum(["add", "subtract", "multiply", "divide"]), * a: z.number().describe("First number"), * b: z.number().describe("Second number"), * }), * execute: ({ operation, a, b }) => { * // Parameters are automatically typed! * switch (operation) { * case "add": return String(a + b); * case "subtract": return String(a - b); * case "multiply": return String(a * b); * case "divide": return String(a / b); * } * }, * }); * ``` * * @example * ```typescript * // Async gadget with custom name and timeout * const weather = createGadget({ * name: "weather", * description: "Fetches current weather for a city", * schema: z.object({ * city: z.string().min(1).describe("City name"), * }), * timeoutMs: 10000, * execute: async ({ city }) => { * const response = await fetch(`https://api.weather.com/${city}`); * const data = await response.json(); * return `Weather in ${city}: ${data.description}, ${data.temp}°C`; * }, * }); * ``` * * @example * ```typescript * // Use with agent * const agent = LLMist.createAgent() * .withGadgets(calculator, weather) * .ask("What's the weather in Paris and what's 10 + 5?"); * ``` */ declare function createGadget(config: CreateGadgetConfig): AbstractGadget; /** * Type-safe gadget factory with automatic parameter inference. * * Gadget eliminates the need for manual type assertions * by automatically inferring parameter types from the Zod schema. * * @example * ```typescript * class Calculator extends Gadget({ * description: "Performs arithmetic operations", * schema: z.object({ * operation: z.enum(["add", "subtract"]), * a: z.number(), * b: z.number(), * }), * }) { * // ✨ params is automatically typed! * execute(params: this['params']): string { * const { operation, a, b } = params; // All typed! * return operation === "add" ? String(a + b) : String(a - b); * } * } * ``` */ /** * Infer the TypeScript type from a Zod schema. */ type InferSchema = T extends ZodType ? U : never; /** * Configuration for creating a typed gadget. */ interface GadgetConfig { /** Human-readable description of what the gadget does */ description: string; /** Zod schema for parameter validation */ schema: TSchema; /** Optional custom name (defaults to class name) */ name?: string; /** Optional timeout in milliseconds */ timeoutMs?: number; /** Optional usage examples to help LLMs understand proper invocation */ examples?: GadgetExample>[]; /** * Maximum concurrent executions. Use to prevent race conditions. * - `1` = Sequential (one at a time) * - `0` or `undefined` = Unlimited (default) * - `N > 1` = At most N concurrent */ maxConcurrent?: number; /** * If true, this gadget must execute alone — no other gadgets in the same * LLM response can run in parallel. Deferred until in-flight gadgets complete. * Use for gadgets that terminate the agent loop (e.g., Finish). */ exclusive?: boolean; } /** * Factory function to create a typed gadget base class. * * The returned class automatically infers parameter types from the Zod schema, * eliminating the need for manual type assertions in the execute method. * * @param config - Configuration with description and schema * @returns Base class to extend with typed execute method * * @example * ```typescript * import { z } from 'zod'; * import { Gadget } from 'llmist'; * * class Calculator extends Gadget({ * description: "Performs arithmetic operations", * schema: z.object({ * operation: z.enum(["add", "subtract", "multiply", "divide"]), * a: z.number().describe("First number"), * b: z.number().describe("Second number"), * }), * }) { * execute(params: this['params']): string { * // params is automatically typed as: * // { operation: "add" | "subtract" | "multiply" | "divide"; a: number; b: number } * const { operation, a, b } = params; * * switch (operation) { * case "add": return String(a + b); * case "subtract": return String(a - b); * case "multiply": return String(a * b); * case "divide": return String(a / b); * } * } * } * ``` * * @example * ```typescript * // With async execution * class WeatherGadget extends Gadget({ * description: "Fetches weather for a city", * schema: z.object({ * city: z.string().min(1).describe("City name"), * }), * timeoutMs: 10000, * }) { * async execute(params: this['params']): Promise { * const { city } = params; // Automatically typed as { city: string } * const weather = await fetchWeather(city); * return `Weather in ${city}: ${weather}`; * } * } * ``` */ declare function Gadget(config: GadgetConfig): { new (): { description: string; parameterSchema: TSchema; name: string | undefined; timeoutMs: number | undefined; examples: GadgetExample>[] | undefined; maxConcurrent: number | undefined; exclusive: boolean | undefined; /** * Type helper property for accessing inferred parameter type. * This is used in the execute method signature: `execute(params: this['params'])` * * Note: This is just for type inference - the actual params in execute() * will be Record which you can safely cast to this['params'] */ readonly params: InferSchema; /** * Execute the gadget. Subclasses should cast params to this['params']. * * @param params - Validated parameters from the LLM * @param ctx - Optional execution context for cost reporting and LLM access * @returns Result as a string, or an object with result and optional cost * * @example * ```typescript * // Simple string return (free gadget) * execute(params: this['params']) { * return String(params.a + params.b); * } * * // Using context for callback-based cost reporting * execute(params: this['params'], ctx) { * ctx.reportCost(0.001); * return "result"; * } * * // Using wrapped LLMist for automatic cost tracking * async execute(params: this['params'], ctx) { * return ctx.llmist.complete('Summarize: ' + params.text); * } * ``` */ execute(params: Record, ctx?: ExecutionContext): GadgetExecuteReturn | Promise; stickyResult?: boolean; iterationBarrier?: boolean; throwIfAborted(ctx?: ExecutionContext): void; onAbort(ctx: ExecutionContext | undefined, cleanup: () => void | Promise): void; createLinkedAbortController(ctx?: ExecutionContext): AbortController; getInstruction(optionsOrArgPrefix?: string | { argPrefix?: string; startPrefix?: string; endPrefix?: string; }): string; } & { params: InferSchema; }; }; /** * Proactive rate limiting for LLM API calls. * * Tracks request and token usage in sliding windows to prevent rate limit errors * before they occur. Works in conjunction with reactive backoff (retry.ts) for * comprehensive rate limit handling. */ /** * Configuration for proactive rate limiting. * * Set these values based on your API tier to prevent rate limit errors. * When limits are approached, requests will be automatically delayed. * * @example * ```typescript * // Gemini free tier limits * const agent = LLMist.createAgent() * .withRateLimits({ * requestsPerMinute: 15, * tokensPerMinute: 1_000_000, * safetyMargin: 0.8, * }); * * // OpenAI Tier 1 limits * const agent = LLMist.createAgent() * .withRateLimits({ * requestsPerMinute: 500, * tokensPerMinute: 200_000, * }); * ``` */ interface RateLimitConfig { /** * Maximum requests per minute. * Set based on your API tier. If not set, RPM limiting is disabled. */ requestsPerMinute?: number; /** * Maximum tokens per minute (input + output combined). * Set based on your API tier. If not set, TPM limiting is disabled. */ tokensPerMinute?: number; /** * Maximum tokens per day (optional). * Useful for Gemini free tier which has daily limits. * If not set, daily limiting is disabled. */ tokensPerDay?: number; /** * Safety margin - start throttling at this percentage of limit. * A value of 0.9 means throttling starts at 90% of the limit. * Lower values provide more safety but may reduce throughput. * @default 0.9 */ safetyMargin?: number; /** * Whether proactive rate limiting is enabled. * @default true (when any limit is configured) */ enabled?: boolean; } /** * Resolved rate limit configuration with all defaults applied. */ interface ResolvedRateLimitConfig { requestsPerMinute?: number; tokensPerMinute?: number; tokensPerDay?: number; safetyMargin: number; enabled: boolean; } /** * Default rate limit configuration values. */ declare const DEFAULT_RATE_LIMIT_CONFIG: Pick; /** * Resolves a partial rate limit configuration by applying defaults. * * @param config - Partial configuration (optional) * @returns Fully resolved configuration */ declare function resolveRateLimitConfig(config?: RateLimitConfig): ResolvedRateLimitConfig; /** * Information about a triggered rate limit. */ interface TriggeredLimitInfo { /** Current usage value */ current: number; /** Configured limit value */ limit: number; /** Effective limit after safety margin (limit × safetyMargin) */ effectiveLimit: number; } /** * Usage statistics from the rate limit tracker. */ interface RateLimitStats { /** Current requests per minute */ rpm: number; /** Current tokens per minute */ tpm: number; /** Tokens used today (UTC) */ dailyTokens: number; /** Whether any limit is currently being approached */ isApproachingLimit: boolean; /** Delay required before next request (0 if none) */ requiredDelayMs: number; /** Which limit(s) triggered throttling, if any (present when requiredDelayMs > 0) */ triggeredBy?: { rpm?: TriggeredLimitInfo; tpm?: TriggeredLimitInfo; daily?: TriggeredLimitInfo; }; } /** * Tracks API usage and calculates required delays for proactive rate limiting. * * Uses sliding windows to track requests and token usage, automatically * calculating delays needed to stay within configured limits. * * @example * ```typescript * const tracker = new RateLimitTracker({ * requestsPerMinute: 60, * tokensPerMinute: 100000, * }); * * // Before each request * const delay = tracker.getRequiredDelayMs(); * if (delay > 0) { * await sleep(delay); * } * * // After each request * tracker.recordUsage(inputTokens, outputTokens); * ``` */ declare class RateLimitTracker { private config; /** Timestamps of requests in the current minute window */ private requestTimestamps; /** Token usage entries in the current minute window */ private tokenUsage; /** Daily token count */ private dailyTokens; /** Date string (YYYY-MM-DD UTC) for daily reset tracking */ private dailyResetDate; /** Count of pending reservations (for backward compatibility) */ private pendingReservations; constructor(config?: RateLimitConfig); /** * Record a completed request with its token usage. * * If reserveRequest() was called before the LLM call (recommended for concurrent * scenarios), the request timestamp was already recorded. Otherwise, this method * will add it for backward compatibility. * * @param inputTokens - Number of input tokens used * @param outputTokens - Number of output tokens generated */ recordUsage(inputTokens: number, outputTokens: number): void; /** * Calculate the delay needed before the next request. * * Returns 0 if no delay is needed, otherwise returns the number of * milliseconds to wait to stay within rate limits. * * @returns Delay in milliseconds (0 if none needed) */ getRequiredDelayMs(): number; /** * Check if we're approaching any configured limits. * * @returns true if any limit is at or above the safety margin threshold */ isApproachingLimit(): boolean; /** * Get current usage statistics. * * @returns Current usage stats for monitoring/logging */ getUsageStats(): RateLimitStats; /** * Reset all tracking state. * Useful for testing or when switching API keys/tiers. */ reset(): void; /** * Update configuration dynamically. * Useful when API tier changes or for testing. * * @param config - New configuration to apply */ updateConfig(config: RateLimitConfig): void; /** * Reserve a request slot before making an LLM call. * * This is critical for concurrent subagents sharing a rate limiter. * Without reservation, multiple subagents checking getRequiredDelayMs() * simultaneously would all see zero usage and proceed, causing rate limit errors. * * Call this AFTER waiting for getRequiredDelayMs() but BEFORE making the LLM call. * The reservation ensures subsequent concurrent checks see the pending request. * * @example * ```typescript * // Proactive rate limiting with reservation * const delay = tracker.getRequiredDelayMs(); * if (delay > 0) await sleep(delay); * * tracker.reserveRequest(); // Claim slot BEFORE making call * try { * const result = await llm.call(); * tracker.recordUsage(result.inputTokens, result.outputTokens); * } catch (error) { * // Request already reserved; recordUsage updates token count * throw error; * } * ``` */ reserveRequest(): void; /** * Calculate delay needed based on RPM limit. */ private calculateRpmDelay; /** * Calculate delay needed based on TPM limit. */ private calculateTpmDelay; /** * Remove entries older than 1 minute from the sliding window. */ private pruneOldEntries; /** * Check if the day has changed (UTC) and reset daily counters. */ private checkDailyReset; /** * Get current date in YYYY-MM-DD format (UTC). */ private getCurrentDateUTC; /** * Calculate milliseconds until midnight UTC. */ private getTimeUntilMidnightUTC; } /** * Model Catalog Types * * Type definitions for LLM model specifications including * context windows, pricing, features, and capabilities. */ interface ModelPricing { /** Price per 1 million input tokens in USD */ input: number; /** Price per 1 million output tokens in USD */ output: number; /** Price per 1 million cached input tokens in USD (if supported) */ cachedInput?: number; /** Price per 1 million cache write tokens in USD (Anthropic: 1.25x input price) */ cacheWriteInput?: number; /** Price per 1 million reasoning/thinking output tokens in USD (defaults to output price if unset) */ reasoningOutput?: number; } interface ModelFeatures { /** Supports streaming responses */ streaming: boolean; /** Supports function/tool calling */ functionCalling: boolean; /** Supports vision/image input */ vision: boolean; /** Supports extended thinking/reasoning */ reasoning?: boolean; /** Supports structured outputs */ structuredOutputs?: boolean; /** Supports fine-tuning */ fineTuning?: boolean; /** * Discoverability flag: the model can also run deep research via * `client.research` (see the research model catalog for capabilities * and research-specific pricing). */ research?: boolean; } interface ModelSpec { /** Provider identifier (e.g., 'openai', 'anthropic', 'gemini') */ provider: string; /** Full model identifier used in API calls */ modelId: string; /** Human-readable display name */ displayName: string; /** Maximum context window size in tokens */ contextWindow: number; /** Maximum output tokens per request */ maxOutputTokens: number; /** Pricing per 1M tokens */ pricing: ModelPricing; /** Training data knowledge cutoff date (YYYY-MM-DD or description) */ knowledgeCutoff: string; /** Supported features and capabilities */ features: ModelFeatures; /** Additional metadata */ metadata?: { /** Model family/series */ family?: string; /** Release date */ releaseDate?: string; /** Deprecation date if applicable */ deprecationDate?: string; /** Notes or special information */ notes?: string; /** Whether manual temperature configuration is supported (defaults to true) */ supportsTemperature?: boolean; }; } interface ModelLimits { contextWindow: number; maxOutputTokens: number; } interface CostEstimate { inputCost: number; /** Cost for cached input tokens (already included in inputCost calculation) */ cachedInputCost: number; /** Cost for cache creation tokens (already included in inputCost calculation, Anthropic only) */ cacheCreationCost: number; outputCost: number; /** Cost for reasoning/thinking tokens (subset of outputCost when reasoningOutput pricing is set) */ reasoningCost: number; totalCost: number; currency: "USD"; } /** * Strategy interface for context compaction. * * Strategies define how conversation history is compressed to fit within * context window limits. Different strategies trade off between: * - Speed (LLM calls vs local processing) * - Context preservation (summary quality vs simple truncation) * - Cost (summarization model usage) */ /** * Context provided to compaction strategies. */ interface CompactionContext { /** Current token count of the conversation */ currentTokens: number; /** Target token count after compaction */ targetTokens: number; /** Model's context window limits */ modelLimits: ModelLimits; /** LLMist client for summarization calls */ client: LLMist; /** Model identifier for token counting and summarization */ model: string; } /** * Result of a compaction operation. */ interface CompactionResult { /** Compacted messages to replace history with */ messages: LLMMessage[]; /** Summary text if summarization was used */ summary?: string; /** The name of the strategy that was ultimately executed */ strategyName: string; /** Metadata about the compaction */ metadata: { /** Number of messages before compaction */ originalCount: number; /** Number of messages after compaction */ compactedCount: number; /** Estimated tokens before compaction */ tokensBefore: number; /** Estimated tokens after compaction */ tokensAfter: number; }; } /** * Interface for compaction strategy implementations. * * Strategies receive the conversation history (excluding base messages like * system prompt and gadget instructions) and must return a compacted version. * * @example * ```typescript * class MyCustomStrategy implements CompactionStrategy { * readonly name = 'my-custom'; * * async compact( * messages: LLMMessage[], * config: ResolvedCompactionConfig, * context: CompactionContext * ): Promise { * // Custom compaction logic * return { * messages: compactedMessages, * metadata: { ... } * }; * } * } * ``` */ interface CompactionStrategy { /** Human-readable name of the strategy */ readonly name: string; /** * Compact the given messages to fit within target token count. * * @param messages - Conversation history messages (excludes system/gadget base) * @param config - Resolved compaction configuration * @param context - Context including token counts and LLM client * @returns Compacted messages with metadata */ compact(messages: LLMMessage[], config: ResolvedCompactionConfig, context: CompactionContext): Promise; } /** * Utility to group messages into logical conversation turns. * * A "turn" is typically a user message followed by an assistant response. * Gadget calls are grouped with the preceding assistant message. */ interface MessageTurn { /** Messages in this turn (user + assistant + any gadget results) */ messages: LLMMessage[]; /** Estimated token count for this turn */ tokenEstimate: number; } /** * Configuration types for the context compaction system. * * Context compaction automatically manages conversation history to prevent * context window overflow in long-running agent conversations. */ /** * Event emitted when compaction occurs. * This is included in StreamEvent for UI visibility. */ interface CompactionEvent { /** The strategy that performed the compaction */ strategy: string; /** Token count before compaction */ tokensBefore: number; /** Token count after compaction */ tokensAfter: number; /** Number of messages before compaction */ messagesBefore: number; /** Number of messages after compaction */ messagesAfter: number; /** Summary text if summarization was used */ summary?: string; /** Agent iteration when compaction occurred */ iteration: number; } /** * Statistics about compaction activity. */ interface CompactionStats { /** Total number of compactions performed */ totalCompactions: number; /** Total tokens saved across all compactions */ totalTokensSaved: number; /** Current context usage */ currentUsage: { tokens: number; percent: number; }; /** Model's context window size */ contextWindow: number; } /** * Configuration for the context compaction system. * * @example * ```typescript * // Custom configuration * const agent = await LLMist.createAgent() * .withModel('sonnet') * .withCompaction({ * triggerThresholdPercent: 70, * targetPercent: 40, * preserveRecentTurns: 10, * }) * .ask('...'); * * // Disable compaction * const agent = await LLMist.createAgent() * .withModel('sonnet') * .withoutCompaction() * .ask('...'); * ``` */ interface CompactionConfig { /** * Enable or disable compaction. * @default true */ enabled?: boolean; /** * The compaction strategy to use. * - 'sliding-window': Fast, drops oldest turns (no LLM call) * - 'summarization': LLM-based compression of old messages * - 'hybrid': Summarizes old messages + keeps recent turns (recommended) * - Or provide a custom CompactionStrategy instance * @default 'hybrid' */ strategy?: "sliding-window" | "summarization" | "hybrid" | CompactionStrategy; /** * Context usage percentage that triggers compaction. * When token count exceeds this percentage of the context window, * compaction is performed before the next LLM call. * @default 80 */ triggerThresholdPercent?: number; /** * Target context usage percentage after compaction. * The compaction will aim to reduce tokens to this percentage. * @default 50 */ targetPercent?: number; /** * Number of recent turns to preserve during compaction. * A "turn" is a user message + assistant response pair. * Recent turns are kept verbatim while older ones are summarized/dropped. * @default 5 */ preserveRecentTurns?: number; /** * Model to use for summarization. * If not specified, uses the agent's model. * @default undefined (uses agent's model) */ summarizationModel?: string; /** * Custom system prompt for summarization. * If not specified, uses a default prompt optimized for context preservation. */ summarizationPrompt?: string; /** * Callback invoked when compaction occurs. * Useful for logging or analytics. */ onCompaction?: (event: CompactionEvent) => void; } /** * Default configuration values for compaction. * Compaction is enabled by default with the hybrid strategy. */ declare const DEFAULT_COMPACTION_CONFIG: Required>; /** * Default prompt used for summarization strategy. */ declare const DEFAULT_SUMMARIZATION_PROMPT = "Summarize this conversation history concisely, preserving:\n1. Key decisions made and their rationale\n2. Important facts and data discovered\n3. Errors encountered and how they were resolved\n4. Current task context and goals\n\nFormat as a brief narrative paragraph, not bullet points.\nPrevious conversation:"; /** * Resolved configuration with all defaults applied. */ interface ResolvedCompactionConfig { enabled: boolean; strategy: "sliding-window" | "summarization" | "hybrid"; triggerThresholdPercent: number; targetPercent: number; preserveRecentTurns: number; summarizationModel?: string; summarizationPrompt: string; onCompaction?: (event: CompactionEvent) => void; } /** * Metadata present when an event originates from a subagent. * Undefined for top-level agent events. * * When using subagent gadgets (like BrowseWeb), hook observers receive events * from both the main agent AND subagents. Check this context to distinguish. * * @example * ```typescript * observers: { * onLLMCallStart: (ctx) => { * if (ctx.subagentContext) { * // Event from a subagent * console.log(`↳ Subagent LLM (depth=${ctx.subagentContext.depth})`); * } else { * // Event from the main agent * console.log('Main agent LLM call'); * } * } * } * ``` */ interface SubagentContext { /** Invocation ID of the parent gadget that spawned this subagent */ parentGadgetInvocationId: string; /** Nesting depth: 1 = direct child, 2 = grandchild, etc. */ depth: number; } /** * Context provided when an LLM call starts. * Read-only observation point. */ interface ObserveLLMCallContext { iteration: number; options: Readonly; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when an LLM call is ready to execute. * Fires AFTER beforeLLMCall controller modifications, BEFORE the actual API call. * Use this for logging the exact request being sent to the LLM. */ interface ObserveLLMCallReadyContext { iteration: number; maxIterations: number; /** Budget limit in USD, if configured */ budget?: number; /** Cumulative cost so far (from execution tree) */ totalCost: number; /** Final options after any controller modifications (e.g., trailing messages) */ options: Readonly; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when an LLM call completes successfully. * Read-only observation point. */ interface ObserveLLMCompleteContext { iteration: number; options: Readonly; finishReason: string | null; /** Token usage including cached token counts when available */ usage?: TokenUsage; /** The complete raw response text */ rawResponse: string; /** The final message that will be added to history (after interceptors) */ finalMessage: string; /** Accumulated thinking/reasoning content from reasoning models */ thinkingContent?: string; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when an LLM call fails. * Read-only observation point. */ interface ObserveLLMErrorContext { iteration: number; options: Readonly; error: Error; /** Whether the error was recovered by a controller */ recovered: boolean; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when a gadget execution starts. * Read-only observation point. * * Note: Observer hooks are derived from ExecutionTree events, ensuring consistent * context (including subagentContext) for both main agent and nested subagent events. */ interface ObserveGadgetStartContext { iteration: number; gadgetName: string; invocationId: string; /** Parameters after interceptor modifications (stored in ExecutionTree) */ parameters: Readonly>; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context for a progressive gadget-argument partial. * Read-only observation point. Values are RAW/uncoerced — see `GadgetArgsPartialEvent`. * * Unlike `onGadgetExecutionStart`, this fires BEFORE the gadget call is complete * (and before any ExecutionTree node exists for the gadget), repeatedly, as the * argument value streams in. The same `invocationId` later appears on the gadget's * `gadget_call` event and (if it executes) its start/complete contexts. */ interface ObserveGadgetArgsPartialContext { iteration: number; invocationId: string; gadgetName: string; fieldPath: string; value: string; delta: string; isFieldComplete: boolean; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when a gadget execution completes. * Read-only observation point. * * Note: Observer hooks are derived from ExecutionTree events, ensuring consistent * context (including subagentContext) for both main agent and nested subagent events. */ interface ObserveGadgetCompleteContext { iteration: number; gadgetName: string; invocationId: string; parameters: Readonly>; /** Final result after interceptors (the value stored in ExecutionTree) */ finalResult?: string; error?: string; executionTimeMs: number; breaksLoop?: boolean; /** Cost of gadget execution in USD. 0 if gadget didn't report cost. */ cost?: number; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when a gadget is skipped due to a failed dependency. * Read-only observation point. * * Note: Observer hooks are derived from ExecutionTree events, ensuring consistent * context (including subagentContext) for both main agent and nested subagent events. */ interface ObserveGadgetSkippedContext { iteration: number; gadgetName: string; invocationId: string; parameters: Readonly>; /** The invocation ID of the dependency that failed */ failedDependency: string; /** The error message from the failed dependency */ failedDependencyError: string; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided for each stream chunk. * Read-only observation point. */ interface ObserveChunkContext { iteration: number; /** The raw chunk from the LLM */ rawChunk: string; /** Accumulated text so far */ accumulatedText: string; /** Token usage if available (providers send usage at stream start/end) */ usage?: TokenUsage; logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Observers: Read-only hooks for side effects. * - Cannot modify data * - Errors are logged but don't crash the system * - Run in parallel (no ordering guarantees) */ interface Observers { /** Called when an LLM call starts (before controller modifications) */ onLLMCallStart?: (context: ObserveLLMCallContext) => void | Promise; /** Called when an LLM call is ready (after controller modifications, before API call) */ onLLMCallReady?: (context: ObserveLLMCallReadyContext) => void | Promise; /** Called when an LLM call completes successfully */ onLLMCallComplete?: (context: ObserveLLMCompleteContext) => void | Promise; /** Called when an LLM call fails */ onLLMCallError?: (context: ObserveLLMErrorContext) => void | Promise; /** Called when a gadget execution starts */ onGadgetExecutionStart?: (context: ObserveGadgetStartContext) => void | Promise; /** * Called for each progressive argument partial while a gadget call is still * streaming (before its `gadget_call`). Read-only; awaited in emission order. * Values are RAW/uncoerced and should be treated as best-effort. Keep cheap. */ onGadgetArgsPartial?: (context: ObserveGadgetArgsPartialContext) => void | Promise; /** Called when a gadget execution completes (success or error) */ onGadgetExecutionComplete?: (context: ObserveGadgetCompleteContext) => void | Promise; /** Called when a gadget is skipped due to a failed dependency */ onGadgetSkipped?: (context: ObserveGadgetSkippedContext) => void | Promise; /** Called for each stream chunk */ onStreamChunk?: (context: ObserveChunkContext) => void | Promise; /** Called when context compaction occurs */ onCompaction?: (context: ObserveCompactionContext) => void | Promise; /** Called when the agent loop is terminated by an abort signal */ onAbort?: (context: ObserveAbortContext) => void | Promise; /** Called when rate limiting causes a throttle delay before an LLM call */ onRateLimitThrottle?: (context: ObserveRateLimitThrottleContext) => void | Promise; /** Called when a retry attempt is made after a failed LLM call */ onRetryAttempt?: (context: ObserveRetryAttemptContext) => void | Promise; /** Called when a skill is activated (via LoadSkill gadget or pre-activation) */ onSkillActivated?: (context: ObserveSkillActivatedContext) => void | Promise; } /** * Context provided when context compaction occurs. * Read-only observation point. */ interface ObserveCompactionContext { /** Agent iteration when compaction occurred */ iteration: number; /** Details of the compaction event */ event: CompactionEvent; /** Cumulative compaction statistics */ stats: CompactionStats; /** Logger instance */ logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when the agent is aborted via AbortSignal. * Read-only observation point. */ interface ObserveAbortContext { /** Current iteration when abort was detected */ iteration: number; /** Abort reason if provided via AbortController.abort(reason) */ reason?: unknown; /** Logger instance */ logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when rate limiting causes a throttle delay. * Read-only observation point. */ interface ObserveRateLimitThrottleContext { /** Current iteration */ iteration: number; /** Delay in milliseconds before the next request can proceed */ delayMs: number; /** Current rate limit statistics */ stats: RateLimitStats; /** Logger instance */ logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when a retry attempt is made after a failed LLM call. * Read-only observation point. */ interface ObserveRetryAttemptContext { /** Current iteration */ iteration: number; /** Current attempt number (1-based) */ attemptNumber: number; /** Number of retries remaining after this attempt */ retriesLeft: number; /** The error that triggered the retry */ error: Error; /** Delay in milliseconds suggested by Retry-After header (if present) */ retryAfterMs?: number; /** Logger instance */ logger: Logger; /** Present when event is from a subagent (undefined for top-level agent) */ subagentContext?: SubagentContext; } /** * Context provided when a skill is activated. * Read-only observation point. */ interface ObserveSkillActivatedContext { /** Name of the activated skill */ skillName: string; /** Arguments passed to the skill (if any) */ arguments?: string; /** Current iteration when activation occurred */ iteration: number; /** Logger instance */ logger: Logger; } /** * Context for skill activation controller. */ interface SkillActivationControllerContext { /** Name of the skill being activated */ skillName: string; /** Arguments passed to the skill */ arguments?: string; /** Current iteration */ iteration: number; /** Logger instance */ logger: Logger; } /** * Action returned by beforeSkillActivation controller. */ type BeforeSkillActivationAction = { action: "proceed"; } | { action: "skip"; reason?: string; }; /** * Context for skill instruction interception. */ interface SkillInstructionInterceptorContext { /** Name of the skill being activated */ skillName: string; /** Arguments passed to the skill */ arguments?: string; /** Logger instance */ logger: Logger; } /** * Context for chunk interception. */ interface ChunkInterceptorContext { iteration: number; accumulatedText: string; logger: Logger; } /** * Context for message interception. */ interface MessageInterceptorContext { iteration: number; /** The raw LLM response */ rawResponse: string; logger: Logger; } /** * Context for gadget parameter interception. */ interface GadgetParameterInterceptorContext { iteration: number; gadgetName: string; invocationId: string; logger: Logger; } /** * Context for gadget result interception. */ interface GadgetResultInterceptorContext { iteration: number; gadgetName: string; invocationId: string; parameters: Readonly>; executionTimeMs: number; logger: Logger; } /** * Interceptors: Synchronous transformations with predictable timing. * - Pure functions with clear input -> output * - Run in sequence (order matters) * - Effect is immediate (no confusion about timing) */ interface Interceptors { /** * Intercept and transform raw chunks from the LLM stream. * Affects current stream immediately. * * @param chunk - The raw chunk text from the LLM * @param context - Context information including iteration and accumulated text * @returns Transformed chunk text, or null to suppress the chunk entirely */ interceptRawChunk?: (chunk: string, context: ChunkInterceptorContext) => string | null; /** * Intercept and transform text chunks before they're displayed. * Affects current output immediately. * * @param chunk - The text chunk to be displayed * @param context - Context information including iteration and accumulated text * @returns Transformed chunk text, or null to suppress the chunk entirely */ interceptTextChunk?: (chunk: string, context: ChunkInterceptorContext) => string | null; /** * Intercept and transform the final assistant message before it's added to conversation history. * This is the last chance to modify what gets stored. * * @param message - The final message text * @param context - Context information including raw response * @returns Transformed message text (cannot be suppressed) */ interceptAssistantMessage?: (message: string, context: MessageInterceptorContext) => string; /** * Intercept and transform gadget parameters before execution. * * IMPORTANT: The intercepted parameters are used to update the original call object. * This means the modified parameters will be visible in subsequent hooks. * * @param parameters - The original parameters (readonly - create new object if modifying) * @param context - Context information including gadget name and invocation ID * @returns Modified parameters object */ interceptGadgetParameters?: (parameters: Readonly>, context: GadgetParameterInterceptorContext) => Record; /** * Intercept and transform gadget results and error messages after execution. * This affects what gets sent back to the LLM and stored in history. * Called for both successful results (result.result) and errors (result.error). * * @param result - The gadget result or error text * @param context - Context information including parameters and execution time * @returns Transformed text (cannot be suppressed) */ interceptGadgetResult?: (result: string, context: GadgetResultInterceptorContext) => string; /** * Intercept and transform skill instructions before they are returned to the LLM. * * @param instructions - The resolved skill instructions * @param context - Context including skill name and arguments * @returns Transformed instructions, or null to suppress the skill activation */ interceptSkillInstructions?: (instructions: string, context: SkillInstructionInterceptorContext) => string | null; } /** * Context for LLM call controller. */ interface LLMCallControllerContext { iteration: number; /** Maximum iterations configured for the agent */ maxIterations: number; /** Budget limit in USD, if configured */ budget?: number; /** Cumulative cost so far (from execution tree) */ totalCost: number; options: LLMGenerationOptions; logger: Logger; } /** * Action returned by beforeLLMCall controller. */ type BeforeLLMCallAction = { action: "proceed"; modifiedOptions?: Partial; } | { action: "skip"; syntheticResponse: string; }; /** * Context for after LLM call controller. */ interface AfterLLMCallControllerContext { iteration: number; /** Maximum iterations configured for the agent */ maxIterations: number; /** Budget limit in USD, if configured */ budget?: number; /** Cumulative cost so far (from execution tree) */ totalCost: number; options: Readonly; finishReason: string | null; /** Token usage including cached token counts when available */ usage?: TokenUsage; /** The final message (after interceptors) that will be added to history */ finalMessage: string; /** Number of gadget calls in the current response */ gadgetCallCount: number; logger: Logger; } /** * Action returned by afterLLMCall controller. */ type AfterLLMCallAction = { action: "continue"; } | { action: "append_messages"; messages: LLMMessage[]; } | { action: "modify_and_continue"; modifiedMessage: string; } | { action: "append_and_modify"; modifiedMessage: string; messages: LLMMessage[]; }; /** * Context for LLM error controller. */ interface LLMErrorControllerContext { iteration: number; options: Readonly; error: Error; logger: Logger; } /** * Action returned by LLM error controller. */ type AfterLLMErrorAction = { action: "rethrow"; } | { action: "recover"; fallbackResponse: string; }; /** * Context for gadget execution controller. */ interface GadgetExecutionControllerContext { iteration: number; gadgetName: string; invocationId: string; /** Parameters after interceptors have run */ parameters: Record; logger: Logger; } /** * Action returned by beforeGadgetExecution controller. */ type BeforeGadgetExecutionAction = { action: "proceed"; } | { action: "skip"; syntheticResult: string; }; /** * Context for after gadget execution controller. */ interface AfterGadgetExecutionControllerContext { iteration: number; gadgetName: string; invocationId: string; parameters: Readonly>; /** Result after interceptors (if successful) */ result?: string; error?: string; executionTimeMs: number; logger: Logger; } /** * Action returned by afterGadgetExecution controller. */ type AfterGadgetExecutionAction = { action: "continue"; } | { action: "recover"; fallbackResult: string; }; /** * Context for dependency skip controller. * Called when a gadget would be skipped due to a failed dependency. */ interface DependencySkipControllerContext { iteration: number; gadgetName: string; invocationId: string; /** Parameters of the gadget that would be skipped */ parameters: Record; /** The invocation ID of the dependency that failed */ failedDependency: string; /** The error message from the failed dependency */ failedDependencyError: string; logger: Logger; } /** * Action returned by onDependencySkipped controller. */ type DependencySkipAction = /** Skip execution and propagate failure to downstream dependents */ { action: "skip"; } /** Execute the gadget anyway despite the failed dependency */ | { action: "execute_anyway"; } /** Skip execution but provide a fallback result (doesn't propagate failure) */ | { action: "use_fallback"; fallbackResult: string; }; /** * Controllers: Async lifecycle hooks that control execution flow. * - Can short-circuit execution * - Can modify options and provide fallbacks * - Run at specific lifecycle points */ interface Controllers { /** * Called before making an LLM API call. * Can modify options or skip the call entirely. */ beforeLLMCall?: (context: LLMCallControllerContext) => Promise; /** * Called after a successful LLM call (after interceptors have run). * Can append messages to conversation or modify the final message. */ afterLLMCall?: (context: AfterLLMCallControllerContext) => Promise; /** * Called after an LLM call fails. * Can provide a fallback response to recover from the error. */ afterLLMError?: (context: LLMErrorControllerContext) => Promise; /** * Called before executing a gadget (after interceptors have run). * Can skip execution and provide a synthetic result. */ beforeGadgetExecution?: (context: GadgetExecutionControllerContext) => Promise; /** * Called after a gadget execution (success or error). * Can provide a fallback result to recover from errors. */ afterGadgetExecution?: (context: AfterGadgetExecutionControllerContext) => Promise; /** * Called before skipping a gadget due to a failed dependency. * Can override the default skip behavior to execute anyway or provide a fallback. */ onDependencySkipped?: (context: DependencySkipControllerContext) => Promise; /** * Called before activating a skill. * Can skip the activation (e.g., for permission checks or cost limits). */ beforeSkillActivation?: (context: SkillActivationControllerContext) => Promise; } /** * Clean hooks system with three distinct categories: * - Observers: Read-only, for logging and metrics * - Interceptors: Synchronous transformations with immediate effect * - Controllers: Async lifecycle control with short-circuit capability */ interface AgentHooks { /** Read-only observation hooks for logging, metrics, etc. */ observers?: Observers; /** Synchronous transformation hooks that affect current execution */ interceptors?: Interceptors; /** Async lifecycle control hooks */ controllers?: Controllers; } /** * Types and interfaces for multimodal generation (image, speech). * * These types support non-token-based billing models where costs are calculated * per-image, per-character, or per-second rather than per-token. */ /** * Options for image generation requests. */ interface ImageGenerationOptions { /** Model to use (e.g., "dall-e-3", "imagen-3.0-generate-002") */ model: string; /** Text prompt describing the desired image */ prompt: string; /** * Image size/dimensions. * - OpenAI: "1024x1024", "1024x1792", "1792x1024" * - Gemini: "1:1", "3:4", "4:3", "9:16", "16:9" */ size?: string; /** * Image quality level. * - OpenAI: "standard", "hd" */ quality?: string; /** * Number of images to generate. * Note: DALL-E 3 only supports n=1 */ n?: number; /** * Response format for the generated image. * - "url": Returns a URL to the image (expires after ~1 hour) * - "b64_json": Returns base64-encoded image data */ responseFormat?: "url" | "b64_json"; } /** * A single generated image. */ interface GeneratedImage { /** URL to the generated image (if responseFormat is "url") */ url?: string; /** Base64-encoded image data (if responseFormat is "b64_json") */ b64Json?: string; /** Revised prompt (if the model modified the original prompt) */ revisedPrompt?: string; } /** * Usage information for image generation. */ interface ImageUsage { /** Number of images generated */ imagesGenerated: number; /** Size of generated images */ size: string; /** Quality level used */ quality: string; } /** * Result of an image generation request. */ interface ImageGenerationResult { /** Array of generated images */ images: GeneratedImage[]; /** Model used for generation */ model: string; /** Usage information */ usage: ImageUsage; /** Estimated cost in USD */ cost?: number; } /** * Available audio formats for speech generation. */ type AudioFormat = "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm" | "pcm16"; /** * Options for speech (TTS) generation requests. */ interface SpeechGenerationOptions { /** Model to use (e.g., "tts-1", "tts-1-hd") */ model: string; /** Text to convert to speech */ input: string; /** * Voice to use for generation. * - OpenAI: "alloy", "echo", "fable", "onyx", "nova", "shimmer" * - Gemini: "Zephyr", "Puck", "Charon", "Kore", etc. */ voice: string; /** Output audio format (default: "mp3") */ responseFormat?: AudioFormat; /** * Speed of the generated audio. * Range: 0.25 to 4.0 (default: 1.0) */ speed?: number; } /** * Usage information for speech generation. */ interface SpeechUsage { /** Number of characters processed */ characterCount: number; } /** * Result of a speech generation request. */ interface SpeechGenerationResult { /** Generated audio data */ audio: ArrayBuffer; /** Model used for generation */ model: string; /** Usage information */ usage: SpeechUsage; /** Estimated cost in USD */ cost?: number; /** Audio format of the result */ format: AudioFormat; } /** * Pricing structure for image models. * Maps size -> quality -> price per image. */ interface ImageModelPricing { /** Simple per-image price (for models with uniform pricing) */ perImage?: number; /** * Size-based pricing. * Maps size (e.g., "1024x1024") to quality-based pricing or flat price. */ bySize?: Record | number>; } /** * Pricing structure for speech models. * Supports both character-based pricing (tts-1, tts-1-hd) and * token-based pricing (gpt-4o-mini-tts). */ interface SpeechModelPricing { /** Price per character (e.g., 0.000015 for $15 per 1M chars) - for tts-1, tts-1-hd */ perCharacter?: number; /** Token-based pricing (for gpt-4o-mini-tts) */ perInputToken?: number; perAudioOutputToken?: number; /** Approximate cost per minute of generated audio (for estimation) */ perMinute?: number; } /** * Specification for an image generation model. */ interface ImageModelSpec { /** Provider identifier (e.g., "openai", "gemini") */ provider: string; /** Model identifier */ modelId: string; /** Human-readable display name */ displayName: string; /** Pricing information */ pricing: ImageModelPricing; /** Supported image sizes */ supportedSizes: string[]; /** Supported quality levels (optional) */ supportedQualities?: string[]; /** Maximum images per request */ maxImages: number; /** Default size if not specified */ defaultSize?: string; /** Default quality if not specified */ defaultQuality?: string; /** Additional feature flags */ features?: { /** Supports conversational/multi-turn image editing */ conversational?: boolean; /** Optimized for text rendering in images */ textRendering?: boolean; /** Supports transparency */ transparency?: boolean; /** Supports image editing/inpainting */ editing?: boolean; /** Supports video generation (Sora) */ videoGeneration?: boolean; /** Supports extended duration video */ extendedDuration?: boolean; }; } /** * Specification for a speech generation model. */ interface SpeechModelSpec { /** Provider identifier (e.g., "openai", "gemini") */ provider: string; /** Model identifier */ modelId: string; /** Human-readable display name */ displayName: string; /** Pricing information */ pricing: SpeechModelPricing; /** Available voice options */ voices: string[]; /** Supported audio formats */ formats: AudioFormat[]; /** Maximum input text length (characters) */ maxInputLength: number; /** Default voice if not specified */ defaultVoice?: string; /** Default format if not specified */ defaultFormat?: AudioFormat; /** Additional feature flags */ features?: { /** Supports multi-speaker output */ multiSpeaker?: boolean; /** Number of supported languages */ languages?: number; /** Supports voice instructions/steering */ voiceInstructions?: boolean; }; } /** * Deep Research — normalized types. * * Deep research runs are long-lived (minutes to an hour), server-side agentic * jobs that browse the web and return cited reports. This module defines the * provider-independent surface: options, the normalized event union, the final * result, and the serializable job reference used to re-attach to a running * background job after a disconnect or process restart. */ /** * Lifecycle status of a research job. * * Superset of provider statuses: * - OpenAI Responses: `queued | in_progress | completed | failed | cancelled | incomplete` * - Gemini Interactions adds `requires_action` and `budget_exceeded` */ type ResearchStatus = "queued" | "in_progress" | "requires_action" | "completed" | "failed" | "cancelled" | "incomplete" | "budget_exceeded"; /** * Data-source / auxiliary tools a research run may use. * * Providers map these to their native tool shapes (e.g. `web_search` becomes * OpenAI's `web_search_preview`). Which types a given model accepts is * declared in {@link ResearchModelSpec.capabilities.tools}. */ type ResearchToolConfig = { type: "web_search"; } | { type: "file_search"; vectorStoreIds: string[]; } | { type: "mcp"; serverLabel: string; serverUrl: string; requireApproval?: "never"; } | { type: "code_interpreter"; }; /** Union of the tool type discriminators. */ type ResearchToolType = ResearchToolConfig["type"]; /** Tool types that count as a data source (OpenAI requires at least one). */ declare const RESEARCH_DATA_SOURCE_TOOL_TYPES: readonly ResearchToolType[]; /** * Options for starting a research run. */ interface ResearchOptions { /** * Model identifier, optionally provider-prefixed: * `"openai:gpt-5.5-pro"`, `"gemini:deep-research-preview-04-2026"`, * `"openrouter:perplexity/sonar-deep-research"`. */ model: string; /** The research question / brief. */ query: string; /** * System-level guidance. Folded into the input on providers without a * system slot (OpenAI deep research), mapped to `system_instruction` * (Gemini) or a system message (OpenRouter) elsewhere. */ systemPrompt?: string; /** * Run as a server-side background job (survives disconnects; enables * {@link ResearchJob.toRef} / attach). Defaults to the model's * `capabilities.background`. Requesting `true` on a provider without * background support is a validation error. */ background?: boolean; /** * Data-source / auxiliary tools. Defaults to the model's * `requiredTools`. Tools outside the model's `capabilities.tools` * are rejected before any network call. */ tools?: ResearchToolConfig[]; /** Cap on total built-in tool calls (cost control; OpenAI `max_tool_calls`). */ maxToolCalls?: number; /** Reasoning configuration (mapped per provider: summaries, effort, thinking). */ reasoning?: ReasoningConfig; /** * Continue from a previous **completed** research job (Gemini * `previous_interaction_id`). Rejected on models without follow-up support. */ previousJobId?: string; /** * Overall time budget for the run as observed by this client. Expiry aborts * the transport (the server-side job keeps running and stays attachable) and * surfaces a `ResearchTimeoutError`. Defaults to * `min(RESEARCH_DEFAULT_TIMEOUT_MS, spec.maxDurationMs)`. */ timeoutMs?: number; /** * Aborts the transport only — a background job keeps running server-side * and can be re-attached via its ref. Use {@link ResearchJob.cancel} to stop * the job on the server. */ signal?: AbortSignal; /** Provider-specific passthrough (same spirit as `LLMGenerationOptions.extra`). */ extra?: Record; } /** A citation attached to the research report. */ interface ResearchCitation { url: string; title?: string; /** Start offset of the cited span in the report text, when provided. */ startIndex?: number; /** End offset of the cited span in the report text, when provided. */ endIndex?: number; /** Excerpt of the cited source content, when provided (OpenRouter). */ content?: string; } /** Token usage extended with research-specific dimensions. */ interface ResearchUsage extends TokenUsage { /** Number of web searches performed, when the provider reports it. */ searches?: number; /** Estimated cost in USD, computed from catalog pricing when available. */ costUSD?: number; } /** Error payload carried by `error` events. */ interface ResearchErrorInfo { message: string; code?: string; /** Whether retrying (or resuming) may succeed. */ retryable: boolean; } /** * Normalized research event union. * * Every event may carry a `cursor` (provider stream position: OpenAI * `sequence_number`, Gemini `event_id`) used for lossless resume, and a * `rawEvent` escape hatch with the provider's original payload. */ type ResearchEvent = { cursor?: string; rawEvent?: unknown; } & ({ type: "created"; /** Server-side job id; `null` on providers without job handles (OpenRouter). */ jobId: string | null; } | { type: "status"; status: ResearchStatus; } | { type: "phase"; /** Coarse activity phase. Providers may emit additional phase strings. */ phase: "planning" | "searching" | "reasoning" | "writing" | (string & {}); } | { type: "thinking"; delta: string; } | { type: "search"; action: "search" | "open_page" | "find_in_page"; status: "started" | "completed"; query?: string; url?: string; } | { type: "tool"; tool: "code_interpreter" | "file_search" | "mcp"; status: "started" | "completed"; detail?: string; } | { type: "text"; delta: string; } | { type: "citation"; citation: ResearchCitation; } | { type: "usage"; usage: ResearchUsage; } | { type: "error"; error: ResearchErrorInfo; } | { type: "done"; result: ResearchDoneInfo; }); /** * Terminal payload emitted by provider normalizers on `done`. * * Providers fill what they know; the job's collector merges it with * accumulated stream state (text deltas, citations, usage) into the final * {@link ResearchResult}. `report` may be empty when the report was fully * streamed as `text` deltas. */ interface ResearchDoneInfo { status: ResearchStatus; /** Full report text when the provider returns it wholesale (else ""). */ report: string; citations?: ResearchCitation[]; usage?: ResearchUsage; /** Final provider object (response / interaction / last chunk). */ raw?: unknown; } /** Final result of a research run. */ interface ResearchResult { /** Server-side job id, or `null` on providers without job handles. */ jobId: string | null; /** Adapter provider id that ran the job (e.g. "openai", "mock"). */ provider: string; /** Model / agent id (unprefixed). */ model: string; status: ResearchStatus; /** The research report text. */ report: string; /** Deduplicated citations. */ citations: ResearchCitation[]; usage: ResearchUsage; /** Wall-clock duration observed by this client, when measurable. */ durationMs?: number; /** Final provider payload, when available. */ raw?: unknown; } /** * JSON-serializable reference to a background research job. * * Round-trip contract: `JSON.parse(JSON.stringify(ref))` is a valid ref, and * `client.research.attach(ref)` resumes the event stream from `cursor` — * across process restarts. */ interface ResearchJobRef { /** Adapter provider id (e.g. "openai", "gemini", "mock"). */ provider: string; /** Model / agent id (unprefixed). */ model: string; /** Server-side job id. */ jobId: string; /** Last observed stream cursor; resume yields events strictly after it. */ cursor?: string; /** ISO timestamp of job creation, when known. */ startedAt?: string; } /** Snapshot returned by status polls. */ interface ResearchStatusSnapshot { status: ResearchStatus; /** Present when the job reached a terminal state and the result is available. */ result?: ResearchResult; } /** * Handle to a research run. * * The job is itself async-iterable (equivalent to iterating * {@link ResearchJob.events}). The event stream may be consumed **once**. */ interface ResearchJob extends AsyncIterable { /** Server-side job id once known (`created` event), else `null`. */ readonly jobId: string | null; /** Adapter provider id. */ readonly provider: string; /** Model / agent id (unprefixed). */ readonly model: string; /** * Live event stream. Auto-reconnects with the last cursor on transient * stream drops when the model is resumable. Single consumption only. */ events(): AsyncIterable; /** * Final result. Drains the event stream internally when not already * consumed; otherwise resolves when iteration completes. */ result(): Promise; /** * One-shot server-side status poll. * @throws ResearchNotPollableError on providers without status polling. */ status(): Promise; /** * Cancel the job server-side where supported; otherwise aborts the * transport. See {@link ResearchOptions.signal} for the abort-vs-cancel * distinction. */ cancel(): Promise; /** * Serializable reference for later {@link ResearchNamespaceLike.attach}. * @throws ResearchJobNotResumableError when the job has no server-side id. */ toRef(): ResearchJobRef; } /** * Research model catalog types. * * Research-capable models get their own catalog (mirroring the image/speech * media catalogs) instead of overloading `ModelSpec`: Gemini research "agents" * have no chat/context-window semantics, and research pricing has dimensions * (`perThousandSearches`, `internalReasoning`) that `ModelPricing` lacks. * * Research capability is **catalog-driven** — nothing outside a catalog file * should test model-id strings. */ /** * Research pricing. Token rates are USD per 1M tokens. */ interface ResearchPricing { /** USD per 1M input tokens. */ input: number; /** USD per 1M output tokens. */ output: number; /** USD per 1M cached input tokens (defaults to `input` when omitted). */ cachedInput?: number; /** * USD per 1M internal reasoning tokens, when the provider prices them * separately from output (Perplexity sonar-deep-research: 3.0). When set, * reasoning tokens are billed at this rate and excluded from `output`. */ internalReasoning?: number; /** * USD per 1,000 web searches (OpenAI web search: 10, Perplexity * sonar-deep-research: 5, sonar-pro-search: 18, Gemini: 14 post-free-tier). */ perThousandSearches?: number; } /** Capability flags for a research model/agent. */ interface ResearchCapabilities { /** Whether live event streaming is supported (gpt-5.5-pro: false → create+poll). */ streaming: boolean; /** Whether server-side background jobs are supported. */ background: boolean; /** Whether a dropped stream can resume from a cursor. */ resumable: boolean; /** Whether follow-up runs can reference a previous job (Gemini). */ followUps?: boolean; /** Tool types accepted by this model (empty = tools are provider-managed). */ tools: ResearchToolType[]; } /** Lifecycle metadata for a research model/agent. */ interface ResearchModelMetadata { releaseDate?: string; /** Date the provider announced deprecation. */ deprecationDate?: string; /** * Date the provider removes the model (ISO date). Starting a run past this * date throws `ResearchDeprecatedModelError`; within the warning window a * warning is logged. */ shutdownDate?: string; /** Recommended replacement model id, surfaced in deprecation errors. */ replacement?: string; notes?: string; } /** * A research-capable model, agent, or preset. */ interface ResearchModelSpec { /** Provider adapter id (e.g. "openai", "gemini", "openrouter"). */ provider: string; /** Model id, agent id, or preset id (unprefixed). */ modelId: string; /** * - `"model"` — a regular model doing research via tools (OpenAI, OpenRouter) * - `"agent"` — a provider-managed research agent (Gemini Interactions) * - `"preset"` — an llmist-orchestrated research preset (reserved; future Anthropic track) */ kind: "model" | "agent" | "preset"; displayName: string; /** Context window in tokens (undefined for agents/presets). */ contextWindow?: number; /** Max output tokens (undefined for agents/presets). */ maxOutputTokens?: number; pricing: ResearchPricing; capabilities: ResearchCapabilities; /** * Tools injected when the caller supplies none (e.g. OpenAI research * requires at least one data source → `[{type: "web_search"}]`). */ requiredTools?: ResearchToolConfig[]; /** Provider-enforced maximum run duration (Gemini: 60 minutes). */ maxDurationMs?: number; metadata?: ResearchModelMetadata; } interface ProviderAdapter { readonly providerId: string; /** * Optional priority for adapter resolution. * Higher numbers = higher priority (checked first). * * When multiple adapters support the same model descriptor, the adapter * with the highest priority is selected. Adapters with equal priority * maintain their registration order (stable sort). * * Default: 0 (normal priority) * Mock adapters use: 100 (high priority) * * @default 0 */ readonly priority?: number; supports(model: ModelDescriptor): boolean; stream(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec?: ModelSpec): LLMStream; /** * Optionally provide model specifications for this provider. * This allows the model registry to discover available models and their capabilities. */ getModelSpecs?(): ModelSpec[]; /** * Count tokens in messages before making an API call. * Uses provider-specific native token counting methods. * @param messages - Array of messages to count tokens for * @param descriptor - Model descriptor * @param spec - Optional model specification * @returns Promise resolving to the number of input tokens */ countTokens?(messages: LLMMessage[], descriptor: ModelDescriptor, spec?: ModelSpec): Promise; /** * Get image model specifications for this provider. * Returns undefined if the provider doesn't support image generation. */ getImageModelSpecs?(): ImageModelSpec[]; /** * Check if this provider supports image generation for a given model. * @param modelId - Model identifier (e.g., "dall-e-3") */ supportsImageGeneration?(modelId: string): boolean; /** * Generate images from a text prompt. * @param options - Image generation options * @returns Promise resolving to the generation result with images and cost */ generateImage?(options: ImageGenerationOptions): Promise; /** * Get speech model specifications for this provider. * Returns undefined if the provider doesn't support speech generation. */ getSpeechModelSpecs?(): SpeechModelSpec[]; /** * Check if this provider supports speech generation for a given model. * @param modelId - Model identifier (e.g., "tts-1", "tts-1-hd") */ supportsSpeechGeneration?(modelId: string): boolean; /** * Generate speech audio from text. * @param options - Speech generation options * @returns Promise resolving to the generation result with audio and cost */ generateSpeech?(options: SpeechGenerationOptions): Promise; /** * Get research model/agent specifications for this provider. * Returns undefined if the provider doesn't support research. */ getResearchModelSpecs?(): ResearchModelSpec[]; /** * Check if this provider supports deep research for a given model/agent id. * @param modelId - Model or agent identifier (unprefixed) */ supportsResearch?(modelId: string): boolean; /** * Start a research run as a normalized event stream. * * Contract: * - The first emitted event MUST be `created` (with the server-side job id, * or `null` on providers without job handles). * - Providers without live streaming implement create + poll internally, * emitting `status` heartbeats and a final `text` + `done`. * - Events SHOULD carry a `cursor` when the provider supports resume. * * @param options - Research options (validated by the namespace before this call) * @param descriptor - Parsed model descriptor * @param spec - Catalog spec when the model is cataloged */ startResearch?(options: ResearchOptions, descriptor: ModelDescriptor, spec?: ResearchModelSpec): AsyncIterable; /** * Re-attach to a background research job, yielding events strictly after * `ref.cursor` (or all events when no cursor is set). */ resumeResearch?(ref: ResearchJobRef, signal?: AbortSignal): AsyncIterable; /** * One-shot status poll for a background research job. Returns the terminal * result when the job has completed. */ getResearchStatus?(ref: ResearchJobRef): Promise; /** Cancel a background research job server-side. */ cancelResearch?(ref: ResearchJobRef): Promise; } /** * Model Registry * * Centralized registry for querying LLM model specifications, * validating configurations, and estimating costs. * * Model data is provided by ProviderAdapter implementations and * automatically populated when providers are registered. */ declare class ModelRegistry { private modelSpecs; private providerMap; /** * Register a provider and collect its model specifications */ registerProvider(provider: ProviderAdapter): void; /** * Register a custom model specification at runtime * * Use this to add models that aren't in the built-in catalog, such as: * - Fine-tuned models with custom pricing * - New models not yet supported by llmist * - Custom deployments with different configurations * * @param spec - Complete model specification * @throws {Error} If spec is missing required fields * * @example * ```ts * client.modelRegistry.registerModel({ * provider: "openai", * modelId: "ft:gpt-4o-2024-08-06:my-org:custom:abc123", * displayName: "My Fine-tuned GPT-4o", * contextWindow: 128_000, * maxOutputTokens: 16_384, * pricing: { input: 7.5, output: 30.0 }, * knowledgeCutoff: "2024-08", * features: { streaming: true, functionCalling: true, vision: true } * }); * ``` */ registerModel(spec: ModelSpec): void; /** * Register multiple custom model specifications at once * * @param specs - Array of complete model specifications * * @example * ```ts * client.modelRegistry.registerModels([ * { provider: "openai", modelId: "gpt-5", ... }, * { provider: "openai", modelId: "gpt-5-mini", ... } * ]); * ``` */ registerModels(specs: ModelSpec[]): void; /** * Get model specification by model ID * @param modelId - Full model identifier, optionally with provider prefix * (e.g., 'gpt-5', 'claude-sonnet-4-5-20250929', 'anthropic:claude-sonnet-4-5') * @returns ModelSpec if found, undefined otherwise */ getModelSpec(modelId: string): ModelSpec | undefined; /** * List all models, optionally filtered by provider * @param providerId - Optional provider ID to filter by (e.g., 'openai', 'anthropic') * @returns Array of ModelSpec objects */ listModels(providerId?: string): ModelSpec[]; /** * Get context window and output limits for a model * @param modelId - Full model identifier * @returns ModelLimits if model found, undefined otherwise */ getModelLimits(modelId: string): ModelLimits | undefined; /** * Estimate API cost for a given model and token usage * @param modelId - Full model identifier * @param inputTokens - Number of input tokens (total, including cached and cache creation) * @param outputTokens - Number of output tokens * @param cachedInputTokens - Number of cached input tokens (subset of inputTokens) * @param cacheCreationInputTokens - Number of cache creation tokens (subset of inputTokens, Anthropic only) * @param reasoningTokens - Number of reasoning/thinking tokens (subset of outputTokens) * @returns CostEstimate if model found, undefined otherwise */ estimateCost(modelId: string, inputTokens: number, outputTokens: number, cachedInputTokens?: number, cacheCreationInputTokens?: number, reasoningTokens?: number): CostEstimate | undefined; /** * Validate that requested token count fits within model limits * @param modelId - Full model identifier * @param requestedTokens - Total tokens requested (input + output) * @returns true if valid, false if model not found or exceeds limits */ validateModelConfig(modelId: string, requestedTokens: number): boolean; /** * Check if a model supports a specific feature * @param modelId - Full model identifier * @param feature - Feature to check ('streaming', 'functionCalling', 'vision', etc.) * @returns true if model supports feature, false otherwise */ supportsFeature(modelId: string, feature: keyof ModelSpec["features"]): boolean; /** * Get all models that support a specific feature * @param feature - Feature to filter by * @param providerId - Optional provider ID to filter by * @returns Array of ModelSpec objects that support the feature */ getModelsByFeature(feature: keyof ModelSpec["features"], providerId?: string): ModelSpec[]; /** * Get the most cost-effective model for a given provider and token budget * @param inputTokens - Expected input tokens * @param outputTokens - Expected output tokens * @param providerId - Optional provider ID to filter by * @returns ModelSpec with lowest total cost, or undefined if no models found */ getCheapestModel(inputTokens: number, outputTokens: number, providerId?: string): ModelSpec | undefined; } /** * Quick execution methods for simple use cases. * * These methods provide convenient shortcuts for common operations * without requiring full agent setup. * * @example * ```typescript * // Quick completion * const answer = await llmist.complete("What is 2+2?"); * * // Quick streaming * for await (const chunk of llmist.stream("Tell me a story")) { * process.stdout.write(chunk); * } * ``` */ /** * Options for text generation methods (complete/stream). */ interface TextGenerationOptions { /** Model to use (supports aliases like "gpt4", "sonnet", "flash") */ model?: string; /** Temperature (0-1) */ temperature?: number; /** System prompt */ systemPrompt?: string; /** Max tokens to generate */ maxTokens?: number; } /** * Quick completion - returns final text response. * * @param client - LLMist client instance * @param prompt - User prompt * @param options - Optional configuration * @returns Complete text response * * @example * ```typescript * const client = new LLMist(); * const answer = await complete(client, "What is 2+2?"); * console.log(answer); // "4" or "2+2 equals 4" * ``` */ declare function complete(client: LLMist, prompt: string, options?: TextGenerationOptions): Promise; /** * Quick streaming - returns async generator of text chunks. * * @param client - LLMist client instance * @param prompt - User prompt * @param options - Optional configuration * @returns Async generator yielding text chunks * * @example * ```typescript * const client = new LLMist(); * * for await (const chunk of stream(client, "Tell me a story")) { * process.stdout.write(chunk); * } * ``` */ declare function stream(client: LLMist, prompt: string, options?: TextGenerationOptions): AsyncGenerator; /** * Retry configuration for LLM API calls. * * Provides exponential backoff with jitter to handle transient failures * like rate limits (429), server errors (5xx), and connection issues. */ /** * Configuration options for retry behavior. * * @example * ```typescript * // Custom retry with monitoring * const agent = LLMist.createAgent() * .withRetry({ * retries: 5, * minTimeout: 2000, * onRetry: (error, attempt) => console.log(`Retry ${attempt}`), * }) * .ask("Hello"); * ``` */ interface RetryConfig { /** * Whether retry is enabled. * @default true */ enabled?: boolean; /** * Maximum number of retry attempts. * @default 3 */ retries?: number; /** * Minimum delay before the first retry in milliseconds. * @default 1000 */ minTimeout?: number; /** * Maximum delay between retries in milliseconds. * @default 30000 */ maxTimeout?: number; /** * Exponential factor for backoff calculation. * @default 2 */ factor?: number; /** * Whether to add random jitter to prevent thundering herd. * @default true */ randomize?: boolean; /** * Called before each retry attempt. * Use for logging or metrics. */ onRetry?: (error: Error, attempt: number) => void; /** * Called when all retries are exhausted and the operation fails. * The error will still be thrown after this callback. */ onRetriesExhausted?: (error: Error, attempts: number) => void; /** * Custom function to determine if an error should trigger a retry. * If not provided, uses the default `isRetryableError` classification. * * @returns true to retry, false to fail immediately */ shouldRetry?: (error: Error) => boolean; /** * Whether to respect Retry-After headers from providers. * When true, delays will be adjusted to honor server-requested wait times. * Supported providers: Anthropic, OpenAI (HTTP headers), Gemini (error message parsing). * @default true */ respectRetryAfter?: boolean; /** * Maximum wait time to honor from Retry-After headers (in milliseconds). * If a server requests a longer wait, this cap is used instead. * @default 120000 (2 minutes) */ maxRetryAfterMs?: number; /** * Whether to treat an empty completion (a 200-OK response with no text, * no tool calls, and no reasoning) as a transient failure and retry it. * When all attempts come back empty, an `EmptyCompletionError` is thrown * rather than committing a silent blank turn. Only takes effect when * `enabled` is true. * @default true */ retryOnEmpty?: boolean; } /** * Resolved retry configuration with all defaults applied. */ interface ResolvedRetryConfig { enabled: boolean; retries: number; minTimeout: number; maxTimeout: number; factor: number; randomize: boolean; onRetry?: (error: Error, attempt: number) => void; onRetriesExhausted?: (error: Error, attempts: number) => void; shouldRetry?: (error: Error) => boolean; respectRetryAfter: boolean; maxRetryAfterMs: number; retryOnEmpty: boolean; } /** * Default retry configuration values. * Conservative defaults: 3 retries with up to 30s delay, respecting Retry-After headers. */ declare const DEFAULT_RETRY_CONFIG: Omit; /** * Resolves a partial retry configuration by applying defaults. * * @param config - Partial configuration (optional) * @returns Fully resolved configuration with defaults applied */ declare function resolveRetryConfig(config?: RetryConfig): ResolvedRetryConfig; /** * Determines if an error is retryable based on common LLM API error patterns. * * Retryable errors include: * - Rate limits (429) * - Server errors (500, 502, 503, 504) * - Timeouts and connection errors * - Provider-specific transient errors * * Non-retryable errors include: * - Authentication errors (401, 403) * - Bad request errors (400) * - Not found errors (404) * - Content policy violations * * @param error - The error to classify * @returns true if the error is retryable */ declare function isRetryableError(error: Error): boolean; /** * Heuristically detects whether an error is likely caused by context overflow * (request too large for the provider/model), as opposed to other 400 errors * like authentication failures or content policy violations. * * Used by the agent's main loop to decide whether forced compaction + retry * is a viable recovery strategy. * * @param error - The error to classify * @returns true if the error is likely a context overflow */ declare function isLikelyContextOverflow(error: Error): boolean; /** * Context for enhanced error formatting. */ interface FormatLLMErrorContext { /** Provider name for provider-specific suggestions */ provider?: "anthropic" | "openai" | "gemini" | string; /** Whether all retry attempts were exhausted */ retriesExhausted?: boolean; } /** * Formats an LLM API error into a clean, user-friendly message. * * Extracts the most relevant information from provider error objects, * hiding verbose JSON/stack traces while preserving actionable details. * * When retries are exhausted and provider is known, includes actionable * suggestions and links to provider documentation. * * @param error - The error to format * @param context - Optional context for enhanced error messages * @returns A clean error message, multi-line when retries exhausted * * @example * ```typescript * // Gemini RESOURCE_EXHAUSTED error * formatLLMError(error); * // Returns: "Rate limit exceeded (429) - retry after a few seconds" * * // With context and exhausted retries * formatLLMError(error, { provider: 'anthropic', retriesExhausted: true }); * // Returns multi-line message with suggestions and documentation link * ``` */ declare function formatLLMError(error: Error, context?: FormatLLMErrorContext): string; /** * Parses a Retry-After header value into milliseconds. * * Supports two formats: * - Seconds: "30" → 30000ms * - HTTP date: "Wed, 21 Oct 2015 07:28:00 GMT" → milliseconds until that time * * @param value - The Retry-After header value * @returns Delay in milliseconds, or null if parsing fails */ declare function parseRetryAfterHeader(value: string): number | null; /** * Extracts Retry-After delay from an error object. * * Supports multiple sources: * - Anthropic/OpenAI: error.headers['retry-after'] or error.response.headers * - Gemini: Parses "retry after Xs" from error message * * @param error - The error to extract Retry-After from * @returns Delay in milliseconds, or null if not found * * @example * ```typescript * // Anthropic/OpenAI SDK error with headers * const delay = extractRetryAfterMs(error); // e.g., 30000 * * // Gemini RESOURCE_EXHAUSTED error * // "Please retry in 45.283754998s" * const delay = extractRetryAfterMs(error); // 45284 * ``` */ declare function extractRetryAfterMs(error: Error): number | null; /** * Subagent configuration types. * * Simple config shapes passed to gadgets for subagent configuration. * No external dependencies — self-contained data shapes. * * @module */ /** * Parent agent configuration passed to gadgets. * Contains settings that subagents can inherit. */ interface AgentContextConfig { /** Model identifier used by the parent agent */ model: string; /** Temperature setting used by the parent agent */ temperature?: number; } /** * Configuration for a single subagent. * Can be defined globally in `[subagents.Name]` or per-profile in `[profile.subagents.Name]`. * * @example * ```toml * [subagents.BrowseWeb] * model = "inherit" # Use parent agent's model * maxIterations = 20 * headless = true * ``` */ interface SubagentConfig { /** * Model to use for this subagent. * - "inherit": Use parent agent's model (default behavior) * - Any model ID: Use specific model (e.g., "sonnet", "haiku", "gpt-4o") */ model?: string; /** Maximum iterations for the subagent loop */ maxIterations?: number; /** Budget limit in USD for the subagent */ budget?: number; /** * Timeout for the subagent gadget execution in milliseconds. * Overrides the gadget's hardcoded timeoutMs when set. * Set to 0 to disable timeout for this gadget. */ timeoutMs?: number; /** * Maximum number of concurrent executions allowed for this gadget. * When the limit is reached, additional calls are queued and processed * as earlier executions complete (FIFO order). * Set to 0 or omit to allow unlimited concurrent executions (default). */ maxConcurrent?: number; /** Additional subagent-specific options */ [key: string]: unknown; } /** * Map of subagent names to their configurations. */ type SubagentConfigMap = Record; /** * Gadget execution mode controlling how multiple gadgets are executed. * * - `'parallel'` (default): Gadgets without dependencies execute concurrently (fire-and-forget). * This maximizes throughput but gadgets may complete in any order. * * - `'sequential'`: Gadgets execute one at a time, each awaiting completion before the next starts. * Useful for: * - Gadgets with implicit ordering dependencies (e.g., file operations) * - Debugging and tracing execution flow * - Resource-constrained environments * - Ensuring deterministic execution order * * Note: Explicit `dependsOn` relationships are always respected regardless of mode. * Sequential mode effectively enforces a global `maxConcurrent: 1` for all gadgets. * * @example * ```typescript * const agent = LLMist.createAgent() * .withModel("sonnet") * .withGadgets(FileReader, FileWriter) * .withGadgetExecutionMode('sequential') // Execute one at a time * .ask("Process files in order"); * ``` */ type GadgetExecutionMode = "parallel" | "sequential"; /** * Image generation namespace with automatic cost reporting. */ interface CostReportingImageNamespace { /** * Generate images from a text prompt. * Costs are automatically reported to the execution context. */ generate(options: ImageGenerationOptions): Promise; } /** * Speech generation namespace with automatic cost reporting. */ interface CostReportingSpeechNamespace { /** * Generate speech audio from text. * Costs are automatically reported to the execution context. */ generate(options: SpeechGenerationOptions): Promise; } /** * LLMist client interface for use within gadgets. * * Provides LLM completion methods that automatically report costs * via the execution context. All LLM calls made through this client * will have their costs tracked and included in the gadget's total cost. * * @example * ```typescript * execute: async ({ text }, ctx) => { * // LLM costs are automatically reported * const summary = await ctx.llmist.complete('Summarize: ' + text, { * model: 'haiku', * }); * return summary; * } * ``` */ interface CostReportingLLMist { /** * Quick completion - returns final text response. * Costs are automatically reported to the execution context. */ complete(prompt: string, options?: TextGenerationOptions): Promise; /** * Quick streaming - returns async generator of text chunks. * Costs are automatically reported when the stream completes. */ streamText(prompt: string, options?: TextGenerationOptions): AsyncGenerator; /** * Low-level stream access for full control. * Costs are automatically reported based on usage metadata in chunks. */ stream(options: LLMGenerationOptions): LLMStream; /** * Access to model registry for cost estimation. */ readonly modelRegistry: ModelRegistry; /** * Image generation with automatic cost reporting. * Costs are reported based on model and generation parameters. */ readonly image: CostReportingImageNamespace; /** * Speech generation with automatic cost reporting. * Costs are reported based on input length and model pricing. */ readonly speech: CostReportingSpeechNamespace; } /** * Execution context provided to gadgets during execution. * * Contains utilities for cost reporting and LLM access. * This parameter is optional for backwards compatibility - * existing gadgets without the context parameter continue to work. * * @example * ```typescript * // Using reportCost() for manual cost reporting * const apiGadget = createGadget({ * description: 'Calls external API', * schema: z.object({ query: z.string() }), * execute: async ({ query }, ctx) => { * const result = await callExternalAPI(query); * ctx.reportCost(0.001); // Report $0.001 cost * return result; * }, * }); * * // Using ctx.llmist for automatic LLM cost tracking * const summarizer = createGadget({ * description: 'Summarizes text using LLM', * schema: z.object({ text: z.string() }), * execute: async ({ text }, ctx) => { * // LLM costs are automatically reported! * return ctx.llmist.complete('Summarize: ' + text); * }, * }); * ``` */ interface ExecutionContext { /** * Report a cost incurred during gadget execution. * * Costs are accumulated and added to the gadget's total cost. * Can be called multiple times during execution. * This is summed with any cost returned from the execute() method * and any costs from ctx.llmist calls. * * @param amount - Cost in USD (e.g., 0.001 for $0.001) * * @example * ```typescript * execute: async (params, ctx) => { * await callExternalAPI(params.query); * ctx.reportCost(0.001); // $0.001 per API call * * await callAnotherAPI(params.data); * ctx.reportCost(0.002); // Can be called multiple times * * return 'done'; * // Total cost: $0.003 * } * ``` */ reportCost(amount: number): void; /** * Pre-configured LLMist client that automatically reports LLM costs * as gadget costs via the reportCost() callback. * * All LLM calls made through this client will have their costs * automatically tracked and included in the gadget's total cost. * * This property is optional - it will be `undefined` if: * - The gadget is executed via CLI `gadget run` command * - The gadget is tested directly without agent context * - No LLMist client was provided to the executor * * Always check for availability before use: `ctx.llmist?.complete(...)` * * @example * ```typescript * execute: async ({ text }, ctx) => { * // Check if llmist is available * if (!ctx.llmist) { * return 'LLM not available in this context'; * } * * // LLM costs are automatically reported * const summary = await ctx.llmist.complete('Summarize: ' + text, { * model: 'haiku', * }); * * // Additional manual costs can still be reported * ctx.reportCost(0.0001); // Processing overhead * * return summary; * } * ``` */ llmist?: CostReportingLLMist; /** * Abort signal for cancellation support. * * When a gadget times out, this signal is aborted before the TimeoutException * is thrown. Gadgets can use this to clean up resources (close browsers, * cancel HTTP requests, etc.) when execution is cancelled. * * The signal is always provided (never undefined) to simplify gadget code. * * @example * ```typescript * // Check for abort at key checkpoints * execute: async (params, ctx) => { * if (ctx.signal.aborted) return 'Aborted'; * * await doExpensiveWork(); * * if (ctx.signal.aborted) return 'Aborted'; * return result; * } * * // Register cleanup handlers * execute: async (params, ctx) => { * const browser = await chromium.launch(); * ctx.signal.addEventListener('abort', () => browser.close(), { once: true }); * // ... use browser * } * * // Pass to fetch for automatic cancellation * execute: async ({ url }, ctx) => { * const response = await fetch(url, { signal: ctx.signal }); * return await response.text(); * } * ``` */ signal: AbortSignal; /** * Parent agent configuration for subagents to inherit. * * Contains the model and settings of the agent that invoked this gadget. * Subagent gadgets (like BrowseWeb) can use this to inherit the parent's * model by default, rather than using hardcoded defaults. * * This is optional - it will be `undefined` for: * - Gadgets executed via CLI `gadget run` command * - Direct gadget testing without agent context * * @example * ```typescript * execute: async (params, ctx) => { * // Inherit parent model unless explicitly specified * const model = params.model ?? ctx.agentConfig?.model ?? "sonnet"; * * const agent = new AgentBuilder(new LLMist()) * .withModel(model) * .build(); * // ... * } * ``` */ agentConfig?: AgentContextConfig; /** * Subagent-specific configuration overrides from CLI config. * * Contains per-subagent settings defined in `[subagents.Name]` or * `[profile.subagents.Name]` sections of cli.toml. Allows users to * customize subagent behavior without modifying gadget parameters. * * Resolution priority (highest to lowest): * 1. Runtime params (explicit gadget call) * 2. Profile-level subagent config * 3. Global subagent config * 4. Parent model (if "inherit") * 5. Package defaults * * @example * ```typescript * execute: async (params, ctx) => { * const subagentConfig = ctx.subagentConfig?.BrowseWeb ?? {}; * * const model = params.model * ?? subagentConfig.model * ?? ctx.agentConfig?.model * ?? "sonnet"; * * const maxIterations = params.maxIterations * ?? subagentConfig.maxIterations * ?? 15; * // ... * } * ``` */ subagentConfig?: SubagentConfigMap; /** * Unique invocation ID for this gadget execution. * Used by `withParentContext()` to identify which parent gadget * nested events belong to. */ invocationId?: string; /** * The execution tree tracking all LLM calls and gadget executions. * * Subagent gadgets can use the tree to: * - Automatically aggregate costs via `tree.getSubtreeCost(nodeId)` * - Collect media outputs via `tree.getSubtreeMedia(nodeId)` * - Query token usage via `tree.getSubtreeTokens(nodeId)` * * When using `withParentContext(ctx)`, the subagent shares the parent's tree, * enabling unified cost tracking and progress visibility across all nesting levels. * * This is optional - it will be `undefined` for: * - Gadgets executed via CLI `gadget run` command * - Direct gadget testing without agent context * - Legacy code that hasn't adopted the ExecutionTree model * * @example * ```typescript * execute: async (params, ctx) => { * // Build subagent with parent context (shares tree) * const agent = new AgentBuilder(client) * .withParentContext(ctx) * .withGadgets(Navigate, Click) * .ask(params.task); * * for await (const event of agent.run()) { * // Process events... * } * * // After subagent completes, costs are automatically tracked in tree * // No need for manual cost aggregation! * const subtreeCost = ctx.tree?.getSubtreeCost(ctx.nodeId!); * * // Media from all nested gadgets also aggregated * const allMedia = ctx.tree?.getSubtreeMedia(ctx.nodeId!); * * return { result: "done", media: allMedia }; * } * ``` */ tree?: ExecutionTree; /** * The tree node ID for this gadget execution. * * This identifies the current gadget's node in the execution tree. * Use with tree methods to query/aggregate data for this subtree: * - `tree.getSubtreeCost(nodeId)` - total cost including nested calls * - `tree.getSubtreeMedia(nodeId)` - all media from nested gadgets * - `tree.getSubtreeTokens(nodeId)` - token usage breakdown * - `tree.getDescendants(nodeId)` - all child nodes * * Note: This is distinct from `invocationId` which identifies the gadget call * (used in conversation history). `nodeId` is the tree node identifier. */ nodeId?: NodeId; /** * Nesting depth of this gadget execution. * * - 0 = Root level (direct gadget call from main agent) * - 1 = First-level subagent (gadget called by a gadget) * - 2+ = Deeper nesting * * Useful for: * - Conditional behavior based on nesting level * - Logging with appropriate indentation * - Limiting recursion depth * * @example * ```typescript * execute: async (params, ctx) => { * // Prevent infinite recursion * if ((ctx.depth ?? 0) > 3) { * return "Maximum nesting depth reached"; * } * * // Log with depth-aware indentation * const indent = " ".repeat(ctx.depth ?? 0); * console.log(`${indent}Executing at depth ${ctx.depth}`); * } * ``` */ depth?: number; /** * Host llmist exports for external gadgets. * * External gadgets MUST use these instead of importing from 'llmist' * to ensure they use the same version as the host CLI, enabling proper * tree sharing and feature compatibility. * * Use the `getHostExports(ctx)` helper function to access these exports * with proper error handling. * * @example * ```typescript * import { getHostExports, Gadget, z } from 'llmist'; * * class BrowseWeb extends Gadget({...}) { * async execute(params, ctx) { * const { AgentBuilder } = getHostExports(ctx); * const agent = new AgentBuilder() * .withParentContext(ctx) * .ask(params.task); * } * } * ``` */ hostExports?: HostExports; /** * Logger instance for structured logging. * * External gadgets should use this for logging instead of importing * defaultLogger directly. This ensures logs respect the CLI's configured * log level, format, and destination (file/console). * * The logger is optional to support standalone gadget execution and testing. * Use optional chaining when logging: `ctx.logger?.debug(...)`. * * @example * ```typescript * execute: async (params, ctx) => { * ctx.logger?.debug("[MyGadget] Starting operation", { itemId: params.id }); * // ... do work ... * ctx.logger?.info("[MyGadget] Completed successfully"); * return "done"; * } * ``` */ logger?: Logger; /** * Request human input during gadget execution. * * When available, gadgets can use this callback to ask the user questions * and receive their answers. This is used internally by gadgets that throw * `HumanInputRequiredException` - the executor catches the exception and * calls this callback if provided. * * Subagents created via `createSubagent()` will automatically inherit this * capability from their parent context, enabling nested agents to bubble up * human input requests to the CLI's TUI. * * This is optional - it will be `undefined` for: * - Gadgets executed via CLI `gadget run` command * - Non-interactive (piped) execution * - Direct gadget testing without agent context * * @example * ```typescript * // Subagents automatically inherit human input capability: * const agent = createSubagent(ctx, { * name: "BrowseWeb", * gadgets: [Navigate, Click, AskUser], * }).ask("Log in to example.com"); * * // The AskUser gadget inside BrowseWeb can now prompt the user * // and the input request will bubble up to the CLI's TUI * ``` */ requestHumanInput?: (question: string) => Promise; /** * Parent agent's observer hooks for subagent visibility. * * When a subagent is created with `withParentContext(ctx)`, these observers * are also called for gadget events (in addition to the subagent's own hooks), * enabling the parent to observe subagent gadget activity. * * Only observer hooks are shared (for visibility), not interceptors (which * modify behavior). This ensures subagents operate independently while * parents can monitor their progress. * * The parent's observer hooks are called with `await` in stream-processor.ts * after the subagent's own hooks, ensuring proper ordering of events * (e.g., GadgetCall.Start always before GadgetCall.Complete). * * This is populated automatically by the parent agent's GadgetExecutor * and should not be set manually. * * @example * ```typescript * // Parent agent's hooks will receive subagent gadget events: * const parentHooks = { * observers: { * onGadgetExecutionStart: async (ctx) => { * if (ctx.subagentContext) { * // This is from a subagent (e.g., BrowseWeb's Navigate call) * console.log(`Subagent gadget: ${ctx.gadgetName}`); * } * } * } * }; * * // When BrowseWeb creates a subagent with withParentContext(ctx), * // the subagent's gadget events will call parentHooks.observers * ``` */ parentObservers?: Observers; /** * Shared rate limit tracker for coordinated throttling across subagents. * * When present, all agents in the tree share this tracker to respect * aggregate RPM/TPM limits. This ensures that a parent configured with * `requestsPerMinute: 10` actually limits the entire agent tree to 10 RPM, * not 10 RPM per agent. * * The tracker is automatically inherited via `withParentContext(ctx)`. * Standalone gadgets (testing, CLI `gadget run`) will have this undefined * and create their own tracker if rate limits are configured. * * @example * ```typescript * // Subagents automatically share the parent's tracker: * const agent = new AgentBuilder(client) * .withParentContext(ctx) // Inherits ctx.rateLimitTracker * .ask("Do something"); * * // All LLM calls from this subagent count toward the parent's limits * ``` */ rateLimitTracker?: RateLimitTracker; /** * Shared retry configuration for consistent backoff behavior across subagents. * * When present, subagents inherit the parent's retry strategy including * max retries, backoff timing, and callbacks. This ensures consistent * error handling across the entire agent tree. * * The config is automatically inherited via `withParentContext(ctx)`. * Standalone gadgets will have this undefined and use default retry config. * * @example * ```typescript * // Subagents automatically share the parent's retry config: * const agent = new AgentBuilder(client) * .withParentContext(ctx) // Inherits ctx.retryConfig * .ask("Do something"); * * // Retry attempts use the same backoff strategy as the parent * ``` */ retryConfig?: ResolvedRetryConfig; } /** * Host llmist exports provided to external gadgets via ExecutionContext. * * This ensures external gadgets use the same class instances as the host CLI, * enabling proper tree sharing and avoiding the "dual-package problem" where * different versions of llmist have incompatible classes. */ interface HostExports { /** AgentBuilder for creating subagents with proper tree sharing */ AgentBuilder: typeof AgentBuilder; /** Gadget factory for defining gadgets */ Gadget: typeof Gadget; /** createGadget for functional gadget definitions */ createGadget: typeof createGadget; /** ExecutionTree for tree operations */ ExecutionTree: typeof ExecutionTree; /** LLMist client */ LLMist: typeof LLMist; /** Zod schema builder */ z: typeof zod.z; } /** * Media output types for gadgets returning images, audio, video, or files. * * This module contains pure data shapes with zero cross-module dependencies. * Imported by execution result types, stream event types, and execution context types. * * @module */ /** * Supported media types for gadget output. * Extensible via union - add new types as needed. */ type MediaKind = "image" | "audio" | "video" | "file"; /** * Type-specific metadata for media outputs. * Extensible via index signature for future media types. */ interface MediaMetadata { /** Width in pixels (images, video) */ width?: number; /** Height in pixels (images, video) */ height?: number; /** Duration in milliseconds (audio, video) */ durationMs?: number; /** Allow additional metadata for future extensions */ [key: string]: unknown; } /** * Media output from a gadget execution. * Supports images, audio, video, and arbitrary files. * * @example * ```typescript * // Image output * const imageOutput: GadgetMediaOutput = { * kind: "image", * data: base64EncodedPng, * mimeType: "image/png", * description: "Screenshot of webpage", * metadata: { width: 1920, height: 1080 } * }; * ``` */ interface GadgetMediaOutput { /** Type of media (discriminator for type-specific handling) */ kind: MediaKind; /** Base64-encoded media data */ data: string; /** Full MIME type (e.g., "image/png", "audio/mp3", "video/mp4") */ mimeType: string; /** Human-readable description of the media */ description?: string; /** Type-specific metadata */ metadata?: MediaMetadata; /** Optional filename to use when saving (if not provided, auto-generated) */ fileName?: string; } /** * Stored media item with metadata and file path. * * Created by MediaStore when a gadget returns media outputs. * Contains the abstract ID, file path, and metadata for display. */ interface StoredMedia { /** Unique ID for this media item (e.g., "media_a1b2c3") */ id: string; /** Type of media */ kind: MediaKind; /** Actual file path on disk (internal use) */ path: string; /** MIME type */ mimeType: string; /** File size in bytes */ sizeBytes: number; /** Human-readable description */ description?: string; /** Type-specific metadata */ metadata?: MediaMetadata; /** Name of the gadget that created this media */ gadgetName: string; /** When the media was stored */ createdAt: Date; } /** * Example of gadget usage to help LLMs understand proper invocation. * * Examples are rendered alongside the schema in `getInstruction()` to provide * concrete usage patterns for the LLM. * * @template TParams - Inferred parameter type from Zod schema (defaults to Record) * * @example * ```typescript * const calculator = createGadget({ * schema: z.object({ a: z.number(), b: z.number() }), * examples: [ * { params: { a: 5, b: 3 }, output: "8", comment: "Addition example" } * ], * // ... * }); * ``` */ interface GadgetExample> { /** Example parameter values (typed to match schema) */ params: TParams; /** Optional expected output/result string */ output?: string; /** Optional description explaining what this example demonstrates */ comment?: string; } /** * Execution result types for gadget calls. * * Contains result types returned by gadget `execute()` methods and the * internal result type used after execution completes. * * @module */ interface GadgetExecutionResult { gadgetName: string; invocationId: string; parameters: Record; result?: string; error?: string; executionTimeMs: number; breaksLoop?: boolean; /** Cost of gadget execution in USD. Defaults to 0 if not provided by gadget. */ cost?: number; /** Media outputs from the gadget (images, audio, video, files) */ media?: GadgetMediaOutput[]; /** Abstract IDs for media outputs (e.g., ["media_a1b2c3"]) */ mediaIds?: string[]; /** Stored media with paths (for CLI display) */ storedMedia?: StoredMedia[]; /** * If true, the message persisting this gadget result is marked sticky * (`metadata.sticky = true`) so compaction strategies preserve it * indefinitely. Use for gadgets whose output the agent needs to remember * for the rest of the conversation (e.g. `LoadSkill` whose body is the * canonical reference the agent will keep consulting). Copied from * `AbstractGadget.stickyResult` by the executor. */ stickyResult?: boolean; } /** * Result returned by gadget execute() method. * Can be a simple string or an object with result and optional cost. * * @example * ```typescript * // Simple string return (free gadget) * execute: () => "result" * * // Object return with cost * execute: () => ({ result: "data", cost: 0.001 }) * ``` */ interface GadgetExecuteResult { /** The execution result as a string */ result: string; /** Optional cost in USD (e.g., 0.001 for $0.001) */ cost?: number; } /** * Extended result type with media support. * Use this when gadget returns images, audio, video, or files. * * @example * ```typescript * // Return with image * execute: () => ({ * result: "Screenshot captured", * media: [{ * kind: "image", * data: base64EncodedPng, * mimeType: "image/png", * description: "Screenshot" * }], * cost: 0.001 * }) * ``` */ interface GadgetExecuteResultWithMedia { /** The execution result as a string */ result: string; /** Media outputs (images, audio, video, files) */ media?: GadgetMediaOutput[]; /** Optional cost in USD (e.g., 0.001 for $0.001) */ cost?: number; } /** * Union type for backwards-compatible execute() return type. * Gadgets can return: * - string (legacy, cost = 0) * - GadgetExecuteResult (result + optional cost) * - GadgetExecuteResultWithMedia (result + optional media + optional cost) */ type GadgetExecuteReturn = string | GadgetExecuteResult | GadgetExecuteResultWithMedia; interface ParsedGadgetCall { gadgetName: string; invocationId: string; parametersRaw: string; parameters?: Record; parseError?: string; /** List of invocation IDs this gadget depends on. Empty array if no dependencies. */ dependencies: string[]; } /** * Stream event types emitted during agent execution. * * Contains all discriminated union members for `StreamEvent`, plus * `StreamCompletionEvent` and `GadgetSkippedEvent`. * * @module */ /** Event emitted when a gadget is skipped due to a failed dependency */ interface GadgetSkippedEvent { type: "gadget_skipped"; gadgetName: string; invocationId: string; parameters: Record; /** The invocation ID of the dependency that failed */ failedDependency: string; /** The error message from the failed dependency */ failedDependencyError: string; } /** * Emitted repeatedly while a gadget call is still streaming, surfacing the * GROWING RAW value of one argument field BEFORE the gadget block terminates. * * Use this for progressive UIs (e.g. a form field that fills in live as the * agent streams a long text value). * * Important semantics: * - Values are RAW and UNCOERCED. The authoritative, validated/coerced * parameters arrive later on the single `gadget_call` event. * - `invocationId` is identical across every partial for a gadget AND its * final `gadget_call`, so consumers can correlate them. * - All partials for an invocation are emitted BEFORE that invocation's * `gadget_call`. * - Prefer `value` (replace) over `delta` (append) for correctness; `delta` * is a convenience that can occasionally differ by a trailing newline. * - A partial does NOT guarantee the gadget will execute (it may still be * skipped by `maxGadgetsPerResponse` or fail validation). */ interface GadgetArgsPartialEvent { type: "gadget_args_partial"; /** Stable invocation id (same on all partials + the final `gadget_call`). */ invocationId: string; gadgetName: string; /** JSON-pointer-ish path of the field, e.g. "title", "config/timeout", "items/0". */ fieldPath: string; /** Full accumulated RAW value for this field so far (one trailing newline stripped). */ value: string; /** Text appended since the previous partial for this field ("" if only completion flipped). */ delta: string; /** True once a later `!!!ARG:` or the terminator proves this field's value is final. */ isFieldComplete: boolean; } /** * Event emitted when stream processing completes, containing metadata. * This allows the async generator to "return" metadata while still yielding events. */ interface StreamCompletionEvent { type: "stream_complete"; /** The reason the LLM stopped generating (e.g., "stop", "tool_use") */ finishReason: string | null; /** Token usage statistics from the LLM call */ usage?: TokenUsage; /** Raw response text from the LLM */ rawResponse: string; /** Final message after all interceptors applied */ finalMessage: string; /** Whether any gadgets were executed during this iteration */ didExecuteGadgets: boolean; /** Whether to break the agent loop (e.g., TaskComplete was called) */ shouldBreakLoop: boolean; /** Accumulated thinking/reasoning content from reasoning models */ thinkingContent?: string; } type StreamEvent = { type: "text"; content: string; } | { type: "thinking"; content: string; thinkingType: "thinking" | "redacted"; } | { type: "gadget_call"; call: ParsedGadgetCall; } | GadgetArgsPartialEvent | { type: "gadget_result"; result: GadgetExecutionResult; } | GadgetSkippedEvent | { type: "human_input_required"; question: string; gadgetName: string; invocationId: string; } | { type: "compaction"; event: CompactionEvent; } | { type: "llm_response_end"; finishReason: string | null; usage?: TokenUsage; } | StreamCompletionEvent; /** * Text-only response handler types. * * Defines the handler configuration for when the LLM returns a text-only response * (no gadget calls). Supports simple strategies, gadget triggers, and custom handlers. * * @module */ type TextOnlyHandler = TextOnlyStrategy | TextOnlyGadgetConfig | TextOnlyCustomHandler; /** * Simple strategies for common cases * - 'terminate': End the loop (default behavior) * - 'acknowledge': Continue to next iteration * - 'wait_for_input': Request human input */ type TextOnlyStrategy = "terminate" | "acknowledge" | "wait_for_input"; /** * Configuration for triggering a gadget when receiving text-only response */ interface TextOnlyGadgetConfig { type: "gadget"; name: string; /** * Optional function to map text to gadget parameters. * If not provided, text will be passed as { text: string } */ parameterMapping?: (text: string) => Record; } /** * Custom handler for complex text-only response scenarios */ interface TextOnlyCustomHandler { type: "custom"; handler: (context: TextOnlyContext) => Promise | TextOnlyAction; } /** * Context provided to custom text-only handlers */ interface TextOnlyContext { /** The complete text response from the LLM */ text: string; /** Current iteration number */ iteration: number; /** Full conversation history */ conversation: LLMMessage[]; /** Logger instance */ logger: Logger; } /** * Actions that can be returned by text-only handlers */ type TextOnlyAction = { action: "continue"; } | { action: "terminate"; } | { action: "wait_for_input"; question?: string; } | { action: "trigger_gadget"; name: string; parameters: Record; }; /** * Abstract base class for gadgets. Most users should use the `Gadget()` factory * or `createGadget()` function instead, as they provide better type safety * and simpler APIs. * * Extend this class directly only when you need advanced control over gadget behavior. */ declare abstract class AbstractGadget { /** * The name of the gadget. Used for identification when LLM calls it. * If not provided, defaults to the class name. */ name?: string; /** * Human-readable description of what the gadget does. */ abstract description: string; /** * Optional Zod schema describing the expected input payload. When provided, * it will be validated before execution and transformed into a JSON Schema * representation that is surfaced to the LLM as part of the instructions. */ parameterSchema?: ZodTypeAny; /** * Optional timeout in milliseconds for gadget execution. * If execution exceeds this timeout, a TimeoutException will be thrown. * If not set, the global defaultGadgetTimeoutMs from runtime options will be used. * Set to 0 or undefined to disable timeout for this gadget. */ timeoutMs?: number; /** * Optional usage examples to help LLMs understand proper invocation. * Examples are rendered in getInstruction() alongside the schema. * * Note: Uses broader `unknown` type to allow typed examples from subclasses * while maintaining runtime compatibility. */ examples?: GadgetExample[]; /** * Maximum number of concurrent executions allowed for this gadget. * Use this to prevent race conditions in gadgets that modify shared state. * * - `1` = Sequential execution (only one instance runs at a time) * - `0` or `undefined` = Unlimited concurrency (default) * - `N > 1` = At most N concurrent executions * * This property sets a safety floor: external configuration (SubagentConfig) * can only make concurrency MORE restrictive, never less. For example, if * a gadget declares `maxConcurrent: 1`, external config cannot override it * to allow parallel execution. * * @example * ```typescript * // File writer that must run sequentially to avoid race conditions * class WriteFile extends Gadget({ * description: 'Writes content to a file', * schema: z.object({ path: z.string(), content: z.string() }), * maxConcurrent: 1, // Sequential - prevents race conditions * }) { * execute(params: this['params']) { ... } * } * ``` */ maxConcurrent?: number; /** * If true, this gadget must execute alone — no other gadgets in the same * LLM response can run in parallel. When an exclusive gadget arrives and * other gadgets are already in-flight, it is deferred until they complete. * * Use for gadgets that terminate the agent loop (e.g., Finish), where * sibling tool results must be visible to the LLM before the loop ends. * * This is a safety floor: external config cannot weaken it. */ exclusive?: boolean; /** * If true, results produced by this gadget are marked sticky on the * conversation (`message.metadata.sticky === true`). Compaction strategies * preserve sticky messages past the truncation point, so the agent retains * the gadget's output for the rest of the conversation rather than having * it dropped on the next compaction pass. * * Use for gadgets whose output is *reference material* the agent will keep * consulting — `LoadSkill` is the canonical example: a multi-KB skill body * the agent needs to remember across iterations. Don't use for routine * gadget outputs (file reads, computation results) — those should churn * normally with the conversation. * * Has no effect on agents that don't enable compaction. */ stickyResult?: boolean; /** * Hints to the consuming agent loop that when this gadget appears in an * LLM iteration's tool batch, no other gadget in the same batch should * execute. Sibling tool calls in the same iteration are expected to be * skipped (not executed) with a synthetic result; the next LLM iteration * gets only this gadget's output back, and must re-plan from there. * * llmist exposes this as declarative metadata only — enforcement is the * consuming agent loop's responsibility (the loop already owns the * stream-event consumption and the `beforeGadgetExecution` controller, so * it can buffer per-iteration calls, decide barrier-status at * `llm_response_end`, and skip non-barrier siblings via the standard * skip-with-synthetic-result mechanism). See `LoadSkill` for the canonical * use case: the agent loop should freeze sibling tool execution so the * LLM sees only the loaded skill body before issuing dependent work. * * Orthogonal to `stickyResult` (which affects compaction) and `exclusive` * (which queues the marked gadget alone, AFTER others — opposite of this * flag's "freeze the others" semantic). */ iterationBarrier?: boolean; /** * Execute the gadget with the given parameters. * Can be synchronous or asynchronous. * * @param params - Parameters passed from the LLM * @param ctx - Optional execution context for cost reporting and LLM access * @returns Result as a string, or an object with result and optional cost * * @example * ```typescript * // Simple string return (free gadget) * execute(params) { * return "result"; * } * * // Object return with cost tracking * execute(params) { * return { result: "data", cost: 0.001 }; * } * * // Using context for callback-based cost reporting * execute(params, ctx) { * ctx.reportCost(0.001); * return "result"; * } * * // Using wrapped LLMist for automatic cost tracking * async execute(params, ctx) { * const summary = await ctx.llmist.complete('Summarize: ' + params.text); * return summary; * } * ``` */ abstract execute(params: Record, ctx?: ExecutionContext): GadgetExecuteReturn | Promise; /** * Throws an AbortException if the execution has been aborted. * * Call this at key checkpoints in long-running gadgets to allow early exit * when the gadget has been cancelled (e.g., due to timeout). This enables * resource cleanup and prevents unnecessary work after cancellation. * * @param ctx - The execution context containing the abort signal * @throws AbortException if ctx.signal.aborted is true * * @example * ```typescript * class DataProcessor extends Gadget({ * description: 'Processes data in multiple steps', * schema: z.object({ items: z.array(z.string()) }), * }) { * async execute(params: this['params'], ctx?: ExecutionContext): Promise { * const results: string[] = []; * * for (const item of params.items) { * // Check before each expensive operation * this.throwIfAborted(ctx); * * results.push(await this.processItem(item)); * } * * return results.join(', '); * } * } * ``` */ throwIfAborted(ctx?: ExecutionContext): void; /** * Register a cleanup function to run when execution is aborted (timeout or cancellation). * The cleanup function is called immediately if the signal is already aborted. * Errors thrown by the cleanup function are silently ignored. * * Use this to clean up resources like browser instances, database connections, * or child processes when the gadget is cancelled due to timeout. * * @param ctx - The execution context containing the abort signal * @param cleanup - Function to run on abort (can be sync or async) * * @example * ```typescript * class BrowserGadget extends Gadget({ * description: 'Fetches web page content', * schema: z.object({ url: z.string() }), * }) { * async execute(params: this['params'], ctx?: ExecutionContext): Promise { * const browser = await chromium.launch(); * this.onAbort(ctx, () => browser.close()); * * const page = await browser.newPage(); * this.onAbort(ctx, () => page.close()); * * await page.goto(params.url); * const content = await page.content(); * * await browser.close(); * return content; * } * } * ``` */ onAbort(ctx: ExecutionContext | undefined, cleanup: () => void | Promise): void; /** * Create an AbortController linked to the execution context's signal. * When the parent signal aborts, the returned controller also aborts with the same reason. * * Useful for passing abort signals to child operations like fetch() while still * being able to abort them independently if needed. * * @param ctx - The execution context containing the parent abort signal * @returns A new AbortController linked to the parent signal * * @example * ```typescript * class FetchGadget extends Gadget({ * description: 'Fetches data from URL', * schema: z.object({ url: z.string() }), * }) { * async execute(params: this['params'], ctx?: ExecutionContext): Promise { * const controller = this.createLinkedAbortController(ctx); * * // fetch() will automatically abort when parent times out * const response = await fetch(params.url, { signal: controller.signal }); * return response.text(); * } * } * ``` */ createLinkedAbortController(ctx?: ExecutionContext): AbortController; /** * Generate instruction text for the LLM. * Combines name, description, and parameter schema into a formatted instruction. * * @param optionsOrArgPrefix - Optional custom prefixes for examples, or just argPrefix string for backwards compatibility * @returns Formatted instruction string */ getInstruction(optionsOrArgPrefix?: string | { argPrefix?: string; startPrefix?: string; endPrefix?: string; }): string; } /** * Context provided to prompt template functions for rendering dynamic content. */ interface PromptContext { /** Custom gadget start prefix */ startPrefix: string; /** Custom gadget end prefix */ endPrefix: string; /** Custom argument prefix for block format */ argPrefix: string; /** Number of gadgets being registered */ gadgetCount: number; /** Names of all gadgets */ gadgetNames: string[]; } /** * Context provided to hint template functions for rendering dynamic hints. */ interface HintContext { /** Current iteration (1-based for readability) */ iteration: number; /** Maximum iterations allowed */ maxIterations: number; /** Iterations remaining (maxIterations - iteration) */ remaining: number; /** Number of gadget calls in the current response */ gadgetCallCount?: number; } /** * Template that can be either a static string or a function that renders based on context. */ type PromptTemplate = string | ((context: PromptContext) => string); /** * Template for hints that can be either a static string or a function that renders based on hint context. */ type HintTemplate = string | ((context: HintContext) => string); /** * Configuration for customizing all prompts used internally by llmist. * * Each field can be either a string (static text) or a function that receives * context and returns a string (for dynamic content). * * @example * ```typescript * const customConfig: PromptTemplateConfig = { * mainInstruction: "USE ONLY THE GADGET MARKERS BELOW:", * criticalUsage: "Important: Follow the exact format shown.", * rules: (ctx) => [ * "Always use the markers to invoke gadgets", * "Never use function calling", * `You have ${ctx.gadgetCount} gadgets available` * ] * }; * ``` */ interface PromptTemplateConfig { /** * Main instruction block that appears at the start of the gadget system prompt. * Default emphasizes using text markers instead of function calling. */ mainInstruction?: PromptTemplate; /** * Critical usage instruction that appears in the usage section. * Default emphasizes the exact format requirement. */ criticalUsage?: PromptTemplate; /** * Format description for the block parameter format. * Default uses the configured argPrefix dynamically. */ formatDescription?: PromptTemplate; /** * Rules that appear in the rules section. * Can be an array of strings or a function that returns an array. * Default includes rules about not using function calling. */ rules?: PromptTemplate | string[] | ((context: PromptContext) => string[]); /** * Custom examples to show in the examples section. * If provided, replaces the default examples entirely. * Should be a function that returns formatted example strings. */ customExamples?: (context: PromptContext) => string; /** * Hint shown when LLM uses only one gadget per response. * Encourages parallel gadget usage for efficiency. */ parallelGadgetsHint?: HintTemplate; /** * Template for iteration progress hint. * Informs the LLM about remaining iterations to help plan work. * * When using a string template, supports placeholders: * - {iteration}: Current iteration (1-based) * - {maxIterations}: Maximum iterations allowed * - {remaining}: Iterations remaining */ iterationProgressHint?: HintTemplate; } /** * Default hint templates used by llmist. */ declare const DEFAULT_HINTS: { readonly parallelGadgetsHint: "Tip: You can call multiple gadgets in a single response for efficiency."; readonly iterationProgressHint: "[Iteration {iteration}/{maxIterations}] Plan your actions accordingly."; }; /** * Default prompt templates used by llmist. */ declare const DEFAULT_PROMPTS: Required & { rules: (context: PromptContext) => string[]; customExamples: null; }>; /** * Resolve a prompt template to a string using the given context. */ declare function resolvePromptTemplate(template: PromptTemplate | undefined, defaultValue: PromptTemplate, context: PromptContext): string; /** * Resolve rules template to an array of strings. */ declare function resolveRulesTemplate(rules: PromptTemplateConfig["rules"] | undefined, context: PromptContext): string[]; /** * Resolve a hint template to a string using the given context. * Supports both function templates and string templates with placeholders. * * @param template - The hint template to resolve * @param defaultValue - Default value if template is undefined * @param context - Context for rendering the template * @returns The resolved hint string */ declare function resolveHintTemplate(template: HintTemplate | undefined, defaultValue: string, context: HintContext): string; type MessageRole = "system" | "user" | "assistant"; /** * Message content can be a simple string (text only) or an array of content parts (multimodal). * Using a string is simpler for text-only messages, while arrays support images and audio. */ type MessageContent = string | ContentPart[]; interface LLMMessage { role: MessageRole; content: MessageContent; name?: string; metadata?: Record; } /** * Normalize message content to an array of content parts. * Converts string content to a single text part. * * @param content - Message content (string or ContentPart[]) * @returns Array of content parts */ declare function normalizeMessageContent(content: MessageContent): ContentPart[]; /** * Extract text from message content. * Concatenates all text parts in the content. * * @param content - Message content (string or ContentPart[]) * @returns Combined text from all text parts */ declare function extractMessageText(content: MessageContent): string; declare class LLMMessageBuilder { private readonly messages; private startPrefix; private endPrefix; private argPrefix; private promptConfig; constructor(promptConfig?: PromptTemplateConfig); /** * Set custom prefixes for gadget markers. * Used to configure history builder to match system prompt markers. */ withPrefixes(startPrefix: string, endPrefix: string, argPrefix?: string): this; addSystem(content: string, metadata?: Record): this; addGadgets(gadgets: AbstractGadget[], options?: { startPrefix?: string; endPrefix?: string; argPrefix?: string; }): this; private buildGadgetsSection; private buildUsageSection; private buildExamplesSection; private buildRulesSection; /** * Add a user message. * Content can be a string (text only) or an array of content parts (multimodal). * * @param content - Message content * @param metadata - Optional metadata * * @example * ```typescript * // Text only * builder.addUser("Hello!"); * * // Multimodal * builder.addUser([ * text("What's in this image?"), * imageFromBuffer(imageData), * ]); * ``` */ addUser(content: MessageContent, metadata?: Record): this; addAssistant(content: string, metadata?: Record): this; /** * Add a user message with an image attachment. * * @param textContent - Text prompt * @param imageData - Image data (Buffer, Uint8Array, or base64 string) * @param mimeType - Optional MIME type (auto-detected if not provided) * * @example * ```typescript * builder.addUserWithImage( * "What's in this image?", * await fs.readFile("photo.jpg"), * "image/jpeg" // Optional - auto-detected * ); * ``` */ addUserWithImage(textContent: string, imageData: Buffer | Uint8Array | string, mimeType?: ImageMimeType): this; /** * Add a user message with an image URL (OpenAI only). * * @param textContent - Text prompt * @param imageUrl - URL to the image * * @example * ```typescript * builder.addUserWithImageUrl( * "What's in this image?", * "https://example.com/image.jpg" * ); * ``` */ addUserWithImageUrl(textContent: string, imageUrl: string): this; /** * Add a user message with an audio attachment (Gemini only). * * @param textContent - Text prompt * @param audioData - Audio data (Buffer, Uint8Array, or base64 string) * @param mimeType - Optional MIME type (auto-detected if not provided) * * @example * ```typescript * builder.addUserWithAudio( * "Transcribe this audio", * await fs.readFile("recording.mp3"), * "audio/mp3" // Optional - auto-detected * ); * ``` */ addUserWithAudio(textContent: string, audioData: Buffer | Uint8Array | string, mimeType?: AudioMimeType): this; /** * Add a user message with multiple content parts. * Provides full flexibility for complex multimodal messages. * * @param parts - Array of content parts * * @example * ```typescript * builder.addUserMultimodal([ * text("Compare these images:"), * imageFromBuffer(image1), * imageFromBuffer(image2), * ]); * ``` */ addUserMultimodal(parts: ContentPart[]): this; /** * Record a gadget execution result in the message history. * Creates an assistant message with the gadget invocation and a user message with the result. * * The invocationId is shown to the LLM so it can reference previous calls when building dependencies. * * @param gadget - Name of the gadget that was executed * @param parameters - Parameters that were passed to the gadget * @param result - Text result from the gadget execution * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies) * @param media - Optional media outputs from the gadget * @param mediaIds - Optional IDs for the media outputs * @param storedMedia - Optional stored media info including file paths */ addGadgetCallResult(gadget: string, parameters: Record, result: string, invocationId: string, media?: GadgetMediaOutput[], mediaIds?: string[], storedMedia?: StoredMedia[], metadata?: Record): this; /** * Format parameters as Block format with JSON Pointer paths. * Uses the configured argPrefix for consistency with system prompt. */ private formatBlockParameters; build(): LLMMessage[]; } /** * Provider-agnostic reasoning effort level. * * Maps to provider-specific values: * - **OpenAI**: "none"|"low"|"medium"|"high"|"xhigh" * - **Anthropic**: budget_tokens (1024–32768) * - **Gemini 3**: thinkingLevel "minimal"|"low"|"medium"|"high" * - **Gemini 2.5**: thinkingBudget (0–24576) * - **DeepSeek**: binary (enabled/disabled) */ type ReasoningEffort = "none" | "low" | "medium" | "high" | "maximum"; /** * Configuration for reasoning/thinking mode on supported models. * * When `enabled` is true, the provider will be instructed to use * extended reasoning before generating its response. */ interface ReasoningConfig { /** Whether reasoning is enabled */ enabled: boolean; /** Reasoning effort level (default: "medium") */ effort?: ReasoningEffort; /** Explicit token budget for thinking (Anthropic/Gemini 2.5, overrides effort) */ budgetTokens?: number; /** Whether to surface thinking content in the stream (default: true) */ includeThinking?: boolean; /** Enable interleaved thinking for multi-turn tool use (Anthropic only) */ interleaved?: boolean; } /** * A chunk of thinking/reasoning content from a reasoning model. * * Emitted during streaming when a reasoning model produces thinking output. * The `type` field distinguishes actual thinking from redacted content * (e.g., Anthropic may redact thinking in certain scenarios). */ interface ThinkingChunk { /** The thinking text content */ content: string; /** Whether this is actual thinking or redacted content */ type: "thinking" | "redacted"; /** Verification signature (Anthropic/Gemini) */ signature?: string; } /** * What content to include in the cache. * * - `"system"`: Cache only system prompt (lowest cost, highest reuse) * - `"conversation"`: Cache system prompt + all conversation turns except the latest user message */ type CachingScope = "system" | "conversation"; /** * Configuration for context caching across providers. * * Context caching allows reusing previously computed key-value pairs across * requests, reducing latency and cost for repeated context. * * Provider behavior: * - **Anthropic**: Automatic ephemeral caching via `cache_control` markers (always-on by default). * Use `enabled: false` to disable markers and opt out of caching. * - **Gemini**: Explicit cache lifecycle via `caches.create()`. Requires `scope` and `ttl`. * - **OpenAI**: Server-side automatic caching (no-op, but respects the unified API). */ interface CachingConfig { /** Whether context caching is enabled */ enabled: boolean; /** * What to cache (Gemini only, default: "conversation"). * - `"system"`: Cache only system-derived messages * - `"conversation"`: Cache system + all turns except the latest user message */ scope?: CachingScope; /** TTL for cache entries (Gemini only, format: "3600s", default: "3600s", min: "300s") */ ttl?: string; /** Minimum token count for content to be eligible for caching (Gemini default: 32768) */ minTokenThreshold?: number; } interface LLMGenerationOptions { model: string; messages: LLMMessage[]; maxTokens?: number; temperature?: number; topP?: number; stopSequences?: string[]; responseFormat?: "text"; metadata?: Record; extra?: Record; /** * Optional abort signal for cancelling the request mid-flight. * * When the signal is aborted, the provider will attempt to cancel * the underlying HTTP request and the stream will terminate with * an abort error. Use `isAbortError()` from `@/core/errors` to * detect cancellation in error handling. * * @example * ```typescript * const controller = new AbortController(); * * const stream = client.stream({ * model: "claude-3-5-sonnet-20241022", * messages: [{ role: "user", content: "Tell me a long story" }], * signal: controller.signal, * }); * * // Cancel after 5 seconds * setTimeout(() => controller.abort(), 5000); * * try { * for await (const chunk of stream) { * process.stdout.write(chunk.text); * } * } catch (error) { * if (isAbortError(error)) { * console.log("\nRequest was cancelled"); * } else { * throw error; * } * } * ``` */ signal?: AbortSignal; /** Reasoning/thinking configuration for reasoning-capable models */ reasoning?: ReasoningConfig; /** Context caching configuration for supported providers */ caching?: CachingConfig; } interface TokenUsage { inputTokens: number; outputTokens: number; totalTokens: number; /** Number of input tokens served from cache (subset of inputTokens) */ cachedInputTokens?: number; /** Number of input tokens written to cache (subset of inputTokens, Anthropic only) */ cacheCreationInputTokens?: number; /** Number of reasoning/thinking tokens used (subset of outputTokens) */ reasoningTokens?: number; } interface LLMStreamChunk { text: string; /** * Indicates that the provider has finished producing output and includes the reason if available. */ finishReason?: string | null; /** * Token usage information, typically available in the final chunk when the stream completes. */ usage?: TokenUsage; /** * Provider specific payload emitted at the same time as the text chunk. This is useful for debugging and tests. */ rawEvent?: unknown; /** Thinking/reasoning content from reasoning models */ thinking?: ThinkingChunk; } interface LLMStream extends AsyncIterable { } type ProviderIdentifier = string; interface ModelDescriptor { provider: string; name: string; } declare class ModelIdentifierParser { private readonly defaultProvider; constructor(defaultProvider?: string); parse(identifier: string): ModelDescriptor; } type GadgetClass = new (...args: unknown[]) => AbstractGadget; type GadgetOrClass = AbstractGadget | GadgetClass; declare class GadgetRegistry { private readonly gadgets; /** * Creates a registry from an array of gadget classes or instances, * or an object mapping names to gadgets. * * @param gadgets - Array of gadgets/classes or object with custom names * @returns New GadgetRegistry with all gadgets registered * * @example * ```typescript * // From array of classes * const registry = GadgetRegistry.from([Calculator, Weather]); * * // From array of instances * const registry = GadgetRegistry.from([new Calculator(), new Weather()]); * * // From object with custom names * const registry = GadgetRegistry.from({ * calc: Calculator, * weather: new Weather({ apiKey: "..." }) * }); * ``` */ static from(gadgets: GadgetOrClass[] | Record): GadgetRegistry; /** * Registers multiple gadgets at once from an array. * * @param gadgets - Array of gadget instances or classes * @returns This registry for chaining * * @example * ```typescript * registry.registerMany([Calculator, Weather, Email]); * registry.registerMany([new Calculator(), new Weather()]); * ``` */ registerMany(gadgets: GadgetOrClass[]): this; register(name: string, gadget: AbstractGadget): void; registerByClass(gadget: AbstractGadget): void; get(name: string): AbstractGadget | undefined; has(name: string): boolean; getNames(): string[]; getAll(): AbstractGadget[]; unregister(name: string): boolean; clear(): void; } /** * Core type definitions for the Skills system. * * Skills follow the Agent Skills open standard (agentskills.io) — markdown-based * instruction packages that extend agent capabilities through prompt injection * and context management, not code execution. * * Three-tier progressive disclosure: * - Tier 1 (metadata): ~100 tokens, always loaded for discovery * - Tier 2 (instructions): <5K tokens, loaded on activation * - Tier 3 (resources): unlimited, loaded on demand * * @module skills/types */ /** * Parsed YAML frontmatter from a SKILL.md file. * Follows the Agent Skills open standard with llmist-specific extensions. */ interface SkillMetadata { /** Skill identifier. Lowercase letters, numbers, hyphens only. Max 64 chars. */ name: string; /** What the skill does and when to use it. Max 1024 chars. Used for auto-triggering. */ description: string; /** Hint shown during autocomplete, e.g., "[issue-number]" or " [format]". */ argumentHint?: string; /** Tools the agent can use when this skill is active. */ allowedTools?: string[]; /** Model override when skill is active, e.g., "sonnet", "flash". */ model?: string; /** Execution context. "fork" runs in an isolated subagent. */ context?: "fork" | "inline"; /** Subagent type for fork mode, e.g., "Explore", "Plan", "general-purpose". */ agent?: string; /** Glob patterns for auto-activation based on files being worked on. */ paths?: string[]; /** Bundled gadget specifiers loaded when skill activates. */ gadgets?: string[]; /** If true, only the user can invoke this skill (LLM cannot auto-trigger). */ disableModelInvocation?: boolean; /** If false, skill is background knowledge only — hidden from user invocation. */ userInvocable?: boolean; /** Shell for !`command` preprocessing. */ shell?: "bash" | "powershell"; /** Semantic version number. */ version?: string; } /** * A resource file within a skill's directory (Tier 3). */ interface SkillResource { /** Path relative to the skill directory. */ relativePath: string; /** Absolute path on disk. */ absolutePath: string; /** Category based on parent directory. */ category: "scripts" | "references" | "assets"; } /** * Where a skill was discovered from. */ type SkillSource = { type: "project"; path: string; } | { type: "user"; path: string; } | { type: "npm"; package: string; } | { type: "git"; url: string; } | { type: "directory"; path: string; }; /** * Fully parsed skill representation (all three tiers). */ interface ParsedSkill { /** Tier 1: Always-loaded metadata from frontmatter. */ metadata: SkillMetadata; /** Tier 2: Full SKILL.md body. null if not yet loaded. */ instructions: string | null; /** Tier 3: Discovered resource manifests. */ resources: SkillResource[]; /** Absolute path to the SKILL.md file. */ sourcePath: string; /** Directory containing the skill. */ sourceDir: string; /** Origin for debugging and priority resolution. */ source: SkillSource; } /** * Result of activating a skill. */ interface SkillActivation { /** The skill that was activated. */ skillName: string; /** Resolved instructions after $ARGUMENTS substitution and !`command` preprocessing. */ resolvedInstructions: string; /** Gadgets made available by this skill (if any). */ gadgets: AbstractGadget[]; /** Resources loaded for this activation (Tier 3). Keyed by relative path. */ loadedResources: Map; } /** * Options for skill activation. */ interface SkillActivationOptions { /** Arguments passed to the skill (substituted into $ARGUMENTS, $0, $1, etc.). */ arguments?: string; /** Whether to load Tier 3 resources eagerly. Default: false. */ eagerResources?: boolean; /** Working directory for !`command` preprocessing. */ cwd?: string; /** Whether to execute !`command` preprocessing. Default: true. */ enableShellPreprocessing?: boolean; /** Timeout for !`command` execution in milliseconds. Default: 10000. */ shellTimeoutMs?: number; } /** * Skill class with lazy-loading progressive disclosure. * * Tier 1 (metadata) is always available after construction. * Tier 2 (instructions) and Tier 3 (resources) are loaded on demand. * * @module skills/skill */ declare class Skill { readonly metadata: SkillMetadata; readonly sourcePath: string; readonly sourceDir: string; readonly source: SkillSource; private _instructions; private _resources; private readonly _resourceCache; private readonly _resourceLoading; constructor(parsed: ParsedSkill); /** Skill name for registry lookup. */ get name(): string; /** Skill description for LLM matching. */ get description(): string; /** Whether the LLM can auto-trigger this skill. */ get isModelInvocable(): boolean; /** Whether the user can invoke this skill via /skill-name. */ get isUserInvocable(): boolean; /** * Load and cache Tier 2 instructions. * If instructions were loaded during parsing, returns the cached value. */ getInstructions(): Promise; /** * List Tier 3 resources. * Resources are discovered at parse time but content is loaded on demand. */ getResources(): SkillResource[]; /** * Load a specific Tier 3 resource by relative path. * Results are cached for the lifetime of this Skill instance. * Concurrent calls for the same resource share a single read. */ getResource(relativePath: string): Promise; /** * Activate this skill with optional arguments. * * Performs: * 1. Variable substitution (${SKILL_DIR}, etc.) * 2. Argument substitution ($ARGUMENTS, $0, $1) * 3. Shell preprocessing (!`command`) * 4. Resource loading (if eagerResources is true) */ activate(options?: SkillActivationOptions): Promise; /** * Create a Skill from a SKILL.md content string. * Useful for testing or dynamic skill creation. */ static fromContent(content: string, sourcePath: string, source?: SkillSource): Skill; } /** * SkillRegistry — manages skill discovery, lookup, and metadata summaries. * * Parallel to GadgetRegistry but with different semantics: skills are not * executable tools registered with the LLM directly. They are available * for activation by the agent or user, surfaced via metadata summaries. * * @module skills/registry */ declare class SkillRegistry { private readonly skills; /** * Register a skill. Overwrites any existing skill with the same name. * * Unlike GadgetRegistry (which throws on duplicates), SkillRegistry allows * overwriting because skills are loaded from multiple sources with intentional * priority ordering (project > user > default). */ register(skill: Skill): void; /** Register multiple skills. */ registerMany(skills: Skill[]): void; /** Remove a skill by name (case-insensitive). Returns true if removed. */ remove(name: string): boolean; /** Remove all registered skills. */ clear(): void; /** Get a skill by name (case-insensitive). */ get(name: string): Skill | undefined; /** Check if a skill exists by name (case-insensitive). */ has(name: string): boolean; /** Get all registered skills. */ getAll(): Skill[]; /** Get all skill names. */ getNames(): string[]; /** Number of registered skills. */ get size(): number; /** * Get skills that are visible to the LLM for auto-triggering. * Excludes skills with disableModelInvocation: true. */ getModelInvocable(): Skill[]; /** * Get skills that the user can invoke via /skill-name. * Excludes skills with userInvocable: false. */ getUserInvocable(): Skill[]; /** * Generate metadata summaries for system prompt injection (Tier 1). * * Each skill contributes a one-line summary: "name — description". * Output is truncated to fit the character budget. * * @param charBudget - Maximum characters for all summaries combined. */ getMetadataSummaries(charBudget?: number): string; /** * Find skills whose `paths` patterns match a given file path. * Used for auto-activation when the user is working on specific files. */ findByFilePath(filePath: string): Skill[]; /** * Merge another registry into this one. * Skills from the other registry overwrite existing skills with the same name. */ merge(other: SkillRegistry): void; /** Create a registry from an array of skills. */ static from(skills: Skill[]): SkillRegistry; } /** * Context available to trailing message functions. * Provides iteration information for dynamic message generation. */ type TrailingMessageContext = Pick; /** * Trailing message can be a static string or a function that generates the message. * The function receives context about the current iteration. */ type TrailingMessage = string | ((ctx: TrailingMessageContext) => string); /** * Message for conversation history. * User messages can be text (string) or multimodal (ContentPart[]). */ type HistoryMessage = { user: string | ContentPart[]; } | { assistant: string; } | { system: string; }; /** * Event handler sugar for cleaner event processing. * * Instead of verbose if/else chains, use named handlers * for each event type. * * @example * ```typescript * await agent.runWith({ * onText: (content) => console.log("LLM:", content), * onGadgetResult: (result) => console.log("Result:", result.result), * }); * ``` */ /** * Named event handlers for different event types. */ interface EventHandlers { /** Called when text is generated by the LLM */ onText?: (content: string) => void | Promise; /** Called when a gadget is about to be executed */ onGadgetCall?: (call: { gadgetName: string; invocationId: string; parameters?: Record; parametersRaw: string; dependencies: string[]; }) => void | Promise; /** * Called for each progressive argument partial while a gadget call is still * streaming (before `onGadgetCall`). Values are RAW/uncoerced; prefer `value` * (replace) over `delta` (append). Great for live form-fill UIs. */ onGadgetArgsPartial?: (partial: { gadgetName: string; invocationId: string; fieldPath: string; value: string; delta: string; isFieldComplete: boolean; }) => void | Promise; /** Called when a gadget execution completes */ onGadgetResult?: (result: { gadgetName: string; invocationId: string; result?: string; error?: string; parameters: Record; }) => void | Promise; /** Called when human input is required */ onHumanInputRequired?: (data: { question: string; gadgetName: string; }) => void | Promise; /** Called for any other event type */ onOther?: (event: StreamEvent) => void | Promise; } /** * Helper to run an agent with named event handlers. * * @param agentGenerator - Agent's run() async generator * @param handlers - Named event handlers * * @example * ```typescript * await runWithHandlers(agent.run(), { * onText: (text) => console.log("LLM:", text), * onGadgetResult: (result) => console.log("Result:", result.result), * }); * ``` */ declare function runWithHandlers(agentGenerator: AsyncGenerator, handlers: EventHandlers): Promise; /** * Helper to collect events by type. * * @param agentGenerator - Agent's run() async generator * @param collect - Object specifying which event types to collect * @returns Object with collected events * * @example * ```typescript * const { text, gadgetResults } = await collectEvents(agent.run(), { * text: true, * gadgetResults: true, * }); * * console.log("Full response:", text.join("")); * console.log("Gadget calls:", gadgetResults.length); * ``` */ declare function collectEvents(agentGenerator: AsyncGenerator, collect: { text?: boolean; gadgetCalls?: boolean; gadgetResults?: boolean; }): Promise<{ text: string[]; gadgetCalls: Array<{ gadgetName: string; parameters: Record; }>; gadgetResults: Array<{ gadgetName: string; result?: string; error?: string; parameters: Record; }>; }>; /** * Helper to collect only text from an agent run. * * @param agentGenerator - Agent's run() async generator * @returns Combined text response * * @example * ```typescript * const response = await collectText(agent.run()); * console.log(response); * ``` */ declare function collectText(agentGenerator: AsyncGenerator): Promise; /** * Fluent builder for creating agents. * * Provides a chainable API for configuring and creating agents, * making the code more expressive and easier to read. */ declare class AgentBuilder { private core; private gadgets; private retry; private subagents; private policies; private skills; private mcp; constructor(client?: LLMist); /** Set the model to use. Supports aliases like "sonnet", "flash". */ withModel(model: string): this; /** Set the system prompt. */ withSystem(prompt: string): this; /** Set the temperature (0-1). */ withTemperature(temperature: number): this; /** Set maximum iterations. */ withMaxIterations(max: number): this; /** Set the budget limit in USD. */ withBudget(amountUSD: number): this; /** Set logger instance. */ withLogger(logger: Logger): this; /** Add hooks for agent lifecycle events. */ withHooks(hooks: AgentHooks): this; /** Configure custom prompts for gadget system messages. */ withPromptTemplateConfig(config: PromptTemplateConfig): this; /** Add gadgets (classes or instances). */ withGadgets(...gadgets: GadgetOrClass[]): this; /** * Attach a Model Context Protocol (MCP) server. * * The agent connects to the server lazily at the start of `run()`, * discovers its tools, and registers them as native gadgets so the LLM * can call them through the standard streaming block format. * * Calling this multiple times accumulates servers. Tools across servers * are merged into a single registry; in plan 1, conflicting tool names * raise a registration warning. Plan 2 introduces deterministic * `__` prefixing for collisions. * * STDIO commands are gated by an allowlist (see allowlist.ts) — pass * `trust: true` on the spec to opt in for non-allowlisted binaries. * * Zero-overhead invariant: if you never call this method, the MCP * runtime module is never loaded. Agents without MCP pay nothing. * * @example * ```typescript * const agent = LLMist.createAgent() * .withModel("sonnet") * .withMcpServer({ * name: "filesystem", * transport: "stdio", * command: "npx", * args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], * }) * .ask("list files in /tmp"); * ``` */ withMcpServer(spec: McpServerSpec): this; /** Inspect the configured MCP server specs. Useful for tests. */ getMcpServerSpecs(): readonly McpServerSpec[]; /** Add conversation history messages. */ withHistory(messages: HistoryMessage[]): this; /** Add a single message to the conversation history. */ addMessage(message: HistoryMessage): this; /** Clear any previously set conversation history. */ clearHistory(): this; /** Continue conversation from a previous agent's history. */ continueFrom(agent: Agent): this; /** Set the human input handler for interactive conversations. */ onHumanInput(handler: (question: string) => Promise): this; /** Set custom gadget marker prefix. */ withGadgetStartPrefix(prefix: string): this; /** Set custom gadget marker suffix. */ withGadgetEndPrefix(suffix: string): this; /** Set custom argument prefix for block format parameters. */ withGadgetArgPrefix(prefix: string): this; /** Set the text-only handler strategy. */ withTextOnlyHandler(handler: TextOnlyHandler): this; /** Set the handler for text content that appears alongside gadget calls. */ withTextWithGadgetsHandler(handler: { gadgetName: string; parameterMapping: (text: string) => Record; resultMapping?: (text: string) => string; }): this; /** Set default timeout for gadget execution. */ withDefaultGadgetTimeout(timeoutMs: number): this; /** Set the gadget execution mode ('parallel' or 'sequential'). */ withGadgetExecutionMode(mode: GadgetExecutionMode): this; /** Set the maximum number of gadgets to execute per LLM response. */ withMaxGadgetsPerResponse(max: number): this; /** Enable or disable gadget output limiting. */ withGadgetOutputLimit(enabled: boolean): this; /** Set the maximum gadget output as a percentage of the context window. */ withGadgetOutputLimitPercent(percent: number): this; /** Configure context compaction. */ withCompaction(config: CompactionConfig): this; /** Disable context compaction. */ withoutCompaction(): this; /** Register a skill registry for this agent. */ withSkills(registry: SkillRegistry): this; /** * Pre-activate a specific skill before the agent starts. * Instructions are injected into the system prompt. * * Note: each call replaces (not appends) the pre-activated skill for that name. * This is safe for REPL loops where the same builder is reused. */ withSkill(name: string, args?: string): this; /** Clear all pre-activated skills. Call between REPL iterations. */ clearPreActivatedSkills(): this; /** Add a directory to scan for skills. */ withSkillsFrom(dir: string): this; /** Configure retry behavior for LLM API calls. */ withRetry(config: RetryConfig): this; /** Disable automatic retry for LLM API calls. */ withoutRetry(): this; /** Configure proactive rate limiting to prevent rate limit errors. */ withRateLimits(config: RateLimitConfig): this; /** Set an abort signal for cancelling requests mid-flight. */ withSignal(signal: AbortSignal): this; /** Enable reasoning/thinking mode for reasoning-capable models. */ withReasoning(config?: ReasoningConfig | ReasoningEffort): this; /** Explicitly disable reasoning for this agent. */ withoutReasoning(): this; /** Enable context caching for supported providers. */ withCaching(config?: CachingConfig): this; /** Explicitly disable context caching. */ withoutCaching(): this; /** Set subagent configuration overrides. */ withSubagentConfig(config: SubagentConfigMap): this; /** Share parent agent's ExecutionTree for unified event visibility. */ withParentContext(ctx: ExecutionContext, depth?: number): this; /** Add an ephemeral trailing message that appears at the end of each LLM request. */ withTrailingMessage(message: TrailingMessage): this; /** Add a synthetic gadget call to the conversation history for in-context learning. */ withSyntheticGadgetCall(gadgetName: string, parameters: Record, result: string, invocationId: string): this; private composeHooks; private resolveSkillRegistry; /** * Resolve pre-activated skill instructions synchronously. * Reads SKILL.md from disk via readFileSync (skills are local files). */ private resolvePreActivatedInstructions; private buildAgentOptions; /** Create agent and start with a user prompt. */ ask(userPrompt: string): Agent; /** Create agent with multimodal input (text + image). */ askWithImage(textPrompt: string, imageData: Buffer | Uint8Array | string, mimeType?: ImageMimeType): Agent; /** Create agent with flexible multimodal content parts. */ askWithContent(content: ContentPart[]): Agent; /** Run agent and collect text response. */ askAndCollect(userPrompt: string): Promise; /** Run agent with event handlers. */ askWith(userPrompt: string, handlers: EventHandlers): Promise; /** Build agent without a prompt (useful for testing/inspection). */ build(): Agent; } /** * Research namespace — `client.research`. * * Mirrors the image/speech capability namespaces: dispatches to the first * adapter (in priority order) that supports the model, after validating the * request against the model's catalog spec. Providers plug in via the * optional research methods on {@link ProviderAdapter}. */ declare class ResearchNamespace { private readonly adapters; private readonly parser; private readonly now; private readonly logger; constructor(adapters: ProviderAdapter[], parser: ModelIdentifierParser, now?: () => number, logger?: Logger); /** * Start a research run. Returns immediately; the provider stream opens * lazily on first iteration (or on `result()`). */ start(options: ResearchOptions): ResearchJob; /** * Re-attach to a background research job from a serialized ref. * No network happens until the returned job is iterated. */ attach(ref: ResearchJobRef): ResearchJob; /** One-shot status poll for a job ref. */ get(ref: ResearchJobRef): Promise; /** Cancel a background job server-side. */ cancel(ref: ResearchJobRef): Promise; /** All research-capable models/agents across registered providers. */ listModels(): ResearchModelSpec[]; /** Whether any registered provider supports research for this model. */ supportsModel(model: string): boolean; private findResearchAdapter; private findAdapterByProviderId; private findSpec; private describeAvailableModels; /** * Pre-flight validation against the catalog spec — fails fast before any * network call and applies spec-driven defaults. */ private validate; private resolveTools; private enforceLifecycle; } /** * Image Generation Namespace * * Provides image generation methods. * * @example * ```typescript * const llmist = new LLMist(); * * const result = await llmist.image.generate({ * model: "dall-e-3", * prompt: "A cat in space", * size: "1024x1024", * quality: "hd", * }); * * console.log(result.images[0].url); * console.log("Cost:", result.cost); * ``` */ declare class ImageNamespace { private readonly adapters; private readonly defaultProvider; constructor(adapters: ProviderAdapter[], defaultProvider: string); /** * Generate images from a text prompt. * * @param options - Image generation options * @returns Promise resolving to the generation result with images and cost * @throws Error if the provider doesn't support image generation */ generate(options: ImageGenerationOptions): Promise; /** * List all available image generation models. */ listModels(): ImageModelSpec[]; /** * Check if a model is supported for image generation. */ supportsModel(modelId: string): boolean; private findImageAdapter; } /** * Speech Generation Namespace * * Provides text-to-speech generation methods. * * @example * ```typescript * const llmist = new LLMist(); * * const result = await llmist.speech.generate({ * model: "tts-1-hd", * input: "Hello, world!", * voice: "nova", * }); * * // Save the audio * fs.writeFileSync("output.mp3", Buffer.from(result.audio)); * console.log("Cost:", result.cost); * ``` */ declare class SpeechNamespace { private readonly adapters; private readonly defaultProvider; constructor(adapters: ProviderAdapter[], defaultProvider: string); /** * Generate speech audio from text. * * @param options - Speech generation options * @returns Promise resolving to the generation result with audio and cost * @throws Error if the provider doesn't support speech generation */ generate(options: SpeechGenerationOptions): Promise; /** * List all available speech generation models. */ listModels(): SpeechModelSpec[]; /** * Check if a model is supported for speech generation. */ supportsModel(modelId: string): boolean; private findSpeechAdapter; } /** * Text Generation Namespace * * Provides text completion and streaming methods. * Replaces the deprecated llmist.complete() and llmist.stream() methods. * * @example * ```typescript * const llmist = new LLMist(); * * // Complete * const answer = await llmist.text.complete("What is 2+2?"); * * // Stream * for await (const chunk of llmist.text.stream("Tell me a story")) { * process.stdout.write(chunk); * } * ``` */ declare class TextNamespace { private readonly client; constructor(client: LLMist); /** * Generate a complete text response. * * @param prompt - User prompt * @param options - Optional configuration * @returns Complete text response */ complete(prompt: string, options?: TextGenerationOptions): Promise; /** * Stream text chunks. * * @param prompt - User prompt * @param options - Optional configuration * @returns Async generator yielding text chunks */ stream(prompt: string, options?: TextGenerationOptions): AsyncGenerator; } /** * Vision Analysis Namespace * * Provides one-shot image analysis without agent setup. * Useful for quick image understanding tasks. * * @example * ```typescript * const llmist = new LLMist(); * * const description = await llmist.vision.analyze({ * model: "gpt-4o", * image: await readFile("photo.jpg"), * prompt: "Describe this image in detail", * }); * * console.log(description); * ``` */ /** * Options for vision analysis. */ interface VisionAnalyzeOptions { /** Model to use (must support vision, e.g., "gpt-4o", "claude-sonnet-4-20250514", "gemini-2.5-flash") */ model: string; /** Image data: Buffer, Uint8Array, base64 string, data URL, or HTTPS URL */ image: string | Buffer | Uint8Array; /** Analysis prompt describing what to do with the image */ prompt: string; /** MIME type (auto-detected if not provided for Buffer/Uint8Array) */ mimeType?: ImageMimeType; /** System prompt for analysis context */ systemPrompt?: string; /** Max tokens for response */ maxTokens?: number; /** Temperature (0-1) */ temperature?: number; } /** * Result of vision analysis. */ interface VisionAnalyzeResult { /** The analysis text */ text: string; /** Model used */ model: string; /** Token usage if available */ usage?: { inputTokens: number; outputTokens: number; totalTokens: number; }; } declare class VisionNamespace { private readonly client; constructor(client: LLMist); /** * Build a message builder with the image content attached. * Handles URLs, data URLs, base64 strings, and binary buffers. */ private buildImageMessage; /** * Stream the response and collect text and usage information. */ private streamAndCollect; /** * Analyze an image with a vision-capable model. * Returns the analysis as a string. * * @param options - Vision analysis options * @returns Promise resolving to the analysis text * @throws Error if the image format is unsupported or model doesn't support vision * * @example * ```typescript * // From file * const result = await llmist.vision.analyze({ * model: "gpt-4o", * image: await fs.readFile("photo.jpg"), * prompt: "What's in this image?", * }); * * // From URL (OpenAI only) * const result = await llmist.vision.analyze({ * model: "gpt-4o", * image: "https://example.com/image.jpg", * prompt: "Describe this image", * }); * ``` */ analyze(options: VisionAnalyzeOptions): Promise; /** * Analyze an image and return detailed result with usage info. * * @param options - Vision analysis options * @returns Promise resolving to the analysis result with usage info */ analyzeWithUsage(options: VisionAnalyzeOptions): Promise; /** * Check if a model supports vision/image input. * * @param modelId - Model ID to check * @returns True if the model supports vision */ supportsModel(modelId: string): boolean; /** * List all models that support vision. * * @returns Array of model IDs that support vision */ listModels(): string[]; } interface LLMistOptions { /** * Provider adapters to register manually. */ adapters?: ProviderAdapter[]; /** * Default provider prefix applied when a model identifier omits it. */ defaultProvider?: string; /** * Automatically discover built-in providers based on environment configuration. * Enabled by default. */ autoDiscoverProviders?: boolean; /** * Custom model specifications to register at initialization. * Use this to define models not in the built-in catalog, such as: * - Fine-tuned models with custom pricing * - New models not yet supported by llmist * - Custom deployments with different configurations * * @example * ```ts * new LLMist({ * customModels: [{ * provider: "openai", * modelId: "ft:gpt-4o-2024-08-06:my-org:custom:abc123", * displayName: "My Fine-tuned GPT-4o", * contextWindow: 128_000, * maxOutputTokens: 16_384, * pricing: { input: 7.5, output: 30.0 }, * knowledgeCutoff: "2024-08", * features: { streaming: true, functionCalling: true, vision: true } * }] * }); * ``` */ customModels?: ModelSpec[]; } declare class LLMist { private readonly parser; private readonly defaultProvider; readonly modelRegistry: ModelRegistry; private readonly adapters; readonly text: TextNamespace; readonly image: ImageNamespace; readonly speech: SpeechNamespace; readonly vision: VisionNamespace; /** * Deep research — long-running, server-side research jobs with cited * reports (OpenAI Responses, Gemini Interactions, OpenRouter research * models). See docs/specs/002-deep-research.md. */ readonly research: ResearchNamespace; constructor(); constructor(adapters: ProviderAdapter[]); constructor(adapters: ProviderAdapter[], defaultProvider: string); constructor(options: LLMistOptions); stream(options: LLMGenerationOptions): LLMStream; /** * Count tokens in messages for a given model. * * Uses provider-specific token counting methods for accurate estimation: * - OpenAI: tiktoken library with model-specific encodings * - Anthropic: Native messages.countTokens() API * - Gemini: SDK's countTokens() method * * Falls back to character-based estimation (4 chars/token) if the provider * doesn't support native token counting or if counting fails. * * This is useful for: * - Pre-request cost estimation * - Context window management * - Request batching optimization * * @param model - Model identifier (e.g., "openai:gpt-4", "anthropic:claude-3-5-sonnet-20241022") * @param messages - Array of messages to count tokens for * @returns Promise resolving to the estimated input token count * * @example * ```typescript * const client = new LLMist(); * const messages = [ * { role: 'system', content: 'You are a helpful assistant.' }, * { role: 'user', content: 'Hello!' } * ]; * * const tokenCount = await client.countTokens('openai:gpt-4', messages); * console.log(`Estimated tokens: ${tokenCount}`); * ``` */ countTokens(model: string, messages: LLMMessage[]): Promise; private resolveAdapter; /** * Quick completion - returns final text response. * Convenient for simple queries without needing agent setup. * * @param prompt - User prompt * @param options - Optional configuration * @returns Complete text response * * @example * ```typescript * const answer = await LLMist.complete("What is 2+2?"); * console.log(answer); // "4" or "2+2 equals 4" * * const answer = await LLMist.complete("Tell me a joke", { * model: "sonnet", * temperature: 0.9 * }); * ``` */ static complete(prompt: string, options?: TextGenerationOptions): Promise; /** * Quick streaming - returns async generator of text chunks. * Convenient for streaming responses without needing agent setup. * * @param prompt - User prompt * @param options - Optional configuration * @returns Async generator yielding text chunks * * @example * ```typescript * for await (const chunk of LLMist.stream("Tell me a story")) { * process.stdout.write(chunk); * } * * // With options * for await (const chunk of LLMist.stream("Generate code", { * model: "gpt4", * systemPrompt: "You are a coding assistant" * })) { * process.stdout.write(chunk); * } * ``` */ static stream(prompt: string, options?: TextGenerationOptions): AsyncGenerator; /** * Instance method: Quick completion using this client instance. * * @param prompt - User prompt * @param options - Optional configuration * @returns Complete text response */ complete(prompt: string, options?: TextGenerationOptions): Promise; /** * Instance method: Quick streaming using this client instance. * * @param prompt - User prompt * @param options - Optional configuration * @returns Async generator yielding text chunks */ streamText(prompt: string, options?: TextGenerationOptions): AsyncGenerator; /** * Create a fluent agent builder. * Provides a chainable API for configuring and creating agents. * * @returns AgentBuilder instance for chaining * * @example * ```typescript * const agent = LLMist.createAgent() * .withModel("sonnet") * .withSystem("You are a helpful assistant") * .withGadgets(Calculator, Weather) * .ask("What's the weather in Paris?"); * * for await (const event of agent.run()) { * // handle events * } * ``` * * @example * ```typescript * // Quick one-liner for simple queries * const answer = await LLMist.createAgent() * .withModel("gpt4-mini") * .askAndCollect("What is 2+2?"); * ``` */ static createAgent(): AgentBuilder; /** * Create agent builder with this client instance. * Useful when you want to reuse a configured client. * * @returns AgentBuilder instance using this client * * @example * ```typescript * const client = new LLMist({ ... }); * * const agent = client.createAgent() * .withModel("sonnet") * .ask("Hello"); * ``` */ createAgent(): AgentBuilder; } /** * MediaStore: Session-scoped storage for gadget media outputs. * * This module provides an abstraction layer between gadgets and the filesystem. * Instead of exposing raw file paths, it assigns unique IDs to stored media * that can be shared with the LLM and user. * * @example * ```typescript * const store = new MediaStore(); * * // Store an image, get back ID * const stored = await store.store({ * kind: "image", * data: base64EncodedPng, * mimeType: "image/png", * description: "Screenshot" * }, "Screenshot"); * * console.log(stored.id); // "media_a1b2c3" * console.log(stored.path); // "/tmp/llmist-media-xxx/Screenshot_001.png" * * // Later: retrieve by ID * const retrieved = store.get("media_a1b2c3"); * ``` */ /** * Session-scoped media storage with ID abstraction. * * Each MediaStore instance manages media for a single agent session. * Media files are stored in a temporary directory and referenced by * short, unique IDs rather than file paths. */ declare class MediaStore { private readonly items; private readonly outputDir; private counter; private initialized; /** * Create a new MediaStore. * * @param sessionId - Optional session ID for the output directory. * If not provided, a random ID is generated. */ constructor(sessionId?: string); /** * Get the output directory path. */ getOutputDir(): string; /** * Ensure the output directory exists. * @throws Error if directory creation fails */ private ensureDir; /** * Generate a unique media ID. * Format: "media_" + 6 random alphanumeric characters */ private generateId; /** * Get file extension from MIME type. */ private getExtension; /** * Store media and return stored metadata with ID. * * @param media - The media output from a gadget * @param gadgetName - Name of the gadget that created this media * @returns Stored media information including generated ID * @throws Error if file write fails */ store(media: GadgetMediaOutput, gadgetName: string): Promise; /** * Get stored media by ID. * * @param id - The media ID (e.g., "media_a1b2c3") * @returns The stored media or undefined if not found */ get(id: string): StoredMedia | undefined; /** * Get the actual file path for a media ID. * Convenience method for gadgets that need the raw path. * * @param id - The media ID * @returns The file path or undefined if not found */ getPath(id: string): string | undefined; /** * List all stored media, optionally filtered by kind. * * @param kind - Optional media kind to filter by * @returns Array of stored media items */ list(kind?: MediaKind): StoredMedia[]; /** * Get the count of stored media items. */ get size(): number; /** * Check if a media ID exists. */ has(id: string): boolean; /** * Clear in-memory store without deleting files. * Resets the counter but leaves files on disk. */ clear(): void; /** * Delete all stored files and clear memory. * Removes the entire session directory. */ cleanup(): Promise; } /** * Internal key for Agent instantiation. * This Symbol is used to ensure only AgentBuilder can create Agent instances. * * @internal */ declare const AGENT_INTERNAL_KEY: unique symbol; /** * Core interfaces for the Agent architecture. * These interfaces define the contracts for the composable services that make up the agent system. */ /** * Manages the conversation history and message building. * This interface abstracts conversation state management from the orchestration logic. */ interface IConversationManager { /** * Adds a user message to the conversation. * Supports multimodal content (text + images/audio). */ addUserMessage(content: MessageContent): void; /** * Adds an assistant message to the conversation. */ addAssistantMessage(content: string): void; /** * Adds a gadget call and its result to the conversation. * The invocationId is shown to the LLM so it can reference previous calls when building dependencies. * Optionally includes media outputs (images, audio, etc.) for multimodal results. * If storedMedia is provided, file paths will be included in the result message. */ addGadgetCallResult(gadgetName: string, parameters: Record, result: string, invocationId: string, media?: GadgetMediaOutput[], mediaIds?: string[], storedMedia?: StoredMedia[], metadata?: Record): void; /** * Gets the complete conversation history including base messages (system prompts, gadget instructions). */ getMessages(): LLMMessage[]; /** * Gets only the conversation history messages (excludes base messages). * Used by compaction to determine what can be compressed. */ getHistoryMessages(): LLMMessage[]; /** * Gets the base messages (system prompts, gadget instructions). * These are never compacted and always included at the start. */ getBaseMessages(): LLMMessage[]; /** * Replaces the conversation history with new messages. * Used by compaction to update history after compression. * @param newHistory - The compacted history messages to replace with */ replaceHistory(newHistory: LLMMessage[]): void; /** * Gets full conversation history including initial messages and runtime history. * Used for REPL session continuation - returns everything except base (system) messages. * This combines: * - initialMessages: History from previous sessions (set via withHistory()) * - historyBuilder: Messages from the current session */ getConversationHistory(): LLMMessage[]; } /** * Storage for large gadget outputs that exceed the configured limit. * * When a gadget returns more data than the configured limit, the output * is stored here and can be browsed later using GadgetOutputViewer. */ /** * Metadata and content for a stored gadget output. */ interface StoredOutput { /** Unique identifier (e.g., "Search_d34db33f") */ id: string; /** Name of the gadget that produced this output */ gadgetName: string; /** Full output content */ content: string; /** Total character count of the stored content */ charCount: number; /** Size in bytes */ byteSize: number; /** Number of lines */ lineCount: number; /** Length of the longest line in characters */ maxLineLength: number; /** When the output was stored */ timestamp: Date; } /** * In-memory store for large gadget outputs. * * Outputs are stored with generated IDs in the format `{GadgetName}_{hex8}`. * The store is tied to an agent run and cleared when the agent completes. * * @example * ```typescript * const store = new GadgetOutputStore(); * const id = store.store("Search", largeOutput); * // id = "Search_a1b2c3d4" * * const stored = store.get(id); * console.log(stored?.lineCount); // 4200 * ``` */ declare class GadgetOutputStore { private outputs; /** * Store a gadget output and return its ID. * * @param gadgetName - Name of the gadget that produced the output * @param content - Full output content to store * @returns Generated ID for retrieving the output later */ store(gadgetName: string, content: string): string; /** * Retrieve a stored output by ID. * * @param id - The output ID (e.g., "Search_d34db33f") * @returns The stored output or undefined if not found */ get(id: string): StoredOutput | undefined; /** * Check if an output exists. * * @param id - The output ID to check * @returns True if the output exists */ has(id: string): boolean; /** * Get all stored output IDs. * * @returns Array of output IDs */ getIds(): string[]; /** * Get the number of stored outputs. */ get size(): number; /** * Clear all stored outputs. * Called when the agent run completes. */ clear(): void; /** * Generate a unique ID for a stored output. * Format: {GadgetName}_{8 hex chars} */ private generateId; } /** * OutputLimitManager - Manages gadget output size limiting. * * Calculates character limits from model context windows, registers * GadgetOutputViewer when enabled, and chains the output limiter * interceptor with user-provided hooks. */ /** * Configuration for output limiting. */ interface OutputLimitConfig { /** Whether output limiting is enabled (default: true) */ enabled?: boolean; /** Max gadget output as % of model context window (default: 15) */ limitPercent?: number; } /** * Configuration for the execution tree context (shared tree model with subagents). */ interface TreeConfig { /** * Shared execution tree for tracking all LLM calls and gadget executions. * If provided (by a parent subagent), nodes are added to this tree. * If not provided, the Agent creates its own tree. */ tree?: ExecutionTree; /** * Parent node ID in the tree (when this agent is a subagent). * Used to set parentId on all nodes created by this agent. */ parentNodeId?: NodeId; /** * Base depth for nodes created by this agent. * Root agents use 0; subagents use (parentDepth + 1). */ baseDepth?: number; /** * Parent agent's observer hooks for subagent visibility. * * When a subagent is created with withParentContext(ctx), these observers * are also called for gadget events (in addition to the subagent's own hooks), * enabling the parent to observe subagent gadget activity. */ parentObservers?: Observers; } /** * Configuration for custom gadget block format prefixes. */ interface PrefixConfig { /** Custom gadget start prefix */ gadgetStartPrefix?: string; /** Custom gadget end prefix */ gadgetEndPrefix?: string; /** Custom gadget argument prefix for block format parameters */ gadgetArgPrefix?: string; } /** * Configuration options for the Agent. */ interface AgentOptions { /** The LLM client */ client: LLMist; /** The model ID */ model: string; /** System prompt */ systemPrompt?: string; /** Initial user prompt (optional if using build()). Can be text or multimodal content. */ userPrompt?: string | ContentPart[]; /** Maximum iterations */ maxIterations?: number; /** Budget limit in USD. Agent loop stops when cumulative cost reaches this limit. */ budget?: number; /** Temperature */ temperature?: number; /** Gadget registry */ registry: GadgetRegistry; /** Logger */ logger?: Logger; /** Clean hooks system */ hooks?: AgentHooks; /** Callback for requesting human input during execution */ requestHumanInput?: (question: string) => Promise; /** * Gadget prefix configuration (start/end/arg prefixes for block format). */ prefixConfig?: PrefixConfig; /** Initial messages. User messages support multimodal content. */ initialMessages?: Array<{ role: "system" | "user" | "assistant"; content: MessageContent; }>; /** Text-only handler */ textOnlyHandler?: TextOnlyHandler; /** * Handler for text content that appears alongside gadget calls. * When set, text accompanying gadgets will be wrapped as a synthetic gadget call. */ textWithGadgetsHandler?: { /** Name of the gadget to use for wrapping text */ gadgetName: string; /** Maps text content to gadget parameters */ parameterMapping: (text: string) => Record; /** Maps text content to the result string (optional, defaults to text) */ resultMapping?: (text: string) => string; }; /** Default gadget timeout */ defaultGadgetTimeoutMs?: number; /** Gadget execution mode: 'parallel' (default) or 'sequential' */ gadgetExecutionMode?: GadgetExecutionMode; /** Custom prompt configuration for gadget system prompts */ promptConfig?: PromptTemplateConfig; /** * Gadget output limit configuration. */ outputLimitConfig?: OutputLimitConfig; /** Context compaction configuration (enabled by default) */ compactionConfig?: CompactionConfig; /** Retry configuration for LLM API calls (enabled by default) */ retryConfig?: RetryConfig; /** Rate limit configuration for proactive throttling */ rateLimitConfig?: RateLimitConfig; /** Optional abort signal for cancelling requests mid-flight */ signal?: AbortSignal; /** Reasoning/thinking configuration for reasoning-capable models */ reasoning?: ReasoningConfig; /** Context caching configuration for supported providers */ caching?: CachingConfig; /** Subagent-specific configuration overrides (from CLI config) */ subagentConfig?: SubagentConfigMap; /** Maximum gadgets to execute per LLM response (0 = unlimited) */ maxGadgetsPerResponse?: number; /** * Execution tree configuration (shared tree model with subagents). */ treeConfig?: TreeConfig; /** * Shared rate limit tracker from parent agent. * * When provided (via withParentContext), this agent uses the parent's tracker * instead of creating its own. All LLM calls count toward the shared limits. */ sharedRateLimitTracker?: RateLimitTracker; /** * Shared retry configuration from parent agent. * * When provided (via withParentContext), this agent uses the parent's retry * settings instead of creating its own. */ sharedRetryConfig?: ResolvedRetryConfig; /** * MCP server specs to attach to the agent. * * When non-empty, the agent connects to each server lazily at the start of * `run()`, lists their tools, wraps them as native gadgets, and registers * them on the registry alongside any explicitly-provided gadgets. The * lifecycle teardown happens in the agent's `finally` block. * * When empty or omitted, the MCP module is never imported — agents that * don't use MCP pay zero overhead at load time. */ mcpSpecs?: McpServerSpec[]; } /** * Agent: Lean orchestrator that delegates to StreamProcessor. * * Responsibilities: * - Run the main agent loop * - Call LLM API * - Delegate stream processing to StreamProcessor * - Coordinate conversation management * - Execute top-level lifecycle controllers * * NOT responsible for: * - Stream parsing (StreamProcessor) * - Hook coordination (StreamProcessor) * - Gadget execution (StreamProcessor -> GadgetExecutor) */ declare class Agent { private readonly client; private readonly model; private readonly maxIterations; private readonly budget?; private readonly temperature?; private readonly logger; private readonly hooks; private readonly conversation; private readonly registry; private readonly prefixConfig?; private readonly conversationUpdater; private readonly defaultMaxTokens?; private hasUserPrompt; private readonly outputLimitManager; private readonly compactionManager?; private readonly mediaStore; private readonly signal?; private readonly reasoning?; private readonly caching?; private readonly retryConfig; private readonly rateLimitTracker?; private readonly completedInvocationIds; private readonly failedInvocationIds; private readonly pendingUserMessages; private readonly tree; private readonly parentNodeId; private readonly streamProcessorFactory; private readonly llmCallLifecycle; private readonly mcpSpecs; private mcpLifecycle; private readonly mcpDiscoveredPrompts; /** * Creates a new Agent instance. * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead. */ constructor(key: typeof AGENT_INTERNAL_KEY, options: AgentOptions); /** * Get the gadget registry for this agent. * * Useful for inspecting registered gadgets in tests or advanced use cases. * * @returns The GadgetRegistry instance * * @example * ```typescript * const agent = new AgentBuilder() * .withModel("sonnet") * .withGadgets(Calculator, Weather) * .build(); * * // Inspect registered gadgets * console.log(agent.getRegistry().getNames()); // ['Calculator', 'Weather'] * ``` */ getRegistry(): GadgetRegistry; /** * Get the media store for this agent session. * * The media store holds all media outputs (images, audio, etc.) produced by gadgets * during this agent's execution. Use this to: * - Access stored media files by ID * - List all stored media * - Clean up temporary files after execution * * @returns The MediaStore instance for this agent * * @example * ```typescript * const agent = new AgentBuilder() * .withModel("sonnet") * .build(); * * // After execution, access stored media * const store = agent.getMediaStore(); * for (const media of store.list()) { * console.log(`${media.id}: ${media.path}`); * } * * // Clean up when done * await store.cleanup(); * ``` */ getMediaStore(): MediaStore; /** * Get the execution tree for this agent. * * The execution tree provides a first-class model of all LLM calls and gadget executions, * including nested subagent activity. Use this to: * - Query execution state: `tree.getNode(id)` * - Get total cost: `tree.getTotalCost()` * - Get subtree cost/media/tokens: `tree.getSubtreeCost(nodeId)` * - Subscribe to events: `tree.on("llm_call_complete", handler)` * - Stream all events: `for await (const event of tree.events())` * * For subagents (created with `withParentContext`), the tree is shared with the parent, * enabling unified tracking and real-time visibility across all nesting levels. * * @returns The ExecutionTree instance * * @example * ```typescript * const agent = LLMist.createAgent() * .withModel("sonnet") * .withGadgets(BrowseWeb) * .ask("Research topic X"); * * for await (const event of agent.run()) { * // Process events... * } * * // After execution, query the tree * const tree = agent.getTree(); * console.log(`Total cost: $${tree.getTotalCost().toFixed(4)}`); * * // Inspect all LLM calls * for (const node of tree.getAllNodes()) { * if (node.type === "llm_call") { * console.log(`LLM #${node.iteration}: ${node.model}`); * } * } * ``` */ getTree(): ExecutionTree; /** * Manually trigger context compaction. * * Forces compaction regardless of threshold. Useful for: * - Pre-emptive context management before expected long operations * - Testing compaction behavior * * @returns CompactionEvent if compaction was performed, null if not configured or no history * * @example * ```typescript * const agent = await LLMist.createAgent() * .withModel('sonnet') * .withCompaction() * .ask('...'); * * // Manually compact before a long operation * const event = await agent.compact(); * if (event) { * console.log(`Saved ${event.tokensBefore - event.tokensAfter} tokens`); * } * ``` */ compact(): Promise; /** * Get compaction statistics. * * @returns CompactionStats if compaction is enabled, null otherwise * * @example * ```typescript * const stats = agent.getCompactionStats(); * if (stats) { * console.log(`Total compactions: ${stats.totalCompactions}`); * console.log(`Tokens saved: ${stats.totalTokensSaved}`); * console.log(`Current usage: ${stats.currentUsage.percent.toFixed(1)}%`); * } * ``` */ getCompactionStats(): CompactionStats | null; /** * Get the conversation manager for this agent. * Used by REPL mode to extract session history for continuation. * * @returns The conversation manager containing all messages * * @example * ```typescript * // After running agent, extract history for next session * const history = agent.getConversation().getConversationHistory(); * // Pass to next agent via builder.withHistory() * ``` */ getConversation(): IConversationManager; /** * Inject a user message to be processed in the next iteration. * Used by REPL mode to allow user input during a running session. * * The message is queued and will be added to the conversation before * the next LLM call. This allows users to provide additional context * or instructions while the agent is executing. * * @param message - The user message to inject * * @example * ```typescript * // While agent is running in TUI: * tui.onMidSessionInput((msg) => { * agent.injectUserMessage(msg); * }); * ``` */ injectUserMessage(message: string): void; /** * Run the agent loop. * Clean, simple orchestration - all complexity is in StreamProcessor. * * ## Event Architecture * * ExecutionTree is the single source of truth for all agent events. * Gadget observer hooks (`onGadgetExecutionStart`, `onGadgetExecutionComplete`, * `onGadgetSkipped`) are derived from tree events via `tree-hook-bridge.ts`. * This ensures consistent `subagentContext` for nested agents - both the TUI * and user hook observers receive identical event context. * * @throws {Error} If no user prompt was provided (when using build() without ask()) */ run(): AsyncGenerator; /** * Execute a single LLM call attempt with full retry orchestration. * * Delegates all retry logic to RetryOrchestrator, then propagates the accumulated * invocation IDs back to the agent's cross-iteration tracking sets. * * Yields stream events in real-time and returns the final stream completion metadata * along with accumulated tracking state from the final successful attempt only. */ private executeWithRetry; /** * Create LLM stream with proactive rate limit protection. * * Note: Retry logic for errors during streaming is handled by the outer loop in run(). * This method only handles proactive rate limiting (delaying requests to stay within limits). */ private createStream; /** * Factory method for constructing a StreamProcessor for a given iteration. * * Delegates to StreamProcessorFactory, which encapsulates all static * StreamProcessor configuration. Cross-iteration mutable state is passed here. */ private createStreamProcessor; /** * Simple sleep utility for rate limit delays. */ private sleep; /** * Resolve max tokens from model catalog. */ private resolveMaxTokensFromCatalog; /** * Check abort signal and notify observers if aborted. * @returns true if agent should terminate */ private checkAbortAndNotify; /** * Check and perform context compaction if needed. * @returns compaction stream event if compaction occurred, null otherwise */ private checkAndPerformCompaction; /** * Log compaction, notify observers, and return a StreamEvent. * Shared by proactive compaction (checkAndPerformCompaction) and * reactive overflow recovery (catch block). */ private emitCompactionEvent; /** * Run agent with named event handlers (syntactic sugar). * * Instead of verbose if/else chains, use named handlers for cleaner code. * * @param handlers - Named event handlers * * @example * ```typescript * await agent.runWith({ * onText: (text) => console.log("LLM:", text), * onGadgetResult: (result) => console.log("Result:", result.result), * onGadgetCall: (call) => console.log("Calling:", call.gadgetName), * }); * ``` */ runWith(handlers: EventHandlers): Promise; } /** * File-based logging for LLM requests and responses. * * Provides hooks to write raw LLM requests and responses to files for debugging, * auditing, and analysis. Supports both programmatic configuration and * zero-code activation via environment variables. * * ## Programmatic Usage * * ```typescript * import { LLMist, HookPresets } from 'llmist'; * * const agent = LLMist.createAgent() * .withHooks(HookPresets.fileLogging({ * directory: './logs/session-001' * })) * .ask("Hello"); * * // Creates: ./logs/session-001/0001.request * // ./logs/session-001/0001.response * ``` * * ## Environment Variable * * Set `LLMIST_LOG_RAW_DIRECTORY` to enable logging without code changes: * * ```bash * export LLMIST_LOG_RAW_DIRECTORY="/tmp/llm-debug" * node my-app.js * ``` */ /** * State container for file logging session. * Encapsulates all mutable state to enable session isolation and proper * handling of concurrent subagents. * * @remarks * Using a state object instead of module-level globals provides: * - **Session isolation**: Multiple agent sessions don't interfere with each other * - **Testability**: Tests can inject fresh state without needing to reset globals * - **Concurrent subagent support**: Context-based keys prevent race conditions */ interface FileLoggingState { /** Counter per directory path (normalized) */ readonly counters: Map; /** * Subagent context key -> assigned directory path. * Key format: `${parentDir}:${parentGadgetInvocationId}` */ readonly subagentDirectories: Map; /** * Context key -> active directory for that execution context. * Key format: `${parentGadgetInvocationId}:${depth}` (or "root:0" for main agent) * * This replaces the previous depth-only keying which caused race conditions * when multiple subagents at the same depth ran concurrently. */ readonly activeDirectoryByContext: Map; } /** * Creates a fresh file logging state container. * * Use this to create isolated state for testing or when running multiple * independent agent sessions that shouldn't share counters. * * @example * ```typescript * // For testing with isolated state: * const state = createFileLoggingState(); * const hooks = createFileLoggingHooks({ directory: './logs' }, state); * * // For production (uses default shared state): * const hooks = createFileLoggingHooks({ directory: './logs' }); * ``` */ declare function createFileLoggingState(): FileLoggingState; /** * Resets the default global state. For testing only. * @internal */ declare function resetFileLoggingState(): void; /** * Options for configuring file-based LLM logging. */ interface FileLoggingOptions { /** * Directory where log files will be written. * Will be created recursively if it doesn't exist. */ directory: string; /** * Starting counter for file numbering. Default: 1 */ startingCounter?: number; /** * Number of digits for zero-padded file numbers. Default: 4 * Example: 4 produces "0001", "0042", etc. */ counterPadding?: number; /** * Skip logging for subagent calls. Default: true * When true, only main agent calls are logged. */ skipSubagents?: boolean; /** * Custom formatter for request content. * By default, uses formatLlmRequest() which produces human-readable output. */ formatRequest?: (messages: LLMMessage[]) => string; /** * Callback invoked after each file is written. * Useful for tracking, metrics, or UI updates. */ onFileWritten?: (info: FileWrittenInfo) => void; } /** * Information about a written log file. */ interface FileWrittenInfo { /** Full path to the written file */ filePath: string; /** Type of log file */ type: "request" | "response"; /** LLM call number (1-indexed) within the current directory */ callNumber: number; /** Length of the written content in characters */ contentLength: number; /** Gadget invocation ID that spawned this subagent (undefined for main agent) */ parentGadgetInvocationId?: string; /** Subagent depth (undefined for main agent) */ depth?: number; } /** * Formats LLM messages as plain text for debugging. * * Each message is formatted with a header showing the role (USER, ASSISTANT, SYSTEM) * followed by the message content. Multimodal content is converted to text. * * @param messages - Array of LLM messages to format * @returns Formatted string with all messages * * @example * ```typescript * const formatted = formatLlmRequest([ * { role: "system", content: "You are a helpful assistant." }, * { role: "user", content: "Hello!" } * ]); * // Output: * // === SYSTEM === * // You are a helpful assistant. * // * // === USER === * // Hello! * ``` */ declare function formatLlmRequest(messages: LLMMessage[]): string; /** * Formats a call number as a zero-padded string. * * @param n - The number to format * @param padding - Number of digits (default: 4) * @returns Zero-padded string (e.g., 1 → "0001", 42 → "0042") * * @example * ```typescript * formatCallNumber(1); // "0001" * formatCallNumber(42); // "0042" * formatCallNumber(1, 6); // "000001" * ``` */ declare function formatCallNumber(n: number, padding?: number): string; /** * Ready-to-use hook configurations for common monitoring, logging, and debugging tasks. * * HookPresets provide instant observability without writing custom hooks. They're the * fastest way to add monitoring to your agents during development and production. * * ## Available Presets * * - **logging(options?)** - Log LLM calls and gadget execution * - **timing()** - Measure execution time for operations * - **tokenTracking()** - Track cumulative token usage and costs * - **progressTracking(options?)** - Track progress with iterations, tokens, cost, and timing (SHOWCASE) * - **errorLogging()** - Log detailed error information * - **silent()** - No output (useful for testing) * - **monitoring(options?)** - All-in-one preset combining logging, timing, tokens, and errors * - **merge(...hookSets)** - Combine multiple hook configurations * * ## Quick Start * * @example * ```typescript * import { LLMist, HookPresets } from 'llmist'; * * // Basic logging * await LLMist.createAgent() * .withHooks(HookPresets.logging()) * .ask("Your prompt"); * * // Full monitoring suite (recommended for development) * await LLMist.createAgent() * .withHooks(HookPresets.monitoring({ verbose: true })) * .ask("Your prompt"); * * // Combine multiple presets * await LLMist.createAgent() * .withHooks(HookPresets.merge( * HookPresets.timing(), * HookPresets.tokenTracking() * )) * .ask("Your prompt"); * * // Environment-based configuration * const hooks = process.env.NODE_ENV === 'production' * ? HookPresets.merge(HookPresets.errorLogging(), HookPresets.tokenTracking()) * : HookPresets.monitoring({ verbose: true }); * * await LLMist.createAgent() * .withHooks(hooks) * .ask("Your prompt"); * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md | Full documentation} */ /** * Options for logging preset. */ interface LoggingOptions { /** Include verbose details like parameters and results */ verbose?: boolean; } /** * Progress statistics reported by progressTracking preset. * * Contains cumulative metrics across all LLM calls in the agent session, * useful for building progress UI, cost monitoring, and performance tracking. */ interface ProgressStats { /** Current iteration number (increments on each LLM call start) */ currentIteration: number; /** Total number of completed LLM calls */ totalCalls: number; /** Cumulative input tokens across all calls */ totalInputTokens: number; /** Cumulative output tokens across all calls */ totalOutputTokens: number; /** Total tokens (input + output) */ totalTokens: number; /** Cumulative cost in USD (includes LLM and gadget costs; requires modelRegistry for LLM cost estimation) */ totalCost: number; /** Elapsed time in seconds since first call */ elapsedSeconds: number; } /** * Options for progressTracking preset. * * Controls how progress data is tracked and reported during agent execution. */ interface ProgressTrackingOptions { /** * Model registry for cost calculation. * * If provided, enables automatic cost estimation based on token usage * and model pricing data. Without it, totalCost will always be 0. * * @example * ```typescript * import { LLMist, HookPresets } from 'llmist'; * * const client = LLMist.create(); * const hooks = HookPresets.progressTracking({ * modelRegistry: client.modelRegistry // Enable cost tracking * }); * ``` */ modelRegistry?: ModelRegistry; /** * Callback invoked after each LLM call completion with cumulative stats. * * Use this to update progress UI, log metrics, or track budgets in real-time. * * @example * ```typescript * HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * onProgress: (stats) => { * console.log(`Iteration #${stats.currentIteration}`); * console.log(`Cost so far: $${stats.totalCost.toFixed(4)}`); * console.log(`Elapsed: ${stats.elapsedSeconds}s`); * } * }) * ``` */ onProgress?: (stats: ProgressStats) => void; /** * Whether to log progress to console after each LLM call. * * When enabled, prints a summary line with tokens, cost, and elapsed time. * Useful for quick debugging without implementing a custom callback. * * Default: false * * @example * ```typescript * // Quick console-based progress tracking * HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * logProgress: true // Log to console * }) * // Output: 📊 Progress: Iteration #2 | 1,234 tokens | $0.0056 | 12.3s * ``` */ logProgress?: boolean; } /** * Common hook presets. */ declare class HookPresets { /** * Logs LLM calls and gadget execution to console with optional verbosity. * * **Output (basic mode):** * - LLM call start/complete events with iteration numbers * - Gadget execution start/complete with gadget names * - Token counts when available * * **Output (verbose mode):** * - All basic mode output * - Full gadget parameters (formatted JSON) * - Full gadget results * - Complete LLM response text * * **Use cases:** * - Basic development debugging and execution flow visibility * - Understanding agent decision-making and tool usage * - Troubleshooting gadget invocations * * **Performance:** Minimal overhead. Console writes are synchronous but fast. * * @param options - Logging options * @param options.verbose - Include full parameters and results. Default: false * @returns Hook configuration that can be passed to .withHooks() * * @example * ```typescript * // Basic logging * await LLMist.createAgent() * .withHooks(HookPresets.logging()) * .ask("Calculate 15 * 23"); * // Output: [LLM] Starting call (iteration 0) * // [GADGET] Executing Calculator * // [GADGET] Completed Calculator * // [LLM] Completed (tokens: 245) * ``` * * @example * ```typescript * // Verbose logging with full details * await LLMist.createAgent() * .withHooks(HookPresets.logging({ verbose: true })) * .ask("Calculate 15 * 23"); * // Output includes: parameters, results, and full responses * ``` * * @example * ```typescript * // Environment-based verbosity * const isDev = process.env.NODE_ENV === 'development'; * .withHooks(HookPresets.logging({ verbose: isDev })) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsloggingoptions | Full documentation} */ static logging(options?: LoggingOptions): AgentHooks; /** * Measures and logs execution time for LLM calls and gadgets. * * **Output:** * - Duration in milliseconds with ⏱️ emoji for each operation * - Separate timing for each LLM iteration * - Separate timing for each gadget execution * * **Use cases:** * - Performance profiling and optimization * - Identifying slow operations (LLM calls vs gadget execution) * - Monitoring response times in production * - Capacity planning and SLA tracking * * **Performance:** Negligible overhead. Uses Date.now() for timing measurements. * * @returns Hook configuration that can be passed to .withHooks() * * @example * ```typescript * // Basic timing * await LLMist.createAgent() * .withHooks(HookPresets.timing()) * .withGadgets(Weather, Database) * .ask("What's the weather in NYC?"); * // Output: ⏱️ LLM call took 1234ms * // ⏱️ Gadget Weather took 567ms * // ⏱️ LLM call took 890ms * ``` * * @example * ```typescript * // Combined with logging for full context * .withHooks(HookPresets.merge( * HookPresets.logging(), * HookPresets.timing() * )) * ``` * * @example * ```typescript * // Correlate performance with cost * .withHooks(HookPresets.merge( * HookPresets.timing(), * HookPresets.tokenTracking() * )) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstiming | Full documentation} */ static timing(): AgentHooks; /** * Tracks cumulative token usage across all LLM calls. * * @public * * **Output:** * - Per-call token count with 📊 emoji * - Cumulative total across all calls * - Call count for average calculations * * **Use cases:** * - Cost monitoring and budget tracking * - Optimizing prompts to reduce token usage * - Comparing token efficiency across different approaches * - Real-time cost estimation * * **Performance:** Minimal overhead. Simple counter increments. * * **Note:** Token counts depend on the provider's response. Some providers * may not include usage data, in which case counts won't be logged. * * @returns Hook configuration that can be passed to .withHooks() * * @example * ```typescript * // Basic token tracking * await LLMist.createAgent() * .withHooks(HookPresets.tokenTracking()) * .ask("Summarize this document..."); * // Output: 📊 Tokens this call: 1,234 * // 📊 Total tokens: 1,234 (across 1 calls) * // 📊 Tokens this call: 567 * // 📊 Total tokens: 1,801 (across 2 calls) * ``` * * @example * ```typescript * // Cost calculation with custom hook * let totalTokens = 0; * .withHooks(HookPresets.merge( * HookPresets.tokenTracking(), * { * observers: { * onLLMCallComplete: async (ctx) => { * totalTokens += ctx.usage?.totalTokens ?? 0; * const cost = (totalTokens / 1_000_000) * 3.0; // $3 per 1M tokens * console.log(`💰 Estimated cost: $${cost.toFixed(4)}`); * }, * }, * } * )) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetstokentracking | Full documentation} */ static tokenTracking(): AgentHooks; /** * Tracks comprehensive progress metrics including iterations, tokens, cost, and timing. * * **This preset showcases llmist's core capabilities by demonstrating:** * - Observer pattern for non-intrusive monitoring * - Integration with ModelRegistry for cost estimation * - Callback-based architecture for flexible UI updates * - Provider-agnostic token and cost tracking * * Unlike `tokenTracking()` which only logs to console, this preset provides * structured data through callbacks, making it perfect for building custom UIs, * dashboards, or progress indicators (like the llmist CLI). * * **Output (when logProgress: true):** * - Iteration number and call count * - Cumulative token usage (input + output) * - Cumulative cost in USD (requires modelRegistry) * - Elapsed time in seconds * * **Use cases:** * - Building CLI progress indicators with live updates * - Creating web dashboards with real-time metrics * - Budget monitoring and cost alerts * - Performance tracking and optimization * - Custom logging to external systems (Datadog, CloudWatch, etc.) * * **Performance:** Minimal overhead. Uses Date.now() for timing and optional * ModelRegistry.estimateCost() which is O(1) lookup. Callback invocation is * synchronous and fast. * * @param options - Progress tracking options * @param options.modelRegistry - ModelRegistry for cost estimation (optional) * @param options.onProgress - Callback invoked after each LLM call (optional) * @param options.logProgress - Log progress to console (default: false) * @returns Hook configuration with progress tracking observers * * @example * ```typescript * // Basic usage with callback (RECOMMENDED - used by llmist CLI) * import { LLMist, HookPresets } from 'llmist'; * * const client = LLMist.create(); * * await client.agent() * .withHooks(HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * onProgress: (stats) => { * // Update your UI with stats * console.log(`#${stats.currentIteration} | ${stats.totalTokens} tokens | $${stats.totalCost.toFixed(4)}`); * } * })) * .withGadgets(Calculator) * .ask("Calculate 15 * 23"); * // Output: #1 | 245 tokens | $0.0012 * ``` * * @example * ```typescript * // Console logging mode (quick debugging) * await client.agent() * .withHooks(HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * logProgress: true // Simple console output * })) * .ask("Your prompt"); * // Output: 📊 Progress: Iteration #1 | 245 tokens | $0.0012 | 1.2s * ``` * * @example * ```typescript * // Budget monitoring with alerts * const BUDGET_USD = 0.10; * * await client.agent() * .withHooks(HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * onProgress: (stats) => { * if (stats.totalCost > BUDGET_USD) { * throw new Error(`Budget exceeded: $${stats.totalCost.toFixed(4)}`); * } * } * })) * .ask("Long running task..."); * ``` * * @example * ```typescript * // Web dashboard integration * let progressBar: HTMLElement; * * await client.agent() * .withHooks(HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * onProgress: (stats) => { * // Update web UI in real-time * progressBar.textContent = `Iteration ${stats.currentIteration}`; * progressBar.dataset.cost = stats.totalCost.toFixed(4); * progressBar.dataset.tokens = stats.totalTokens.toString(); * } * })) * .ask("Your prompt"); * ``` * * @example * ```typescript * // External logging (Datadog, CloudWatch, etc.) * await client.agent() * .withHooks(HookPresets.progressTracking({ * modelRegistry: client.modelRegistry, * onProgress: async (stats) => { * await metrics.gauge('llm.iteration', stats.currentIteration); * await metrics.gauge('llm.cost', stats.totalCost); * await metrics.gauge('llm.tokens', stats.totalTokens); * } * })) * .ask("Your prompt"); * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsprogresstrackingoptions | Full documentation} * @see {@link ProgressTrackingOptions} for detailed options * @see {@link ProgressStats} for the callback data structure */ static progressTracking(options?: ProgressTrackingOptions): AgentHooks; /** * Logs detailed error information for debugging and troubleshooting. * * @public * * **Output:** * - LLM errors with ❌ emoji, including model and recovery status * - Gadget errors with full context (parameters, error message) * - Separate logging for LLM and gadget failures * * **Use cases:** * - Troubleshooting production issues * - Understanding error patterns and frequency * - Debugging error recovery behavior * - Collecting error metrics for monitoring * * **Performance:** Minimal overhead. Only logs when errors occur. * * @returns Hook configuration that can be passed to .withHooks() * * @example * ```typescript * // Basic error logging * await LLMist.createAgent() * .withHooks(HookPresets.errorLogging()) * .withGadgets(Database) * .ask("Fetch user data"); * // Output (on LLM error): ❌ LLM Error (iteration 1): Rate limit exceeded * // Model: gpt-5-nano * // Recovered: true * // Output (on gadget error): ❌ Gadget Error: Database * // Error: Connection timeout * // Parameters: {...} * ``` * * @example * ```typescript * // Combine with monitoring for full context * .withHooks(HookPresets.merge( * HookPresets.monitoring(), // Includes errorLogging * customErrorAnalytics * )) * ``` * * @example * ```typescript * // Error analytics collection * const errors: any[] = []; * .withHooks(HookPresets.merge( * HookPresets.errorLogging(), * { * observers: { * onLLMCallError: async (ctx) => { * errors.push({ type: 'llm', error: ctx.error, recovered: ctx.recovered }); * }, * }, * } * )) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetserrorlogging | Full documentation} */ static errorLogging(): AgentHooks; /** * Tracks context compaction events. * * @public * * **Output:** * - Compaction events with 🗜️ emoji * - Strategy name, tokens before/after, and savings * - Cumulative statistics * * **Use cases:** * - Monitoring long-running conversations * - Understanding when and how compaction occurs * - Debugging context management issues * * **Performance:** Minimal overhead. Simple console output. * * @returns Hook configuration that can be passed to .withHooks() * * @example * ```typescript * await LLMist.createAgent() * .withHooks(HookPresets.compactionTracking()) * .ask("Your prompt"); * ``` */ static compactionTracking(): AgentHooks; /** * Logs LLM requests and responses to files for debugging and audit trails. * * Files are named `{counter}.request` and `{counter}.response` where counter * is a zero-padded number that increments with each LLM call. * * **Output:** * - Request files containing formatted LLM message history * - Response files containing raw LLM output * * **Use cases:** * - Debugging complex agent interactions * - Creating audit trails for compliance * - Analyzing LLM behavior patterns * - Replaying conversations for testing * * **Performance:** Minimal overhead - only file I/O, no synchronous blocking. * * **Note:** Can also be enabled via `LLMIST_LOG_RAW_DIRECTORY` environment * variable for zero-code activation. * * @param options - File logging options * @param options.directory - Directory where log files will be written * @param options.startingCounter - Starting counter (default: 1) * @param options.counterPadding - Number of digits for padding (default: 4) * @param options.skipSubagents - Skip subagent calls (default: true) * @param options.formatRequest - Custom request formatter * @param options.onFileWritten - Callback after each file is written * @returns Hook configuration that can be passed to .withHooks() * * @example * ```typescript * // Basic file logging * await LLMist.createAgent() * .withHooks(HookPresets.fileLogging({ * directory: './debug-logs' * })) * .ask("Hello"); * // Creates: ./debug-logs/0001.request * // ./debug-logs/0001.response * ``` * * @example * ```typescript * // With callback for tracking * await LLMist.createAgent() * .withHooks(HookPresets.fileLogging({ * directory: './logs', * onFileWritten: (info) => { * console.log(`Wrote ${info.type}: ${info.filePath}`); * } * })) * .ask("Hello"); * ``` * * @example * ```typescript * // Combined with other presets * .withHooks(HookPresets.merge( * HookPresets.fileLogging({ directory: logDir }), * HookPresets.progressTracking({ onProgress: updateUI }), * HookPresets.errorLogging() * )) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsfileloggingoptions | Full documentation} */ static fileLogging(options: FileLoggingOptions): AgentHooks; /** * Returns empty hook configuration for clean output without any logging. * * @public * * **Output:** * - None. Returns {} (empty object). * * **Use cases:** * - Clean test output without console noise * - Production environments where logging is handled externally * - Baseline for custom hook development * - Temporary disable of all hook output * * **Performance:** Zero overhead. No-op hook configuration. * * @returns Empty hook configuration * * @example * ```typescript * // Clean test output * describe('Agent tests', () => { * it('should calculate correctly', async () => { * const result = await LLMist.createAgent() * .withHooks(HookPresets.silent()) // No console output * .withGadgets(Calculator) * .askAndCollect("What is 15 times 23?"); * * expect(result).toContain("345"); * }); * }); * ``` * * @example * ```typescript * // Conditional silence based on environment * const isTesting = process.env.NODE_ENV === 'test'; * .withHooks(isTesting ? HookPresets.silent() : HookPresets.monitoring()) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetssilent | Full documentation} */ static silent(): AgentHooks; /** * Combines multiple hook configurations into one. * * Merge allows you to compose preset and custom hooks for modular monitoring * configurations. Understanding merge behavior is crucial for proper composition. * * **Merge behavior:** * - **Observers:** Composed - all handlers run sequentially in order * - **Interceptors:** Last one wins - only the last interceptor applies * - **Controllers:** Last one wins - only the last controller applies * * **Why interceptors/controllers don't compose:** * - Interceptors have different signatures per method, making composition impractical * - Controllers return specific actions that can't be meaningfully combined * - Only observers support composition because they're read-only and independent * * **Use cases:** * - Combining multiple presets (logging + timing + tokens) * - Adding custom hooks to presets * - Building modular, reusable monitoring configurations * - Environment-specific hook composition * * **Performance:** Minimal overhead for merging. Runtime performance depends on merged hooks. * * @param hookSets - Variable number of hook configurations to merge * @returns Single merged hook configuration with composed/overridden handlers * * @example * ```typescript * // Combine multiple presets * .withHooks(HookPresets.merge( * HookPresets.logging(), * HookPresets.timing(), * HookPresets.tokenTracking() * )) * // All observers from all three presets will run * ``` * * @example * ```typescript * // Add custom observer to preset (both run) * .withHooks(HookPresets.merge( * HookPresets.timing(), * { * observers: { * onLLMCallComplete: async (ctx) => { * await saveMetrics({ tokens: ctx.usage?.totalTokens }); * }, * }, * } * )) * ``` * * @example * ```typescript * // Multiple interceptors (last wins!) * .withHooks(HookPresets.merge( * { * interceptors: { * interceptTextChunk: (chunk) => chunk.toUpperCase(), // Ignored * }, * }, * { * interceptors: { * interceptTextChunk: (chunk) => chunk.toLowerCase(), // This wins * }, * } * )) * // Result: text will be lowercase * ``` * * @example * ```typescript * // Modular environment-based configuration * const baseHooks = HookPresets.errorLogging(); * const devHooks = HookPresets.merge(baseHooks, HookPresets.monitoring({ verbose: true })); * const prodHooks = HookPresets.merge(baseHooks, HookPresets.tokenTracking()); * * const hooks = process.env.NODE_ENV === 'production' ? prodHooks : devHooks; * .withHooks(hooks) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmergehooksets | Full documentation} */ static merge(...hookSets: AgentHooks[]): AgentHooks; /** * Composite preset combining logging, timing, tokenTracking, and errorLogging. * * @public * * This is the recommended preset for development and initial production deployments, * providing comprehensive observability with a single method call. * * **Includes:** * - All output from `logging()` preset (with optional verbosity) * - All output from `timing()` preset (execution times) * - All output from `tokenTracking()` preset (token usage) * - All output from `errorLogging()` preset (error details) * * **Output format:** * - Event logging: [LLM]/[GADGET] messages * - Timing: ⏱️ emoji with milliseconds * - Tokens: 📊 emoji with per-call and cumulative counts * - Errors: ❌ emoji with full error details * * **Use cases:** * - Full observability during development * - Comprehensive monitoring in production * - One-liner for complete agent visibility * - Troubleshooting and debugging with full context * * **Performance:** Combined overhead of all four presets, but still minimal in practice. * * @param options - Monitoring options * @param options.verbose - Passed to logging() preset for detailed output. Default: false * @returns Merged hook configuration combining all monitoring presets * * @example * ```typescript * // Basic monitoring (recommended for development) * await LLMist.createAgent() * .withHooks(HookPresets.monitoring()) * .withGadgets(Calculator, Weather) * .ask("What is 15 times 23, and what's the weather in NYC?"); * // Output: All events, timing, tokens, and errors in one place * ``` * * @example * ```typescript * // Verbose monitoring with full details * await LLMist.createAgent() * .withHooks(HookPresets.monitoring({ verbose: true })) * .ask("Your prompt"); * // Output includes: parameters, results, and complete responses * ``` * * @example * ```typescript * // Environment-based monitoring * const isDev = process.env.NODE_ENV === 'development'; * .withHooks(HookPresets.monitoring({ verbose: isDev })) * ``` * * @see {@link https://github.com/zbigniewsobiecki/llmist/blob/main/docs/HOOKS.md#hookpresetsmonitoringoptions | Full documentation} */ static monitoring(options?: LoggingOptions): AgentHooks; } /** * CompactionManager - Central orchestrator for context compaction. * * Monitors token usage and coordinates compaction strategies to keep * conversation context within model limits. */ /** * Pre-computed token counts to avoid redundant counting. * Passed from checkAndCompact to compact for efficiency. */ interface PrecomputedTokens { historyMessages: LLMMessage[]; baseMessages: LLMMessage[]; historyTokens: number; baseTokens: number; currentTokens: number; } /** * CompactionManager orchestrates context compaction for an agent. * * It: * - Monitors token usage before each LLM call * - Triggers compaction when threshold is exceeded * - Coordinates with ConversationManager to update history * - Tracks statistics for observability */ declare class CompactionManager { private readonly client; private readonly model; private readonly config; private readonly strategy; private readonly logger; private modelLimits?; private hasWarnedModelNotFound; private hasWarnedNoTokenCounting; private totalCompactions; private totalTokensSaved; private lastTokenCount; constructor(client: LLMist, model: string, config?: CompactionConfig, logger?: Logger); /** * Check if compaction is needed and perform it if so. * * @param conversation - The conversation manager to compact * @param iteration - Current agent iteration (for event metadata) * @returns CompactionEvent if compaction was performed, null otherwise */ checkAndCompact(conversation: IConversationManager, iteration: number): Promise; /** * Force compaction regardless of threshold. * * @param conversation - The conversation manager to compact * @param iteration - Current agent iteration (for event metadata). Use -1 for manual compaction. * @param precomputed - Optional pre-computed token counts (passed from checkAndCompact for efficiency) * @returns CompactionEvent with compaction details */ compact(conversation: IConversationManager, iteration: number, precomputed?: PrecomputedTokens): Promise; /** * Feed API-reported input token count for reactive threshold checking. * Call this after each LLM response with the actual inputTokens from usage. */ updateUsage(inputTokens: number): void; /** * Check if compaction should trigger based on API-reported usage. * Unlike checkAndCompact() which uses estimated token counts, * this uses the ground-truth token count from the last LLM response. */ shouldCompactFromUsage(): boolean; /** * Resolve and cache model limits from registry. Warns once if not found. * @returns true if limits are available, false otherwise */ private resolveModelLimits; /** * Get compaction statistics. */ getStats(): CompactionStats; /** * Check if compaction is enabled. */ isEnabled(): boolean; } /** * Hybrid Compaction Strategy * * Combines sliding window and summarization for the best of both worlds: * 1. Identifies which turns to compact vs keep (like sliding window) * 2. Summarizes the older turns (like summarization) * 3. Returns summary + recent turns intact * * Falls back to sliding window if there are too few turns to summarize. */ /** * Hybrid strategy - summarizes old turns + keeps recent turns. * * This is the recommended default strategy as it: * - Preserves important historical context via summarization * - Keeps recent conversation turns verbatim for continuity * - Falls back gracefully to sliding window when appropriate */ declare class HybridStrategy implements CompactionStrategy { readonly name = "hybrid"; private readonly slidingWindow; private readonly summarization; compact(messages: LLMMessage[], config: ResolvedCompactionConfig, context: CompactionContext): Promise; } /** * Sliding Window Compaction Strategy * * A fast, no-LLM-call strategy that simply keeps the most recent N turns * and drops older ones. Best for: * - Long-running conversations where older context becomes irrelevant * - Scenarios requiring minimal latency * - As a fallback when summarization is too slow */ /** * Sliding window strategy - keeps recent turns, drops older ones. * * This strategy: * 1. Groups messages into logical turns (user + assistant pairs) * 2. Keeps the `preserveRecentTurns` most recent turns * 3. Inserts a truncation marker at the beginning * 4. Requires no LLM call - very fast */ declare class SlidingWindowStrategy implements CompactionStrategy { readonly name = "sliding-window"; compact(messages: LLMMessage[], config: ResolvedCompactionConfig, context: CompactionContext): Promise; } /** * Summarization Compaction Strategy * * Uses an LLM to summarize older conversation messages into a concise summary. * Best for: * - Tasks where historical context matters * - Complex multi-step reasoning * - When accuracy is more important than speed */ /** * Summarization strategy - uses LLM to compress conversation history. * * This strategy: * 1. Groups messages into logical turns * 2. Keeps recent turns intact * 3. Summarizes older turns using LLM * 4. Returns summary + recent turns */ declare class SummarizationStrategy implements CompactionStrategy { readonly name = "summarization"; compact(messages: LLMMessage[], config: ResolvedCompactionConfig, context: CompactionContext): Promise; /** * Formats messages into a readable conversation format for summarization. */ private formatTurnsForSummary; /** * Generates a summary using the configured LLM. */ private generateSummary; } /** * ConversationManager handles conversation state and message building. * Extracted from AgentLoop to follow Single Responsibility Principle. */ /** * Options for ConversationManager constructor. */ interface ConversationManagerOptions { /** Custom gadget start marker prefix */ startPrefix?: string; /** Custom gadget end marker prefix */ endPrefix?: string; /** Custom argument prefix for block format */ argPrefix?: string; } /** * Default implementation of IConversationManager. * Manages conversation history by building on top of base messages (system prompt, gadget instructions). */ declare class ConversationManager implements IConversationManager { private baseMessages; private readonly initialMessages; private historyBuilder; private readonly startPrefix?; private readonly endPrefix?; private readonly argPrefix?; constructor(baseMessages: LLMMessage[], initialMessages: LLMMessage[], options?: ConversationManagerOptions); addUserMessage(content: MessageContent): void; addAssistantMessage(content: string): void; addGadgetCallResult(gadgetName: string, parameters: Record, result: string, invocationId: string, media?: GadgetMediaOutput[], mediaIds?: string[], storedMedia?: StoredMedia[], metadata?: Record): void; getMessages(): LLMMessage[]; getHistoryMessages(): LLMMessage[]; getBaseMessages(): LLMMessage[]; /** * Replace the base (system + gadget catalog) messages. * * Used when async setup (e.g. MCP server connect-and-list) discovers * additional gadgets after the agent was constructed. Conversation history * is preserved; only the leading system block is swapped. */ replaceBaseMessages(newBase: LLMMessage[]): void; replaceHistory(newHistory: LLMMessage[]): void; getConversationHistory(): LLMMessage[]; } /** * LLM Assistance Hints System * * Provides reusable hook factories that inject helpful context and coaching * messages to guide LLM behavior during agentic execution. * * ## Two Types of Hints * * 1. **Proactive (beforeLLMCall)**: Inject context before LLM generates response * - Example: Iteration progress ("You're on iteration 3/10") * * 2. **Reactive (afterLLMCall)**: Coach based on what LLM did * - Example: "Tip: You can call multiple gadgets in parallel" * * ## Usage * * ```typescript * import { createHints, iterationProgressHint, parallelGadgetHint } from "llmist"; * * // Option 1: Use individual hints * const agent = new AgentBuilder() * .withHooks(HookPresets.merge( * iterationProgressHint({ timing: "late" }), * parallelGadgetHint(), * )) * .build(); * * // Option 2: Use convenience factory * const agent = new AgentBuilder() * .withHooks(createHints({ * iterationProgress: { timing: "late" }, * parallelGadgets: true, * })) * .build(); * ``` * * @module agent/hints */ /** * Options for iteration progress hint. */ interface IterationHintOptions { /** * When to show the hint. * - "always": Show on every iteration * - "late": Show only when >= 50% through iterations * - "urgent": Show only when >= 80% through iterations * @default "always" */ timing?: "always" | "late" | "urgent"; /** * Whether to include urgency indicators for late iterations. * Adds extra text when running low on iterations. * @default true */ showUrgency?: boolean; /** * Custom template. Supports placeholders: {iteration}, {maxIterations}, {remaining} * Or a function receiving HintContext. * @default DEFAULT_HINTS.iterationProgressHint */ template?: HintTemplate; } /** * Options for parallel gadget usage hint. */ interface ParallelGadgetHintOptions { /** * Minimum number of gadget calls to consider "efficient". * If response has fewer calls, hint will suggest parallelization. * @default 2 */ minGadgetsForEfficiency?: number; /** * Custom message when single gadget detected. * @default DEFAULT_HINTS.parallelGadgetsHint */ message?: string; /** * Whether to enable this hint. * @default true */ enabled?: boolean; } /** * Combined hints configuration for createHints(). */ interface HintsConfig { /** * Enable iteration progress hints. * Pass `true` for defaults, or options object for customization. */ iterationProgress?: boolean | IterationHintOptions; /** * Enable parallel gadget hints. * Pass `true` for defaults, or options object for customization. */ parallelGadgets?: boolean | ParallelGadgetHintOptions; /** * Additional custom hooks to merge. */ custom?: AgentHooks[]; } /** * Creates a proactive hint that informs the LLM about iteration progress. * * This hint is injected before each LLM call (via beforeLLMCall controller), * helping the LLM understand how much "budget" remains for completing the task. * * @param options - Configuration options * @returns AgentHooks that can be merged with other hooks * * @example * ```typescript * // Basic usage - show on every iteration * const hooks = iterationProgressHint(); * * // Show only when running low on iterations * const hooks = iterationProgressHint({ timing: "late" }); * * // Custom template * const hooks = iterationProgressHint({ * template: "Turn {iteration} of {maxIterations}. {remaining} turns left.", * }); * ``` */ declare function iterationProgressHint(options?: IterationHintOptions): AgentHooks; /** * Creates a reactive hint that encourages parallel gadget usage. * * This hint analyzes the LLM's response and, if only a single gadget was called, * appends a reminder that multiple gadgets can be used in parallel for efficiency. * * @param options - Configuration options * @returns AgentHooks that can be merged with other hooks * * @example * ```typescript * // Basic usage * const hooks = parallelGadgetHint(); * * // Custom threshold and message * const hooks = parallelGadgetHint({ * minGadgetsForEfficiency: 3, * message: "Consider calling multiple gadgets at once!", * }); * ``` */ declare function parallelGadgetHint(options?: ParallelGadgetHintOptions): AgentHooks; /** * Creates combined hints from a configuration object. * * This is a convenience function that creates and merges multiple hints * based on a simple configuration object. * * @param config - Configuration for which hints to enable * @returns Merged AgentHooks * * @example * ```typescript * const hooks = createHints({ * iterationProgress: { timing: "late" }, * parallelGadgets: true, * }); * * const agent = new AgentBuilder() * .withHooks(HookPresets.merge(existingHooks, hooks)) * .build(); * ``` */ declare function createHints(config: HintsConfig): AgentHooks; /** * StreamProcessor: The heart of the new hooks architecture. * * Replaces the complex wiring between Agent, ResponseProcessor, and GadgetRuntime. * Owns ALL stream processing and hook coordination with a clean, predictable flow. * * After refactoring, StreamProcessor is a thin orchestrator (~300 lines) that: * - Iterates over raw LLM stream chunks * - Applies raw-chunk and text-chunk interceptors * - Delegates gadget dispatch to GadgetDispatcher * - Yields events in real-time * - Applies the final assistant-message interceptor * * Extracted classes: * - GadgetLimitGuard — maxGadgetsPerResponse enforcement * - GadgetHookLifecycle — full hook sequence for a single gadget * - GadgetDispatcher — dispatch decision tree + concurrency + dependency */ /** * Configuration for the StreamProcessor. */ interface StreamProcessorOptions { /** Current iteration number */ iteration: number; /** Gadget registry for execution */ registry: GadgetRegistry; /** Custom gadget start prefix */ gadgetStartPrefix?: string; /** Custom gadget end prefix */ gadgetEndPrefix?: string; /** Custom argument prefix for block format */ gadgetArgPrefix?: string; /** Hooks for lifecycle events */ hooks?: AgentHooks; /** Logger instance */ logger?: Logger; /** Callback for requesting human input during execution */ requestHumanInput?: (question: string) => Promise; /** Default gadget timeout */ defaultGadgetTimeoutMs?: number; /** Maximum time (ms) to wait for in-flight gadgets to complete. Default: 300s. */ inFlightTimeoutMs?: number; /** Gadget execution mode ('parallel' | 'sequential') */ gadgetExecutionMode?: GadgetExecutionMode; /** LLMist client for ExecutionContext.llmist */ client?: LLMist; /** MediaStore for storing gadget media outputs */ mediaStore?: MediaStore; /** Parent agent configuration for subagents to inherit */ agentConfig?: AgentContextConfig; /** Subagent-specific configuration overrides */ subagentConfig?: SubagentConfigMap; /** Execution tree for tracking LLM calls and gadget executions */ tree?: ExecutionTree; /** Parent node ID (for gadget nodes created by this processor) */ parentNodeId?: NodeId | null; /** Base depth for nodes created by this processor */ baseDepth?: number; /** * Set of invocation IDs that completed in previous iterations. * Used to resolve dependencies on gadgets from prior LLM responses. */ priorCompletedInvocations?: Set; /** * Set of invocation IDs that failed in previous iterations. * Used to skip gadgets that depend on previously-failed gadgets. */ priorFailedInvocations?: Set; /** * Parent agent's observer hooks for subagent visibility. * * When a subagent is created with withParentContext(ctx), these observers * are also called for gadget events (in addition to the subagent's own hooks), * enabling the parent to observe subagent gadget activity. */ parentObservers?: Observers; /** Shared rate limit tracker for coordinated throttling across subagents */ rateLimitTracker?: RateLimitTracker; /** Shared retry config for consistent backoff behavior across subagents */ retryConfig?: ResolvedRetryConfig; /** Maximum gadgets to execute per response (0 = unlimited) */ maxGadgetsPerResponse?: number; } /** * Result of stream processing. * * @deprecated StreamProcessor.process() is now an async generator that yields * StreamEvent items directly. Use StreamCompletionEvent (the final yielded event) * to obtain the metadata formerly returned in this type. This interface is retained * for backward compatibility but is not used internally. */ interface StreamProcessingResult { /** All emitted events */ outputs: StreamEvent[]; /** Whether the loop should break */ shouldBreakLoop: boolean; /** Whether any gadgets were executed */ didExecuteGadgets: boolean; /** LLM finish reason */ finishReason: string | null; /** Token usage (including cached token counts when available) */ usage?: TokenUsage; /** The raw accumulated response text */ rawResponse: string; /** The final message (after interceptors) */ finalMessage: string; } /** * StreamProcessor: Thin orchestrator for stream processing and hook coordination. * * Execution order: * 1. Raw chunk arrives from LLM * 2. Interceptor: interceptRawChunk (transform raw text) * 3. Observer: onStreamChunk (logging) * 4. Parse for gadgets * 5. If gadget found → delegate to GadgetDispatcher * 6. If text chunk: * a. Interceptor: interceptTextChunk (transform display text) * b. Yield to user * 7. Stream complete * 8. Interceptor: interceptAssistantMessage (transform final message) */ declare class StreamProcessor { private readonly iteration; private readonly hooks; private readonly logger; private readonly parser; private readonly tree?; /** LLM-call node these gadgets hang off; used to derive subagentContext for partials. */ private readonly parentNodeId?; /** Parent agent observers (subagent visibility) — also notified for arg partials. */ private readonly parentObservers?; private responseText; private readonly dependencyResolver; /** Queue of completed gadget results ready to be yielded (for real-time streaming) */ private completedResultsQueue; private readonly dispatcher; private readonly limitGuard; constructor(options: StreamProcessorOptions); /** * Process an LLM stream and yield events in real-time. * * This is an async generator that yields events immediately as they occur: * - Text events are yielded as text is streamed from the LLM * - gadget_call events are yielded immediately when a gadget call is parsed * - gadget_result events are yielded when gadget execution completes * * The final event is always a StreamCompletionEvent containing metadata. */ process(stream: AsyncIterable): AsyncGenerator; /** * Process a single parsed event, yielding events in real-time. */ private processEventGenerator; /** * Process a text event through interceptors. */ private processTextEvent; /** * Drain all completed results from the queue. * Used to yield results as they complete during stream processing. * @returns Generator that yields all events currently in the queue */ private drainCompletedResults; /** * Update gadget result tracking flags based on a stream event. * Checks if the event is a gadget_result and, if so, marks gadgets as executed * and sets the break-loop flag when the result requests it. * * @param evt - The stream event to inspect * @param state - Mutable state object holding the tracking flags */ private trackGadgetResult; /** * Execute multiple observers in parallel. * All observers run concurrently and failures are tracked but don't crash. */ private runObserversInParallel; /** * Get all invocation IDs that completed successfully in this iteration. * Used by Agent to accumulate completed IDs across iterations. */ getCompletedInvocationIds(): Set; /** * Get all invocation IDs that failed in this iteration. * Used by Agent to accumulate failed IDs across iterations. */ getFailedInvocationIds(): Set; } declare const GADGET_START_PREFIX = "!!!GADGET_START:"; declare const GADGET_END_PREFIX = "!!!GADGET_END"; declare const GADGET_ARG_PREFIX = "!!!ARG:"; /** * Error utilities for llmist. */ /** * Thrown when an LLM provider returns a completion with no usable output — * no text, no tool calls, and no reasoning — typically a transient provider * glitch (e.g. a 200-OK response with an empty body). The retry orchestrator * treats this as a retryable failure; if every attempt comes back empty it * surfaces this error rather than committing a silent blank turn. */ declare class EmptyCompletionError extends Error { /** Agent iteration on which the empty completion was observed. */ readonly iteration: number; /** Finish reason reported alongside the empty body (often null). */ readonly finishReason: string | null; constructor(params: { iteration: number; finishReason: string | null; }); } /** * Detects if an error is an abort/cancellation error from any provider. * * Different providers throw different error types when a request is aborted: * - Standard: `AbortError` (name) - from fetch/AbortController * - Anthropic SDK: `APIConnectionAbortedError` * - OpenAI SDK: `APIUserAbortError` * - Generic: errors with "abort", "cancelled", or "canceled" in the message * * @param error - The error to check * @returns `true` if the error is an abort-related error, `false` otherwise * * @example * ```typescript * import { isAbortError } from "@llmist/core/errors"; * * const controller = new AbortController(); * * try { * for await (const chunk of client.stream({ signal: controller.signal, ... })) { * // Process chunks... * } * } catch (error) { * if (isAbortError(error)) { * console.log("Request was cancelled - this is expected"); * return; // Graceful exit * } * // Re-throw unexpected errors * throw error; * } * ``` */ declare function isAbortError(error: unknown): boolean; /** * Model shortcuts and aliases for more expressive DX. * * This module provides convenient aliases for common model names, * allowing developers to use short, memorable names instead of * verbose provider:model-id formats. * * @example * ```typescript * // Instead of: * model: "openai:gpt-5-nano" * * // You can use: * model: "gpt5-nano" * // or even: * model: "gpt-5-nano" // Auto-detects provider * ``` */ /** * Map of common model aliases to their full provider:model-id format. * * Updated: 2025-12-20 */ declare const MODEL_ALIASES: Record; /** * Options for resolveModel function. */ interface ResolveModelOptions { /** * If true, throw an error for unknown model names instead of falling back to OpenAI. * This helps catch typos like "gp4" instead of "gpt4". * Default: false */ strict?: boolean; /** * If true, suppress warnings for unknown model names. * Default: false */ silent?: boolean; } /** * Resolves a model name to its full provider:model format. * * Supports: * - Direct aliases: 'gpt5', 'sonnet', 'flash' * - Auto-detection: 'gpt-5-nano' → 'openai:gpt-5-nano' * - Pass-through: 'openai:gpt-5' → 'openai:gpt-5' * * Warnings: * - Logs a warning when an unknown model name falls back to OpenAI * - Use { strict: true } to throw an error instead * - Use { silent: true } to suppress warnings * * @param model - Model name or alias * @param options - Resolution options * @returns Full provider:model-id string * * @example * ```typescript * resolveModel('gpt5') // → 'openai:gpt-5' * resolveModel('sonnet') // → 'anthropic:claude-sonnet-4-5' * resolveModel('gpt-5-nano') // → 'openai:gpt-5-nano' * resolveModel('openai:gpt-5') // → 'openai:gpt-5' (passthrough) * resolveModel('claude-3-5-sonnet') // → 'anthropic:claude-3-5-sonnet' * * // Typo detection * resolveModel('gp5') // ⚠️ Warning: Unknown model 'gp5', falling back to 'openai:gp5' * * // Strict mode (throws on typos) * resolveModel('gp5', { strict: true }) // ❌ Error: Unknown model 'gp5' * ``` */ declare function resolveModel(model: string, options?: ResolveModelOptions): string; /** * Check if a model string is already in provider:model format. * * @param model - Model string to check * @returns True if the model has a provider prefix * * @example * ```typescript * hasProviderPrefix('openai:gpt-4o') // → true * hasProviderPrefix('gpt4') // → false * hasProviderPrefix('claude-3-5-sonnet') // → false * ``` */ declare function hasProviderPrefix(model: string): boolean; /** * Extract the provider from a full model string. * * @param model - Full model string (provider:model-id) * @returns Provider name, or undefined if no prefix * * @example * ```typescript * getProvider('openai:gpt-4o') // → 'openai' * getProvider('anthropic:claude') // → 'anthropic' * getProvider('gpt4') // → undefined * ``` */ declare function getProvider(model: string): string | undefined; /** * Extract the model ID from a full model string. * * @param model - Full model string (provider:model-id) * @returns Model ID, or the original string if no prefix * * @example * ```typescript * getModelId('openai:gpt-4o') // → 'gpt-4o' * getModelId('anthropic:claude') // → 'claude' * getModelId('gpt4') // → 'gpt4' * ``` */ declare function getModelId(model: string): string; /** * Strip the provider prefix from a model string. * * Identical to {@link getModelId}: removes the `provider:` portion and returns * just the model ID. If there is no prefix, the original string is returned. * * Use this when you need the bare model ID (e.g. for a cost-registry lookup) * and the input may or may not carry a provider prefix. * * @param modelId - Full model string, optionally with provider prefix * @returns Model ID without provider prefix * * @example * ```typescript * stripProviderPrefix('openai:gpt-4o') // → 'gpt-4o' * stripProviderPrefix('anthropic:claude-sonnet') // → 'claude-sonnet' * stripProviderPrefix('gpt-4o') // → 'gpt-4o' (no prefix — returned as-is) * ``` */ declare function stripProviderPrefix(modelId: string): string; /** * Signal that a gadget throws to indicate task completion and agent termination. * * When a gadget throws this signal, the agent loop will: * 1. Complete the current iteration * 2. Return the signal message as the gadget's result * 3. Exit the loop instead of continuing to the next iteration * * @example * ```typescript * import { z } from 'zod'; * * class FinishGadget extends Gadget({ * name: 'Finish', * description: 'Signals task completion', * schema: z.object({ * message: z.string().optional(), * }), * }) { * execute(params: this['params']): string { * const message = params.message || 'Task completed'; * throw new TaskCompletionSignal(message); * } * } * ``` */ declare class TaskCompletionSignal extends Error { constructor(message?: string); } /** * Exception that gadgets can throw to request human input during execution. * * When a gadget throws this exception, the agent loop will: * 1. Pause execution and wait for human input * 2. If `requestHumanInput` callback is provided, call it and await the answer * 3. Return the user's answer as the gadget's result * 4. Continue the loop with the answer added to conversation history * * If no callback is provided, the loop will yield a `human_input_required` event * and the caller must handle it externally. * * @example * ```typescript * import { z } from 'zod'; * * class AskUserGadget extends Gadget({ * name: 'AskUser', * description: 'Ask the user a question and get their answer', * schema: z.object({ * question: z.string().min(1, 'Question is required'), * }), * }) { * execute(params: this['params']): string { * throw new HumanInputRequiredException(params.question); * } * } * ``` */ declare class HumanInputRequiredException extends Error { readonly question: string; constructor(question: string); } /** * Exception thrown when a gadget execution exceeds its timeout limit. * * When a gadget's execution time exceeds either: * - The gadget's own `timeoutMs` property, or * - The global `defaultGadgetTimeoutMs` configured in runtime/agent loop options * * The executor will automatically throw this exception and return it as an error. * * @example * ```typescript * import { z } from 'zod'; * * class SlowApiGadget extends Gadget({ * name: 'SlowApi', * description: 'Calls a slow external API', * timeoutMs: 5000, // 5 second timeout * schema: z.object({ * endpoint: z.string(), * }), * }) { * async execute(params: this['params']): Promise { * // If this takes longer than 5 seconds, execution will be aborted * const response = await fetch(params.endpoint); * return await response.text(); * } * } * ``` */ declare class TimeoutException extends Error { readonly timeoutMs: number; readonly gadgetName: string; constructor(gadgetName: string, timeoutMs: number); } /** * Exception thrown when gadget execution is aborted. * * Gadgets can throw this exception when they detect the abort signal has been * triggered. This is typically used via the `throwIfAborted()` helper method * on the Gadget base class. * * @example * ```typescript * class LongRunningGadget extends Gadget({ * name: 'LongRunning', * description: 'Performs a long operation with checkpoints', * schema: z.object({ data: z.string() }), * }) { * async execute(params: this['params'], ctx: ExecutionContext): Promise { * // Check at key points - throws AbortException if aborted * this.throwIfAborted(ctx); * * await this.doPartOne(params.data); * * this.throwIfAborted(ctx); * * await this.doPartTwo(params.data); * * return 'completed'; * } * } * ``` */ declare class AbortException extends Error { constructor(message?: string); } /** * Exception thrown when a budget limit is set but the model has no valid pricing information. * * This is thrown during agent construction when: * - `budget` is set in agent options * - The model is not found in the model registry, or has zero pricing (input === 0 && output === 0) * * To fix: either register pricing for the model via `client.modelRegistry.registerModel()`, * or remove the budget constraint. * * @example * ```typescript * // This will throw BudgetPricingUnavailableError because "my-custom-model" * // has no pricing in the registry: * const agent = LLMist.createAgent() * .withModel("my-custom-model") * .withBudget(1.00) * .ask("Hello"); * ``` */ declare class BudgetPricingUnavailableError extends Error { readonly model: string; readonly budget: number; constructor(model: string, budget: number); } interface ErrorFormatterOptions { /** Custom argument prefix for block format examples. Default: "!!!ARG:" */ argPrefix?: string; /** Custom start prefix for block format examples. Default: "!!!GADGET_START:" */ startPrefix?: string; /** Custom end prefix for block format examples. Default: "!!!GADGET_END" */ endPrefix?: string; } /** * Options for constructing a GadgetExecutor. */ interface GadgetExecutorOptions { /** Gadget registry used to look up and execute gadgets */ registry: GadgetRegistry; /** Optional callback to request human input during gadget execution */ requestHumanInput?: (question: string) => Promise; /** Logger instance; defaults to a new "llmist:executor" logger if omitted */ logger?: Logger; /** Default timeout in milliseconds applied to all gadgets without an explicit timeout */ defaultGadgetTimeoutMs?: number; /** Options for formatting gadget execution errors */ errorFormatterOptions?: ErrorFormatterOptions; /** LLMist client made available to gadgets via ExecutionContext.llmist */ client?: LLMist; /** Media store for persisting gadget media outputs */ mediaStore?: MediaStore; /** Parent agent configuration inherited by subagents */ agentConfig?: AgentContextConfig; /** Per-gadget configuration overrides (e.g., timeoutMs, model) */ subagentConfig?: SubagentConfigMap; /** Execution tree for tracking LLM calls and gadget executions */ tree?: ExecutionTree; /** Parent node ID in the execution tree */ parentNodeId?: NodeId | null; /** Base depth for nodes created during execution */ baseDepth?: number; /** * Parent agent's observer hooks for subagent visibility. * When a subagent uses withParentContext(ctx), these observers are also called * for gadget events in addition to the subagent's own hooks. */ parentObservers?: Observers; /** * Current agent's observers. * Passed to ExecutionContext.parentObservers so gadgets creating subagents * can inherit them via withParentContext(ctx). */ currentObservers?: Observers; /** Shared rate limit tracker for coordinated throttling across subagents */ rateLimitTracker?: RateLimitTracker; /** Shared retry config for consistent backoff behavior across subagents */ retryConfig?: ResolvedRetryConfig; } declare class GadgetExecutor { private readonly registry; private readonly requestHumanInput?; private readonly defaultGadgetTimeoutMs?; private readonly client?; private readonly mediaStore?; private readonly agentConfig?; private readonly subagentConfig?; private readonly tree?; private readonly parentNodeId?; private readonly baseDepth?; private readonly parentObservers?; private readonly currentObservers?; private readonly rateLimitTracker?; private readonly retryConfig?; private readonly logger; private readonly errorFormatter; private readonly argPrefix; constructor(options: GadgetExecutorOptions); /** * Creates a promise that rejects with a TimeoutException after the specified timeout. * Aborts the provided AbortController before rejecting, allowing gadgets to clean up. * Returns both the promise and a cancel function to clear the timeout when no longer needed. */ private createTimeoutPromise; /** * Unify gadget execute result to consistent internal format. * Handles string returns (backwards compat), object returns with cost, * and object returns with media. */ private unifyExecuteResult; execute(call: ParsedGadgetCall): Promise; } /** * Helper functions for gadget authors. * * This module provides: * 1. Response formatting helpers (gadgetSuccess, gadgetError, withErrorHandling) * 2. Media output helpers (resultWithImage, resultWithAudio, etc.) * * @example Response helpers * ```typescript * import { gadgetSuccess, gadgetError, withErrorHandling } from "llmist"; * * // Simple response formatting * return gadgetSuccess({ url: "https://example.com", title: "Example" }); * return gadgetError("Element not found", { selector: ".missing" }); * * // Automatic error handling wrapper * const safeExecute = withErrorHandling(async (params) => { * // your code here - errors are automatically caught and formatted * return gadgetSuccess({ result: "done" }); * }); * ``` * * @example Media output helpers * ```typescript * import { resultWithImage } from "llmist"; * * const screenshotGadget = createGadget({ * name: "Screenshot", * schema: z.object({ url: z.string() }), * execute: async ({ url }) => { * const screenshot = await takeScreenshot(url); * return resultWithImage( * `Screenshot of ${url}`, * screenshot, * { description: "Webpage screenshot" } * ); * }, * }); * ``` */ /** * Create a success response as JSON string. * * This is a convenience helper for gadgets that return JSON-formatted responses. * It automatically adds `success: true` and stringifies the result. * * @param data - Additional data to include in the response * @returns JSON string with success: true and provided data * * @example * ```typescript * return gadgetSuccess({ url: page.url(), title: await page.title() }); * // Returns: '{"success":true,"url":"...","title":"..."}' * ``` */ declare function gadgetSuccess(data?: Record): string; /** * Create an error response as JSON string. * * This is a convenience helper for gadgets that return JSON-formatted errors. * It stringifies the error message and any additional details. * * @param message - Error message * @param details - Additional error details (e.g., suggestions, context) * @returns JSON string with error message and details * * @example * ```typescript * return gadgetError("Element not found", { selector: ".missing", suggestions: ["Try #id instead"] }); * // Returns: '{"error":"Element not found","selector":".missing","suggestions":[...]}' * ``` */ declare function gadgetError(message: string, details?: Record): string; /** * Safely extract error message from unknown error type. * * Handles both Error instances and other thrown values. * * @param error - Unknown error value * @returns String error message */ declare function getErrorMessage(error: unknown): string; /** * Wrap a gadget execute function with automatic error handling. * * This higher-order function catches any errors thrown during execution * and converts them to properly formatted error responses. * * @param execute - The execute function to wrap * @returns A wrapped function that catches errors and returns gadgetError responses * * @example * ```typescript * const safeExecute = withErrorHandling(async (params: MyParams) => { * // Your code here - if it throws, error is caught and formatted * const result = await riskyOperation(params.id); * return gadgetSuccess({ result }); * }); * * // In gadget: * execute(params) { * return safeExecute(params); * } * ``` */ declare function withErrorHandling(execute: (params: TParams, ctx?: ExecutionContext) => Promise | string): (params: TParams, ctx?: ExecutionContext) => Promise; /** * Create a GadgetMediaOutput from raw data. * * @param kind - Type of media * @param data - Raw binary data (Buffer or Uint8Array) * @param mimeType - MIME type string * @param options - Optional description, metadata, and fileName * @returns A GadgetMediaOutput ready to include in results */ declare function createMediaOutput(kind: MediaKind, data: Buffer | Uint8Array, mimeType: string, options?: { description?: string; metadata?: MediaMetadata; fileName?: string; }): GadgetMediaOutput; /** * Create a result with multiple media outputs. * * @param result - Text result string * @param media - Array of GadgetMediaOutput items (must not be empty) * @param cost - Optional cost in USD * @returns A GadgetExecuteResultWithMedia * @throws Error if media array is empty * * @example * ```typescript * return resultWithMedia( * "Generated 2 charts", * [ * createMediaOutput("image", barChartPng, "image/png", { description: "Bar chart" }), * createMediaOutput("image", pieChartPng, "image/png", { description: "Pie chart" }), * ], * 0.002 * ); * ``` */ declare function resultWithMedia(result: string, media: GadgetMediaOutput[], cost?: number): GadgetExecuteResultWithMedia; /** * Options for resultWithImage helper. */ interface ImageOptions { /** MIME type (auto-detected if not provided) */ mimeType?: string; /** Human-readable description */ description?: string; /** Image dimensions and other metadata */ metadata?: MediaMetadata; /** Cost in USD */ cost?: number; /** Filename to use when saving (if not provided, auto-generated) */ fileName?: string; } /** * Create a result with a single image output. * * @param result - Text result string * @param imageData - Raw image data (PNG, JPEG, GIF, WebP) * @param options - Optional MIME type, description, metadata, cost * @returns A GadgetExecuteResultWithMedia * * @example * ```typescript * const screenshot = await page.screenshot({ type: "png" }); * return resultWithImage( * "Screenshot captured", * screenshot, * { description: "Homepage screenshot", metadata: { width: 1920, height: 1080 } } * ); * ``` */ declare function resultWithImage(result: string, imageData: Buffer | Uint8Array, options?: ImageOptions): GadgetExecuteResultWithMedia; /** * Image item for resultWithImages helper. */ interface ImageItem { /** Raw image data */ data: Buffer | Uint8Array; /** MIME type (auto-detected if not provided) */ mimeType?: string; /** Human-readable description */ description?: string; /** Image dimensions and other metadata */ metadata?: MediaMetadata; /** Filename to use when saving (if not provided, auto-generated) */ fileName?: string; } /** * Create a result with multiple image outputs. * * @param result - Text result string * @param images - Array of image items (must not be empty) * @param cost - Optional cost in USD * @returns A GadgetExecuteResultWithMedia * @throws Error if images array is empty * * @example * ```typescript * return resultWithImages( * "Generated comparison images", * [ * { data: beforeImg, description: "Before" }, * { data: afterImg, description: "After" }, * ], * 0.01 * ); * ``` */ declare function resultWithImages(result: string, images: ImageItem[], cost?: number): GadgetExecuteResultWithMedia; /** * Options for resultWithAudio helper. */ interface AudioOptions { /** MIME type (auto-detected if not provided) */ mimeType?: string; /** Human-readable description */ description?: string; /** Duration in milliseconds */ durationMs?: number; /** Cost in USD */ cost?: number; /** Filename to use when saving (if not provided, auto-generated) */ fileName?: string; } /** * Create a result with a single audio output. * * @param result - Text result string * @param audioData - Raw audio data (MP3, WAV, OGG, etc.) * @param options - Optional MIME type, description, duration, cost * @returns A GadgetExecuteResultWithMedia * * @example * ```typescript * const speech = await generateSpeech(text); * return resultWithAudio( * `Generated speech for: "${text.slice(0, 50)}..."`, * speech, * { mimeType: "audio/mp3", durationMs: 5000 } * ); * ``` */ declare function resultWithAudio(result: string, audioData: Buffer | Uint8Array, options?: AudioOptions): GadgetExecuteResultWithMedia; /** * Options for resultWithFile helper. */ interface FileOptions { /** Human-readable description */ description?: string; /** Cost in USD */ cost?: number; /** Filename to use when saving (if not provided, auto-generated) */ fileName?: string; } /** * Create a result with a generic file output. * * Use this for arbitrary file types that don't fit image/audio categories. * * @param result - Text result string * @param fileData - Raw file data * @param mimeType - MIME type (required, not auto-detected) * @param options - Optional description and cost * @returns A GadgetExecuteResultWithMedia * * @example * ```typescript * const pdf = await generatePdf(content); * return resultWithFile( * "Generated PDF report", * pdf, * "application/pdf", * { description: "Monthly report" } * ); * ``` */ declare function resultWithFile(result: string, fileData: Buffer | Uint8Array, mimeType: string, options?: FileOptions): GadgetExecuteResultWithMedia; /** * Create a GadgetOutputViewer gadget instance bound to a specific output store. * * @param store - The GadgetOutputStore to read outputs from * @param maxOutputChars - Maximum characters to return (default: 76,800 = ~19k tokens) * @returns A GadgetOutputViewer gadget instance */ declare function createGadgetOutputViewer(store: GadgetOutputStore, maxOutputChars?: number): AbstractGadget; interface StreamParserOptions { startPrefix?: string; endPrefix?: string; /** Prefix for block format arguments. Default: "!!!ARG:" */ argPrefix?: string; } /** * Parser for extracting gadget invocations from LLM text output. * Processes text chunks incrementally and emits events for text and gadget calls. */ declare class GadgetCallParser { private buffer; private lastEmittedTextOffset; /** Non-null only while a single trailing gadget block is mid-stream. */ private currentPartial; private readonly startPrefix; private readonly endPrefix; private readonly argPrefix; /** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */ private readonly maxMarkerLength; constructor(options?: StreamParserOptions); /** * Extract and consume text up to the given index. * Returns undefined if no meaningful text to emit. */ private extractTextSegment; /** * Parse gadget invocation metadata from the header line. * * Supported formats: * - `GadgetName` - Auto-generate ID, no dependencies * - `GadgetName:my_id` - Explicit ID, no dependencies * - `GadgetName:my_id:dep1,dep2` - Explicit ID with dependencies * - `GadgetName:my_id:dep1:dep2:dep3` - Colons treated as dep separators (LLM resilience) * * Dependencies can be comma-separated or colon-separated invocation IDs. */ private parseInvocationMetadata; /** * Extract the error message from a parse error. * Preserves full message since the error formatter adds contextual help * that benefits from precise, detailed error information. */ private extractParseError; /** * Parse parameter string using block format */ private parseParameters; feed(chunk: string): Generator; /** Create fresh partial-tracking state for a newly-started streaming gadget. */ private newPartialState; /** * Emit per-field "growing value" partials for an in-progress (or, when * `allComplete`, a just-completed) gadget body delimited by [bodyStart, bodyEnd). * * Incremental by design: each call resumes the `!!!ARG:` scan near where the last * one stopped (backing off by one marker's worth so a marker split across a chunk * boundary is still found) and only re-touches the in-progress field, so a long * streamed body costs O(new bytes) per feed instead of O(body). Every field except * the in-progress (last) one is complete — a following `!!!ARG:` terminated it; the * last field is tentative unless `allComplete`. The tentative field holds back any * suffix that is a partial prefix of a gadget marker so it never leaks into a value. * * We deliberately do NOT run stripMarkdownFences here: an unbalanced opening fence * sits before the first `!!!ARG:` (never emitted) and the authoritative gadget_call * still strips fences from the full raw parameters. */ private emitArgPartials; /** * Emit a single field whose `!!!ARG:` marker starts at `markerAbs` and whose value * runs to `valueEndAbs` (the next marker, or the body end). Mirrors the per-field * semantics of the old split-based emitter: field-path line, hold-back for a * tentative value, single trailing-newline strip. */ private emitFieldRange; /** * Emit a single partial for a field, but only when its value grew or it newly * completed — keeping event volume proportional to field growth, not characters. */ private emitFieldDelta; /** * Length of the longest suffix of `value` that is a proper prefix of any gadget * marker (start/end/arg). Used to hold back the beginning of an incoming marker * so it never appears inside a streamed value. */ private trailingPartialMarkerLength; finalize(): Generator; reset(): void; } /** * Default-safe allowlist for MCP STDIO server commands. * * Mitigates the CVE-2026-30623 family of stdio-spawn RCE vulnerabilities. The * gate refuses to spawn any executable whose basename is not in the allowlist * unless the spec is marked `trust: true` (library) / `trust = true` (TOML) * / `--mcp-trust ` (CLI). It also rejects whole-string commands that * embed args or shell metacharacters — callers must pass arguments as a * separate array. * * @module mcp/allowlist */ /** * Default allowlist of MCP stdio server runtimes that are safe to spawn * without explicit user opt-in. Add entries to this list only when the * basename is universally a runtime, not a tool that takes arbitrary code * (e.g. don't add `bash` or `sh`). */ declare const DEFAULT_MCP_COMMAND_ALLOWLIST: ReadonlySet; /** * Throws McpUntrustedCommandError if `command` is not safe to spawn under * the allowlist policy. */ declare function assertCommandAllowed(command: string, trusted: boolean, customAllowlist?: ReadonlySet): void; /** * Wraps the official MCP SDK's stdio Client into a small, llmist-flavored * surface. Encapsulates the SDK so the rest of llmist depends on the typed * shapes in `./types.ts` rather than vendor types. * * Lazy-imports the SDK so agents that don't use MCP pay zero overhead at * load time. * * @module mcp/client */ interface McpClientOptions { /** * Inject a pre-built transport for testing. When omitted, the client * builds a stdio transport from the spec at connect() time. */ transport?: Transport; /** * Override the client identity sent during initialize. */ clientInfo?: { name: string; version: string; }; } declare class McpClient { readonly spec: McpServerSpec; private sdkClient; private spawnedPid; private closed; private readonly injectedTransport?; private readonly clientInfo; constructor(spec: McpServerSpec, opts?: McpClientOptions); get serverName(): string; get pid(): number | null; get serverCapabilities(): McpServerCapabilities | null; connect(): Promise; listTools(): Promise; callTool(name: string, args: unknown): Promise; listPrompts(): Promise; getPrompt(name: string, args?: Record): Promise; close(): Promise; private requireClient; private withTimeout; } /** * Typed errors raised by the MCP integration. * * These wrap underlying SDK and transport errors so the rest of llmist can * react to MCP failures with stable, narrow types instead of catching the * SDK's internal error classes. * * @module mcp/errors */ declare class McpError extends Error { readonly serverName?: string; constructor(message: string, serverName?: string); } declare class McpUntrustedCommandError extends McpError { readonly command: string; constructor(command: string, serverName?: string); } declare class McpConnectError extends McpError { readonly cause?: unknown; constructor(message: string, opts?: { serverName?: string; cause?: unknown; }); } declare class McpToolCallError extends McpError { readonly toolName: string; readonly cause?: unknown; constructor(toolName: string, message: string, opts?: { serverName?: string; cause?: unknown; }); } declare class JsonSchemaConversionError extends Error { readonly schemaFragment: unknown; readonly reason: string; constructor(reason: string, schemaFragment: unknown); } /** * Convert native llmist gadgets into MCP tool descriptors and run them on * behalf of an MCP server. The inverse of `tool-adapter.ts` (which converts * MCP tools into native gadgets). * * @module mcp/gadget-exporter */ /** Convert a native gadget into an MCP tool descriptor. */ declare function gadgetToMcpTool(gadget: AbstractGadget): McpToolDescriptor; /** * Convert a gadget's execute() return value into MCP content blocks. * * Shapes handled: * - string → single `text` block * - { result, media[] } → `text` block + per-media block * - other (object) → JSON-stringified `text` block */ declare function gadgetResultToMcpContent(ret: GadgetExecuteReturn): McpContentBlock[]; /** * Validate params and run the gadget, converting both the success and error * paths into MCP tool result shapes. * * Used by the McpServer's `tools/call` handler. */ declare function runGadgetForMcp(gadget: AbstractGadget, rawParams: unknown): Promise; /** * Minimal JSON Schema → Zod converter for MCP tool input schemas. * * MCP tool descriptors expose `inputSchema` as JSON Schema (typically a * subset of draft-2020-12 with `type`, `properties`, `required`, `items`, * `enum`, `default`, `description`, `nullable`). This converter handles * exactly that subset — anything richer ($ref, allOf, format-only schemas, * non-primitive oneOf composition) throws so we surface the gap rather than * silently coercing a wrong schema. * * @module mcp/json-schema-to-zod */ interface JSONSchemaLike { type?: string | string[]; description?: string; default?: unknown; enum?: unknown[]; nullable?: boolean; properties?: Record; required?: string[]; items?: JSONSchemaLike | JSONSchemaLike[]; oneOf?: JSONSchemaLike[]; anyOf?: JSONSchemaLike[]; allOf?: JSONSchemaLike[]; $ref?: string; [k: string]: unknown; } /** * Convert a JSON Schema fragment into a Zod schema. * * Throws JsonSchemaConversionError on features that have no clean Zod analog * in the MCP subset. */ declare function jsonSchemaToZod(schema: JSONSchemaLike | undefined): ZodTypeAny; /** * Tracks spawned MCP clients for an agent run and closes them all on * teardown. Plan 1 ships the basics: register, closeAll, idempotent. Signal * handling and graceful shutdown windows are added in plan 2. * * @module mcp/lifecycle */ declare class McpLifecycle { private clients; private closing; private signalHandlersInstalled; private sigtermHandler; private sigintHandler; get size(): number; register(client: McpClient): void; /** * Attach SIGTERM/SIGINT handlers that close every registered client when * the parent process is asked to exit. Idempotent (double install is a * no-op) and removable via `removeSignalHandlers()`. */ installSignalHandlers(): void; removeSignalHandlers(): void; /** * Close every registered client in parallel. Errors from individual close() * calls are swallowed (logged via console.warn) — a teardown path must not * throw because that would mask the original reason the agent is shutting * down. Idempotent: concurrent calls all return the same in-flight promise. */ closeAll(): Promise; } /** * Wrap the MCP SDK's Server class with llmist semantics: register native * gadgets as MCP tools and (optionally) llmist skills as MCP prompts. * * Lazy-imports the SDK so callers that don't expose anything pay no cost. * * @module mcp/server */ interface CreateMcpServerOptions { gadgets: GadgetRegistry; skills?: SkillRegistry; /** Override the protocol version advertised. Defaults to 2025-06-18. */ protocolVersion?: string; /** Server identity sent on initialize. */ serverInfo?: { name: string; version: string; }; } interface McpServerHandle { /** Connect the server to a Transport (stdio, in-memory test transport, etc.). */ connect(transport: Transport): Promise; /** Close the underlying server cleanly. Idempotent. */ stop(): Promise; /** True between connect() and stop(). */ readonly running: boolean; } declare function createMcpServer(opts: CreateMcpServerOptions): McpServerHandle; /** * Convert native llmist Skills into MCP prompt descriptors and render them * on behalf of an MCP server. * * Plan 3 maps: * - Skill metadata.name → prompt name * - Skill metadata.description → prompt description * - argumentHint (when present) → a single optional `arguments` parameter * (the existing skill-substitution machinery handles `$ARGUMENTS`, `$0`, * `$1`, etc., from a single string) * - Skill body, after argument substitution, becomes a single user-role * text message in the prompt response * * @module mcp/skill-exporter */ declare function skillToMcpPrompt(skill: Skill): McpPromptDescriptor; /** * Render a skill's body as a single MCP prompt message after argument * substitution. * * `args.arguments` is a string interpreted by the existing skill activation * pipeline ($ARGUMENTS, $0, $1, ...). We don't try to map MCP's per-argument * named parameters into the existing positional substitution model — the * skill author already chose the substitution shape. */ declare function renderSkillForMcpPrompt(skill: Skill, args: Record): Promise; /** * Wraps an MCP tool descriptor as a native llmist gadget so the existing * gadget executor consumes it without any awareness of MCP. * * @module mcp/tool-adapter */ interface McpToolAdapterOptions { /** Prefix prepended to the gadget name. Used for multi-server name conflict resolution (plan 2). */ prefix?: string; } /** * Convert an MCP tool descriptor into a native gadget that delegates to the * supplied MCP client. */ declare function mcpToolToGadget(tool: McpToolDescriptor, client: McpClient, opts?: McpToolAdapterOptions): AbstractGadget; /** * Character-to-token ratio for fallback token estimation. * * Used only when tiktoken (the primary fallback) is unavailable. A value of 2 * errs on the side of overestimating token count, which is safer for * compaction triggers and output limiting. * * Rationale: The previous value of 4 was based on English prose averages, but * agentic sessions are dominated by JSON, code, and structured data where the * real ratio is ~1.5-2.5 chars/token. A 4-char estimate underestimated tokens * by up to 250%, causing compaction and output limiting to never trigger. */ declare const FALLBACK_CHARS_PER_TOKEN = 2; /** * Aggregates a research event stream into a final {@link ResearchResult}. * * Provider normalizers emit what they know; the collector merges streamed * state (text deltas, citations, usage) with the terminal `done` payload: * - report: `done.report` wins when non-empty, else accumulated text deltas * - citations: union of streamed + done citations, deduplicated * - usage: last-write-wins for token fields, max for cumulative counters * - costUSD: computed from catalog pricing when a spec is provided */ interface ResearchResultContext { provider: string; model: string; jobId: string | null; } declare class ResearchResultCollector { private readonly spec?; private readonly now; private reportParts; private citations; private usage; private lastStatus; private doneReport; private doneRaw; private hasDone; private firstEventAt; private terminalAt; /** * Terminal error info from an `error` event. * @internal Read by tests only — not part of the public consumer contract. */ terminalError: ResearchErrorInfo | undefined; constructor(spec?: ResearchModelSpec | undefined, now?: () => number); ingest(event: ResearchEvent): void; toResult(context: ResearchResultContext): ResearchResult; /** Whether a terminal event (`done` or `error`) was ingested. */ get isTerminal(): boolean; private addCitation; private mergeUsage; } /** * Research cost estimation. * * Separate from `ModelRegistry.estimateCost` because research pricing has * dimensions the chat cost model lacks: per-search fees and separately-priced * internal reasoning tokens (see {@link ResearchPricing}). */ /** * Estimate the USD cost of a research run. * * Semantics: * - `cachedInputTokens` are a subset of `inputTokens` and billed at * `cachedInput` (falling back to `input` when unset). * - `reasoningTokens` are a subset of `outputTokens`. When * `internalReasoning` is priced, reasoning tokens are billed at that rate * and excluded from the output rate; otherwise they remain part of output. * - `searches` are billed at `perThousandSearches / 1000` each. */ declare function estimateResearchCost(pricing: ResearchPricing, usage: ResearchUsage): number; /** * Typed errors for the research surface. * * Follows the core error convention (plain `Error` subclasses with `name` * set) — see `core/errors.ts`. */ /** Thrown when no registered provider supports research for the given model. */ declare class ResearchNotSupportedError extends Error { constructor(message: string); } /** * Thrown when a job cannot produce a serializable ref (no server-side job id — * e.g. OpenRouter research runs) or a ref cannot be resumed on its provider. */ declare class ResearchJobNotResumableError extends Error { constructor(message: string); } /** Thrown when status polling is requested on a provider without it. */ declare class ResearchNotPollableError extends Error { constructor(message: string); } /** * Thrown when the client-side time budget expires. The transport is aborted; * a background job keeps running server-side and its ref stays valid. */ declare class ResearchTimeoutError extends Error { readonly timeoutMs: number; constructor(timeoutMs: number); } /** Thrown when starting a run on a model past its announced shutdown date. */ declare class ResearchDeprecatedModelError extends Error { readonly modelId: string; readonly shutdownDate: string; readonly replacement?: string; constructor(params: { modelId: string; shutdownDate: string; replacement?: string; }); } /** Thrown when options fail pre-flight validation against the model's spec. */ declare class ResearchValidationError extends Error { constructor(message: string); } /** Thrown when a job's event stream is consumed more than once. */ declare class ResearchStreamConsumedError extends Error { constructor(); } /** * Subagent creation helper for gadget authors. * * Simplifies the common pattern of creating subagents from within gadgets. * Handles: * - Getting host exports (AgentBuilder, LLMist) from context * - Model resolution with "inherit" support * - Parent context sharing for cost tracking * - Common configuration options * * @module agent/subagent * * @example * ```typescript * import { createSubagent, Gadget, z } from "llmist"; * import type { ExecutionContext } from "llmist"; * * class BrowseWeb extends Gadget({ * name: "BrowseWeb", * schema: z.object({ * task: z.string(), * url: z.string().url(), * model: z.string().optional(), * }), * }) { * async execute(params: this["params"], ctx?: ExecutionContext) { * const agent = createSubagent(ctx!, { * name: "BrowseWeb", * gadgets: [Navigate, Click, Screenshot], * systemPrompt: BROWSER_SYSTEM_PROMPT, * model: params.model, // Optional override * maxIterations: 15, * }).ask(params.task); * * for await (const event of agent.run()) { * // Process events... * } * * return result; * } * } * ``` */ /** * Options for creating a subagent. */ interface SubagentOptions { /** * Name of the subagent (used for config resolution). * Should match the gadget name in CLI config, e.g., "BrowseWeb". */ name: string; /** * Gadgets to register with the subagent. */ gadgets: AbstractGadget[]; /** * System prompt for the subagent. */ systemPrompt?: string; /** * Model to use. If not provided, inherits from parent or uses default. * Can be a runtime parameter from gadget params. */ model?: string; /** * Default model if no other source provides one. * @default "sonnet" */ defaultModel?: string; /** * Maximum iterations for the agent loop. */ maxIterations?: number; /** * Default max iterations if not specified. * @default 15 */ defaultMaxIterations?: number; /** * Agent hooks for observers, interceptors, controllers. */ hooks?: AgentHooks; /** * Temperature for LLM calls. */ temperature?: number; } /** * Create a subagent from within a gadget. * * This helper simplifies the common pattern of creating nested agents. * It automatically: * - Gets the correct AgentBuilder from host exports * - Resolves model with "inherit" support from CLI config * - Shares the parent's execution tree for cost tracking * - Forwards the abort signal for proper cancellation * - Inherits human input handler (for 2FA, CAPTCHAs, etc.) * * @param ctx - ExecutionContext passed to gadget's execute() * @param options - Subagent configuration options * @returns Configured AgentBuilder ready for .ask() * * @example * ```typescript * // Basic usage * const agent = createSubagent(ctx, { * name: "BrowseWeb", * gadgets: [Navigate, Click], * }).ask("Go to google.com"); * * // With all options * const agent = createSubagent(ctx, { * name: "BrowseWeb", * gadgets: [Navigate, Click, Screenshot], * systemPrompt: "You are a browser automation agent...", * model: params.model, // Runtime override * defaultModel: "sonnet", * maxIterations: 20, * hooks: { * observers: { * onLLMCallReady: () => refreshPageState(), * }, * }, * }).ask(params.task); * * for await (const event of agent.run()) { * // Events flow through shared tree automatically * } * * // Human input bubbles up automatically: * // If a gadget throws HumanInputRequiredException, * // the parent's onHumanInput handler will be called * ``` */ declare function createSubagent(ctx: ExecutionContext, options: SubagentOptions): AgentBuilder; /** * Check if an execution context has valid host exports. * * Useful for conditional logic when gadgets may run standalone or via agent. * * @param ctx - Execution context * @returns True if host exports are available */ declare function hasHostExports(ctx?: ExecutionContext): boolean; /** * Zod schema to JSON Schema conversion with instance mismatch detection. * * When consumers use their own `import { z } from "zod"` instead of * `import { z } from "llmist"`, the `.describe()` metadata can be lost * because Zod stores metadata on schema instances and `toJSONSchema()` * only reads from schemas created by the same Zod module instance. * * This module provides a `schemaToJSONSchema()` function that: * 1. Converts Zod schema to JSON Schema using the standard API * 2. Detects if descriptions were lost due to instance mismatch * 3. Logs a warning recommending `import { z } from "llmist"` * 4. Falls back to extracting descriptions from `schema._def` * * @module gadgets/schema-to-json */ /** * Convert a Zod schema to JSON Schema with description fallback. * * If descriptions exist in schema._def but are missing from the generated * JSON Schema (indicating a Zod instance mismatch), this function: * 1. Logs a warning recommending `import { z } from "llmist"` * 2. Extracts descriptions from _def and merges them into the JSON Schema * * @param schema - Zod schema to convert * @param options - Conversion options (target JSON Schema version) * @returns JSON Schema object with descriptions preserved * * @example * ```typescript * import { schemaToJSONSchema } from './schema-to-json.js'; * import { z } from 'zod'; * * const schema = z.object({ * name: z.string().describe('User name'), * }); * * const jsonSchema = schemaToJSONSchema(schema); * // { type: 'object', properties: { name: { type: 'string', description: 'User name' } } } * ``` */ declare function schemaToJSONSchema(schema: ZodTypeAny, options?: { target?: "draft-7" | "draft-2020-12"; }): Record; /** * Validates that a Zod schema doesn't contain z.unknown() which produces * incomplete JSON schemas without type information. * * @param schema - The Zod schema to validate * @param gadgetName - Name of the gadget (for error messages) * @throws Error if z.unknown() is detected with helpful suggestions */ declare function validateGadgetSchema(schema: ZodTypeAny, gadgetName: string): void; /** * Validation utilities for gadget parameters. * * Provides standalone validation with Zod schema support, including * default application and formatted error output. * * @module gadgets/validation */ /** * Individual validation issue with path and message. */ interface ValidationIssue { /** Dot-separated path to the invalid field (e.g., "user.email") */ path: string; /** Human-readable error message */ message: string; } /** * Result of parameter validation. * Discriminated union based on `success` field. */ type ValidationResult> = { success: true; /** Validated and transformed data with defaults applied */ data: T; } | { success: false; /** Formatted error message */ error: string; /** Individual validation issues */ issues: ValidationIssue[]; }; /** * Validate parameters against a Zod schema and apply defaults/transformations. * * This replicates the validation behavior from GadgetExecutor, making it * available for direct use in tests and other contexts. * * @param schema - Zod schema to validate against * @param params - Raw parameters to validate * @returns ValidationResult with either validated data or error details * * @example * ```typescript * import { validateAndApplyDefaults } from 'llmist'; * import { z } from 'zod'; * * const schema = z.object({ * delay: z.number().default(100), * retries: z.number().int().min(0).default(3), * }); * * const result = validateAndApplyDefaults(schema, { delay: 50 }); * if (result.success) { * console.log(result.data); // { delay: 50, retries: 3 } * } * ``` */ declare function validateAndApplyDefaults>(schema: ZodTypeAny, params: Record): ValidationResult; /** * Validate gadget parameters using the gadget's schema. * * Convenience wrapper that extracts the schema from a gadget instance. * If the gadget has no schema, validation always succeeds with the * original parameters. * * @param gadget - Gadget instance with optional parameterSchema * @param params - Raw parameters to validate * @returns ValidationResult with either validated data or error details * * @example * ```typescript * import { validateGadgetParams, createGadget } from 'llmist'; * import { z } from 'zod'; * * const calculator = createGadget({ * description: 'Add numbers', * schema: z.object({ * a: z.number(), * b: z.number().default(0), * }), * execute: ({ a, b }) => String(a + b), * }); * * const result = validateGadgetParams(calculator, { a: 5 }); * if (result.success) { * console.log(result.data); // { a: 5, b: 0 } * } * ``` */ declare function validateGadgetParams(gadget: AbstractGadget, params: Record): ValidationResult; /** * Logger configuration options for the library. */ interface LoggerOptions { /** * Log level: 0=silly, 1=trace, 2=debug, 3=info, 4=warn, 5=error, 6=fatal * @default 4 (warn) */ minLevel?: number; /** * Output type: 'pretty' for development, 'json' for production * @default 'pretty' */ type?: "pretty" | "json" | "hidden"; /** * Logger name (appears in logs) */ name?: string; /** * When true, reset (truncate) the log file instead of appending. * Useful for getting clean logs per session. * @default false */ logReset?: boolean; /** * When true AND file logging is active (LLMIST_LOG_FILE), * also write formatted log lines to stdout (console). * Useful for Docker/container environments where you need both * file logs for upload and stdout for container log aggregation. * No effect when file logging is not active. * @default false */ teeToConsole?: boolean; } /** * Create a new logger instance for the library. * * @param options - Logger configuration options * @returns Configured Logger instance * * @example * ```typescript * // Development logger with pretty output * const logger = createLogger({ type: 'pretty', minLevel: 2 }); * * // Production logger with JSON output * const logger = createLogger({ type: 'json', minLevel: 3 }); * * // Silent logger for tests * const logger = createLogger({ type: 'hidden' }); * ``` */ declare function createLogger(options?: LoggerOptions): Logger; /** * Default logger instance for the library. * Users can replace this with their own configured logger. */ declare const defaultLogger: Logger; /** * Package manifest types for llmist gadget packages. * * These types define the structure of the `llmist` field in package.json * for gadget packages. This enables: * - Preset-based gadget loading * - Subagent discovery * - Factory function support * - Session management metadata * * @module package/manifest * * @example package.json * ```json * { * "name": "dhalsim", * "llmist": { * "gadgets": "./dist/index.js", * "factory": "./dist/index.js", * "presets": { * "minimal": ["Navigate", "GetFullPageContent"], * "readonly": ["Navigate", "GetFullPageContent", "Screenshot"], * "all": "*" * }, * "subagents": { * "BrowseWeb": { * "entryPoint": "./dist/index.js", * "export": "Dhalsim", * "description": "Autonomous web browser agent", * "defaultModel": "sonnet", * "maxIterations": 15 * } * }, * "session": { * "factory": "getSessionManager", * "type": "browser" * } * } * } * ``` */ /** * Subagent definition in the manifest. */ interface SubagentManifestEntry { /** * Entry point file path relative to package root. * @example "./dist/index.js" */ entryPoint: string; /** * Export name from the entry point. * @example "Dhalsim" or "BrowseWeb" */ export: string; /** * Human-readable description of what this subagent does. */ description?: string; /** * List of gadget names this subagent uses internally. * Useful for documentation and dependency tracking. */ uses?: string[]; /** * Default model for this subagent. * Can be "inherit" to use parent's model. * @default "inherit" */ defaultModel?: string; /** * Default maximum iterations. * @default 15 */ maxIterations?: number; } /** * Session factory metadata in the manifest. */ interface SessionManifestEntry { /** * Export name of the session factory function. * @example "getSessionManager" */ factory: string; /** * Type of session for categorization. * @example "browser", "api", "database" */ type: string; } /** * Preset definition - either an array of gadget names or "*" for all. */ type PresetDefinition = string[] | "*"; /** * llmist package manifest structure. * * This is the shape of the `llmist` field in package.json * for gadget packages. */ interface LLMistPackageManifest { /** * Entry point for all gadgets. * The module should export gadgets or a gadgets array. * @example "./dist/index.js" */ gadgets?: string; /** * Entry point for factory functions. * Should export `createGadgetsByPreset(preset)` and/or `createGadgetsByName(names)`. * @example "./dist/index.js" */ factory?: string; /** * Subagent definitions. * Key is the subagent name as it appears in CLI config. */ subagents?: Record; /** * Preset definitions. * Key is preset name, value is array of gadget names or "*" for all. * @example { "minimal": ["Navigate", "Screenshot"], "all": "*" } */ presets?: Record; /** * Session factory metadata. */ session?: SessionManifestEntry; /** * Skills directory relative to package root. * Contains subdirectories with SKILL.md files. * @example "./skills" */ skills?: string; } /** * Factory function types that packages can export. */ interface GadgetFactoryExports { /** * Create gadgets by preset name. */ createGadgetsByPreset?: (preset: string, config?: unknown) => unknown; /** * Create gadgets by specific names. */ createGadgetsByName?: (names: string[], config?: unknown) => unknown; /** * Create all gadgets with optional config. */ createGadgets?: (config?: unknown) => unknown; } /** * Read and parse the llmist manifest from a package.json object. * * @param packageJson - Parsed package.json object * @returns Manifest or undefined if not present * * @example * ```typescript * import { readFileSync } from "fs"; * * const pkg = JSON.parse(readFileSync("package.json", "utf-8")); * const manifest = parseManifest(pkg); * * if (manifest?.presets?.minimal) { * console.log("Minimal preset:", manifest.presets.minimal); * } * ``` */ declare function parseManifest(packageJson: Record): LLMistPackageManifest | undefined; /** * Check if a manifest has a specific preset. */ declare function hasPreset(manifest: LLMistPackageManifest | undefined, presetName: string): boolean; /** * Get gadget names for a preset. * Returns undefined if preset not found, empty array if preset is invalid. */ declare function getPresetGadgets(manifest: LLMistPackageManifest | undefined, presetName: string): string[] | "*" | undefined; /** * Check if a manifest has subagent definitions. */ declare function hasSubagents(manifest: LLMistPackageManifest | undefined): boolean; /** * Get subagent entry by name. */ declare function getSubagent(manifest: LLMistPackageManifest | undefined, name: string): SubagentManifestEntry | undefined; /** * List all subagent names in a manifest. */ declare function listSubagents(manifest: LLMistPackageManifest | undefined): string[]; /** * List all preset names in a manifest. */ declare function listPresets(manifest: LLMistPackageManifest | undefined): string[]; /** * Base Provider Adapter * * Abstract base class for provider adapters that implements the Template Method pattern. * This class defines the skeleton of the streaming algorithm, leaving provider-specific * details to be implemented by concrete subclasses. * * The streaming workflow consists of four main steps: * 1. Prepare messages (optional transformation for provider-specific requirements) * 2. Build the request payload (provider-specific formatting) * 3. Execute the stream request (call the provider's SDK) * 4. Wrap the stream (transform provider-specific chunks into universal format) */ declare abstract class BaseProviderAdapter implements ProviderAdapter { protected readonly client: unknown; abstract readonly providerId: string; constructor(client: unknown); abstract supports(descriptor: ModelDescriptor): boolean; /** * Optionally provide model specifications for this provider. * This allows the model registry to discover available models and their capabilities. */ getModelSpecs?(): ModelSpec[]; /** * Template method that defines the skeleton of the streaming algorithm. * This orchestrates the four-step process without dictating provider-specific details. */ stream(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec?: ModelSpec): LLMStream; /** * Prepare messages for the request. * Default implementation returns messages unchanged. * Override this to implement provider-specific message transformations * (e.g., Gemini's consecutive message merging, Anthropic's system message extraction). * * @param messages - The input messages * @returns Prepared messages */ protected prepareMessages(messages: LLMMessage[]): LLMMessage[]; /** * Build the provider-specific API request. * This method must be implemented by each concrete provider. * * @param options - The generation options * @param descriptor - The model descriptor * @param spec - Optional model specification with metadata * @param messages - The prepared messages * @returns Provider-specific request object ready for the API call */ protected abstract buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec: ModelSpec | undefined, messages: LLMMessage[]): unknown; /** * Execute the stream request using the provider's SDK. * This method must be implemented by each concrete provider. * * @param payload - The provider-specific payload * @param signal - Optional abort signal for cancelling the request * @returns An async iterable of provider-specific chunks */ protected abstract executeStreamRequest(payload: unknown, signal?: AbortSignal): Promise>; /** * Normalize the provider-specific stream into the universal LLMStream format. * This method must be implemented by each concrete provider. * * @param rawStream - The provider-specific stream * @returns Universal LLMStream */ protected abstract normalizeProviderStream(rawStream: AsyncIterable): LLMStream; } declare class AnthropicMessagesProvider extends BaseProviderAdapter { readonly providerId: "anthropic"; supports(descriptor: ModelDescriptor): boolean; getModelSpecs(): ModelSpec[]; supportsImageGeneration(_modelId: string): boolean; generateImage(): Promise; supportsSpeechGeneration(_modelId: string): boolean; generateSpeech(): Promise; protected buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec: ModelSpec | undefined, messages: LLMMessage[]): MessageCreateParamsStreaming; /** * Convert llmist content to Anthropic's content block format. * Handles text, images (base64 only), and applies cache_control. */ private convertToAnthropicContent; /** * Convert an image content part to Anthropic's image block format. */ private convertImagePart; protected executeStreamRequest(payload: MessageCreateParamsStreaming, signal?: AbortSignal): Promise>; protected normalizeProviderStream(iterable: AsyncIterable): LLMStream; /** * Count tokens in messages using Anthropic's native token counting API. * * This method provides accurate token estimation for Anthropic models by: * - Using the native messages.countTokens() API * - Properly handling system messages and conversation structure * - Transforming messages to Anthropic's expected format * * @param messages - The messages to count tokens for * @param descriptor - Model descriptor containing the model name * @param _spec - Optional model specification (currently unused) * @returns Promise resolving to the estimated input token count * * @throws Never throws - falls back to character-based estimation (4 chars/token) on error * * @example * ```typescript * const count = await provider.countTokens( * [{ role: "user", content: "Hello!" }], * { provider: "anthropic", name: "claude-3-5-sonnet-20241022" } * ); * ``` */ countTokens(messages: LLMMessage[], descriptor: ModelDescriptor, _spec?: ModelSpec): Promise; } declare function createAnthropicProviderFromEnv(): AnthropicMessagesProvider | null; declare function discoverProviderAdapters(): ProviderAdapter[]; /** * Gemini content part - can be text or inline data (images/audio). */ type GeminiPart = { text: string; } | { inlineData: { mimeType: string; data: string; }; }; /** * Gemini content with role and multimodal parts. */ type GeminiContent = { role: string; parts: GeminiPart[]; }; type GeminiChunk = { text?: () => string; candidates?: Array<{ content?: { parts?: Array<{ text?: string; thought?: boolean; thoughtSignature?: string; }>; }; finishReason?: string; }>; usageMetadata?: { promptTokenCount?: number; candidatesTokenCount?: number; totalTokenCount?: number; cachedContentTokenCount?: number; thoughtsTokenCount?: number; }; }; declare class GeminiGenerativeProvider extends BaseProviderAdapter { readonly providerId: "gemini"; private readonly cacheManager; constructor(client: unknown); supports(descriptor: ModelDescriptor): boolean; getModelSpecs(): ModelSpec[]; /** * Override the base stream method to inject cache logic. * * When caching is enabled, we: * 1. Prepare messages as usual * 2. Attempt to get/create a cache for the cacheable prefix * 3. If a cache is available, strip cached contents from the request and add cachedContent ref * 4. Otherwise, proceed normally (graceful degradation) */ stream(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec?: ModelSpec): LLMStream; getImageModelSpecs(): ImageModelSpec[]; supportsImageGeneration(modelId: string): boolean; generateImage(options: ImageGenerationOptions): Promise; getSpeechModelSpecs(): SpeechModelSpec[]; supportsSpeechGeneration(modelId: string): boolean; generateSpeech(options: SpeechGenerationOptions): Promise; getResearchModelSpecs(): ResearchModelSpec[]; supportsResearch(agentId: string): boolean; startResearch(options: ResearchOptions, descriptor: ModelDescriptor, spec?: ResearchModelSpec): AsyncIterable; resumeResearch(ref: ResearchJobRef, signal?: AbortSignal): AsyncIterable; getResearchStatus(ref: ResearchJobRef): Promise; cancelResearch(ref: ResearchJobRef): Promise; protected buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, _spec: ModelSpec | undefined, messages: LLMMessage[]): { model: string; contents: GeminiContent[]; config: Record; }; /** * Build API request from pre-converted Gemini contents. * * When a cache name is provided, the cached prefix is stripped from contents * and the cache reference is added to the config. This tells Gemini to use * the pre-computed KV pairs instead of reprocessing the cached content. */ private buildApiRequestFromContents; protected executeStreamRequest(payload: { model: string; contents: GeminiContent[]; config: Record; }, signal?: AbortSignal): Promise>; /** * Convert LLM messages to Gemini contents format. * * For Gemini, we convert system messages to user+model exchanges instead of * using systemInstruction, because: * 1. systemInstruction doesn't work with countTokens() API * 2. This approach gives perfect token counting accuracy (0% error) * 3. The model receives and follows system instructions identically * * System message: "You are a helpful assistant" * Becomes: * - User: "You are a helpful assistant" * - Model: "Understood." */ private convertMessagesToContents; /** * Merge consecutive messages with the same role (required by Gemini). * Handles multimodal content by converting to Gemini's part format. */ private mergeConsecutiveMessages; /** * Convert llmist content to Gemini's part format. * Handles text, images, and audio (Gemini supports all three). */ private convertToGeminiParts; private buildGenerationConfig; protected normalizeProviderStream(iterable: AsyncIterable): LLMStream; /** * Extract both regular text and thinking text from a chunk. * Gemini marks thinking parts with `thought: true`. */ private extractTextAndThinking; private extractFinishReason; private extractUsage; /** * Count tokens in messages using Gemini's native token counting API. * * This method provides accurate token estimation for Gemini models by: * - Using the SDK's countTokens() method * - Converting system messages to user+model exchanges (same as in generation) * - This gives perfect token counting accuracy (0% error vs actual usage) * * @param messages - The messages to count tokens for * @param descriptor - Model descriptor containing the model name * @param _spec - Optional model specification (currently unused) * @returns Promise resolving to the estimated input token count * * @throws Never throws - falls back to character-based estimation (4 chars/token) on error * * @example * ```typescript * const count = await provider.countTokens( * [{ role: "user", content: "Hello!" }], * { provider: "gemini", name: "gemini-1.5-pro" } * ); * ``` */ countTokens(messages: LLMMessage[], descriptor: ModelDescriptor, _spec?: ModelSpec): Promise; } declare function createGeminiProviderFromEnv(): GeminiGenerativeProvider | null; /** * OpenAI-Compatible Provider Base Class * * Base class for "meta-providers" that expose an OpenAI-compatible API * but route to multiple underlying models/providers. Examples include: * - OpenRouter (openrouter.ai) * - TogetherAI (together.ai) * - Fireworks (fireworks.ai) * - Anyscale (anyscale.com) * * This base class provides: * - OpenAI SDK integration with custom baseURL * - Message conversion to OpenAI format * - Streaming normalization * - Character-based token estimation * - Custom header support for analytics/tracking * - Pluggable error enhancement * * Subclasses implement: * - providerId and providerAlias * - getModelSpecs() for available models * - getCustomHeaders() for provider-specific headers * - enhanceError() for provider-specific error messages * - buildProviderSpecificParams() for provider-specific request options */ /** * Base configuration for OpenAI-compatible providers. * Subclasses can extend this with provider-specific options. */ interface OpenAICompatibleConfig { /** * Optional custom headers to include in all requests. * Useful for analytics/tracking. */ customHeaders?: Record; } /** * Abstract base class for providers using OpenAI-compatible APIs. * * @example * ```typescript * class MyMetaProvider extends OpenAICompatibleProvider { * readonly providerId = "myprovider" as const; * protected readonly providerAlias = "mp"; * * getModelSpecs() { return MY_MODELS; } * * protected getCustomHeaders(): Record { * return { "X-My-Header": this.config.myValue }; * } * * protected enhanceError(error: unknown): Error { * // Provider-specific error handling * } * } * ``` */ declare abstract class OpenAICompatibleProvider extends BaseProviderAdapter { abstract readonly providerId: string; /** * Short alias for the provider (e.g., "or" for openrouter, "hf" for huggingface). * If not set, only the full providerId is accepted. */ protected readonly providerAlias?: string; protected readonly config: TConfig; constructor(client: OpenAI, config: TConfig); /** * Check if this provider supports the given model descriptor. * Accepts both the full providerId and the short alias. */ supports(descriptor: ModelDescriptor): boolean; /** * Return the model specs for this provider. * Must be implemented by subclasses. */ abstract getModelSpecs(): ModelSpec[]; /** * Get custom headers to include in requests. * Override in subclasses for provider-specific headers. */ protected getCustomHeaders(): Record; /** * Enhance error messages with provider-specific guidance. * Override in subclasses for better error messages. */ protected enhanceError(error: unknown): Error; /** * Build provider-specific request parameters. * Override in subclasses to add custom parameters from `extra`. * * @param extra - The extra options from LLMGenerationOptions * @returns Object with provider-specific params to merge into the request */ protected buildProviderSpecificParams(_extra: Record | undefined): Record; protected buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, _spec: ModelSpec | undefined, messages: LLMMessage[]): Parameters[0]; /** * Check if a key should be filtered from passthrough. * Override in subclasses to filter provider-specific keys from extra. */ protected isProviderSpecificKey(_key: string): boolean; /** * Convert an LLMMessage to OpenAI's ChatCompletionMessageParam format. */ protected convertMessage(message: LLMMessage): ChatCompletionMessageParam; /** * Convert llmist content to OpenAI's content format. */ protected convertContent(content: MessageContent): string | ChatCompletionContentPart[]; /** * Convert an image content part to OpenAI's image_url format. */ protected convertImagePart(part: ImageContentPart): ChatCompletionContentPart; protected executeStreamRequest(payload: Parameters[0], signal?: AbortSignal): Promise>; protected normalizeProviderStream(iterable: AsyncIterable): LLMStream; /** * Count tokens using tiktoken o200k_base encoding. * * While o200k_base isn't model-exact for non-OpenAI models routed through * meta-providers like OpenRouter, BPE tokenizers with 200K vocab produce * counts within 10-20% of true values — far better than the character-based * fallback which can be off by 250% for JSON/code-heavy content. * * Falls back to character-based estimation if tiktoken fails. */ countTokens(messages: LLMMessage[], descriptor: ModelDescriptor, _spec?: ModelSpec): Promise; } /** * Hugging Face Provider Adapter * * Supports both serverless inference (router.huggingface.co) and * dedicated inference endpoints. Uses OpenAI SDK for API compatibility * since HF APIs follow OpenAI's chat completions format. * * Environment variables: * - HF_TOKEN (primary) or HUGGING_FACE_API_KEY (fallback) * - HF_ENDPOINT_URL (optional) - for dedicated endpoints * * Provider selection syntax (serverless only): * - model:fastest - route to fastest available provider * - model:cheapest - route to cheapest provider * - model:sambanova, model:groq, etc. - route to specific provider */ /** * Configuration for HuggingFace provider. */ interface HuggingFaceConfig extends OpenAICompatibleConfig { /** * Endpoint type for HuggingFace inference. * - 'serverless': Use HF serverless inference (default) * - 'dedicated': Use dedicated inference endpoint */ endpointType?: "serverless" | "dedicated"; } declare class HuggingFaceProvider extends OpenAICompatibleProvider { readonly providerId: "huggingface"; protected readonly providerAlias = "hf"; constructor(client: OpenAI, config?: HuggingFaceConfig); getModelSpecs(): ModelSpec[]; /** * Override buildApiRequest to inject DeepSeek-specific thinking parameters. * DeepSeek models use `extra_body: { thinking: { type: "enabled" } }` for reasoning. */ protected buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec: ModelSpec | undefined, messages: LLMMessage[]): Parameters[0]; /** * Enhance error messages with HuggingFace-specific guidance. */ protected enhanceError(error: unknown): Error; } /** * Create a Hugging Face provider from environment variables. * * Environment variables: * - HF_TOKEN (primary) or HUGGING_FACE_API_KEY (fallback) - Required for authentication * - HF_ENDPOINT_URL (optional) - Custom endpoint URL for dedicated deployments * * @returns HuggingFaceProvider instance or null if no API key is found * * @example * ```bash * # Serverless inference (default) * export HF_TOKEN="hf_..." * * # Dedicated endpoint * export HF_TOKEN="hf_..." * export HF_ENDPOINT_URL="https://xxx.endpoints.huggingface.cloud" * ``` */ declare function createHuggingFaceProviderFromEnv(): HuggingFaceProvider | null; declare class OpenAIChatProvider extends BaseProviderAdapter { readonly providerId: "openai"; supports(descriptor: ModelDescriptor): boolean; getModelSpecs(): ModelSpec[]; getImageModelSpecs(): ImageModelSpec[]; supportsImageGeneration(modelId: string): boolean; generateImage(options: ImageGenerationOptions): Promise; getSpeechModelSpecs(): SpeechModelSpec[]; supportsSpeechGeneration(modelId: string): boolean; generateSpeech(options: SpeechGenerationOptions): Promise; getResearchModelSpecs(): ResearchModelSpec[]; supportsResearch(modelId: string): boolean; startResearch(options: ResearchOptions, descriptor: ModelDescriptor, spec?: ResearchModelSpec): AsyncIterable; resumeResearch(ref: ResearchJobRef, signal?: AbortSignal): AsyncIterable; getResearchStatus(ref: ResearchJobRef): Promise; cancelResearch(ref: ResearchJobRef): Promise; protected buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec: ModelSpec | undefined, messages: LLMMessage[]): Parameters[0]; /** * Convert an LLMMessage to OpenAI's ChatCompletionMessageParam. * Handles role-specific content type requirements: * - system/assistant: string content only * - user: string or multimodal array content */ private convertToOpenAIMessage; /** * Convert llmist content to OpenAI's content format. * Optimizes by returning string for text-only content, array for multimodal. */ private convertToOpenAIContent; /** * Convert an image content part to OpenAI's image_url format. * Supports both URLs and base64 data URLs. */ private convertImagePart; protected executeStreamRequest(payload: Parameters[0], signal?: AbortSignal): Promise>; protected normalizeProviderStream(iterable: AsyncIterable): LLMStream; /** * Count tokens in messages using OpenAI's tiktoken library. * * This method provides accurate token estimation for OpenAI models by: * - Using the model-specific tokenizer encoding * - Accounting for message formatting overhead * - Falling back to gpt-4o encoding for unknown models * * @param messages - The messages to count tokens for * @param descriptor - Model descriptor containing the model name * @param _spec - Optional model specification (currently unused) * @returns Promise resolving to the estimated input token count * * @throws Never throws - falls back to character-based estimation (4 chars/token) on error * * @example * ```typescript * const count = await provider.countTokens( * [{ role: "user", content: "Hello!" }], * { provider: "openai", name: "gpt-4" } * ); * ``` */ countTokens(messages: LLMMessage[], descriptor: ModelDescriptor, _spec?: ModelSpec): Promise; } declare function createOpenAIProviderFromEnv(): OpenAIChatProvider | null; /** * OpenRouter Provider Adapter * * Provides access to 400+ AI models from dozens of providers through * OpenRouter's unified API gateway. * * Environment variables: * - OPENROUTER_API_KEY (required) - Your OpenRouter API key * - OPENROUTER_SITE_URL (optional) - Your app URL for analytics * - OPENROUTER_APP_NAME (optional) - Your app name for analytics * * Model naming format: provider/model-name * Examples: * - anthropic/claude-sonnet-4-5 * - openai/gpt-4o * - meta-llama/llama-3.3-70b-instruct * * @see https://openrouter.ai/docs */ /** * Configuration for OpenRouter provider. */ interface OpenRouterConfig extends OpenAICompatibleConfig { /** * Your app's URL for OpenRouter analytics dashboard. * Maps to HTTP-Referer header. */ siteUrl?: string; /** * Your app's name shown in OpenRouter analytics. * Maps to X-Title header. */ appName?: string; } /** * OpenRouter-specific routing options for model selection. * Pass these via the `extra` parameter in generation options. * * @example * ```typescript * agent.withExtra({ * routing: { * models: ["anthropic/claude-sonnet-4-5", "openai/gpt-4o"], * route: "cheapest", * }, * }) * ``` */ interface OpenRouterRouting { /** * Ordered list of models to try as fallbacks. * If the first model fails or is unavailable, OpenRouter tries the next. */ models?: string[]; /** * Specific provider to route to for models available from multiple providers. */ provider?: string; /** * Ordered list of providers to prefer. */ order?: string[]; /** * Routing preference for model selection. * - 'fastest': Route to the fastest available provider * - 'cheapest': Route to the cheapest provider * - 'quality': Route to the highest quality provider (default) */ route?: "fastest" | "cheapest" | "quality"; } declare class OpenRouterProvider extends OpenAICompatibleProvider { readonly providerId: "openrouter"; protected readonly providerAlias = "or"; constructor(client: OpenAI, config?: OpenRouterConfig); getModelSpecs(): ModelSpec[]; getResearchModelSpecs(): ResearchModelSpec[]; supportsResearch(modelId: string): boolean; startResearch(options: ResearchOptions, descriptor: ModelDescriptor, _spec?: ResearchModelSpec): AsyncIterable; /** * Override buildApiRequest to inject reasoning parameters and cache_control breakpoints. * OpenRouter normalizes reasoning into the standard OpenAI format, * and supports cache_control on message content blocks for both * Anthropic Claude and Google Gemini models. */ protected buildApiRequest(options: LLMGenerationOptions, descriptor: ModelDescriptor, spec: ModelSpec | undefined, messages: LLMMessage[]): Parameters[0]; /** Minimal shape for messages in the already-built OpenAI-compatible request. */ private static readonly CACHE_CONTROL; /** * Add cache_control breakpoints to the last system message and last user message. * This enables OpenRouter's prompt caching for supported providers (Anthropic, Gemini). * * Operates on the already-built request object. We cast through `unknown` because * OpenAI's `ChatCompletionMessageParam` union is too narrow to assign content arrays * with the non-standard `cache_control` property. */ private injectCacheBreakpoints; /** * Return a new content array with cache_control on the last block. * String content is promoted to a single-element text block array. */ private withCacheControl; /** * Get custom headers for OpenRouter analytics. */ protected getCustomHeaders(): Record; /** * Build OpenRouter-specific request parameters from `extra.routing`. */ protected buildProviderSpecificParams(extra: Record | undefined): Record; /** * Filter out the 'routing' key from extra passthrough. */ protected isProviderSpecificKey(key: string): boolean; /** * Enhance error messages with OpenRouter-specific guidance. */ protected enhanceError(error: unknown): Error; /** * Get speech model specifications for OpenRouter. */ getSpeechModelSpecs(): SpeechModelSpec[]; /** * Check if this provider supports speech generation for a given model. * Handles both prefixed (openrouter:openai/gpt-audio-mini) and unprefixed model IDs. */ supportsSpeechGeneration(modelId: string): boolean; /** * Generate speech audio from text using OpenRouter's audio-capable models. * * OpenRouter TTS works via chat completions with audio modality, not a * dedicated TTS endpoint. The model receives a prompt asking it to say * the text, and returns audio data via streaming. * * @param options - Speech generation options * @returns Promise resolving to the generation result with audio and cost * @throws Error if model is unknown, voice/format are invalid, or no audio is returned */ generateSpeech(options: SpeechGenerationOptions): Promise; } /** * Create an OpenRouter provider from environment variables. * * Environment variables: * - OPENROUTER_API_KEY (required) - Your OpenRouter API key * - OPENROUTER_SITE_URL (optional) - Your app URL for analytics * - OPENROUTER_APP_NAME (optional) - Your app name for analytics * * @returns OpenRouterProvider instance or null if no API key is found * * @example * ```bash * # Basic usage * export OPENROUTER_API_KEY="sk-or-..." * * # With analytics tracking * export OPENROUTER_API_KEY="sk-or-..." * export OPENROUTER_SITE_URL="https://myapp.com" * export OPENROUTER_APP_NAME="MyApp" * ``` */ declare function createOpenRouterProviderFromEnv(): OpenRouterProvider | null; /** * Session management interface and base class for gadget packages. * * Provides a standardized way to manage sessions (browser instances, API clients, etc.) * across gadgets. This enables: * - Consistent session lifecycle management * - Per-agent session isolation * - Automatic cleanup * * @module session/manager * * @example * ```typescript * import { BaseSessionManager, ISessionManager } from "llmist"; * * // Extend for browser sessions * class BrowserSessionManager extends BaseSessionManager { * async createSession(config?: BrowserConfig): Promise { * const browser = await launchBrowser(config); * const page = await browser.newPage(); * const id = this.generateId("p"); * this.sessions.set(id, page); * return id; * } * * async closeSession(id: string): Promise { * const page = this.sessions.get(id); * if (page) { * await page.close(); * this.sessions.delete(id); * } * } * } * ``` */ /** * Interface for session managers. * * Session managers track and manage external resources (browser pages, API connections, etc.) * that need to be shared across multiple gadgets and properly cleaned up. * * @typeParam TSession - Type of session object (e.g., Page, APIClient) * @typeParam TConfig - Configuration type for creating sessions */ interface ISessionManager { /** * Create a new session. * * @param config - Optional configuration for the session * @returns Promise resolving to the session ID */ createSession(config?: TConfig): Promise; /** * Get a session by ID. * * @param id - Session ID * @returns Session object or undefined if not found */ getSession(id: string): TSession | undefined; /** * Get a session by ID, throwing if not found. * * @param id - Session ID * @returns Session object * @throws Error if session not found */ requireSession(id: string): TSession; /** * Close and remove a session. * * @param id - Session ID to close */ closeSession(id: string): Promise; /** * Close all sessions. */ closeAll(): Promise; /** * List all active session IDs. * * @returns Array of session IDs */ listSessions(): string[]; /** * Check if a session exists. * * @param id - Session ID * @returns True if session exists */ hasSession(id: string): boolean; } /** * Base implementation of session manager with common functionality. * * Extend this class to create domain-specific session managers. * You only need to implement `createSession` and `closeSession`. * * @typeParam TSession - Type of session object * @typeParam TConfig - Configuration type for creating sessions * * @example * ```typescript * class APIClientManager extends BaseSessionManager { * async createSession(config?: APIConfig): Promise { * const client = new APIClient(config); * const id = this.generateId("api"); * this.sessions.set(id, client); * return id; * } * * async closeSession(id: string): Promise { * const client = this.sessions.get(id); * if (client) { * await client.disconnect(); * this.sessions.delete(id); * } * } * } * ``` */ declare abstract class BaseSessionManager implements ISessionManager { /** Map of session ID to session object */ protected sessions: Map; /** Counter for generating unique session IDs */ protected idCounter: number; /** * Generate a unique session ID with the given prefix. * * @param prefix - Prefix for the ID (e.g., "p" for pages, "b" for browsers) * @returns Unique ID like "p1", "p2", etc. */ protected generateId(prefix: string): string; /** * Create a new session. * Must be implemented by subclasses. */ abstract createSession(config?: TConfig): Promise; /** * Close and remove a session. * Must be implemented by subclasses. */ abstract closeSession(id: string): Promise; /** * Get a session by ID. */ getSession(id: string): TSession | undefined; /** * Get a session by ID, throwing if not found. */ requireSession(id: string): TSession; /** * List all active session IDs. */ listSessions(): string[]; /** * Check if a session exists. */ hasSession(id: string): boolean; /** * Close all sessions. * Closes sessions in reverse order (most recent first). */ closeAll(): Promise; } /** * Simple in-memory session manager for testing or lightweight use cases. * * Sessions are just stored objects with no special cleanup logic. * * @example * ```typescript * const manager = new SimpleSessionManager(); * const id = await manager.createSession({ value: 42 }); * const data = manager.requireSession(id); // { value: 42 } * await manager.closeSession(id); * ``` */ declare class SimpleSessionManager extends BaseSessionManager { /** * Create a session by storing the provided data. */ createSession(data?: TSession): Promise; /** * Close a session by removing it from the map. */ closeSession(id: string): Promise; /** * Set session data directly. */ setSession(id: string, data: TSession): void; } /** * Skill activation logic. * * Handles $ARGUMENTS substitution and !`command` shell preprocessing * to resolve skill instructions before injection into agent context. * * @module skills/activation */ /** * Substitute $ARGUMENTS and positional $0, $1, etc. in skill instructions. */ declare function substituteArguments(instructions: string, args?: string): string; /** * Substitute ${VARIABLE} environment-style variables in skill instructions. * * Supported variables: * - ${CLAUDE_SKILL_DIR} / ${SKILL_DIR} - directory containing SKILL.md * - ${CLAUDE_SESSION_ID} / ${SESSION_ID} - current session ID (if provided) */ declare function substituteVariables(instructions: string, variables: Record): string; /** * Full activation pipeline: variables -> arguments -> shell preprocessing. */ declare function resolveInstructions(instructions: string, options?: { arguments?: string; variables?: Record; cwd?: string; shell?: "bash" | "powershell"; shellTimeoutMs?: number; enableShellPreprocessing?: boolean; }): string; /** * LoadSkill meta-gadget — bridges the skill system into the gadget execution pipeline. * * When skills are registered with an agent, this gadget is auto-created and added * to the gadget registry. The LLM invokes it with an array of skill names; each * skill's resolved instructions are composed into a single multi-section result. * * This approach requires zero changes to the stream processor or agent loop. * * @module skills/load-skill-gadget */ /** Name for the auto-generated LoadSkill gadget. */ declare const LOAD_SKILL_GADGET_NAME = "LoadSkill"; /** * Create the LoadSkill meta-gadget from a skill registry. * * The gadget's tool description includes a summary of all available skills, so * the LLM knows what skills exist and when to load them. Setting * `iterationBarrier: true` and `stickyResult: true` are the two declarative * flags every LoadSkill should carry: * * - `iterationBarrier`: tells the consuming agent loop to skip every sibling * tool call in the same iteration's batch. The next LLM iteration sees * only the loaded skill bodies and re-plans from there. * - `stickyResult`: tells the compaction layer to preserve the result past * truncation, so the agent doesn't re-load the same skill ten turns later. */ declare function createLoadSkillGadget(registry: SkillRegistry): AbstractGadget; /** * Filesystem-based skill discovery and loading. * * Scans directories for subdirectories containing SKILL.md files. * Supports standard discovery locations and custom directories. * * @module skills/loader */ /** * Load skills from a directory. * * Recursively scans for subdirectories containing a SKILL.md file. * Each such directory is treated as a single skill. * * @param dir - Directory to scan * @param source - Origin for all discovered skills */ declare function loadSkillsFromDirectory(dir: string, source: SkillSource, onWarning?: (msg: string) => void): Skill[]; /** * Discover skills from standard locations. * * Discovery order (later sources overwrite earlier on name collision): * 1. User skills: ~/.llmist/skills/ * 2. Project skills: /.llmist/skills/ * 3. Additional directories (explicit) */ declare function discoverSkills(options?: { projectDir?: string; userDir?: string; additionalDirs?: string[]; }): SkillRegistry; /** * SKILL.md parser for the Agent Skills open standard. * * Parses YAML frontmatter (between --- markers) and markdown body. * Scans skill directories for Tier 3 resources (scripts/, references/, assets/). * * @module skills/parser */ /** * Parse YAML frontmatter from SKILL.md content. * * Extracts the YAML block between the first pair of `---` markers * and the remaining markdown body. */ declare function parseFrontmatter(content: string): { frontmatter: Record; body: string; }; /** * Convert raw frontmatter to validated SkillMetadata. * * Maps kebab-case YAML keys to camelCase TypeScript properties. * Falls back to directory name for missing name field. */ declare function parseMetadata(frontmatter: Record, fallbackName?: string): SkillMetadata; /** * Scan a skill directory for Tier 3 resource files. */ declare function scanResources(skillDir: string): SkillResource[]; /** * Parse a SKILL.md file from disk. * * This performs Tier 1 parsing (metadata) and optionally Tier 2 (instructions). * Resources are discovered but not loaded. * * @param skillMdPath - Absolute path to SKILL.md * @param source - Where this skill was discovered from * @param loadInstructions - Whether to load Tier 2 body (default: false for lazy loading) */ declare function parseSkillFile(skillMdPath: string, source: SkillSource, loadInstructions?: boolean): ParsedSkill; /** * Parse SKILL.md content from a string. * * Useful for testing without filesystem access. */ declare function parseSkillContent(content: string, sourcePath: string, source: SkillSource, loadInstructions?: boolean): ParsedSkill; /** * Validate skill metadata. * Returns an array of validation issues (empty if valid). */ declare function validateMetadata(metadata: SkillMetadata): string[]; /** * Config resolution utility for subagent gadgets. * * Simplifies the common pattern of resolving configuration from multiple sources: * 1. Runtime params (explicit gadget call parameters) * 2. Subagent config (from CLI [subagents.Name] sections) * 3. Parent agent config (model inheritance) * 4. Package defaults * * @module utils/config-resolver */ /** * Options for resolving a single config value. */ interface ResolveValueOptions { /** Runtime parameter value (highest priority) */ runtime?: T; /** Subagent config key to check */ subagentKey?: string; /** Parent config key to check (for inheritance) - "model" or "temperature" */ parentKey?: "model" | "temperature"; /** Default value (lowest priority) */ defaultValue: T; /** Whether "inherit" string means use parent value */ handleInherit?: boolean; } /** * Resolve a single configuration value through the priority chain. * * Priority (highest to lowest): * 1. Runtime parameter (if provided and not undefined) * 2. Subagent config (from ctx.subagentConfig[gadgetName][key]) * 3. Parent config (from ctx.agentConfig[key], if parentKey specified) * 4. Default value * * Special handling for "inherit" string: * - If handleInherit is true and value is "inherit", falls through to parent/default * * @param ctx - ExecutionContext from gadget execution * @param gadgetName - Name of the subagent gadget (e.g., "BrowseWeb") * @param options - Resolution options * @returns Resolved value * * @example * ```typescript * const model = resolveValue(ctx, "BrowseWeb", { * runtime: params.model, * subagentKey: "model", * parentKey: "model", * defaultValue: "sonnet", * handleInherit: true, * }); * ``` */ declare function resolveValue(ctx: ExecutionContext, gadgetName: string, options: ResolveValueOptions): T; /** * Bulk configuration resolution for subagent gadgets. * * Takes a map of config keys to their resolution options and returns * a fully resolved configuration object. * * @param ctx - ExecutionContext from gadget execution * @param gadgetName - Name of the subagent gadget (e.g., "BrowseWeb") * @param config - Map of config keys to resolution options * @returns Fully resolved configuration object * * @example * ```typescript * // Before: 27 lines of manual fallback logic * const subagentConfig = ctx.subagentConfig?.Dhalsim ?? {}; * const parentModel = ctx.agentConfig?.model; * const model = params.model ?? subagentConfig.model ?? parentModel ?? "sonnet"; * const maxIterations = params.maxIterations ?? subagentConfig.maxIterations ?? 15; * const headless = params.headless ?? subagentConfig.headless ?? true; * * // After: One function call * const { model, maxIterations, headless } = resolveConfig(ctx, "BrowseWeb", { * model: { runtime: params.model, subagentKey: "model", parentKey: "model", defaultValue: "sonnet", handleInherit: true }, * maxIterations: { runtime: params.maxIterations, subagentKey: "maxIterations", defaultValue: 15 }, * headless: { runtime: params.headless, subagentKey: "headless", defaultValue: true }, * }); * ``` */ declare function resolveConfig>(ctx: ExecutionContext, gadgetName: string, config: { [K in keyof T]: ResolveValueOptions; }): T; /** * Convenience function for resolving subagent model with "inherit" support. * * This is the most common resolution pattern for subagent gadgets: * - Use runtime model if provided * - Check subagent config for model override * - Inherit parent model if configured * - Fall back to default * * @param ctx - ExecutionContext from gadget execution * @param gadgetName - Name of the subagent gadget * @param runtimeModel - Model from gadget parameters * @param defaultModel - Default model if nothing else specified * @returns Resolved model string * * @example * ```typescript * const model = resolveSubagentModel(ctx, "BrowseWeb", params.model, "sonnet"); * ``` */ declare function resolveSubagentModel(ctx: ExecutionContext, gadgetName: string, runtimeModel: string | undefined, defaultModel: string): string; /** * Convenience function for resolving subagent timeout. * * Resolves timeout from the configuration priority chain: * - Use runtime timeout if provided * - Check subagent config for timeout override * - Fall back to default * * @param ctx - ExecutionContext from gadget execution * @param gadgetName - Name of the subagent gadget * @param runtimeTimeout - Timeout from gadget parameters * @param defaultTimeout - Default timeout if nothing else specified * @returns Resolved timeout in milliseconds * * @example * ```typescript * const timeoutMs = resolveSubagentTimeout(ctx, "BrowseWeb", params.timeoutMs, 300000); * ``` */ declare function resolveSubagentTimeout(ctx: ExecutionContext, gadgetName: string, runtimeTimeout: number | undefined, defaultTimeout: number): number; /** * Formatting utilities for gadget authors and CLI output. * * Provides common formatting functions for: * - Text truncation * - Byte size formatting * - Date formatting * - Duration formatting * * @module utils/format * * @example * ```typescript * import { format } from "llmist"; * * format.truncate("Long text...", 10); // "Long tex..." * format.bytes(1536); // "1.5 KB" * format.date("2024-01-15T10:30:00Z"); // "Jan 15, 2024 10:30 AM" * format.duration(125000); // "2m 5s" * ``` */ /** * Truncate text to a maximum length, adding suffix if truncated. * * @param text - Text to truncate * @param maxLength - Maximum length including suffix * @param suffix - Suffix to append when truncated (default: "...") * @returns Truncated text * * @example * ```typescript * truncate("Hello, World!", 10); // "Hello, ..." * truncate("Short", 10); // "Short" * truncate("Custom", 6, "…"); // "Custo…" * ``` */ declare function truncate(text: string, maxLength: number, suffix?: string): string; /** * Format bytes as human-readable string. * * @param bytes - Number of bytes * @param decimals - Number of decimal places (default: 1) * @returns Formatted string (e.g., "1.5 KB", "2.3 MB") * * @example * ```typescript * formatBytes(0); // "0 B" * formatBytes(1024); // "1 KB" * formatBytes(1536); // "1.5 KB" * formatBytes(1048576); // "1 MB" * ``` */ declare function formatBytes(bytes: number, decimals?: number): string; /** * Format ISO date string as human-readable date. * * @param isoDate - ISO date string (e.g., "2024-01-15T10:30:00Z") * @param options - Intl.DateTimeFormat options * @returns Formatted date string * * @example * ```typescript * formatDate("2024-01-15T10:30:00Z"); * // "Jan 15, 2024, 10:30 AM" (in local timezone) * * formatDate("2024-01-15T10:30:00Z", { dateStyle: "short" }); * // "1/15/24" * ``` */ declare function formatDate(isoDate: string, options?: Intl.DateTimeFormatOptions): string; /** * Format duration in milliseconds as human-readable string. * * @param ms - Duration in milliseconds * @param options - Formatting options * @returns Formatted duration string * * @example * ```typescript * formatDuration(500); // "500ms" * formatDuration(1500); // "1.5s" * formatDuration(65000); // "1m 5s" * formatDuration(3725000); // "1h 2m 5s" * ``` */ declare function formatDuration(ms: number, options?: { compact?: boolean; }): string; /** * Format namespace object for convenient access. * * @example * ```typescript * import { format } from "llmist"; * * format.truncate("text", 5); * format.bytes(1024); * format.date("2024-01-15"); * format.duration(5000); * ``` */ declare const format: { readonly truncate: typeof truncate; readonly bytes: typeof formatBytes; readonly date: typeof formatDate; readonly duration: typeof formatDuration; }; /** * Timing utilities for gadget authors. * * Provides common timing functions for: * - Random delays (human-like timing) * - Timeout handling * - Retry logic with backoff * * @module utils/timing * * @example * ```typescript * import { timing } from "llmist"; * * // Human-like delays for browser automation * await timing.humanDelay(50, 150); * * // Add timeout to async operations * const result = await timing.withTimeout( * () => fetchData(), * 5000, * signal * ); * * // Retry with exponential backoff * const data = await timing.withRetry( * () => unreliableApi(), * { maxRetries: 3, delay: 1000, backoff: "exponential" } * ); * ``` */ /** * Generate a random delay within a range. * * @param min - Minimum delay in milliseconds * @param max - Maximum delay in milliseconds * @returns Random integer between min and max (inclusive) * * @example * ```typescript * const delay = randomDelay(50, 150); // e.g., 87 * ``` */ declare function randomDelay(min: number, max: number): number; /** * Sleep for a random duration (for human-like timing). * * Useful for browser automation to appear more human-like. * * @param min - Minimum delay in milliseconds (default: 50) * @param max - Maximum delay in milliseconds (default: 150) * @returns Promise that resolves after the random delay * * @example * ```typescript * // Default human-like delay (50-150ms) * await humanDelay(); * * // Custom range for slower actions * await humanDelay(100, 300); * ``` */ declare function humanDelay(min?: number, max?: number): Promise; /** * Execute an async function with a timeout. * * @param fn - Async function to execute * @param timeoutMs - Timeout in milliseconds * @param signal - Optional AbortSignal for early cancellation * @returns Promise that resolves with the function result or rejects on timeout * @throws Error with "Operation timed out" message if timeout is exceeded * * @example * ```typescript * const result = await withTimeout( * () => fetch("https://api.example.com/data"), * 5000 * ); * * // With abort signal * const controller = new AbortController(); * const result = await withTimeout( * () => longRunningTask(), * 30000, * controller.signal * ); * ``` */ declare function withTimeout(fn: () => Promise, timeoutMs: number, signal?: AbortSignal): Promise; /** * Options for retry logic. */ interface RetryOptions { /** Maximum number of retry attempts (default: 3) */ maxRetries?: number; /** Initial delay between retries in milliseconds (default: 1000) */ delay?: number; /** Backoff strategy: "linear" adds delay, "exponential" doubles it (default: "exponential") */ backoff?: "linear" | "exponential"; /** Maximum delay cap in milliseconds (default: 30000) */ maxDelay?: number; /** Optional function to determine if error is retryable (default: all errors) */ shouldRetry?: (error: unknown, attempt: number) => boolean; /** Optional callback on each retry attempt */ onRetry?: (error: unknown, attempt: number, delay: number) => void; } /** * Execute an async function with retry logic. * * @param fn - Async function to execute * @param options - Retry options * @returns Promise that resolves with the function result or rejects after all retries exhausted * * @example * ```typescript * // Basic retry with defaults (3 retries, exponential backoff) * const result = await withRetry(() => unreliableApi()); * * // Custom retry configuration * const result = await withRetry( * () => fetchWithErrors(), * { * maxRetries: 5, * delay: 500, * backoff: "exponential", * shouldRetry: (error) => error.status === 429 || error.status >= 500, * onRetry: (error, attempt, delay) => { * console.log(`Retry ${attempt} after ${delay}ms`); * } * } * ); * ``` */ declare function withRetry(fn: () => Promise, options?: RetryOptions): Promise; /** * Timing namespace object for convenient access. * * @example * ```typescript * import { timing } from "llmist"; * * await timing.humanDelay(); * const result = await timing.withTimeout(() => fetch(url), 5000); * const data = await timing.withRetry(() => api.call(), { maxRetries: 3 }); * ``` */ declare const timing: { readonly randomDelay: typeof randomDelay; readonly humanDelay: typeof humanDelay; readonly withTimeout: typeof withTimeout; readonly withRetry: typeof withRetry; }; /** * Get host llmist exports from execution context. * * External gadgets MUST use this instead of importing classes directly from 'llmist' * to ensure they use the same version as the host CLI, enabling proper tree sharing * and avoiding the "dual-package problem". * * @param ctx - The execution context passed to gadget.execute() * @returns The host's llmist exports (AgentBuilder, Gadget, etc.) * @throws Error if ctx or ctx.hostExports is undefined * * @example * ```typescript * import { getHostExports, Gadget, z } from 'llmist'; * import type { ExecutionContext } from 'llmist'; * * class BrowseWeb extends Gadget({ * name: 'BrowseWeb', * description: 'Browse a website autonomously', * schema: z.object({ task: z.string(), url: z.string() }), * }) { * async execute(params: this['params'], ctx?: ExecutionContext) { * // Get host's AgentBuilder to ensure tree sharing works correctly * const { AgentBuilder } = getHostExports(ctx!); * * const agent = new AgentBuilder() * .withParentContext(ctx!) * .withGadgets(Navigate, Click, Screenshot) * .ask(params.task); * * for await (const event of agent.run()) { * // Events flow through host's shared tree * } * } * } * ``` */ declare function getHostExports(ctx: ExecutionContext): HostExports; export { AbortException, AbstractGadget, type AddGadgetParams, type AddLLMCallParams, type AfterGadgetExecutionAction, type AfterGadgetExecutionControllerContext, type AfterLLMCallAction, type AfterLLMCallControllerContext, type AfterLLMErrorAction, Agent, AgentBuilder, type AgentHooks, type AgentOptions, AnthropicMessagesProvider, type AudioContentPart, type AudioMimeType, type AudioSource, type BaseExecutionEvent, BaseSessionManager, type BeforeGadgetExecutionAction, type BeforeLLMCallAction, type BeforeSkillActivationAction, BudgetPricingUnavailableError, type CachingConfig, type CachingScope, type ChunkInterceptorContext, type CompactionConfig, type CompactionContext, type CompactionEvent, CompactionManager, type CompactionResult, type CompactionStats, type CompactionStrategy, type CompleteGadgetParams, type CompleteLLMCallParams, type ContentPart, type Controllers, ConversationManager, type CostEstimate, type CostReportingLLMist, type CreateGadgetConfig, type CreateMcpServerOptions, DEFAULT_COMPACTION_CONFIG, DEFAULT_HINTS, DEFAULT_MCP_COMMAND_ALLOWLIST, DEFAULT_PROMPTS, DEFAULT_RATE_LIMIT_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_SUMMARIZATION_PROMPT, EmptyCompletionError, type EventHandlers, type ExecutionContext, type ExecutionEvent, type ExecutionEventType, type ExecutionNode, type ExecutionNodeType, ExecutionTree, FALLBACK_CHARS_PER_TOKEN, type FileLoggingOptions, type FileLoggingState, type FileWrittenInfo, type FormatLLMErrorContext, GADGET_ARG_PREFIX, GADGET_END_PREFIX, GADGET_START_PREFIX, Gadget, type GadgetArgsPartialEvent, type GadgetCallEvent, GadgetCallParser, type GadgetClass, type GadgetCompleteEvent, type GadgetConfig, type GadgetErrorEvent, type GadgetEvent, type GadgetExample, type GadgetExecuteResult, type GadgetExecuteResultWithMedia, type GadgetExecuteReturn, type GadgetExecutionControllerContext, type GadgetExecutionMode, type GadgetExecutionResult, GadgetExecutor, type GadgetExecutorOptions, type GadgetFactoryExports, type GadgetMediaOutput, type GadgetNode, type GadgetOrClass, GadgetOutputStore, type GadgetParameterInterceptorContext, GadgetRegistry, type GadgetResultInterceptorContext, type GadgetSkippedEvent, type GadgetStartEvent, type GadgetState, GeminiGenerativeProvider, type HintContext, type HintTemplate, type HintsConfig, type HistoryMessage, HookPresets, type HostExports, type HttpMcpServerSpec, HuggingFaceProvider, type HumanInputRequiredEvent, HumanInputRequiredException, HybridStrategy, type IConversationManager, type ISessionManager, type ImageBase64Source, type ImageContentPart, type ImageGenerationOptions, type ImageGenerationResult, type ImageMimeType, type ImageModelSpec, type ImageSource, type ImageUrlSource, type Interceptors, type IterationHintOptions, type JSONSchemaLike, JsonSchemaConversionError, type LLMCallCompleteEvent, type LLMCallControllerContext, type LLMCallErrorEvent, type LLMCallNode, type LLMCallStartEvent, type LLMCallStreamEvent, type LLMErrorControllerContext, type LLMEvent, type LLMGenerationOptions, type LLMMessage, LLMMessageBuilder, type LLMResponseEndEvent, type LLMStream, type LLMStreamChunk, LLMist, type LLMistOptions, type LLMistPackageManifest, LOAD_SKILL_GADGET_NAME, type LoggerOptions, type LoggingOptions, MODEL_ALIASES, McpClient, type McpClientOptions, McpConnectError, type McpContentBlock, McpError, McpLifecycle, type McpServerCapabilities, type McpServerHandle, type McpServerSpec, type McpToolAdapterOptions, McpToolCallError, type McpToolDescriptor, type McpToolResult, McpUntrustedCommandError, type MediaKind, type MediaMetadata, MediaStore, type MessageContent, type MessageInterceptorContext, type MessageRole, type MessageTurn, type ModelDescriptor, type ModelFeatures, ModelIdentifierParser, type ModelLimits, type ModelPricing, ModelRegistry, type ModelSpec, type NodeId, type ObserveChunkContext, type ObserveCompactionContext, type ObserveGadgetArgsPartialContext, type ObserveGadgetCompleteContext, type ObserveGadgetStartContext, type ObserveLLMCallContext, type ObserveLLMCompleteContext, type ObserveLLMErrorContext, type ObserveRateLimitThrottleContext, type ObserveRetryAttemptContext, type ObserveSkillActivatedContext, type Observers, OpenAIChatProvider, type OpenAICompatibleConfig, OpenAICompatibleProvider, type OpenRouterConfig, OpenRouterProvider, type OpenRouterRouting, type OutputLimitConfig, type ParallelGadgetHintOptions, type ParsedGadgetCall, type ParsedSkill, type PrefixConfig, type PresetDefinition, type PromptContext, type PromptTemplate, type PromptTemplateConfig, type ProviderAdapter, type ProviderIdentifier, RESEARCH_DATA_SOURCE_TOOL_TYPES, type RateLimitConfig, type RateLimitStats, RateLimitTracker, type ReasoningConfig, type ReasoningEffort, type ResearchCapabilities, type ResearchCitation, ResearchDeprecatedModelError, type ResearchDoneInfo, type ResearchErrorInfo, type ResearchEvent, type ResearchJob, ResearchJobNotResumableError, type ResearchJobRef, type ResearchModelMetadata, type ResearchModelSpec, ResearchNamespace, ResearchNotPollableError, ResearchNotSupportedError, type ResearchOptions, type ResearchPricing, type ResearchResult, ResearchResultCollector, type ResearchStatus, type ResearchStatusSnapshot, ResearchStreamConsumedError, ResearchTimeoutError, type ResearchToolConfig, type ResearchToolType, type ResearchUsage, ResearchValidationError, type ResolveValueOptions, type ResolvedCompactionConfig, type ResolvedRateLimitConfig, type ResolvedRetryConfig, type RetryConfig, type RetryOptions, type SessionManifestEntry, SimpleSessionManager, Skill, type SkillActivation, type SkillActivationControllerContext, type SkillActivationOptions, type SkillInstructionInterceptorContext, type SkillMetadata, SkillRegistry, type SkillResource, type SkillSource, SlidingWindowStrategy, type SpeechGenerationOptions, type SpeechGenerationResult, type SpeechModelSpec, type StdioMcpServerSpec, type StoredMedia, type StoredOutput, type StreamCompleteEvent, type StreamEvent, type StreamProcessingResult, StreamProcessor, type StreamProcessorOptions, type SubagentConfig, type SubagentConfigMap, type SubagentContext, type SubagentManifestEntry, type SubagentOptions, SummarizationStrategy, TaskCompletionSignal, type TextContentPart, type TextEvent, type TextGenerationOptions, type TextOnlyAction, type TextOnlyContext, type TextOnlyCustomHandler, type TextOnlyGadgetConfig, type TextOnlyHandler, type TextOnlyStrategy, type ThinkingChunk, type ThinkingEvent, TimeoutException, type TokenUsage, type TrailingMessage, type TrailingMessageContext, type CompactionEvent$1 as TreeCompactionEvent, type TreeConfig, type GadgetSkippedEvent$1 as TreeGadgetSkippedEvent, type TriggeredLimitInfo, type ValidationIssue, type ValidationResult, type VisionAnalyzeOptions, type VisionAnalyzeResult, assertCommandAllowed, audioFromBase64, audioFromBuffer, collectEvents, collectText, complete, createAnthropicProviderFromEnv, createFileLoggingState, createGadget, createGadgetOutputViewer, createGeminiProviderFromEnv, createHints, createHuggingFaceProviderFromEnv, createLoadSkillGadget, createLogger, createMcpServer, createMediaOutput, createOpenAIProviderFromEnv, createOpenRouterProviderFromEnv, createSubagent, defaultLogger, detectAudioMimeType, detectImageMimeType, discoverProviderAdapters, discoverSkills, estimateResearchCost, extractMessageText, extractRetryAfterMs, filterByDepth, filterByParent, filterRootEvents, format, formatBytes, formatCallNumber, formatDate, formatDuration, formatLLMError, formatLlmRequest, gadgetError, gadgetResultToMcpContent, gadgetSuccess, gadgetToMcpTool, getErrorMessage, getHostExports, getModelId, getPresetGadgets, getProvider, getSubagent, groupByParent, hasHostExports, hasPreset, hasProviderPrefix, hasSubagents, humanDelay, imageFromBase64, imageFromBuffer, imageFromUrl, isAbortError, isAudioPart, isDataUrl, isGadgetEvent, isImagePart, isLLMEvent, isLikelyContextOverflow, isRetryableError, isRootEvent, isSubagentEvent, isTextPart, iterationProgressHint, jsonSchemaToZod, listPresets, listSubagents, loadSkillsFromDirectory, mcpToolToGadget, normalizeMessageContent, parallelGadgetHint, parseDataUrl, parseFrontmatter, parseManifest, parseMetadata, parseRetryAfterHeader, parseSkillContent, parseSkillFile, randomDelay, renderSkillForMcpPrompt, resetFileLoggingState, resolveConfig, resolveHintTemplate, resolveInstructions, resolveModel, resolvePromptTemplate, resolveRateLimitConfig, resolveRetryConfig, resolveRulesTemplate, resolveSubagentModel, resolveSubagentTimeout, resolveValue, resultWithAudio, resultWithFile, resultWithImage, resultWithImages, resultWithMedia, runGadgetForMcp, runWithHandlers, scanResources, schemaToJSONSchema, skillToMcpPrompt, stream, stripProviderPrefix, substituteArguments, substituteVariables, text, timing, toBase64, truncate, validateAndApplyDefaults, validateGadgetParams, validateGadgetSchema, validateMetadata, withErrorHandling, withRetry, withTimeout };