import { streamText } from "ai";
import { getPrefixedLogger } from "../../../util/logger.js";
import { LLMObsTracer } from "../../llmobs/tracer.js";
import { isTokenLimitError } from "../error.js";
import { COMPACTION_PROMPT, formatCompactionRequest, formatSummaryMessage, } from "./prompts/compaction.js";
import { createTruncationFallback } from "./truncation-fallback.js";
import { toStoredMessage } from "./types.js";
function getModelId(model) {
    if (typeof model === "string")
        return model;
    return model.modelId ?? "unknown";
}
function getProviderId(model) {
    if (typeof model === "string")
        return "string-model";
    return model.provider ?? "unknown";
}
const logger = getPrefixedLogger("[phase2-session-summarizer]");
function getMediaType(value) {
    if (value.mediaType) {
        return value.mediaType;
    }
    if (value.type === "media" && value.data?.startsWith("data:")) {
        const match = value.data.match(/^data:([^;,]+)/);
        return match?.[1] ?? null;
    }
    return null;
}
function formatMediaTag(mediaType) {
    if (!mediaType)
        return "[media]";
    if (mediaType.startsWith("image/"))
        return "[image]";
    if (mediaType.startsWith("video/"))
        return "[video]";
    if (mediaType.startsWith("audio/"))
        return "[audio]";
    if (mediaType === "application/pdf")
        return "[pdf]";
    return `[media: ${mediaType}]`;
}
function stripMedia(msg) {
    if (!Array.isArray(msg.content)) {
        return msg;
    }
    const strippedContent = msg.content.map((part) => {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "image") {
            return { type: "text", text: "[image]" };
        }
        if (part.type === "tool-result" &&
            part.output?.type === "content" &&
            Array.isArray(part.output.value)) {
            const firstMediaType = part.output.value
                .map(getMediaType)
                .find((t) => t !== null);
            if (firstMediaType) {
                const tag = formatMediaTag(firstMediaType);
                return { type: "text", text: tag };
            }
        }
        return part;
    });
    return { ...msg, content: strippedContent };
}
function stripProviderOptions(msg) {
    if (!Array.isArray(msg.content)) {
        return msg;
    }
    const strippedContent = msg.content.map((part) => {
        if (part.type === "reasoning") {
            const { providerOptions, ...rest } = part;
            return rest;
        }
        return part;
    });
    return { ...msg, content: strippedContent };
}
function stripSuperblocksMeta({ _superblocks, ...msg }) {
    return msg;
}
/**
 * Summarizes a session, automatically handling oversized inputs by recursively
 * splitting messages in half until they fit within the model's context window.
 */
export async function summarizeSession(options) {
    const { messages, model, targetTokens, abortSignal, tracer = LLMObsTracer.instance, } = options;
    return tracer.trace({ name: "session.summarize", kind: "task" }, async (summarizeSpan) => {
        async function callLLM(msgs) {
            return tracer.trace({ name: "session.summarize.llm", kind: "task" }, async (llmSpan) => {
                const cleanedMessages = msgs
                    .map(stripMedia)
                    .map(stripProviderOptions)
                    .map(stripSuperblocksMeta);
                // Serialize messages as JSON to avoid provider-specific format issues
                // (e.g., reasoning blocks, signatures) that cause errors on Bedrock/Vertex
                const messagesJson = JSON.stringify(cleanedMessages, null, 2);
                const userMessage = formatCompactionRequest(messagesJson);
                // Capture API errors via onError callback.
                // The Vercel AI SDK silently swallows errors like "prompt is too long" -
                // the stream just ends without throwing. Using onError is the only way
                // to capture the real error for proper handling (e.g., token limit detection).
                let capturedError = null;
                const inputMetadata = {
                    messageCount: msgs.length,
                    promptLength: userMessage.length,
                };
                const streamResult = streamText({
                    model,
                    system: COMPACTION_PROMPT,
                    messages: [{ role: "user", content: userMessage }],
                    maxOutputTokens: 8192,
                    abortSignal,
                    onError: (event) => {
                        capturedError = event.error;
                    },
                });
                let summary = "";
                for await (const part of streamResult.textStream) {
                    summary += part;
                }
                if (capturedError) {
                    tracer.annotate(llmSpan, {
                        metadata: {
                            input: inputMetadata,
                        },
                        error: capturedError,
                    });
                    throw capturedError;
                }
                const [usage, finishReason, warnings, response] = await Promise.all([
                    streamResult.usage,
                    streamResult.finishReason,
                    streamResult.warnings,
                    streamResult.response,
                ]);
                if (!summary) {
                    const debugInfo = {
                        model: {
                            id: getModelId(model),
                            provider: getProviderId(model),
                        },
                        messageCount: msgs.length,
                        finishReason,
                        usage: {
                            inputTokens: usage?.inputTokens,
                            outputTokens: usage?.outputTokens,
                            cachedInputTokens: usage?.cachedInputTokens,
                        },
                        warnings,
                        response: {
                            id: response?.id,
                            modelId: response?.modelId,
                            timestamp: response?.timestamp,
                            headers: response?.headers,
                        },
                    };
                    logger.warn("Summarization produced empty summary - debug info:", debugInfo);
                    throw new Error(`Summarization produced empty summary. finishReason=${finishReason}, ` +
                        `model=${getModelId(model)}, inputTokens=${usage?.inputTokens}, ` +
                        `outputTokens=${usage?.outputTokens}`);
                }
                tracer.annotate(llmSpan, {
                    metadata: {
                        input: inputMetadata,
                        result: {
                            summaryLength: summary.length,
                            inputTokens: usage?.inputTokens,
                            outputTokens: usage?.outputTokens,
                            finishReason,
                        },
                    },
                });
                const summaryWithPreamble = formatSummaryMessage(summary);
                const summaryMessage = toStoredMessage({ role: "user", content: summaryWithPreamble }, { isSummary: true, tokenCount: usage?.outputTokens });
                return {
                    summary,
                    messages: [summaryMessage],
                };
            });
        }
        async function summarizeWithSplitting(msgs) {
            // Guard against infinite recursion - need at least 2 messages to split
            if (msgs.length < 2) {
                return callLLM(msgs);
            }
            try {
                const result = await callLLM(msgs);
                return result;
            }
            catch (error) {
                if (!isTokenLimitError(error)) {
                    logger.error(`LLM summarization failed. Falling back to truncation. Error: ${JSON.stringify(error)}`, error);
                    return createTruncationFallback(messages, tracer);
                }
                // Token limit exceeded - split messages in half and summarize recursively
                const midpoint = Math.floor(msgs.length / 2);
                const firstHalf = msgs.slice(0, midpoint);
                const secondHalf = msgs.slice(midpoint);
                logger.warn(`Token limit exceeded with ${msgs.length} messages. ` +
                    `Splitting: first half (${firstHalf.length}), second half (${secondHalf.length})`);
                // Each recursive split gets its own session.summarize span
                const firstResult = await tracer.trace({ name: "session.summarize.split", kind: "task" }, async (splitSpan) => {
                    tracer.annotate(splitSpan, {
                        metadata: {
                            input: {
                                messageCount: firstHalf.length,
                                splitHalf: "first",
                            },
                        },
                    });
                    return summarizeWithSplitting(firstHalf);
                });
                return tracer.trace({ name: "session.summarize.split", kind: "task" }, async (splitSpan) => {
                    tracer.annotate(splitSpan, {
                        metadata: {
                            input: {
                                messageCount: firstResult.messages.length + secondHalf.length,
                                splitHalf: "combined",
                            },
                        },
                    });
                    return summarizeWithSplitting([
                        ...firstResult.messages,
                        ...secondHalf,
                    ]);
                });
            }
        }
        const result = await summarizeWithSplitting(messages);
        tracer.annotate(summarizeSpan, {
            metadata: {
                input: {
                    messageCount: messages.length,
                },
                config: {
                    targetTokens,
                },
                result: {
                    messageCount: result.messages.length,
                    summary: result.summary,
                    summaryLength: result.summary.length,
                },
            },
        });
        return result;
    });
}
//# sourceMappingURL=phase2-session-summarizer.js.map