import type { AssistantMessage, StopReason, Tool, ToolResultMessage, Usage, UserMessage } from "../internal/llm.js";
import type { Brain, Model, ModelRef, ModelRole, ModelRoles, TaskSpec, ThinkingLevel } from "./types.js";
import { type DegradationInfo } from "../brain/degrading.js";
/** A tool DEFINITION exposed to the model in a side query (never executed by the engine). */
export interface SideQueryToolDef {
    name: string;
    description: string;
    /** JSON-schema (typebox-compatible) parameters object. */
    parameters: Tool["parameters"];
}
/**
 * The side-query INPUT message union — REQUEST shapes, not transcript entries. Each
 * role keeps only what the brain wire actually consumes; the assistant arm drops the response
 * metadata (`api`/`provider`/`model`/`usage`/`stopReason`) a transcript entry carries — requiring
 * those would force callers to FABRICATE attribution, the exact shape-forging this verb exists to
 * kill. A caller MAY still pass a full transcript {@link Message}; extra fields ride through
 * untouched. Intake normalizes: missing `timestamp` stamped, a string assistant `content` folded to
 * one text block, missing assistant response metadata filled with inert placeholders (they never
 * reach the provider wire — history messages serialize by role/content only).
 */
export type SideQueryMessage = {
    role: "user";
    content: UserMessage["content"];
    timestamp?: number;
} | {
    role: "assistant";
    content: string | AssistantMessage["content"];
    timestamp?: number;
    api?: AssistantMessage["api"];
    provider?: string;
    model?: string;
    usage?: Usage;
    stopReason?: StopReason;
} | {
    role: "toolResult";
    toolCallId: string;
    toolName: string;
    content: ToolResultMessage["content"];
    isError?: boolean;
    timestamp?: number;
};
export interface SideQuerySpec {
    /** Model reference — same resolution as TaskSpec.model (pool name / role / @mention). Absent ⇒ the
     *  deployment's default role chain. */
    model?: ModelRef;
    /** Optional role override used when `model` is absent (same semantics as TaskSpec.modelRole). */
    modelRole?: ModelRole;
    thinking?: ThinkingLevel;
    systemPrompt?: string;
    /** Full multi-turn message list (user/assistant/toolResult), passed to the brain UNFLATTENED — through the same
     *  tool-turn law the harness applies to a session transcript (`ensureToolTurnInvariants`): an assistant's tool calls
     *  become its trailing blocks, each call is followed by exactly one result, and a call this list leaves unanswered
     *  (a trailing tool call, with the query's own prompt appended after it) is closed with a synthesized
     *  "interrupted, outcome unknown" result and an operator notice. A caller that does not want that close answers or
     *  drops the open call before handing the list over. The caller's array is not modified. */
    messages: SideQueryMessage[];
    /** Tool definitions the model may call in its reply. The engine NEVER executes them — any calls
     *  come back in `SideQueryResult.toolCalls` for the caller to interpret. */
    tools?: SideQueryToolDef[];
    /** Per-request output cap (provider max_tokens). */
    maxOutputTokens?: number;
    /**
     * Per-model auth — MIRRORS {@link TaskSpec.getApiKeyAndHeaders} (same signature, resolved per
     * call against the RESOLVED model, exactly like the task path's per-call hook). The brain
     * contract is the route pairing law (route-adjudicator.ts): a per-model credential (this hook,
     * or a credential header on `Model.headers`) rides to the origin it first went out to and is
     * refused anywhere else (`route.credential_origin_mismatch`); an answer handing the key beside
     * the lane's own key header is refused as one verdict (`route.credential_answer_conflict`); the
     * deployment credential rides only to the deployment's own root — a model carrying a root of its
     * own is refused (`route.credential_mismatch` / `route.credential_missing`), never silently followed.
     * Historically the fallback was unconditional (`options.apiKey ?? config.apiKey`, with a model's
     * own `baseUrl` outranking the brain's), so a side query routed to a model carrying its own
     * `baseUrl` + per-model key sent the GATEWAY key to the per-model (possibly external) URL — the
     * credential leak the pairing law now stops. Absent seat ⇒ no options are minted: the
     * construction-time credential applies on the deployment's own root, and a model rooted elsewhere
     * hard-fails the side query with the loud refusal (`stopReason: "error"`) instead of leaking —
     * there is no primary model for a side query to fall back to.
     */
    getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
    signal?: AbortSignal;
}
export interface SideQueryResult {
    /** Concatenated assistant text (may be empty when the reply is tool-calls-only). */
    text: string;
    /** Tool calls the model made against the DEFINITIONS (never executed by the engine). `id` is the engine id;
     *  `wireId` is the provider's own spelling, present only when the two differ (`ToolCall.wireId`, the same
     *  source). A caller replaying a call carries both on its `toolCall` block and keys the result by `id`; one that
     *  talks to the provider itself hands it `wireId ?? id`. */
    toolCalls: Array<{
        id: string;
        wireId?: string;
        name: string;
        args: unknown;
    }>;
    /** The RESOLVED pool model id the query was ROUTED to (the caller's ref resolved). See
     *  `servedModel` for the engine-selected id the reply came back stamped with. */
    model: string;
    /** The ENGINE-SELECTED id the reply came back stamped with (a degrading deployment may fall back
     *  mid-query — attribution follows the entry that ran, not the routing intent); equals `model` when
     *  nothing degraded. Still THIS catalog's id: what the PROVIDER called it is `responseModel` below. */
    servedModel: string;
    /** Structured degradation marker when a fallback model served (from/to/reason/chain). */
    degraded?: DegradationInfo;
    /** The PROVIDER's own name for the model that answered, when it differs from `servedModel` above.
     *  Absent ⇒ it named the same id or named none — one reading, as on `AssistantMessage.responseModel`. */
    responseModel?: string;
    /** Usage with `input` NORMALIZED to the uncached prompt quantity (the Anthropic-protocol
     *  `input_tokens` meaning) for EVERY provider family — the same gate the run loop's accounting
     *  applies (`usage-accounting.ts`). The openai-completions family reports `input` INCLUDING the
     *  cached subset; hand that through raw and a consumer summing `input + cacheRead` double-counts
     *  every cache hit. `cacheRead`/`cacheWrite`/`output`/`totalTokens`/`cost` are the provider's own
     *  figures, untouched. `usageMissing` ⇒ the numbers are UNKNOWN shells, not zeros. */
    usage: Usage;
    usageMissing?: true;
    stopReason: StopReason;
    /** Present on `stopReason:"error"|"aborted"` — the query did not produce a usable reply. The verb
     *  RETURNS the honest failure instead of throwing: utility callers uniformly need "no answer +
     *  why", and a throw would lose usage/model attribution for the failed attempt. */
    errorMessage?: string;
}
/** Dependencies the Runner passes through (a strict subset of RunnerDeps — no session/tool machinery). */
export interface SideQueryDeps {
    brain: Brain;
    models?: Record<string, Model>;
    roles?: ModelRoles;
}
export declare function runSideQuery(spec: SideQuerySpec, deps: SideQueryDeps): Promise<SideQueryResult>;
