/** * Multi-step agent loop with tool calling (v0.11). * * const loop = new AgentLoop({ * llm: createAISDKAgentLLM(google('gemini-2.5-flash')), * tools: new ToolRegistry([bookAppointment, cancelAppointment]), * memory: new ConversationMemory({ db: env.DB }), * ledger, // optional AICallLedger — one row per LLM call, correlated by turnId * }); * * const result = await loop.run({ * whatsapp: '5511987654321', * userText: 'Book Monday 10am', * systemPrompt: MY_STATIC_PROMPT, * context: { env, db: env.DB, whatsapp: '5511987654321' }, * }); * * The loop: * 1. Load recent history (memory.loadWindow) + system + userText * 2. Call LLM * 3. If assistant response has no tool calls → persist + return (finishReason: 'final') * 4. Otherwise: dispatch each tool call in parallel, append tool results, loop * 5. Stop when: no more tool calls / maxSteps reached / stopWhen(steps) true * * The loop is provider-neutral — it talks to the `AgentLLM` interface. Plug * in the AI SDK adapter for OpenAI/Google/Anthropic/etc. via * `wa-agent/ai-sdk`, or write your own adapter for a bespoke setup. * * Distinct from `AIRouter`: * - `AIRouter` is a single stateless call with multi-provider failover. * Keep using it for classifiers / summarizers / enrichers. * - `AgentLoop` is a multi-step conversation with persistent memory + tools. * Reach for it when the model needs to reason over multiple actions. */ import type { AICallLedger } from '../ai/ai_call_log.js'; import type { ConversationMemory } from './conversation_memory.js'; import type { ToolRegistry } from './tool_registry.js'; import type { AgentLLM, AgentRunResult, AgentStep } from './types.js'; export interface AgentLoopOptions { llm: AgentLLM; tools: ToolRegistry; memory: ConversationMemory; /** Optional per-call ledger. When set, one row per LLM call with `turnId` populated. */ ledger?: AICallLedger | null; /** Task label written to ledger rows (dashboard grouping). Default `'agent_loop'`. */ task?: string; /** Max steps before terminating with `finishReason: 'max_steps'`. Default 10. */ maxSteps?: number; /** Per-LLM-call timeout in ms. Adapter honours via AbortSignal. Default 15000. */ timeoutMs?: number; /** How many recent messages to load from memory into each turn. Default 20. */ historyLimit?: number; /** Optional cost estimator forwarded to ledger.record. */ estimateCost?: (provider: string, tokensIn: number | null, tokensOut: number | null) => number | null; /** `Date.now`-compatible clock (test override). */ now?: () => number; } export interface RunArgs { whatsapp: string; /** The new user message this turn begins with. */ userText: string; /** * Full system prompt. The loop injects it as the first message every * step; the app usually assembles it from a static persona + dynamic * state (draft block, user profile, etc.). */ systemPrompt: string; /** Opaque context handed to every tool's `execute(input, ctx)`. */ context: TContext; /** * Custom stop predicate — called after each step's tool results are * appended. Return true to terminate with `finishReason: 'stop'`. * Useful for early-exit when a tool signals completion. */ stopWhen?: (steps: ReadonlyArray) => boolean; /** * Optional multi-tenant scoping for LLM adapter routing / ledger rows. * ConversationMemory scoping is set on the memory instance itself. */ tenantId?: string | null; /** Optional external cancellation. */ signal?: AbortSignal; } export declare class AgentLoop { readonly llm: AgentLLM; readonly tools: ToolRegistry; readonly memory: ConversationMemory; readonly ledger: AICallLedger | null; readonly task: string; readonly maxSteps: number; readonly timeoutMs: number; readonly historyLimit: number; readonly estimateCost: AgentLoopOptions['estimateCost']; readonly now: () => number; constructor(opts: AgentLoopOptions); run(args: RunArgs): Promise; private logLedger; } //# sourceMappingURL=loop.d.ts.map