/**
 * EntryScorer — the pluggable STRATEGY for ranking a skill graph's entry
 * candidates by relevance to the user's message.
 *
 * `skillGraph().entryBy(scorer)` selects a strategy; the agent's PickEntry stage
 * runs it ONCE per turn (off the hot loop) and starts the cursor at the winner.
 * Two built-ins ship:
 *   • `keywordScorer()`      — no dependency, no model call: word overlap between
 *     the message and each skill's `description`. The zero-config way to route.
 *   • `embeddingScorer(e)`   — semantic: cosine similarity of embeddings (needs an
 *     Embedder). `.entryByRelevance(embedder)` is sugar for this.
 * Bring your own by implementing `EntryScorer`.
 *
 * The scorer OWNS both the surfaced `relevance` % AND the `chosen` winner, so the
 * explanation and the decision can never disagree (softmax is order-preserving, so
 * argmax-score == argmax-relevance).
 */
import type { Embedder } from '../memory/embedding/types.js';
import type { IntentScorer } from './intentScorer.js';
/** One entry candidate's relevance to the user's message. */
export interface EntryScore {
    /** The entry skill id. */
    readonly id: string;
    /** Raw, strategy-specific score — cosine for `embedding`, word-overlap for
     *  `keyword`. Higher = more relevant. Not normalized across strategies. */
    readonly score: number;
    /** Softmax share across candidates, 0..1 — the surfaced "Why this skill?" %. */
    readonly relevance: number;
}
/** Result of scoring the entries — the picked entry, the full ranking, and which
 *  scorer produced it. */
export interface EntryScoring {
    /** The scorer's `name` (e.g. `'keyword'`, `'embedding'`) — surfaced so a lens /
     *  the "Why this skill?" panel can say HOW the entry was chosen. */
    readonly scorer: string;
    /** Winning entry id (highest `score`), or undefined if no candidate. */
    readonly chosen: string | undefined;
    /** Every scored candidate, in declaration order. */
    readonly ranked: readonly EntryScore[];
}
/** A candidate the scorer ranks — its id + the text it's matched on (the skill's
 *  `description`). */
export interface EntryCandidate {
    readonly id: string;
    readonly description: string;
}
/** What a scorer receives: the user's message + the candidates to rank. */
export interface EntryScorerInput {
    readonly userMessage: string;
    readonly candidates: readonly EntryCandidate[];
}
/**
 * A strategy for ranking entry candidates. Pure given its inputs; may be async (an
 * embedder makes network calls). Runs ONCE per turn off the hot loop, so cost here
 * never touches the ReAct inner loop.
 */
export interface EntryScorer {
    /** Short, stable name — shown in the lens / "Why this skill?" panel + any
     *  strategy picker. */
    readonly name: string;
    score(input: EntryScorerInput, signal?: AbortSignal): Promise<EntryScoring> | EntryScoring;
}
/**
 * Shared finisher: raw scores → `EntryScoring`. Softmax turns the (sanitized) raw
 * scores into a `relevance` share (sums to 1); the winner is the argmax with
 * declaration order breaking ties. Because softmax is order-preserving over the
 * sanitized scores, the WINNER is always the argmax `relevance` — the surfaced %
 * and the pick can never disagree.
 *
 * `EntryScorer` is a public, consumer-implementable interface, so a third-party
 * scorer might return `NaN` / `±Infinity`. We sanitize those to `-Infinity` for BOTH
 * the softmax input AND the winner pick, so a non-finite score can never silently win
 * (`NaN > x` is always false — a leading `NaN` would otherwise seed-and-keep a naive
 * reduce) and the softmax stays well-defined. The raw (possibly non-finite) score is
 * still surfaced on `EntryScore.score` for honest debugging.
 */
export declare function rankEntries(scorerName: string, candidates: readonly EntryCandidate[], rawScores: readonly number[]): EntryScoring;
/**
 * keywordScorer — rank by word overlap between the message and each description.
 * No embedder, no model call, deterministic. Scores the set-cosine of lowercased
 * word tokens (length-normalized so a long description can't win on sheer size),
 * minus a small stop-word list. The zero-config router: good enough when skill
 * descriptions use the words a user would.
 *
 * ALSO an {@link IntentScorer} (SG-C): handed a candidates array as the second
 * argument it scores the message's set-cosine against each intent's sentence +
 * examples, and declares `floor: 0` — zero token overlap IS honestly "did not
 * match at all", so this scorer can say *unmatched*. One factory, one name,
 * two arities; existing `EntryScorer` callers are byte-unaffected.
 */
export declare function keywordScorer(options?: {
    readonly stopWords?: readonly string[];
}): EntryScorer & IntentScorer;
/**
 * embeddingScorer — rank by SEMANTIC similarity. Embeds the message + each
 * description and cosine-scores them. Needs an `Embedder` (a model call per text);
 * runs once per turn off the hot loop. `.entryByRelevance(embedder)` is sugar for
 * `.entryBy(embeddingScorer(embedder))`.
 *
 * ALSO an {@link IntentScorer} (SG-C): handed a candidates array it embeds the
 * message + one text per candidate (the intent sentence with its examples) and
 * cosine-scores them. **No floor unless you declare one** (`{ floor }`): an
 * absolute embedding threshold is a lie by default — measured in the field,
 * real intent clusters separate by single-digit percentages of cosine, so a
 * built-in cutoff would flip between "unmatched" and "decisive" on noise.
 * Without a floor this scorer can say *stay*, *move* or *near-tie*, never
 * *unmatched* — the verdict table documents that honestly.
 */
export declare function embeddingScorer(embedder: Embedder, options?: {
    readonly floor?: number;
}): EntryScorer & IntentScorer;
