/**
 * LLM observability utilities.
 *
 * Provides:
 * - Prompt/response content summarization for redaction-by-default telemetry
 * - instrumentedLlmCall() wrapper that emits LLM_REQUEST_STARTED + enriched LLM_RESPONSE_RECEIVED
 * - includeRawPrompts config awareness
 */
import { createHash } from 'node:crypto';
import { llmRequestsTotal, llmRequestDurationMs, llmTokensInput, llmTokensOutput, llmRequestsStarted, } from './metrics.js';
/**
 * Summarize prompt/response content for telemetry emission.
 * When includeRawPrompts is true, returns the raw string.
 * Default (false): returns a PromptSummary with length, sha256, first100chars.
 */
export function summarizePromptContent(content, includeRaw) {
    if (includeRaw)
        return content;
    return {
        length: content.length,
        sha256: createHash('sha256').update(content).digest('hex'),
        first100chars: content.slice(0, 100),
    };
}
/**
 * Wrap any LLM provider call to track duration, retry count, and
 * record platformMeter instruments (llmRequestsTotal, llmRequestDurationMs,
 * llmTokensInput, llmTokensOutput).
 *
 * Providers wrap their .chat() / .generate() calls with this helper.
 *
 * Usage:
 * ```ts
 * const { result, durationMs, retries } = await instrumentedLlmCall(
 *   async () => await this.chat(params),
 *   { provider: this.name, model: params.model ?? this.defaultModel },
 * );
 * ```
 */
/**
 * Emit LLM_REQUEST_STARTED metric at the llm-observability layer.
 *
 * Records the llmRequestsStarted counter so callers without bus access still
 * get per-provider/model observability. Callers that also hold a RuntimeEventBus
 * should additionally call emitLlmRequestStarted(bus, ctx, data) from emitters/turn.
 */
function recordLlmRequestStartedMetric(opts) {
    const labels = {};
    if (opts.provider)
        labels.provider = opts.provider;
    if (opts.model)
        labels.model = opts.model;
    llmRequestsStarted.add(1, labels);
}
export async function instrumentedLlmCall(fn, opts) {
    const maxRetries = opts?.maxRetries ?? 0;
    const retryDelayMs = opts?.retryDelayMs ?? 0;
    const startedAt = Date.now();
    // Auto-emit LLM_REQUEST_STARTED metric on entry (no bus required)
    recordLlmRequestStartedMetric({ provider: opts?.provider, model: opts?.model });
    opts?.onStarted?.();
    let lastError;
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
            const result = await fn();
            const durationMs = Date.now() - startedAt;
            if (opts?.provider !== undefined || opts?.model !== undefined) {
                const labels = {};
                if (opts.provider)
                    labels.provider = opts.provider;
                if (opts.model)
                    labels.model = opts.model;
                llmRequestsTotal.add(1, { ...labels, status: 'success' });
                llmRequestDurationMs.record(durationMs, labels);
                if (opts.extractTokens) {
                    const tokens = opts.extractTokens(result);
                    if (tokens?.inputTokens !== undefined && tokens.inputTokens > 0) {
                        llmTokensInput.record(tokens.inputTokens, labels);
                    }
                    if (tokens?.outputTokens !== undefined && tokens.outputTokens > 0) {
                        llmTokensOutput.record(tokens.outputTokens, labels);
                    }
                }
            }
            return { result, durationMs, retries: attempt };
        }
        catch (err) {
            lastError = err;
            if (attempt < maxRetries && retryDelayMs > 0) {
                await new Promise((resolve) => {
                    const timer = setTimeout(resolve, retryDelayMs);
                    timer.unref?.();
                });
            }
        }
    }
    // Record error metric on final failure
    if (opts?.provider !== undefined || opts?.model !== undefined) {
        const labels = {};
        if (opts?.provider)
            labels.provider = opts.provider;
        if (opts?.model)
            labels.model = opts.model;
        llmRequestsTotal.add(1, { ...labels, status: 'error' });
        llmRequestDurationMs.record(Date.now() - startedAt, labels);
    }
    throw lastError;
}
