/**
 * Sync (fire-and-forget) LangChain callback handler.
 */
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
import { OrqTracesClient } from "./client.mjs";
import { Events } from "./events.mjs";
import { EventType, createInFlightEvent } from "./models.mjs";
import { buildOtlpSpan } from "./span-builder.mjs";
import { coerceChainPayload, extractAssistantToolCalls, extractModelName, extractModelParameters, extractTokenUsage, extractToolName, formatError, nanoTimestamp, normalizeMessages, resolveSpanName, rootOutputDelta, stringifyToolOutput, } from "./utils.mjs";
export class OrqLangchainCallback extends BaseCallbackHandler {
    constructor(apiKey, apiUrl = "https://my.orq.ai") {
        super();
        this.name = "OrqLangchainCallback";
        this._events = new Events();
        this._client = new OrqTracesClient(apiKey, apiUrl);
    }
    _startEvent(runId, parentRunId, eventType, serialized, metadata, tags, spanName) {
        this._events.mapParent(runId, parentRunId);
        const traceId = this._events.getTraceId(runId);
        const spanId = this._events.getSpanId(runId);
        const parentSpanId = parentRunId
            ? this._events.getSpanId(parentRunId)
            : null;
        const event = createInFlightEvent({
            runId: spanId,
            parentRunId: parentSpanId,
            traceId,
            eventType,
            name: resolveSpanName(spanName, metadata, serialized),
            startTimeNs: nanoTimestamp(),
            serialized,
            metadata: metadata ?? null,
            tags: tags ?? null,
        });
        this._events.store(runId, event);
        return event;
    }
    _finishAndSend(runId) {
        const event = this._events.get(runId);
        if (!event)
            return;
        if (!event.endTimeNs) {
            event.endTimeNs = nanoTimestamp();
        }
        const span = buildOtlpSpan(event);
        this._client.sendSpan(span);
        this._events.remove(runId);
        this._events.clearRoot(runId);
    }
    // ── LLM callbacks ──────────────────────────────────────────────
    handleLLMStart(llm, prompts, runId, parentRunId, extraParams, tags, metadata, _runName) {
        try {
            const event = this._startEvent(runId, parentRunId, EventType.LLM, llm, metadata, tags);
            event.prompts = prompts;
            event.modelName = extractModelName(llm, extraParams);
            event.modelParameters = extractModelParameters(extraParams);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleChatModelStart(llm, messages, runId, parentRunId, extraParams, tags, metadata, _runName) {
        try {
            const event = this._startEvent(runId, parentRunId, EventType.LLM, llm, metadata, tags);
            event.messages = normalizeMessages(messages);
            event.modelName = extractModelName(llm, extraParams);
            event.modelParameters = extractModelParameters(extraParams);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleLLMNewToken(token, _idx, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (event) {
                if (event.streamingTokens === null) {
                    event.streamingTokens = [];
                }
                event.streamingTokens.push(token);
            }
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleLLMEnd(output, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            event.tokenUsage = extractTokenUsage(output);
            const choices = [];
            if (output.generations?.length) {
                const firstGen = output.generations[0];
                if (firstGen) {
                    for (let idx = 0; idx < firstGen.length; idx++) {
                        const gen = firstGen[idx];
                        const genInfo = gen.generationInfo;
                        const finishReason = genInfo?.["finish_reason"] ?? null;
                        let content = gen.text;
                        if (!content && event.streamingTokens) {
                            content = event.streamingTokens.join("");
                        }
                        const message = {
                            role: "assistant",
                            content,
                        };
                        const toolCalls = extractAssistantToolCalls(gen["message"]);
                        if (toolCalls) {
                            message["tool_calls"] = toolCalls;
                        }
                        choices.push({
                            index: idx,
                            message,
                            finish_reason: finishReason,
                        });
                    }
                }
            }
            event.responseChoices = choices;
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleLLMError(err, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            const formatted = formatError(err);
            event.error = {
                type: err.constructor.name,
                message: formatted.message,
                traceback: formatted.traceback,
            };
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    // ── Chain callbacks ────────────────────────────────────────────
    handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName) {
        try {
            const event = this._startEvent(runId, parentRunId, EventType.CHAIN, chain, metadata, tags, runName);
            event.inputs = coerceChainPayload(inputs);
            if (parentRunId == null) {
                this._events.setRootIfNeeded(runId);
            }
            else if (this._events.isGraphNode(runId)) {
                const spanId = this._events.getSpanId(runId);
                const rootRunId = this._events.rootRunId;
                this._events.graph.onNodeStart(event.name, spanId, rootRunId);
            }
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleChainEnd(outputs, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            let outputsDict = coerceChainPayload(outputs);
            if (this._events.isRoot(runId)) {
                outputsDict = rootOutputDelta(event.inputs, outputsDict);
            }
            event.outputs = outputsDict;
            if (this._events.isGraphNode(runId)) {
                this._events.graph.onNodeEnd(event.name, this._events.rootRunId);
            }
            if (this._events.isRoot(runId) && this._events.graph.hasNodes()) {
                event.graphJson = this._events.graph.build(runId, this._events.getSpanId(runId));
            }
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleChainError(err, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            const formatted = formatError(err);
            event.error = {
                type: err.constructor.name,
                message: formatted.message,
                traceback: formatted.traceback,
            };
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    // ── Tool callbacks ─────────────────────────────────────────────
    handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
        try {
            const serialized = tool;
            const toolName = runName ?? extractToolName(serialized) ?? undefined;
            const event = this._startEvent(runId, parentRunId, EventType.TOOL, serialized, metadata, tags, toolName);
            event.toolInput = input;
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleToolEnd(output, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            event.toolOutput = stringifyToolOutput(output);
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleToolError(err, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            const formatted = formatError(err);
            event.error = {
                type: err.constructor.name,
                message: formatted.message,
                traceback: formatted.traceback,
            };
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    // ── Retriever callbacks ────────────────────────────────────────
    handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, _name) {
        try {
            const event = this._startEvent(runId, parentRunId, EventType.RETRIEVAL, retriever, metadata, tags);
            event.query = query;
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleRetrieverEnd(documents, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            event.documents = documents.map((doc) => ({
                page_content: doc.pageContent,
                metadata: doc.metadata,
            }));
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleRetrieverError(err, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            const formatted = formatError(err);
            event.error = {
                type: err.constructor.name,
                message: formatted.message,
                traceback: formatted.traceback,
            };
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    // ── Agent callbacks ────────────────────────────────────────────
    handleAgentAction(action, runId, parentRunId, _tags) {
        try {
            let event = this._events.get(runId);
            if (!event) {
                event = this._startEvent(runId, parentRunId, EventType.AGENT, {}, undefined, undefined, "agent");
            }
            if (event.agentActions === null) {
                event.agentActions = [];
            }
            event.agentActions.push({
                tool: action.tool,
                tool_input: String(action.toolInput),
                log: action.log,
            });
        }
        catch {
            // silently ignore tracing errors
        }
    }
    handleAgentEnd(finish, runId, _parentRunId, _tags) {
        try {
            const event = this._events.get(runId);
            if (!event)
                return;
            event.endTimeNs = nanoTimestamp();
            event.agentFinish = {
                output: String(finish.returnValues),
                log: finish.log,
            };
            event.outputs = finish.returnValues;
            this._finishAndSend(runId);
        }
        catch {
            // silently ignore tracing errors
        }
    }
    close() {
        return this._client.close();
    }
}
//# sourceMappingURL=handler.mjs.map