/**
 * Per-run and per-thread token-usage accumulator. The runtime feeds parent and
 * child agent loop events here; completed runs emit immutable run-stats entries
 * back to the host.
 */
function isAssistantMessage(message) {
    if (!message || typeof message !== 'object')
        return false;
    const msg = message;
    return (msg.role === 'assistant' &&
        typeof msg.usage === 'object' &&
        msg.usage !== null &&
        typeof msg.model === 'string');
}
function emptyUsage() {
    return {
        input_tokens: 0,
        output_tokens: 0,
        total_tokens: 0,
        cache_read_input_tokens: 0,
        cache_creation_input_tokens: 0
    };
}
/** `total_tokens` tracks billable input+output; cache fields stay separate. */
function aiUsageToBackendShape(usage) {
    return {
        input_tokens: usage.input ?? 0,
        output_tokens: usage.output ?? 0,
        total_tokens: (usage.input ?? 0) + (usage.output ?? 0),
        cache_read_input_tokens: usage.cacheRead ?? 0,
        cache_creation_input_tokens: usage.cacheWrite ?? 0
    };
}
function mergeUsageInto(target, modelId, delta) {
    const existing = target[modelId] ?? emptyUsage();
    target[modelId] = {
        input_tokens: existing.input_tokens + delta.input_tokens,
        output_tokens: existing.output_tokens + delta.output_tokens,
        total_tokens: existing.total_tokens + delta.total_tokens,
        cache_read_input_tokens: existing.cache_read_input_tokens + delta.cache_read_input_tokens,
        cache_creation_input_tokens: existing.cache_creation_input_tokens + delta.cache_creation_input_tokens
    };
}
function cloneUsageMap(source) {
    const copy = {};
    for (const [model, usage] of Object.entries(source)) {
        copy[model] = { ...usage };
    }
    return copy;
}
/** UUID minter for environments without `crypto.randomUUID` (test/sandbox). */
function makeFallbackUuid(prefix) {
    let counter = 0;
    return () => {
        counter += 1;
        return `${prefix}-${Date.now()}-${counter}`;
    };
}
function defaultMintMessageId() {
    if (typeof globalThis.crypto?.randomUUID === 'function') {
        return globalThis.crypto.randomUUID();
    }
    return makeFallbackUuid('msg')();
}
export function createRunStatsAccumulator(options) {
    let monotonicRunCounter = 0;
    const defaultMintRunId = () => {
        monotonicRunCounter += 1;
        return `run-${Date.now()}-${monotonicRunCounter}`;
    };
    const mintRunId = options.mintRunId ?? defaultMintRunId;
    const mintMessageId = options.mintMessageId ?? defaultMintMessageId;
    const now = options.now ?? Date.now;
    /** Cumulative across runs; matches backend `thread_token_usage` reducer. */
    const threadUsage = {};
    let activeRun = null;
    let totalRuns = 0;
    /** When set, the next `agent_end` drops the active run without finalizing. */
    let cancelPending = false;
    const resolveRunConfig = () => {
        try {
            return options.resolveRunConfig?.() ?? {};
        }
        catch (err) {
            console.warn('[run-stats] resolveRunConfig threw:', err);
            return {};
        }
    };
    const recordAssistantUsage = (event, isParent) => {
        if (event.type !== 'message_end')
            return;
        if (!activeRun)
            return;
        if (!isAssistantMessage(event.message))
            return;
        const modelId = event.message.model;
        const delta = aiUsageToBackendShape(event.message.usage);
        mergeUsageInto(activeRun.perRunUsage, modelId, delta);
        mergeUsageInto(threadUsage, modelId, delta);
        // Only parent assistant messages surface in the parent snapshot, so only
        // they can become `last_message_id` for the run entry.
        if (isParent) {
            const message = event.message;
            if (typeof message.id !== 'string' || message.id.length === 0) {
                message.id = mintMessageId();
            }
            activeRun.lastAssistantMessageId = message.id;
        }
    };
    const startRunInternal = () => {
        if (activeRun)
            return activeRun.runId;
        totalRuns += 1;
        activeRun = {
            runId: mintRunId(),
            runNumber: totalRuns,
            startedAt: now(),
            perRunUsage: {},
            config: resolveRunConfig(),
            lastAssistantMessageId: undefined
        };
        return activeRun.runId;
    };
    return {
        handle(event) {
            if (event.type === 'agent_start') {
                startRunInternal();
                return;
            }
            if (event.type === 'message_end') {
                recordAssistantUsage(event, true);
                return;
            }
            if (event.type === 'agent_end') {
                if (!activeRun)
                    return;
                if (cancelPending) {
                    activeRun = null;
                    cancelPending = false;
                    return;
                }
                const endedAtMs = now();
                const startedAtIso = new Date(activeRun.startedAt).toISOString();
                const endedAtIso = new Date(endedAtMs).toISOString();
                const runEntry = {
                    run_id: activeRun.runId,
                    run_number: activeRun.runNumber,
                    usage: cloneUsageMap(activeRun.perRunUsage),
                    started_at: startedAtIso,
                    ended_at: endedAtIso,
                    duration_ms: Math.max(0, endedAtMs - activeRun.startedAt),
                    ...(activeRun.config.modelName !== undefined
                        ? { model_name: activeRun.config.modelName }
                        : {}),
                    ...(activeRun.config.effortLevel !== undefined
                        ? { effort_level: activeRun.config.effortLevel }
                        : {}),
                    ...(activeRun.config.speed !== undefined ? { speed: activeRun.config.speed } : {}),
                    ...(activeRun.lastAssistantMessageId !== undefined
                        ? { last_message_id: activeRun.lastAssistantMessageId }
                        : {})
                };
                const snapshot = {
                    threadTokenUsage: cloneUsageMap(threadUsage),
                    runEntry
                };
                activeRun = null;
                try {
                    options.onRunComplete(snapshot);
                }
                catch (err) {
                    console.warn('[run-stats] onRunComplete threw:', err);
                }
            }
        },
        handleChildEvent(event) {
            // Subagent assistant messages roll into parent totals; other kinds no-op.
            recordAssistantUsage(event, false);
        },
        getActiveRunId() {
            return activeRun?.runId;
        },
        beginRun() {
            return startRunInternal();
        },
        refreshRunConfig() {
            if (!activeRun)
                return;
            activeRun.config = resolveRunConfig();
        },
        abortRun() {
            activeRun = null;
            cancelPending = false;
        },
        markCancelling() {
            cancelPending = true;
        },
        seedFromHistory(entries) {
            if (activeRun !== null) {
                throw new Error('seedFromHistory called during an active run; would corrupt run-number counter.');
            }
            // Idempotent: callers re-seed after truncate so totals reflect the new history.
            for (const key of Object.keys(threadUsage))
                delete threadUsage[key];
            totalRuns = 0;
            for (const entry of entries) {
                if (entry.type !== 'message')
                    continue;
                const message = entry.message;
                if (message.role === 'user') {
                    totalRuns += 1;
                    continue;
                }
                if (isAssistantMessage(entry.message)) {
                    mergeUsageInto(threadUsage, entry.message.model, aiUsageToBackendShape(entry.message.usage));
                }
            }
        }
    };
}
//# sourceMappingURL=run-stats.js.map