/**
 * Embedding engine internals.
 *
 * `EmbeddingEngine` owns one GGUF model + llama.cpp context and serializes
 * embed calls against it. Multiple engines can coexist in one process — one
 * per registered Harper model entry — so the native addon binding is shared
 * through a process-resident registry keyed by addon path (dlopen of the same
 * path returns the same handle; llama.cpp keeps all state in heap objects, so
 * engines stay independent through the shared native code).
 */
import { type PoolingName } from './gguf.js';
/**
 * Per-inputType prompt templates, declared as data on a model entry (built-in
 * registry or `templates` in engine/config options) instead of detected by
 * model-name heuristics in code. See issue #4.
 *
 * Placeholders are `{name}` tokens: `{text}` is the input text (always
 * provided); `{task}` comes from the embed call's `task` option, falling back
 * to `defaults.task`; any other placeholder must have a value in `defaults`.
 * Literal braces are escaped as `{{` / `}}`. Interpolation is single-pass —
 * placeholder values are never re-scanned for placeholders.
 *
 * A missing template for an inputType means passthrough, and an omitted
 * `inputType` is ALWAYS passthrough regardless of templates — that is the
 * compatibility contract that keeps vectors from older versions comparable.
 */
export interface EmbedTemplates {
    /** Template applied when `inputType: 'document'`. */
    document?: string;
    /** Template applied when `inputType: 'query'`. */
    query?: string;
    /** Fallback values for non-`{text}` placeholders (e.g. `task`, `title`). */
    defaults?: Record<string, string>;
}
export interface EngineOptions {
    /** Absolute path to a .gguf model file. */
    modelPath?: string;
    /** Directory containing (or to download to) model files. */
    modelsDir?: string;
    /** Model name from the built-in registry. */
    modelName?: string;
    /** Token context window size. */
    contextSize?: number;
    /**
     * Batch processing size. In node-llama-cpp this sets both `n_batch` AND
     * `n_ubatch` on the llama.cpp context. llama.cpp's encoder asserts
     * `n_ubatch >= n_tokens` for every input — inputs that tokenize above
     * `batchSize` trigger `GGML_ASSERT` → `ggml_abort`, killing the host
     * process. Defaults to `contextSize` so the full context window is
     * usable out of the box.
     */
    batchSize?: number;
    /** CPU threads for inference. */
    threads?: number;
    /** Layers to offload to GPU (0 = CPU only). */
    gpuLayers?: number;
    /** Override path to llama-addon.node. */
    addonPath?: string;
    /**
     * Prompt templates for this model. Overrides the built-in registry entry's
     * templates; validated at construction (registration time). Without this —
     * and without registry templates — template-less models fall back to the
     * legacy nomic name-prefix heuristic.
     */
    templates?: EmbedTemplates;
    /**
     * Expected pooling for this model (`'none' | 'mean' | 'cls' | 'last' |
     * 'rank'`). Verification, not override: the native addon exposes no pooling
     * option, so the llama.cpp context always uses the model's own
     * `<arch>.pooling_type` metadata. Declaring the expectation makes init fail
     * loudly when the GGUF omits or contradicts it — the alternative is a
     * metadata-less conversion silently mean-pooling a last-token model
     * (issue #12). Omitted = accept whatever the model resolves to.
     */
    pooling?: PoolingName;
}
export interface EmbedManyOptions {
    /**
     * For models that distinguish document-vs-query embeddings. Applies the
     * model's template for that side (or the legacy nomic prefix for
     * template-less models). Omitted = passthrough, always.
     */
    inputType?: 'document' | 'query';
    /**
     * Free-text task instruction for models whose templates use `{task}`
     * (instruct-style embedders). Overrides the entry's `templates.defaults.task`.
     */
    task?: string;
    /** Best-effort cancellation — checked between inputs, not mid-decode. */
    signal?: AbortSignal;
}
export interface EmbedManyResult {
    /** One L2-normalized vector per input, in input order. */
    vectors: Float32Array[];
    /** Total tokens decoded across all inputs (including BOS/EOS). */
    tokens: number;
}
/**
 * Native embedding context handle. Exported because `decodeAndEmbed` — public
 * so tests can exercise it against a fake — takes one as a parameter;
 * `declaration: true` requires every type reachable from an exported
 * signature to itself be exported.
 */
export interface LlamaContext {
    init(): Promise<boolean>;
    dispose(): Promise<void>;
    initBatch(size: number): void;
    addToBatch(seq: number, pos: number, tokens: Uint32Array, logitIndexes: Uint32Array): void;
    decodeBatch(): Promise<void>;
    getEmbedding(tokenCount: number): Float32Array;
    /**
     * Evict every KV-cache cell for a sequence (`llama_memory_seq_rm(seq, -1, -1)`
     * under the hood). Synchronous; throws if the native eviction fails. A no-op
     * on a sequence with no cached cells (e.g. a freshly created context).
     */
    disposeSequence(seq: number): void;
}
/**
 * One GGUF model + llama.cpp embedding context.
 *
 * - Construction validates configuration synchronously (missing model source,
 *   unknown model name) so misconfiguration fails at registration, not first use.
 * - Heavy work (download, addon + model load) happens in `ensureReady()`, which
 *   is lazy, shared across concurrent callers, and retryable after failure.
 * - `embedMany()` serializes against a per-engine queue — the llama.cpp context
 *   is not safe for concurrent use.
 */
export declare class EmbeddingEngine {
    #private;
    constructor(options: EngineOptions);
    /** Model name (or model file basename) — used for backend naming and prefix detection. */
    get modelIdentity(): string;
    /**
     * Kick off (or join) initialization. Lazy and retryable: a failed attempt
     * resets so the next call tries again (e.g. a transient download failure).
     */
    ensureReady(): Promise<void>;
    /**
     * Generate one L2-normalized vector per input, in input order.
     *
     * Serialized against the engine's queue; init is awaited lazily on first
     * call. `opts.signal` is checked between inputs (best-effort — a decode in
     * flight can't be interrupted).
     */
    embedMany(texts: string[], opts?: EmbedManyOptions): Promise<EmbedManyResult>;
    /** Get the embedding vector dimensionality. */
    dimensions(): number;
    /**
     * Clean up native resources.
     *
     * Drains the embed queue before touching native handles — disposing the
     * llama.cpp context while a decodeBatch is executing is a use-after-free
     * that can kill the host process. Embeds accepted before this call complete
     * (when the engine is initialized); embeds that would need a fresh init
     * during disposal, and any submitted after, reject with a disposed error.
     */
    dispose(): Promise<void>;
}
/**
 * Decode one token sequence against a single-sequence llama.cpp context
 * (`sequences: 1` — see `#doInit`) and return its L2-normalized embedding.
 *
 * Always decodes at `seq=0, pos=0`: there is exactly one KV-cache sequence
 * slot on the context, reused across every call. `disposeSequence(0)` evicts
 * whatever cache cells the *previous* call on this context left behind
 * before writing new cells at pos 0 — without it, a second decode on the
 * same context sees inconsistent KV-cache/position state at pos 0 and
 * `llama_decode` hard-aborts the host process (a native `GGML_ASSERT`, not a
 * catchable JS error). A no-op on a fresh context (issue #8).
 *
 * A free function (not a private method) so it can be unit tested against a
 * fake `LlamaContext` double — the real native context can't be constructed
 * without a loaded GGUF model.
 */
export declare function decodeAndEmbed(context: LlamaContext, input: Uint32Array): Promise<{
    vector: Float32Array;
    tokens: number;
}>;
/**
 * Resolve the templates an engine will use: explicit `options.templates` wins;
 * a registry model falls back to its entry's templates; an explicit `modelPath`
 * with no explicit templates gets none (the legacy name-prefix heuristic still
 * applies at embed time). Exported for tests — the models-backend production
 * path constructs via `modelName` and must resolve the registry branch.
 */
export declare function resolveEngineTemplates(options: EngineOptions): EmbedTemplates | undefined;
/**
 * Validate templates at construction (registration) time, so misconfiguration
 * fails at Harper boot instead of on the first embed call. Unrecognized
 * top-level keys are rejected (a typo'd side like `documnet:` would otherwise
 * silently fall back to unprefixed embeds); every placeholder must be `{text}`,
 * `{task}` (call-suppliable), or covered by `defaults`; any other `{`/`}` must
 * be escaped as `{{` / `}}`.
 */
export declare function validateTemplates(templates: EmbedTemplates): void;
/** Single-pass interpolation. `vars` must cover every placeholder (validated at registration for all but a default-less `{task}`). */
export declare function renderTemplate(template: string, vars: Record<string, string>): string;
/**
 * Download a model from Hugging Face.
 *
 * Cross-worker coordination: exclusive creation of `<file>.downloading` elects
 * one downloader; the rest poll. A lock whose owner died is reclaimed after
 * `LOCK_STALE_MS`, and a waiter whose winner failed (lock vanished, no final
 * file) retakes the lock and retries rather than timing out.
 */
export declare function downloadModel(dir: string, modelName?: string): Promise<string>;
//# sourceMappingURL=engine.d.ts.map