/**
 * In-process tool-call agent loop for `Models.generate({ toolMode: 'auto' })`.
 *
 * Each iteration: (1) call `models.generate` with the running message list,
 * (2) if the model emitted tool calls, dispatch each via `opts.toolHandlers`,
 * (3) append the tool results to the message list, repeat. Terminate when the
 * model returns a non-`tool_calls` finish reason or when the iteration cap trips.
 *
 * Analytics: the loop calls back through `models.generate(..., {toolMode: 'return'})`
 * per iteration so each backend round flows the single-shot path in `Models.ts` and
 * writes its own `hdb_model_calls` row. The outer auto call stays out of the table.
 *
 * **Abort wiring** (commit 3). Each invocation creates a loop-level
 * `AbortController` composed with the caller's signal via `AbortSignal.any`. The
 * composed signal flows to both the inner `models.generate` call and the
 * `ToolHandlerContext.signal` handlers receive. Today the loop-level controller is
 * only fired externally (caller aborts → composed signal aborts); commit 4 wires
 * it to also fire on budget trips so an in-flight LLM call cancels cleanly.
 *
 * Streaming auto path + `opts.conversation.append` hook on both sync and streaming.
 * The streaming loop yields each round's content / tool-call deltas to the caller as
 * they arrive, accumulates the round's tool-call assembly internally, and treats
 * `finishReason` as an INTERNAL signal: it is stripped from every forwarded chunk and
 * re-emitted as exactly one terminal chunk AFTER the terminal assistant turn has been
 * persisted, so a consumer that stops on the finish-reason chunk can never race past
 * the conversation append. Tools run between rounds; the next backend stream resumes.
 * Budget / abort / error-mode semantics match the sync path, with one streaming-specific
 * gap: `maxToolTokens` / `maxCostUsd` are not yet enforced for streamed calls because
 * `GenerateChunk` doesn't expose `usage` in v1 (follow-up to extend the chunk shape +
 * backend final-chunk handling).
 *
 * Resilience posture (this module is foundational infra — fail loud, never silent):
 *   - Handler lookup uses `Object.hasOwn` + a callable check; a model-emitted tool name
 *     that collides with an Object prototype member (`toString`, `constructor`, …) does
 *     NOT resolve a built-in as a handler.
 *   - Missing handler splits two ways: a name the caller DECLARED in `tools` but didn't
 *     supply a handler for is a caller config bug (hard `ClientError(400)`); an UNdeclared
 *     name is a model hallucination, surfaced as a recoverable tool error (`toolErrorMode`
 *     decides recover-vs-abort).
 *   - A token/cost budget set against a backend that reports no `usage` warns once that it
 *     is unenforceable rather than silently no-opping.
 *   - An incomplete streamed tool call (id without a name) is recorded in the trace + warned,
 *     not silently dropped.
 *   - `toolErrorMode: 'abort'` throws BEFORE the conversation sink sees the round's tool
 *     turns, so the store never holds a recover-style error turn the model never consumed.
 *
 * Modes still deferred to follow-ups throw 501 at entry:
 *   - `toolArgValidation: 'strict' | 'lenient'`  → JSON Schema validator (TBD)
 *   - `maxToolTokens` / `maxCostUsd` (streaming) → backend `usage` on chunks (TBD)
 *
 * Registry seam: v1 dispatches via caller-supplied `opts.toolHandlers`. #615 replaces
 * that lookup with a `scope.resources` resolution using the same call signature — the
 * declared-vs-undeclared split maps onto resolvable-but-misconfigured vs unknown.
 */
import { ClientError, ServerError } from '../../utility/errors/hdbError.ts';
import type { AccountingContext, GenerateChunk, GenerateInput, GenerateOpts, GenerateResult, Models, TokenUsage, ToolTraceEntry } from './types.ts';
export interface RunAgentLoopArgs {
    models: Models;
    input: GenerateInput;
    opts: GenerateOpts;
    accounting: AccountingContext;
    signal?: AbortSignal;
}
export declare function runAgentLoop(args: RunAgentLoopArgs): Promise<GenerateResult>;
export declare function runAgentLoopStream(args: RunAgentLoopArgs): AsyncIterable<GenerateChunk>;
/**
 * Loop tripped one of its budgets (iterations, tokens, cost). The trace built so
 * far rides along on `partialTrace` so callers can inspect what already ran. The
 * loop always populates this regardless of `opts.includeToolTrace` so debugging an
 * exhausted budget never needs a second run with tracing turned on.
 *
 * Extends `ClientError` — the caller set the budget, so exceeding it is a 4xx
 * caller-bounds condition, not a Harper-internal fault. Anything that branches on
 * `err instanceof ClientError` (e.g. "don't page on this") classifies it correctly.
 */
export declare class BudgetExceededError extends ClientError {
    kind: 'iterations' | 'tokens' | 'cost';
    partialTrace: ToolTraceEntry[];
    constructor(kind: 'iterations' | 'tokens' | 'cost', message: string, partialTrace: ToolTraceEntry[]);
}
/**
 * Surfaced under `toolErrorMode: 'abort'` when a tool handler throws (or its
 * result fails to serialize). Carries the original throw on `.cause` and the
 * partial trace — including the failing entry — on `.partialTrace` so callers
 * always have the full picture on the abort path.
 *
 * `statusCode` mirrors the underlying error when it carries one (e.g. a handler
 * throwing `ClientError(400)` surfaces as `ToolHandlerError(400)`), otherwise
 * defaults to 500.
 *
 * **`instanceof` caveat:** extends `ServerError` regardless of `statusCode`, so a
 * handler-thrown `ClientError(403)` becomes a `ToolHandlerError` whose `statusCode`
 * is 403 but where `instanceof ClientError === false`. Callers that route on
 * client-vs-server class should branch on `err.statusCode` or
 * `err.cause instanceof ClientError`, not on `err instanceof ClientError` directly.
 */
export declare class ToolHandlerError extends ServerError {
    toolName: string;
    toolCallId: string;
    partialTrace: ToolTraceEntry[];
    constructor(toolName: string, toolCallId: string, partialTrace: ToolTraceEntry[], cause: unknown);
}
/**
 * Test-only override. Public callers must not depend on this — it exists so unit
 * tests can prove the `maxCostUsd` trip path works end-to-end before a real rate
 * card lands. Leading underscore marks the intent.
 */
export declare function _setComputeCallCostUsdForTests(fn: (usage: TokenUsage | undefined, model: string | undefined) => number): void;
/**
 * Reset the cost function to the v1 stub. Pair with `_setComputeCallCostUsdForTests`
 * in test `afterEach` so suites don't leak state into each other.
 */
export declare function _resetComputeCallCostUsdForTests(): void;
/**
 * `toolArgValidation: 'strict'` rejected a tool call's arguments against its
 * declared `parameters` JSON Schema. Surfaces as a 400 — the model produced output
 * that doesn't satisfy the contract the caller declared. Reserved for the
 * validator wiring (currently the strict mode is itself gated at loop entry).
 */
export declare class ToolValidationError extends ClientError {
    toolName: string;
    toolCallId: string;
    validationErrors: object[];
    constructor(toolName: string, toolCallId: string, validationErrors: object[], message: string);
}
