/** * ResearchAgent — provider-agnostic tool-use loop over the Lucidly research toolkit. * * Architecture: * * ResearchAgent ─── normalized AgentMessage[] ─→ LLMProvider ─→ Anthropic / Groq / DeepSeek / OpenAI * ←─ normalized LLMResponse ───── * * Two entry points: * • `chat(prompt)` — single async call, returns the final answer. * • `chatStream(prompt, callbacks)` — same loop, but emits incremental text-delta + tool-call * events as the model streams. Use this when wiring into * a UI that wants to show the answer assembling live. * Supports `abortSignal` for user-stop and `getStopped` * flag for stop-between-tool-calls. * * `chat()` is now a thin wrapper around `chatStream()` — both share the same loop body and the * same result shape. Adding a new entry point (e.g. `chatStreamAsync()` returning an AsyncIterable) * is one more wrapper, not a duplicate loop implementation. */ import type { LucidlyClient } from "./client.js"; import type { AgentStopReason, LLMProvider } from "./llm.js"; export interface ResearchAgentConfig { /** LLM provider — AnthropicProvider, GroqProvider, DeepSeekProvider, OpenAIProvider. */ provider: LLMProvider; /** Max output tokens per individual model call. Default 4096. */ maxTokensPerCall?: number; /** Total input+output token cap for one chat() session. Default 60_000. */ sessionTokenCap?: number; /** Max number of model calls per session. Default 10. */ maxIterations?: number; /** Appended to the default system prompt. */ extraSystemPrompt?: string; } export interface ResearchToolCall { name: string; input: Record; resultJson: string; isError: boolean; } export interface ResearchAgentResult { text: string; toolCalls: ResearchToolCall[]; tokensUsed: { input: number; output: number; }; cappedOut: boolean; /** True iff the user (via abortSignal or getStopped) stopped the run. */ stopped: boolean; iterations: number; stopReason: AgentStopReason; providerInfo: { name: string; model: string; }; } /** Callbacks for `chatStream`. All optional. */ export interface ChatStreamCallbacks { /** Called at the start of each iteration (each model API call). 1-indexed. */ onIterationStart?: (iteration: number) => void; /** Called for each streamed text fragment from the model. Use for live-typing UI. */ onTextDelta?: (chunk: string) => void; /** Called once per tool call after it's been dispatched + result captured. */ onToolCall?: (call: ResearchToolCall) => void; /** Called when token usage updates after each iteration. */ onTokens?: (tokens: { input: number; output: number; }) => void; /** Called when the provider hit 429 and is backing off. Forwarded from provider. */ onRateLimit?: (info: { waitSec: number; attempt: number; maxRetries: number; }) => void; /** AbortSignal — abort the in-flight fetch + bail the loop. */ abortSignal?: AbortSignal; /** Optional polled stop predicate. Checked between tool calls + at iteration boundaries. */ getStopped?: () => boolean; } export declare class ResearchAgent { private readonly tools; private readonly provider; private readonly maxTokensPerCall; private readonly sessionTokenCap; private readonly maxIterations; private readonly systemPrompt; constructor(client: LucidlyClient, cfg: ResearchAgentConfig); get providerInfo(): { name: string; model: string; }; /** * Run a single research question end-to-end with no UI feedback. Returns when the provider * reports `end_turn` with no further tool_call blocks, or when we hit an iteration/token cap. * * Thin wrapper around `chatStream()` with no callbacks. Use `chatStream` for live UI updates. */ chat(userPrompt: string): Promise; /** * Run a single research question with streaming callbacks. Emits text deltas as the model * streams, tool-call events as tools execute, and respects abortSignal / getStopped. */ chatStream(userPrompt: string, callbacks: ChatStreamCallbacks): Promise; private runOneTool; }