import type { ThinkingLevel } from "../internal/harness-types.js";
import type { Model } from "../internal/llm.js";
import { type ModelRef, type ModelRole, type ModelRoles, type RoleSpec } from "./types.js";
export type { ModelRole, RoleSpec, ModelRoles } from "./types.js";
export { MODEL_ROLES } from "./types.js";
/** Resolve a string ModelRef against the catalog (a Model object passes through). */
export declare function resolveModel(ref: ModelRef, models?: Record<string, Model>): Model;
/** 档位降档链(强→弱)。解析某档无绑定时沿链向后找第一个有绑定的档(fail-open 可用性优先;绑定缺口
 *  在部署 catalog 审计面可见)。`ultra` 预留:排在 `max` 前,今天不绑,未来加档零码改。 */
export declare const DEFAULT_TIER_ORDER: readonly string[];
/** CC 档位词 → sema 档位(内置固定,兼容层):fable/mythos→max · opus→pro · sonnet→flash · haiku→lite。 */
export declare const CC_MODEL_TIER_ALIASES: Readonly<Record<string, string>>;
/** the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
 *  sema tier name (lite/flash/…) for labels — the alias verbatim reads as a strong claim about a
 *  specific vendor's model while the actual routing may land anywhere ("haiku" showing over a
 *  DeepSeek run, an operator field report). A non-alias string is the deployment's own model key — its
 *  honest name — and passes through unchanged. Display only; routing (`expandTiers`) is untouched. */
export declare function resolveModelDisplayLabel(ref: string): string;
/**
 * Expand a deployment's tier bindings into the model catalog: every tier name (and every CC alias whose
 * tier resolves) becomes a catalog key pointing at the bound Model, so the ENTIRE resolve surface
 * (`resolveModel` / roles / `@model` mentions / Agent tool `model` / workflow `agent() opts.model`)
 * accepts tier vocabulary with ZERO per-callsite changes. Called ONCE at Runner construction.
 *
 * Rules:
 * - a tier with no binding falls back DOWN {@link DEFAULT_TIER_ORDER} to the first bound tier
 *   (e.g. `max` unbound + `pro` bound ⇒ "max" resolves to the pro model — availability over strictness;
 *   the gap stays visible in the deployment's catalog audit, not silently absent);
 * - an existing catalog key of the same name WINS (deployment SHADOW semantics, mirroring agents):
 *   a deployment that already ships a model literally named "pro" keeps it untouched;
 * - a binding may itself be a catalog name or a Model object (resolved through `resolveModel`);
 *   an unknown binding name throws at construction (config error surfaces at boot, not first use);
 * - `"best"` (dynamic head-of-chain alias, CC `$L` parity) resolves to the FIRST tier in
 *   {@link DEFAULT_TIER_ORDER} that carries a DIRECT binding — "the strongest model this deployment
 *   actually bound", not a fixed tier — under the same SHADOW rule (a deployment catalog key named
 *   `best` wins); with no bound known-order tier (or no tiers at all) the key stays absent and
 *   `resolveModel("best")` refuses like any unknown ref.
 */
export declare function expandTiers(models: Record<string, Model> | undefined, tiers: Record<string, ModelRef> | undefined): Record<string, Model> | undefined;
/**
 * The words a catalog word stands in for under the tier layer, nearest first — what {@link expandTiers}
 * recorded for a key IT wrote: the tier words its resolution passed through and the binding's own catalog
 * word, each naming the model the key resolves to. A deployment's own key (a shadowing one included), and any
 * catalog that was not tier-expanded, stands in for nothing.
 */
export declare function tierAliasVia(word: string, models: Record<string, Model> | undefined): readonly string[];
export interface ModelMention {
    /** The matched model name — always one of `allowedNames` — or undefined if none was found. */
    model?: string;
    /** The message with the matched `@model` token removed and whitespace tidied. */
    cleanedText: string;
}
/**
 * Parse a leading/inline `@model` mention out of a user message, restoring the chat "@-model" UX.
 * Returns the matched model **name** (a catalog ref the caller resolves), never a Model object — and
 * **only ever a name from `allowedNames`**. This is the security boundary: untrusted end-user text can
 * pick *which configured model* to use, but can never inject an arbitrary `baseUrl`/`apiKey`/Model
 * (see design/11). Matches the first allow-listed mention; the rest of the text is returned cleaned.
 *
 * **Case-INSENSITIVE, deliberately** — and deliberately UNLIKE the catalog, whose lookups
 * ({@link resolveModel}) are exact. The two answer different questions and the asymmetry is the
 * design, not an oversight: a chat mention is a HUMAN-typed UX surface where `@Opus` and `@opus` are
 * the same intent, so intake is wide; a catalog key is a machine identifier a deployment declared, so
 * lookup is exact. The join is safe because the wide half never widens the narrow one: this returns
 * the allowlist's OWN canonical spelling (never the user's casing), so the value handed to
 * `resolveModel` is always a key the deployment wrote. The only visible consequence is that a catalog
 * carrying two keys differing ONLY in case is addressed by whichever the allowlist iterates last —
 * `@x` cannot name both, and there is no spelling that disambiguates them.
 *
 * Typical use: `const { model, cleanedText } = parseModelMention(userMsg, Object.keys(runner.agentCatalog.models ?? {}));`
 * then `runTask({ objective: cleanedText, model })` (omit `model` to fall back to the default role).
 *
 * **Feed it the EXPANDED table.** The allowlist this takes is the host's own, and on a deployment that
 * configured `RunnerDeps.tiers` the host's `deps.models` is NOT what the Runner resolves against: the
 * constructor runs {@link expandTiers} once and keeps the expansion PRIVATE, so tier words and CC
 * aliases (`pro`/`flash`/`opus`/`sonnet`/`best`/…) are catalog keys inside the Runner and absent from
 * the host's table. Passing `Object.keys(deps.models)` therefore makes a user's `@opus` silently not
 * match — the mention falls through as ordinary text and the task quietly runs on the default role.
 * `runner.agentCatalog.models` is the expanded table (a per-read copy); a caller that has no Runner in
 * hand can pre-expand its own with the exported {@link expandTiers}.
 */
export declare function parseModelMention(text: string, allowedNames: Iterable<string>): ModelMention;
/**
 * Role → 档位默认表(裁定 2026-07-11:「role 体系用户不感知」)。配了 `RunnerDeps.tiers` 的部署,
 * 各 role 未显式配置时自动落到语义档位——用户/壳只感知档位,roles 降级为内部路由层:
 * - `default` → `pro`(主力档兜底;用户/部署 roles.default / TaskSpec.model 恒可覆盖);
 * - `summarize` → `flash`(压缩本该便宜;flash 未绑沿降档链到 lite);
 * - `verifier` → `pro`;
 * - `classifier` → `flash`(design/143 批2b:auto-mode 权限分类腿是便宜档;flash 未绑沿降档链到 lite);
 * - `subagent` 无档位默认 = 走 FALLBACK 继承主模型(LLM 可经 model 参数自选档);
 * - `consolidate` 同样无档位默认,但它的 FALLBACK 刻意只有 `summarize` 一站(design/376,见
 *   {@link FALLBACK} 的注):未配即响亮拒,不静默骑主模型。
 * 未配 tiers 的部署:catalog 无 TIERS_ACTIVE 标记 → 表整体 INERT,行为与从前逐字节一致
 * (即使 catalog 恰有裸模型名 `pro`/`flash` 也不入档位路由——INERT 靠配置成立,不靠键名运气)。
 */
export declare const ROLE_TIER_DEFAULTS: Readonly<Partial<Record<ModelRole, string>>>;
/**
 * Whether a RoleSpec NAMES a model — a string ref, a Model object, or a config object carrying `model` or
 * `select` — as opposed to a preset-only object (`{ thinking }`, `{ systemPrompt }`) that shapes the role's
 * request but leaves its model to the defaults. The classifier seat's own default (the tier below the run's
 * main model, `classifier-ladder.ts`) applies exactly when this is false: a `{select}` keeps its selection, a
 * preset-only role does not disable the relative pick. One reader, the same parts {@link specParts} reads.
 */
export declare function roleSpecNamesModel(spec: RoleSpec | undefined): boolean;
export interface ResolvedRole {
    model: Model;
    /**
     * WHICH REF CHOSE {@link model} — the word, when a word chose it (the explicit `spec.model` string, the
     * role's own `model` ref, or the role→tier default's catalog key), else the `Model` object itself (an
     * explicit object, or one a `select` criteria picked: neither has a catalog word).
     *
     * The distinction is load-bearing wherever a LATER tick must ask the catalog the same question again: a
     * word re-resolves against the catalog generation in force at that tick (a hot swap redirects it, a
     * retirement refuses it loudly), while an object is pinned and never re-resolved. The engine-spawned
     * child that inherits its parent's model inherits THIS, not the resolved object, so it lands on the
     * generation its own prepare sees rather than on the one its parent started under; the same word keys
     * the deployment's per-model context-window overlay.
     */
    modelRef: ModelRef;
    /** The effective thinking level: explicit `spec.thinking` wins, else the role's default. */
    thinking?: ThinkingLevel;
    /** The requested role's preset system prompt, if it set one (independent of model fallback). */
    systemPrompt?: string;
}
/**
 * Resolve the model (and default thinking) for a task. Explicit `spec.model` wins; otherwise walk the
 * `modelRole`'s fallback chain over the merged roles (task overrides runner) and use the first role
 * that supplies a model. Throws a helpful error if nothing resolves.
 *
 * @contract roles.side_channels_deployment_only — an engine SIDE CHANNEL (the auto-mode classifier,
 * the compaction/summarize lane, memory consolidation, next-prompt suggestion) resolves here with a
 * `modelRole` and NO run model: it runs on the deployment's roles alone — the boot default at the end of
 * its chain — and never follows the model a run was switched to. Only a run's CHILDREN follow the parent:
 * the delegation seat (`childModelSeat`) passes the parent's `Model` as `spec.model` with the role kept
 * on `modelRole` — and keeps that role on an EXPLICIT-model seat too, so the explicit branch below reads
 * the child's presets off the subagent role, not the default one. A side channel that must follow a run
 * passes that run's model explicitly; nothing here infers it.
 */
export declare function resolveTaskModel(spec: {
    model?: ModelRef;
    modelRole?: ModelRole;
    roles?: ModelRoles;
    thinking?: ThinkingLevel;
}, deps: {
    models?: Record<string, Model>;
    roles?: ModelRoles;
}): ResolvedRole;
/**
 * Resolve a role's model **only if that exact role key is configured** (no fallback chain). Used for
 * the `summarize` compaction model: override only when a summarize-specific model is set, otherwise
 * let the caller fall back to the main model (unchanged behavior).
 */
export declare function roleModelIfSet(role: ModelRole, spec: {
    roles?: ModelRoles;
}, deps: {
    models?: Record<string, Model>;
    roles?: ModelRoles;
}): Model | undefined;
