import { identifyTurns } from "./turns.js";
import { INPUT_COMPACTION_THRESHOLD_CHARS } from "./types.js";
/** Default placeholder when no summary is available for outputs */
const DEFAULT_OUTPUT_PLACEHOLDER = "output compacted";
// --- Type guards ---
function isToolMessage(msg) {
    return msg.role === "tool" && Array.isArray(msg.content);
}
function isAssistantMessage(msg) {
    return msg.role === "assistant" && Array.isArray(msg.content);
}
/**
 * Determines which tool message indices should be protected based on
 * the protectedTokens budget. Protects the most recent turns first.
 */
function getProtectedToolIndices(messages, protectedTokens) {
    const turns = identifyTurns(messages);
    const protected_ = new Set();
    let budget = protectedTokens;
    // Walk backward to protect most recent turns first
    for (let i = turns.length - 1; i >= 0 && budget > 0; i--) {
        const turn = turns[i];
        if (turn.toolMessageIndices.length === 0)
            continue;
        if (budget >= turn.tokenCount) {
            budget -= turn.tokenCount;
            for (const idx of turn.toolMessageIndices) {
                protected_.add(idx);
            }
        }
    }
    return protected_;
}
// --- Tool result compaction (for tool messages) ---
function compactToolResult(part, toolSummaries, protectedResults) {
    // Skip compaction for protected results
    if (protectedResults?.[part.toolCallId]) {
        return part;
    }
    const outputText = toolSummaries?.[part.toolCallId] ?? DEFAULT_OUTPUT_PLACEHOLDER;
    return {
        type: "tool-result",
        toolCallId: part.toolCallId,
        toolName: part.toolName,
        output: { type: "text", value: `[summarized] ${outputText}` },
    };
}
// --- Tool call compaction (for assistant messages) ---
function compactToolCall(part, toolSummaries) {
    const rawInput = typeof part.input === "string" ? part.input : JSON.stringify(part.input);
    // Only compact large inputs
    if (rawInput.length <= INPUT_COMPACTION_THRESHOLD_CHARS) {
        return part;
    }
    const summaryText = toolSummaries?.[part.toolCallId];
    // Preserve original top-level keys as placeholders so the LLM sees the correct
    // schema when reviewing compacted history.
    const skeleton = typeof part.input === "object" && part.input !== null
        ? Object.fromEntries(Object.keys(part.input).map((key) => [
            key,
            "[compacted]",
        ]))
        : {};
    // Input must be an object (dictionary) for LLM providers - wrap summary in object
    const input = {
        ...skeleton,
        _compacted: summaryText
            ? `[summarized: do not use this as tool input] ${summaryText}`
            : `[truncated: do not use this as tool input] ${rawInput.slice(0, INPUT_COMPACTION_THRESHOLD_CHARS - 3)}...`,
    };
    return {
        type: "tool-call",
        toolCallId: part.toolCallId,
        toolName: part.toolName,
        input,
    };
}
// --- Message compaction ---
function compactToolMessage(msg) {
    const toolSummaries = msg._superblocks?.toolSummaries;
    const protectedResults = msg._superblocks?.protected;
    const compactedContent = msg.content.map((part) => {
        if (part.type === "tool-result") {
            return compactToolResult(part, toolSummaries, protectedResults);
        }
        return part;
    });
    // Create new metadata without toolSummaries (cleanup after use)
    // Keep protected record as it's still needed for future reference
    const { toolSummaries: _, ...restSuperblocks } = msg._superblocks ?? {};
    return {
        role: "tool",
        content: compactedContent,
        _superblocks: {
            ...restSuperblocks,
            timestamp: msg._superblocks?.timestamp ?? Date.now(),
            summarized: true,
        },
    };
}
function compactAssistantMessage(msg) {
    const toolSummaries = msg._superblocks?.toolSummaries;
    const compactedContent = msg.content.map((part) => {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-call") {
            return compactToolCall(part, toolSummaries);
        }
        return part;
    });
    // Create new metadata without toolSummaries (cleanup after use)
    const { toolSummaries: _, ...restSuperblocks } = msg._superblocks ?? {};
    return {
        ...msg,
        content: compactedContent,
        _superblocks: restSuperblocks,
    };
}
export function summarizeToolOutputs(options) {
    const { messages, protectedTokens } = options;
    const protectedIndices = getProtectedToolIndices(messages, protectedTokens);
    let summarizedCount = 0;
    const result = messages.map((msg, i) => {
        // Compact unprotected tool messages
        // Individual tool results may still be protected via msg._superblocks?.protected[toolCallId]
        if (isToolMessage(msg) && !protectedIndices.has(i)) {
            summarizedCount++;
            return compactToolMessage(msg);
        }
        // Compact tool-call inputs in assistant messages (always unprotected)
        if (isAssistantMessage(msg)) {
            return compactAssistantMessage(msg);
        }
        return msg;
    });
    return { messages: result, summarizedCount };
}
/**
 * Check if a tool message has any unprotected results that can be compacted.
 * Returns true if the message has at least one result that is not protected.
 */
function hasUnprotectedResults(messages, index) {
    const msg = messages[index];
    if (!msg || msg.role !== "tool" || !Array.isArray(msg.content)) {
        return false;
    }
    const protectedRecord = msg._superblocks?.protected;
    // If no protection record, all results are unprotected
    if (!protectedRecord)
        return true;
    // Check if any tool result in this message is NOT protected
    for (const part of msg.content) {
        if (typeof part === "object" &&
            part !== null &&
            "type" in part &&
            part.type === "tool-result" &&
            "toolCallId" in part) {
            const toolCallId = part.toolCallId;
            if (!protectedRecord[toolCallId]) {
                return true;
            }
        }
    }
    return false;
}
/**
 * Calculates the total tokens that can be pruned by Phase 1.
 * This is the sum of token counts for turns with tool messages that have unprotected results.
 */
export function calculatePruneableTokens(messages, protectedTokens) {
    const turns = identifyTurns(messages);
    const protectedIndices = getProtectedToolIndices(messages, protectedTokens);
    let pruneable = 0;
    for (const turn of turns) {
        // Check if any tool messages in this turn can be compacted
        // (not protected by token budget AND has at least one unprotected result)
        const hasPruneableTools = turn.toolMessageIndices.some((idx) => !protectedIndices.has(idx) && hasUnprotectedResults(messages, idx));
        if (hasPruneableTools) {
            pruneable += turn.tokenCount;
        }
    }
    return pruneable;
}
//# sourceMappingURL=phase1-tool-summarizer.js.map