import type { ModelProvider } from '../../spi/model-provider.js';
/**
 * The structured side of every tree-LLM request: what is being asked, as data. The prompt says the
 * same thing in prose; `task` exists so a deterministic fake (tests, offline dev — see
 * {@link import('./keyword-tree-llm.js').keywordTreeLlm}) can answer without parsing prose, and so a
 * host can log or route requests by kind.
 */
export type TreeLlmTask = {
    kind: 'structure';
    /** The window of pages the LLM is asked to find section starts in. */
    units: {
        unit: number;
        page?: number;
        text: string;
    }[];
} | {
    kind: 'summarize';
    title: string;
    text: string;
} | {
    kind: 'describe';
    title?: string;
    outline: {
        title: string;
        summary?: string;
    }[];
} | {
    kind: 'navigate';
    query: string;
    /** `single-pass`: the whole tree is shown; `beam`: one level of candidates at a time. */
    mode: 'single-pass' | 'beam';
    candidates: TreeLlmCandidate[];
    /** Max nodes the answer may name. */
    maxNodes: number;
} | {
    kind: 'select';
    query: string;
    documents: {
        id: string;
        title?: string;
        description?: string;
    }[];
    maxDocuments: number;
};
/** A node as shown to the navigating LLM. */
export interface TreeLlmCandidate {
    id: string;
    title: string;
    summary?: string;
    pages?: string;
    /** Present on beam candidates: whether the node can be expanded. */
    hasChildren?: boolean;
    /** Present in single-pass mode: depth (0 = top level). */
    depth?: number;
}
export interface TreeLlmRequest {
    task: TreeLlmTask;
    system: string;
    prompt: string;
    /** A cap for the reply. Adapters pass it as `max_tokens` when they can. */
    maxOutputTokens: number;
    /** Aborted when the call's timeout or the build/navigation deadline passes. */
    signal: AbortSignal;
}
export interface TreeLlmResponse {
    text: string;
    /** Provider-reported usage. When absent, the budget estimates ~4 characters per token. */
    usage?: {
        inputTokens?: number;
        outputTokens?: number;
    };
}
/**
 * The only LLM surface tree building and navigation need: one prompt in, one text reply out. Replies
 * are expected to be JSON (for structure / navigate / select) or plain text (summaries); parsing is
 * lenient (code fences, prose around the object). Bring any model:
 * {@link treeLlmFromModelProvider} (the agent's own `ModelProvider`), {@link openAiChatTreeLlm}
 * (any OpenAI-compatible `/chat/completions`), or a function of your own. Use a deterministic
 * setting (temperature 0) — trees are cached by content, so a flaky model makes rebuilds noisy.
 */
export type TreeLlm = (request: TreeLlmRequest) => Promise<TreeLlmResponse>;
/**
 * Hard limits for one document build or one navigation. Every LLM call is checked against them
 * **before** it is made (with the prompt's estimated size), so a budget is never overshot by more
 * than one reply, and nothing retries past it: the failure mode this exists for is an indexer that
 * burned ~1,240 calls on one PDF it could not structure.
 */
export interface TreeBudget {
    /** Max LLM calls. */
    maxCalls?: number;
    /** Max input (prompt) tokens across all calls, estimated at ~4 chars/token before each call. */
    maxInputTokens?: number;
    /** Max output tokens across all calls. */
    maxOutputTokens?: number;
    /** Wall-clock deadline for the whole operation, ms. */
    timeoutMs?: number;
    /** Per-call timeout, ms. Default: whatever is left of `timeoutMs`. */
    callTimeoutMs?: number;
}
/** Thrown internally when a budget or deadline would be exceeded; callers turn it into a fallback. */
export declare class TreeBudgetExceededError extends Error {
    readonly limit: 'calls' | 'inputTokens' | 'outputTokens' | 'timeout';
    constructor(limit: 'calls' | 'inputTokens' | 'outputTokens' | 'timeout');
}
/** ~4 characters per token: the budget's estimate when a provider reports no usage. */
export declare function estimateTokens(text: string): number;
/** Tracks spend against a {@link TreeBudget} and runs calls under it. Not exported from the package. */
export declare class BudgetedLlm {
    private readonly llm;
    private readonly budget;
    private readonly outerSignal?;
    calls: number;
    inputTokens: number;
    outputTokens: number;
    exhausted: boolean;
    private readonly deadline;
    constructor(llm: TreeLlm, budget: Required<Pick<TreeBudget, 'maxCalls' | 'maxInputTokens' | 'maxOutputTokens'>> & TreeBudget, outerSignal?: AbortSignal | undefined);
    /** Time left before the deadline, ms (`Infinity` without one). */
    remainingMs(): number;
    /** Would a call with this prompt fit? Does not reserve anything; a `false` marks the budget exhausted. */
    fits(prompt: string, maxOutputTokens: number): boolean;
    private limitHit;
    /**
     * Make one call, or throw {@link TreeBudgetExceededError} without calling. Provider errors
     * propagate (after being counted). Never retries.
     */
    call(task: TreeLlmTask, system: string, prompt: string, maxOutputTokens: number): Promise<string>;
}
/**
 * Pull the JSON object out of a model reply: bare, fenced in ```json, or surrounded by prose.
 * Returns `undefined` when there is none (never throws).
 */
export declare function parseJsonReply(text: string): Record<string, unknown> | undefined;
/** The strings in a JSON array value (numbers stringified); anything else yields `[]`. */
export declare function stringList(value: unknown): string[];
/**
 * A {@link TreeLlm} over the agent's own {@link ModelProvider}: one `runTurn` with no tools and a
 * discarding sink. Usage comes from the provider's report.
 */
export declare function treeLlmFromModelProvider(provider: ModelProvider): TreeLlm;
export interface OpenAiChatTreeLlmOptions {
    model: string;
    /** Base URL up to and including `/v1`. Default `https://api.openai.com/v1`. */
    baseUrl?: string;
    apiKey?: string;
    headers?: Record<string, string>;
    /** Default 0 — trees are cached by content, determinism keeps rebuilds quiet. */
    temperature?: number;
    /**
     * Send `response_format: { type: 'json_object' }` on JSON tasks. Default `false`: not every
     * OpenAI-compatible server accepts it, and replies are parsed leniently anyway.
     */
    jsonMode?: boolean;
    /** Extra body fields (a reasoning switch, provider routing). */
    body?: Record<string, unknown>;
    fetch?: typeof fetch;
}
/**
 * A {@link TreeLlm} over any OpenAI-compatible `POST /v1/chat/completions` — OpenAI, a gateway,
 * vLLM, Ollama, DeepSeek, Qwen… — with no SDK dependency. Throws `HttpModelError` on a non-2xx.
 */
export declare function openAiChatTreeLlm(options: OpenAiChatTreeLlmOptions): TreeLlm;
/** A get/set cache — a `Map`, an LRU, a Redis wrapper. Values are whole replies. */
export interface TreeLlmCache {
    get(key: string): TreeLlmResponse | undefined | Promise<TreeLlmResponse | undefined>;
    set(key: string, value: TreeLlmResponse): unknown;
}
/**
 * Memoize a {@link TreeLlm} by the exact request (task kind, system prompt, prompt, output cap).
 * With a deterministic model that makes a rebuild of an unchanged section, or a repeated question,
 * free. Cached replies report zero usage, so they cost nothing against a budget but a call slot.
 */
export declare function cachedTreeLlm(llm: TreeLlm, cache?: TreeLlmCache): TreeLlm;
/**
 * A fast, deterministic, non-cryptographic 64-bit hash (two independent 32-bit FNV-1a/murmur-mixed
 * lanes) as 16 hex chars — content fingerprints and cache keys, not security.
 */
export declare function stableHash(text: string): string;
//# sourceMappingURL=llm.d.ts.map