/**
 * THE canonical agentic tool-loop. One inference turn → run any requested tools → fold the
 * results back as `tool` messages → repeat, until the model answers without a tool call or the
 * turn budget is hit. One turn = one inference call (the equal-compute unit vs random@k).
 *
 * The inference is an INJECTABLE seam (`ToolLoopChat`): a router model, a sandboxed CLI
 * harness, or a scripted mock all satisfy it — so the loop is backend-agnostic. The metered /
 * steerable concerns the call sites add (a driver's conserved-pool + deadline bound; an inline
 * executor's inbox flush + abort) attach via optional `hooks`; the skeleton stays one copy.
 */

type Msg = Record<string, unknown>;
/** One inference turn over the running conversation + the tool specs → the model's text, any
 *  tool calls, and token usage. The seam every brain satisfies. */
type ToolLoopChat = (messages: ReadonlyArray<Msg>, tools: ReadonlyArray<ToolSpec>) => Promise<{
    content?: string | null;
    toolCalls: RouterToolCall[];
    usage?: {
        input: number;
        output: number;
    };
    /** The turn's inference cost (usd) when the provider priced it — for callers that meter usd
     *  into a conserved pool (the supervisor brain). `runBrainLoop` itself ignores it. */
    costUsd?: number;
}>;
/** Self-compaction — bound the loop's OWN context window the way a fresh-respawn (dumb-Ralph) loop
 *  does, but in place. A stateless chat API re-sends the WHOLE running conversation every turn, so an
 *  agent that accumulates dozens of turns of tool results re-bills its entire transcript on every
 *  inference — the context-overflow-one-level-up that the conserved budget pool cannot fix. With
 *  compaction set, once the conversation exceeds `thresholdTokens` the accumulated middle (every prior
 *  assistant turn + tool result) is distilled into ONE compact progress note and the conversation is
 *  reset to `[...head, digest]`: the preserved head (system + the original task) survives, the stale
 *  turn-by-turn history does not. The model keeps deciding; it stops re-billing the whole transcript.
 *  Fires at a CLEAN turn boundary (after a turn's tool results are folded in, before the next
 *  inference) so it never orphans an assistant `tool_calls` from its `tool` replies. */
interface ToolLoopCompaction {
    /** Compact once the estimated token count of the conversation exceeds this. */
    readonly thresholdTokens: number;
    /** Distill the conversation into a compact progress note that REPLACES the middle. Receives the
     *  full conversation (so it can summarize everything done so far); returns the digest string. */
    readonly distill: (messages: ReadonlyArray<Msg>) => Promise<string> | string;
    /** Leading messages preserved verbatim (system + the original task). Default 2. */
    readonly preserveHead?: number;
    /** Token estimator over the conversation. Default ≈ chars/4 (incl. tool-call arguments). */
    readonly estimateTokens?: (messages: ReadonlyArray<Msg>) => number;
    /** Notified each time a compaction fires — for observability/metering. */
    readonly onCompact?: (info: {
        turn: number;
        beforeTokens: number;
        afterTokens: number;
    }) => void;
}
/** Public supervisor-facing compaction config: same knobs as the primitive, but `distill` is optional
 *  because the supervisor has a default digest that combines a brain note with live worker state. */
type ToolLoopCompactionOptions = Omit<ToolLoopCompaction, 'distill'> & {
    readonly distill?: ToolLoopCompaction['distill'];
};

/**
 * The one router chat client: direct OpenAI-compatible completions through the
 * Tangle router — the cheapest dial, no sandbox. Three layers: `routerChatWithUsage`
 * (chat-only), `routerChatWithTools` (one completion with function tools), and
 * `routerToolLoop` (the off-box agentic loop over tool-calling). Shared by the
 * built-in executors and the bench/lab harnesses.
 *
 * Reports REAL token usage so the backend-integrity guard sees a real backend.
 * Returns `undefined` usage when the provider omitted it — never a fabricated 0
 * (a phantom 0 reads as a free call downstream, which the gate would act on).
 */

interface RouterConfig {
    routerBaseUrl: string;
    routerKey: string;
    model: string;
    /**
     * Optional completion transport. When set, `routerChatWithUsage` / `routerChatWithTools` call it
     * with the OpenAI-shape request body and use the parsed `/chat/completions` JSON it returns,
     * INSTEAD of `fetch(routerBaseUrl + '/chat/completions')`. When absent the fetch path runs
     * unchanged — the live router stays the default. The injection seam an offline benchmark uses to
     * drive the worker with no network: a deterministic in-process responder satisfies it, no server.
     */
    complete?: (body: Record<string, unknown>) => Promise<unknown>;
}
interface RouterChatResult {
    /** The final answer, with any inline `<think>...</think>` block stripped into `reasoning`. */
    content: string;
    /**
     * Thinking-model reasoning, when the provider surfaced it — either as a separate
     * `reasoning`/`reasoning_content` message field (OpenRouter style) or inlined into
     * `content` as a `<think>` block (Groq style). Undefined for non-thinking models.
     * Downstream parsers that match single-token answers must read `content`, which is
     * clean either way; before this split, Groq-style inlining made the same model look
     * broken on one provider and fine on another.
     */
    reasoning?: string;
    /** REAL usage, or undefined when the provider reported none. */
    usage?: {
        input: number;
        output: number;
    };
    /** Derived from usage via `estimateCost` when the model is priced; else undefined. */
    costUsd?: number;
}
/** One OpenAI-compatible chat completion through the Tangle router, returning text + REAL token usage (`undefined` when the provider omits it — never a fabricated 0). */
declare function routerChatWithUsage(cfg: RouterConfig, messages: Array<{
    role: string;
    content: string;
}>, opts?: {
    temperature?: number;
    signal?: AbortSignal;
    maxTokens?: number;
    /**
     * Reasoning control for thinking models, forwarded as `reasoning_effort`.
     * 'none' is the load-bearing value: binary/single-token decisions (routing,
     * gating) on a thinking model otherwise burn the whole token budget inside
     * the think block — on slow backends (CPU-local) that turns into a client
     * timeout, not just waste. Providers that ignore the field are handled by
     * the reasoning/content split in `parseChatResult`.
     */
    reasoningEffort?: 'none' | 'low' | 'medium' | 'high';
}): Promise<RouterChatResult>;
/** A tool-call the model emitted (provider-neutral; mirrors the runtime's ToolCallRequest). */
interface RouterToolCall {
    id: string;
    name: string;
    /** Raw JSON arguments string as emitted by the model. */
    arguments: string;
}
interface RouterChatToolsResult {
    content: string | null;
    toolCalls: RouterToolCall[];
    usage?: {
        input: number;
        output: number;
    };
    costUsd?: number;
}
/**
 * A router completion WITH tool-calling — the operator driver's LLM seam. Passes OpenAI-shape
 * `messages` (system/user/assistant-with-tool_calls/tool roles) + function `tools`, and returns the
 * assistant text plus the tool calls the model wants run. Same fail-loud + real-usage discipline as
 * `routerChatWithUsage`. `tool_choice: 'auto'` lets the model decide; the driver loops on the result.
 */
declare function routerChatWithTools(cfg: RouterConfig, messages: ReadonlyArray<Record<string, unknown>>, tools: ReadonlyArray<{
    type: 'function';
    function: {
        name: string;
        description?: string;
        parameters: unknown;
    };
}>, opts?: {
    temperature?: number;
    signal?: AbortSignal;
    toolChoice?: 'auto' | 'required' | 'none';
    maxTokens?: number;
}): Promise<RouterChatToolsResult>;
interface ToolSpec {
    type: 'function';
    function: {
        name: string;
        description?: string;
        parameters: unknown;
    };
}
interface RouterToolLoopResult {
    /** The model's final assistant text (the turn where it stopped calling tools, or the budget turn). */
    final: string;
    /** Inference turns spent (≤ maxTurns) — the equal-budget unit vs random@k. */
    turns: number;
    toolCalls: number;
    /** The behavior trace: each tool call + its result, in order. What a trace-analyst
     *  steerer reads (behavior, never the verdict) to diagnose + redirect the next shot. */
    toolTrace: Array<{
        name: string;
        args: string;
        result: string;
    }>;
    usage: {
        input: number;
        output: number;
    };
    /** The full conversation after the loop (seed + every assistant/tool turn). Lets a caller
     *  CARRY the messages into the next shot (depth continuation) and read the trajectory. */
    messages: Array<Record<string, unknown>>;
}
/**
 * The tool-using router backend: a real agentic loop OVER the Tangle router (which
 * supports tool-calling), off-box — no sandbox. Each turn is one router completion
 * with `tools`; if the model emits tool_calls, `execute` runs them on the host and
 * their results are folded back as `tool` messages; the loop repeats until the
 * model answers without a tool call or the turn budget is hit. One turn = one
 * inference call, so `maxTurns` is the equal-compute unit against random@k.
 *
 * This is the depth substrate for agentic gates (the worker ACTS, observes the real
 * result, and continues) that the chat-only `routerChatWithUsage` cannot express.
 */
declare function routerToolLoop(cfg: RouterConfig, system: string, user: string, tools: ReadonlyArray<ToolSpec>, execute: (name: string, args: Record<string, unknown>) => Promise<string>, opts?: {
    maxTurns?: number;
    temperature?: number;
    signal?: AbortSignal;
    maxTokens?: number;
    /** Seed the loop with an existing conversation (depth continuation) instead of
     *  `[system, user]`. When set, `system`/`user` are ignored. The array is copied. */
    initialMessages?: ReadonlyArray<Record<string, unknown>>;
}): Promise<RouterToolLoopResult>;
/**
 * The router as a supervisor BRAIN: the canonical `ToolLoopChat` seam backed by the router's
 * tool-calling. The driver's spawn/observe/steer/await/stop turns become real router tool-calls.
 * The turnkey production brain — tests script a mock `ToolLoopChat`; production passes
 * `routerBrain(cfg)`. No message translation: the loop already speaks the router's OpenAI shape.
 */
declare function routerBrain(cfg: RouterConfig, opts?: {
    temperature?: number;
}): ToolLoopChat;

export { type RouterConfig as R, type ToolSpec as T, type ToolLoopChat as a, type ToolLoopCompactionOptions as b, type RouterChatResult as c, type RouterChatToolsResult as d, type RouterToolCall as e, type RouterToolLoopResult as f, type ToolLoopCompaction as g, routerChatWithTools as h, routerChatWithUsage as i, routerToolLoop as j, routerBrain as r };
