// Embedding capability verification + identity compatibility for the Local
// Knowledge Connector. Decides whether a configured embedding model is reachable
// and whether its vector space remains compatible with a previously stored
// embedding identity. Every failure path produces a `safeMessage` string that
// MUST NOT contain API keys, full URLs, raw provider bodies, IPs, or the
// user-supplied input. Probes are pure-ish: I/O is encapsulated in the injected
// adapter function.
const PROBE_INPUT = "ping";
const SAFE_MESSAGES = Object.freeze({
    "missing-credentials": "model gateway credentials are not configured",
    unavailable: "model gateway is not reachable",
    "wrong-header": "model gateway rejected the request — check API key configuration",
    "rate-limited": "model gateway is rate-limited — retry after the configured backoff",
    "dimension-mismatch": "embedding vector dimensions do not match the expected value",
    timeout: "embedding probe timed out",
    cancelled: "embedding probe was cancelled by the caller",
    "unsupported-model": "embedding model is not available on the configured gateway",
    "invalid-response": "embedding response was malformed",
    "incompatible-with-stored-identity": "embedding model identity changed — existing capsules are no longer compatible",
});
function fail(reason) {
    return { ok: false, reason, safeMessage: SAFE_MESSAGES[reason] };
}
function reasonFromAdapter(kind) {
    switch (kind) {
        case "wrong-header":
            return "wrong-header";
        case "rate-limited":
            return "rate-limited";
        case "unsupported-model":
            return "unsupported-model";
        case "timeout":
            return "timeout";
        case "cancelled":
            return "cancelled";
        case "invalid-response":
            return "invalid-response";
        case "transport":
            return "unavailable";
    }
}
function hasCredentials(adapter) {
    return adapter.apiKey.trim().length > 0;
}
function buildIdentity(options, detectedModelId, detectedDimensions, modelRevision) {
    return {
        provider: options.provider,
        modelId: detectedModelId,
        vectorDimensions: detectedDimensions,
        vectorMetric: options.vectorMetric,
        ...(modelRevision !== undefined ? { modelRevision } : {}),
    };
}
export async function verifyEmbeddingCapability(adapter, options) {
    if (!hasCredentials(adapter)) {
        return fail("missing-credentials");
    }
    const request = {
        endpoint: adapter.endpoint,
        apiKey: adapter.apiKey,
        ...(adapter.apiKeyHeaderName !== undefined
            ? { apiKeyHeaderName: adapter.apiKeyHeaderName }
            : {}),
        ...(adapter.egress !== undefined ? { egress: adapter.egress } : {}),
        modelId: options.modelId,
        input: PROBE_INPUT,
        ...(options.signal !== undefined ? { signal: options.signal } : {}),
        ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
    };
    const outcome = await adapter.request(request);
    if (!outcome.ok) {
        return fail(reasonFromAdapter(outcome.kind));
    }
    const detected = outcome.value.vector.length;
    if (detected === 0) {
        return fail("invalid-response");
    }
    if (options.expectedDimensions !== undefined && options.expectedDimensions !== detected) {
        return fail("dimension-mismatch");
    }
    return {
        ok: true,
        identity: buildIdentity(options, outcome.value.modelId, detected, outcome.value.modelRevision),
    };
}
function structuralFieldsEqual(a, b) {
    return (a.provider === b.provider &&
        a.modelId === b.modelId &&
        a.vectorDimensions === b.vectorDimensions &&
        a.vectorMetric === b.vectorMetric);
}
function revisionDiffers(stored, current) {
    return stored.modelRevision !== current.modelRevision;
}
function buildRevisionWarning(stored, current) {
    return {
        code: "model-revision-changed",
        safeMessage: "embedding model revision changed — capsules remain compatible but should be re-validated",
        ...(stored.modelRevision !== undefined ? { previousRevision: stored.modelRevision } : {}),
        ...(current.modelRevision !== undefined ? { currentRevision: current.modelRevision } : {}),
    };
}
export function assertCompatibleEmbeddingIdentity(stored, current) {
    if (!structuralFieldsEqual(stored, current)) {
        return fail("incompatible-with-stored-identity");
    }
    if (revisionDiffers(stored, current)) {
        // Return the CURRENT identity (not stored) so the caller can persist the new revision
        // and avoid a permanent warning on every subsequent compatibility check. The warning
        // carries the previous revision for diagnostics. #192 Copilot finding.
        return { ok: true, identity: current, warning: buildRevisionWarning(stored, current) };
    }
    return { ok: true, identity: stored };
}
