import { agentConfigRevisions, agents, heartbeatRunEvents, heartbeatRuns, issues, organizations } from "@rudderhq/db";
import { buildLangfuseRunScores, diagnoseRun, observedRunFromFilesystem, } from "@rudderhq/run-intelligence-core";
import { and, asc, desc, eq, gt, inArray, lt, sql } from "drizzle-orm";
import { createHash } from "node:crypto";
import { notFound } from "../errors.js";
import { getExecutionLangfuseLink } from "../langfuse.js";
import { resolveHeartbeatRunIdReference } from "./heartbeat-run-reference.js";
import { getRunLogStore } from "./run-log-store.js";
function hashValue(value) {
    return createHash("sha256").update(JSON.stringify(value ?? null)).digest("hex");
}
function asRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
}
function readString(value) {
    return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function fallbackSkillLabel(key) {
    const parts = key.split(/[/:]/u).filter(Boolean);
    return parts.at(-1) ?? key;
}
function normalizeSkillEvidenceEntry(value) {
    if (typeof value === "string") {
        const key = value.trim();
        return key ? { key, label: fallbackSkillLabel(key) } : null;
    }
    const record = asRecord(value);
    const key = readString(record.key) ?? readString(record.runtimeName) ?? readString(record.name) ?? readString(record.label);
    if (!key)
        return null;
    return {
        key,
        label: readString(record.label) ?? readString(record.runtimeName) ?? readString(record.name) ?? fallbackSkillLabel(key),
    };
}
function normalizeSkillQuery(value) {
    return value.trim().toLocaleLowerCase();
}
function skillEntryMatchesQuery(entry, query) {
    return [entry.key, entry.label].some((candidate) => candidate?.trim().toLocaleLowerCase() === query);
}
export function extractSkillEvidenceMatch(input) {
    const payload = asRecord(input.payload);
    const keysField = input.evidenceType === "used" ? "usedSkillKeys" : "loadedSkillKeys";
    const skillsField = input.evidenceType === "used" ? "usedSkills" : "loadedSkills";
    const query = normalizeSkillQuery(input.skillQuery);
    const candidates = [
        ...(Array.isArray(payload[skillsField]) ? payload[skillsField] : []),
        ...(Array.isArray(payload[keysField]) ? payload[keysField] : []),
    ]
        .map((entry) => normalizeSkillEvidenceEntry(entry))
        .filter((entry) => Boolean(entry));
    const match = candidates.find((entry) => skillEntryMatchesQuery(entry, query));
    if (!match)
        return null;
    const eventCreatedAt = input.eventCreatedAt instanceof Date
        ? input.eventCreatedAt.toISOString()
        : input.eventCreatedAt ?? null;
    return {
        evidenceType: input.evidenceType,
        matchedSkillKey: match.key,
        matchedSkillLabel: match.label,
        sourceEventType: input.eventType,
        sourceEventId: input.eventId,
        sourceEventCreatedAt: eventCreatedAt,
    };
}
function buildSkillExistsCondition(evidenceType, skillQuery) {
    const keysField = evidenceType === "used" ? "usedSkillKeys" : "loadedSkillKeys";
    const skillsField = evidenceType === "used" ? "usedSkills" : "loadedSkills";
    const normalized = normalizeSkillQuery(skillQuery);
    return sql `exists (
    select 1
    from heartbeat_run_events skill_events
    where skill_events.run_id = ${heartbeatRuns.id}
      and skill_events.org_id = ${heartbeatRuns.orgId}
      and skill_events.event_type in ('adapter.invoke', 'adapter.skill_usage')
      and (
        exists (
          select 1
          from jsonb_array_elements_text(
            case
              when jsonb_typeof(skill_events.payload -> ${keysField}) = 'array' then skill_events.payload -> ${keysField}
              else '[]'::jsonb
            end
          ) as skill_key(value)
          where lower(skill_key.value) = ${normalized}
        )
        or exists (
          select 1
          from jsonb_array_elements(
            case
              when jsonb_typeof(skill_events.payload -> ${skillsField}) = 'array' then skill_events.payload -> ${skillsField}
              else '[]'::jsonb
            end
          ) as skill_entry(value)
          where lower(coalesce(skill_entry.value ->> 'key', '')) = ${normalized}
             or lower(coalesce(skill_entry.value ->> 'label', '')) = ${normalized}
             or lower(coalesce(skill_entry.value ->> 'runtimeName', '')) = ${normalized}
             or lower(coalesce(skill_entry.value ->> 'name', '')) = ${normalized}
        )
      )
  )`;
}
function resolveBundleForRun(run, revisionsByAgentId) {
    const revisions = revisionsByAgentId.get(run.agentId) ?? [];
    const runCreatedAt = new Date(run.createdAt).getTime();
    const revision = revisions.find((candidate) => new Date(candidate.createdAt).getTime() <= runCreatedAt) ?? null;
    const afterConfig = revision?.afterConfig ?? {
        agentRuntimeConfig: run.agentRuntimeConfig,
        runtimeConfig: run.runtimeConfig,
    };
    const afterConfigRecord = typeof afterConfig === "object" && afterConfig !== null ? afterConfig : {};
    return {
        agentRuntimeType: run.agentRuntimeType,
        agentConfigRevisionId: revision?.id ?? null,
        agentConfigRevisionCreatedAt: revision?.createdAt ? new Date(revision.createdAt).toISOString() : null,
        agentConfigFingerprint: hashValue(afterConfigRecord.agentRuntimeConfig ?? run.agentRuntimeConfig),
        runtimeConfigFingerprint: hashValue(afterConfigRecord.runtimeConfig ?? run.runtimeConfig),
    };
}
async function loadIssuesForRuns(db, runRows) {
    const issueIds = [...new Set(runRows.map((row) => row.issueId).filter((value) => Boolean(value)))];
    if (issueIds.length === 0)
        return new Map();
    const rows = await db
        .select({
        id: issues.id,
        identifier: issues.identifier,
        title: issues.title,
    })
        .from(issues)
        .where(inArray(issues.id, issueIds));
    return new Map(rows.map((row) => [row.id, row]));
}
async function loadRevisionsForRuns(db, runRows) {
    const agentIds = [...new Set(runRows.map((row) => row.agentId))];
    if (agentIds.length === 0)
        return new Map();
    const rows = await db
        .select()
        .from(agentConfigRevisions)
        .where(inArray(agentConfigRevisions.agentId, agentIds))
        .orderBy(desc(agentConfigRevisions.createdAt));
    const revisionsByAgentId = new Map();
    for (const row of rows) {
        const revisions = revisionsByAgentId.get(row.agentId) ?? [];
        revisions.push(row);
        revisionsByAgentId.set(row.agentId, revisions);
    }
    return revisionsByAgentId;
}
async function serializeRunRow(row, issueMap, revisionsByAgentId, skillEvidenceMap) {
    const errorSummary = row.errorCode ?? row.error ?? row.stderrExcerpt ?? null;
    return {
        run: {
            id: row.id,
            orgId: row.orgId,
            agentId: row.agentId,
            invocationSource: row.invocationSource,
            triggerDetail: row.triggerDetail,
            status: row.status,
            startedAt: row.startedAt,
            finishedAt: row.finishedAt,
            error: row.error,
            wakeupRequestId: row.wakeupRequestId,
            exitCode: row.exitCode,
            signal: row.signal,
            usageJson: row.usageJson,
            resultJson: row.resultJson,
            sessionIdBefore: row.sessionIdBefore,
            sessionIdAfter: row.sessionIdAfter,
            logStore: row.logStore,
            logRef: row.logRef,
            logBytes: row.logBytes,
            logSha256: row.logSha256,
            logCompressed: row.logCompressed,
            stdoutExcerpt: row.stdoutExcerpt,
            stderrExcerpt: row.stderrExcerpt,
            errorCode: row.errorCode,
            externalRunId: row.externalRunId,
            chatConversationId: row.chatConversationId,
            processPid: row.processPid,
            processStartedAt: row.processStartedAt,
            retryOfRunId: row.retryOfRunId,
            processLossRetryCount: row.processLossRetryCount,
            contextSnapshot: row.contextSnapshot,
            createdAt: row.createdAt,
            updatedAt: row.updatedAt,
        },
        agentName: row.agentName,
        orgName: row.orgName,
        issue: row.issueId ? issueMap.get(row.issueId) ?? null : null,
        bundle: resolveBundleForRun(row, revisionsByAgentId),
        langfuse: await getExecutionLangfuseLink(row.id),
        errorSummary,
        skillEvidence: skillEvidenceMap?.get(row.id) ?? null,
    };
}
async function loadRunRows(db, input) {
    const conditions = [eq(heartbeatRuns.orgId, input.orgId)];
    if (input.updatedAfter)
        conditions.push(gt(heartbeatRuns.updatedAt, input.updatedAfter));
    if (input.createdBefore)
        conditions.push(lt(heartbeatRuns.createdAt, input.createdBefore));
    if (input.agentId)
        conditions.push(eq(heartbeatRuns.agentId, input.agentId));
    if (input.status)
        conditions.push(eq(heartbeatRuns.status, input.status));
    if (input.runtime)
        conditions.push(eq(agents.agentRuntimeType, input.runtime));
    if (input.runIdPrefix) {
        const runIdPrefix = input.runIdPrefix.replace(/-/g, "").toLowerCase();
        conditions.push(sql `replace(${heartbeatRuns.id}::text, '-', '') like ${`${runIdPrefix}%`}`);
    }
    if (input.issueId)
        conditions.push(sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${input.issueId}`);
    if (input.usedSkill)
        conditions.push(buildSkillExistsCondition("used", input.usedSkill));
    if (input.loadedSkill)
        conditions.push(buildSkillExistsCondition("loaded", input.loadedSkill));
    return await db
        .select({
        id: heartbeatRuns.id,
        orgId: heartbeatRuns.orgId,
        agentId: heartbeatRuns.agentId,
        invocationSource: heartbeatRuns.invocationSource,
        triggerDetail: heartbeatRuns.triggerDetail,
        status: heartbeatRuns.status,
        startedAt: heartbeatRuns.startedAt,
        finishedAt: heartbeatRuns.finishedAt,
        error: heartbeatRuns.error,
        wakeupRequestId: heartbeatRuns.wakeupRequestId,
        exitCode: heartbeatRuns.exitCode,
        signal: heartbeatRuns.signal,
        usageJson: heartbeatRuns.usageJson,
        resultJson: heartbeatRuns.resultJson,
        sessionIdBefore: heartbeatRuns.sessionIdBefore,
        sessionIdAfter: heartbeatRuns.sessionIdAfter,
        logStore: heartbeatRuns.logStore,
        logRef: heartbeatRuns.logRef,
        logBytes: heartbeatRuns.logBytes,
        logSha256: heartbeatRuns.logSha256,
        logCompressed: heartbeatRuns.logCompressed,
        stdoutExcerpt: heartbeatRuns.stdoutExcerpt,
        stderrExcerpt: heartbeatRuns.stderrExcerpt,
        errorCode: heartbeatRuns.errorCode,
        externalRunId: heartbeatRuns.externalRunId,
        chatConversationId: heartbeatRuns.chatConversationId,
        processPid: heartbeatRuns.processPid,
        processStartedAt: heartbeatRuns.processStartedAt,
        retryOfRunId: heartbeatRuns.retryOfRunId,
        processLossRetryCount: heartbeatRuns.processLossRetryCount,
        contextSnapshot: heartbeatRuns.contextSnapshot,
        createdAt: heartbeatRuns.createdAt,
        updatedAt: heartbeatRuns.updatedAt,
        agentName: agents.name,
        agentRuntimeType: agents.agentRuntimeType,
        agentRuntimeConfig: agents.agentRuntimeConfig,
        runtimeConfig: agents.runtimeConfig,
        orgName: organizations.name,
        issueId: sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`.as("issueId"),
    })
        .from(heartbeatRuns)
        .innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
        .innerJoin(organizations, eq(heartbeatRuns.orgId, organizations.id))
        .where(and(...conditions))
        .orderBy(desc(heartbeatRuns.createdAt))
        .limit(input.limit);
}
async function loadSkillEvidenceForRuns(db, runRows, input) {
    const skillQuery = input.usedSkill ?? input.loadedSkill ?? null;
    const evidenceType = input.usedSkill ? "used" : "loaded";
    if (!skillQuery || runRows.length === 0)
        return new Map();
    const runIds = runRows.map((row) => row.id);
    const rows = await db
        .select({
        id: heartbeatRunEvents.id,
        runId: heartbeatRunEvents.runId,
        eventType: heartbeatRunEvents.eventType,
        payload: heartbeatRunEvents.payload,
        createdAt: heartbeatRunEvents.createdAt,
    })
        .from(heartbeatRunEvents)
        .where(and(eq(heartbeatRunEvents.orgId, runRows[0].orgId), inArray(heartbeatRunEvents.runId, runIds), inArray(heartbeatRunEvents.eventType, ["adapter.invoke", "adapter.skill_usage"])))
        .orderBy(asc(heartbeatRunEvents.createdAt), asc(heartbeatRunEvents.id));
    const evidenceByRunId = new Map();
    for (const row of rows) {
        if (evidenceByRunId.has(row.runId))
            continue;
        const match = extractSkillEvidenceMatch({
            payload: row.payload,
            evidenceType,
            skillQuery,
            eventType: row.eventType,
            eventId: row.id,
            eventCreatedAt: row.createdAt,
        });
        if (match)
            evidenceByRunId.set(row.runId, match);
    }
    return evidenceByRunId;
}
async function loadRunRowById(db, runId) {
    const rows = await db
        .select({
        id: heartbeatRuns.id,
        orgId: heartbeatRuns.orgId,
        agentId: heartbeatRuns.agentId,
        invocationSource: heartbeatRuns.invocationSource,
        triggerDetail: heartbeatRuns.triggerDetail,
        status: heartbeatRuns.status,
        startedAt: heartbeatRuns.startedAt,
        finishedAt: heartbeatRuns.finishedAt,
        error: heartbeatRuns.error,
        wakeupRequestId: heartbeatRuns.wakeupRequestId,
        exitCode: heartbeatRuns.exitCode,
        signal: heartbeatRuns.signal,
        usageJson: heartbeatRuns.usageJson,
        resultJson: heartbeatRuns.resultJson,
        sessionIdBefore: heartbeatRuns.sessionIdBefore,
        sessionIdAfter: heartbeatRuns.sessionIdAfter,
        logStore: heartbeatRuns.logStore,
        logRef: heartbeatRuns.logRef,
        logBytes: heartbeatRuns.logBytes,
        logSha256: heartbeatRuns.logSha256,
        logCompressed: heartbeatRuns.logCompressed,
        stdoutExcerpt: heartbeatRuns.stdoutExcerpt,
        stderrExcerpt: heartbeatRuns.stderrExcerpt,
        errorCode: heartbeatRuns.errorCode,
        externalRunId: heartbeatRuns.externalRunId,
        chatConversationId: heartbeatRuns.chatConversationId,
        processPid: heartbeatRuns.processPid,
        processStartedAt: heartbeatRuns.processStartedAt,
        retryOfRunId: heartbeatRuns.retryOfRunId,
        processLossRetryCount: heartbeatRuns.processLossRetryCount,
        contextSnapshot: heartbeatRuns.contextSnapshot,
        createdAt: heartbeatRuns.createdAt,
        updatedAt: heartbeatRuns.updatedAt,
        agentName: agents.name,
        agentRuntimeType: agents.agentRuntimeType,
        agentRuntimeConfig: agents.agentRuntimeConfig,
        runtimeConfig: agents.runtimeConfig,
        orgName: organizations.name,
        issueId: sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`.as("issueId"),
    })
        .from(heartbeatRuns)
        .innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
        .innerJoin(organizations, eq(heartbeatRuns.orgId, organizations.id))
        .where(eq(heartbeatRuns.id, runId))
        .limit(1);
    return rows[0] ?? null;
}
async function loadRunEvents(db, runId) {
    const rows = await db
        .select()
        .from(heartbeatRunEvents)
        .where(eq(heartbeatRunEvents.runId, runId))
        .orderBy(heartbeatRunEvents.seq, heartbeatRunEvents.id);
    return rows.map((row) => ({
        ...row,
        stream: row.stream,
        level: row.level,
    }));
}
async function loadRunLogContent(run) {
    if (!run.logStore || !run.logRef)
        return "";
    const logStore = getRunLogStore();
    const result = await logStore.read({
        store: run.logStore,
        logRef: run.logRef,
    }, {
        offset: 0,
        limitBytes: Math.max(256_000, Number(run.logBytes ?? 0) || 256_000),
    });
    return result.content;
}
export async function listObservedRuns(db, input) {
    const rows = await loadRunRows(db, input);
    const [issueMap, revisionsByAgentId, skillEvidenceMap] = await Promise.all([
        loadIssuesForRuns(db, rows),
        loadRevisionsForRuns(db, rows),
        loadSkillEvidenceForRuns(db, rows, input),
    ]);
    return Promise.all(rows.map((row) => serializeRunRow(row, issueMap, revisionsByAgentId, skillEvidenceMap)));
}
export async function getObservedRun(db, runId, scope = {}) {
    const resolvedRunId = await resolveHeartbeatRunIdReference(db, runId, scope);
    const row = await loadRunRowById(db, resolvedRunId);
    if (!row)
        return null;
    const [issueMap, revisionsByAgentId] = await Promise.all([
        loadIssuesForRuns(db, [row]),
        loadRevisionsForRuns(db, [row]),
    ]);
    return serializeRunRow(row, issueMap, revisionsByAgentId);
}
export async function getObservedRunEvents(db, runId, scope = {}) {
    const resolvedRunId = await resolveHeartbeatRunIdReference(db, runId, scope);
    const run = await db
        .select({ id: heartbeatRuns.id })
        .from(heartbeatRuns)
        .where(eq(heartbeatRuns.id, resolvedRunId))
        .limit(1)
        .then((rows) => rows[0] ?? null);
    if (!run)
        throw notFound("Agent run not found");
    return loadRunEvents(db, resolvedRunId);
}
export async function getObservedRunLog(db, runId, scope = {}) {
    const resolvedRunId = await resolveHeartbeatRunIdReference(db, runId, scope);
    const run = await db
        .select()
        .from(heartbeatRuns)
        .where(eq(heartbeatRuns.id, resolvedRunId))
        .limit(1)
        .then((rows) => rows[0] ?? null);
    if (!run)
        throw notFound("Agent run not found");
    return { content: await loadRunLogContent(run) };
}
export async function getObservedRunDetail(db, runId, scope = {}) {
    const resolvedRunId = await resolveHeartbeatRunIdReference(db, runId, scope);
    const [observedRun, events] = await Promise.all([
        getObservedRun(db, resolvedRunId, scope),
        loadRunEvents(db, resolvedRunId),
    ]);
    if (!observedRun)
        return null;
    const run = { ...observedRun.run, chatConversationId: observedRun.run.chatConversationId ?? null };
    const logContent = await loadRunLogContent(run).catch(() => "");
    return observedRunFromFilesystem({
        run,
        agentName: observedRun.agentName,
        orgName: observedRun.orgName,
        issue: observedRun.issue,
        bundle: observedRun.bundle,
        events,
        logContent,
    });
}
export async function diagnoseObservedRun(db, runId, mode = "auto") {
    const detail = await getObservedRunDetail(db, runId);
    if (!detail)
        throw notFound("Agent run not found");
    return {
        detail,
        diagnosis: diagnoseRun(detail, mode),
    };
}
export async function buildObservedRunLangfuseScores(db, runId) {
    const { detail, diagnosis } = await diagnoseObservedRun(db, runId);
    return {
        detail,
        diagnosis,
        scores: buildLangfuseRunScores(detail, diagnosis),
    };
}
//# sourceMappingURL=run-intelligence.js.map