/**
 * Fire-and-forget tool-call summary emission.
 *
 * Captures tool args on execution_start, generates a one-line summary via
 * a cheap LLM (Haiku) on execution_end, and emits a tool_summary event.
 * Summaries are best-effort UX — never block the agent loop.
 */
import { generateToolSummaryDetailed } from '../background/tool-summaries.js';
import { TOOL_SUMMARY_MAX_CONCURRENT, TOOL_SUMMARY_MAX_OUTPUT_TOKENS, TOOL_SUMMARY_MAX_PER_RUN } from './tool-summary-policy.js';
/**
 * Manages the lifecycle of tool-call summary generation.
 *
 * Call {@link reset} on agent_start, {@link captureArgs} on tool_execution_start,
 * {@link maybeGenerate} on tool_execution_end, and {@link abort} on agent_end.
 */
export class ToolSummaryEmitter {
    /** Captured tool args keyed by toolCallId. */
    _argsCapture = new Map();
    /** AbortController for in-flight summary calls — aborted on agent_end. */
    _abort = null;
    _enabled;
    _isFeatureEnabled;
    _isEligibleTool;
    _maxConcurrent;
    _maxPerRun;
    _maxOutputTokens;
    _getShortcutLlmProxyClient;
    _onDiagnostic;
    _scheduledThisRun = 0;
    _inFlight = 0;
    _queue = [];
    constructor(options = {}) {
        this._enabled = options.enabled ?? true;
        this._isFeatureEnabled = options.isFeatureEnabled ?? (() => true);
        this._isEligibleTool = options.isEligibleTool ?? (() => true);
        this._maxConcurrent = options.maxConcurrent ?? TOOL_SUMMARY_MAX_CONCURRENT;
        this._maxPerRun = options.maxPerRun ?? TOOL_SUMMARY_MAX_PER_RUN;
        this._maxOutputTokens = options.maxOutputTokens ?? TOOL_SUMMARY_MAX_OUTPUT_TOKENS;
        this._getShortcutLlmProxyClient = options.getShortcutLlmProxyClient;
        this._onDiagnostic = options.onDiagnostic;
    }
    /**
     * Reset state at the start of a new agent run.
     */
    reset() {
        this._abort?.abort();
        this._abort = new AbortController();
        this._argsCapture.clear();
        this._scheduledThisRun = 0;
        this._inFlight = 0;
        this._queue = [];
    }
    /**
     * Capture tool arguments when a tool execution starts.
     */
    captureArgs(toolCallId, args) {
        this._argsCapture.set(toolCallId, args);
    }
    /**
     * On tool_execution_end, fire-and-forget a summary generation call.
     * Emits a ToolSummaryEvent via the provided callback if successful.
     *
     * This method owns the cheap preflight checks and queue admission. The async
     * worker below owns proxy failures, so the agent event pipeline never awaits
     * or throws on summary generation.
     */
    maybeGenerate(toolCallId, toolName, result, emit) {
        this._diagnose(toolCallId, toolName, 'seen');
        const args = this._argsCapture.get(toolCallId);
        this._argsCapture.delete(toolCallId);
        const signal = this._abort?.signal;
        if (!signal || signal.aborted) {
            this._diagnose(toolCallId, toolName, 'skipped', 'no-active-run');
            return;
        }
        if (!this._enabled) {
            this._diagnose(toolCallId, toolName, 'skipped', 'surface-disabled');
            return;
        }
        if (!this._isFeatureEnabled()) {
            this._diagnose(toolCallId, toolName, 'skipped', 'setting-disabled');
            return;
        }
        if (!this._isEligibleTool(toolName)) {
            this._diagnose(toolCallId, toolName, 'skipped', 'ineligible-tool');
            return;
        }
        if (this._scheduledThisRun >= this._maxPerRun) {
            this._diagnose(toolCallId, toolName, 'skipped', 'max-per-run');
            return;
        }
        // Count scheduled work before queueing so a long backlog cannot exceed the
        // per-run cap while waiting for concurrency slots.
        this._scheduledThisRun += 1;
        const start = () => {
            if (signal.aborted) {
                this._diagnose(toolCallId, toolName, 'skipped', 'aborted-before-start');
                return;
            }
            this._diagnose(toolCallId, toolName, 'started');
            this._inFlight += 1;
            void this._generateAndEmit(toolCallId, toolName, args, result, emit, signal).finally(() => {
                this._inFlight = Math.max(0, this._inFlight - 1);
                this._drainQueue();
            });
        };
        if (this._inFlight >= this._maxConcurrent) {
            this._queue.push(start);
            this._diagnose(toolCallId, toolName, 'queued', 'waiting-for-concurrency-slot');
            return;
        }
        start();
    }
    /**
     * Abort all in-flight summary calls and clear captured args.
     * Call on agent_end.
     */
    abort() {
        this._abort?.abort();
        this._abort = null;
        this._argsCapture.clear();
        this._inFlight = 0;
        this._queue = [];
    }
    _drainQueue() {
        const signal = this._abort?.signal;
        if (!signal || signal.aborted)
            return;
        while (this._inFlight < this._maxConcurrent && this._queue.length > 0) {
            const next = this._queue.shift();
            next?.();
        }
    }
    /**
     * Run one summary request and translate all outcomes into diagnostics/events.
     *
     * Empty summaries are diagnostic noise, auth/configuration failures are real
     * failures, and all cases stay best-effort so the main agent loop proceeds.
     */
    async _generateAndEmit(toolCallId, toolName, args, result, emit, signal) {
        try {
            if (signal.aborted) {
                this._diagnose(toolCallId, toolName, 'failed', 'aborted');
                return;
            }
            const shortcutLlmProxy = this._getShortcutLlmProxyClient?.();
            if (!shortcutLlmProxy) {
                this._diagnose(toolCallId, toolName, 'failed', 'missing-shortcut-llm-proxy-client');
                return;
            }
            const summaryResult = await generateToolSummaryDetailed({ toolName, args, result }, shortcutLlmProxy, {
                signal,
                maxOutputTokens: this._maxOutputTokens
            });
            if (summaryResult.summary && !signal.aborted) {
                this._diagnose(toolCallId, toolName, 'success');
                emit({ type: 'tool_summary', toolCallId, summary: summaryResult.summary });
            }
            else if (signal.aborted) {
                this._diagnose(toolCallId, toolName, 'failed', 'aborted');
            }
            else {
                const configurationFailure = summaryResult.failureReason === 'missing-auth' ||
                    summaryResult.failureReason === 'missing-base-url';
                const detailedReason = summaryResult.failureReason === 'http-error' && summaryResult.httpStatus
                    ? `http-error-${summaryResult.httpStatus}`
                    : summaryResult.failureReason === 'missing-auth'
                        ? 'missing-shortcut-api-key'
                        : summaryResult.failureReason === 'missing-base-url'
                            ? 'missing-shortcut-base-url'
                            : (summaryResult.failureReason ?? 'no-summary-text');
                this._diagnose(toolCallId, toolName, configurationFailure ? 'failed' : 'empty', detailedReason);
            }
        }
        catch {
            this._diagnose(toolCallId, toolName, 'failed', 'summary-request-error');
            // Best-effort — never block the agent loop
        }
    }
    _diagnose(toolCallId, toolName, status, reason) {
        this._onDiagnostic?.({ toolCallId, toolName, status, reason });
    }
}
//# sourceMappingURL=tool-summary-emitter.js.map