import { Agent } from "@earendil-works/pi-agent-core";
import { createAssistantMessageEventStream, isContextOverflow, streamSimple } from "@earendil-works/pi-ai/compat";
import { formatCopilotProviderError } from "../copilot-errors.js";
import { CONTEXT_COMPACTION_AUTO_QUERY } from "./context-compaction-types.js";
import { createContextCompactionBudgetDetails, contextCompactionProgressKey, contextCompactionProgressPercent, contextCompactionTargetLabel, contextCompactionTargetMet, formatErrorMessage, } from "./context-compaction-metrics.js";
import { getTranscriptCompactionParameters, normalizeContextCompactionParameters } from "./context-compaction-strategy.js";
import { CONTEXT_COMPACTION_BUDGET_TOOL_NAME, CONTEXT_DELETE_TOOL_NAME, CONTEXT_GREP_DELETE_TOOL_NAME, CONTEXT_READ_ENTRY_TOOL_NAME, CONTEXT_SEARCH_TRANSCRIPT_TOOL_NAME, } from "./context-deletion-tool-definitions.js";
import { createContextDeletionTool } from "./context-deletion-tools.js";
import { buildContextCompactionPrompt, CONTEXT_COMPACTION_SYSTEM_PROMPT, writeContextCompactionTranscriptFile, } from "./context-compaction-prompt.js";
import { CONTEXT_COMPACTION_CRITICAL_OVERFLOW_PROMPT, criticalCompactionParameters, relaxTranscriptForCriticalEviction, } from "./context-compaction-critical.js";
import { runDeterministicContextEviction } from "./context-compaction-eviction.js";
export const CONTEXT_COMPACTION_MAX_TURNS = 50;
export const CONTEXT_COMPACTION_MAX_PLANNER_NUDGES = 50;
function createContextCompactionTargetNudgeMessage(result, parameters) {
    const currentReductionPercent = contextCompactionProgressPercent(result);
    const targetLabel = contextCompactionTargetLabel(parameters);
    const tokensToDelete = result
        ? createContextCompactionBudgetDetails(result.stats, 0, undefined, parameters, 0, 0).tokensToDeleteForTarget
        : undefined;
    const remainingText = tokensToDelete !== undefined ? ` Delete about ${tokensToDelete} more token(s) if safe candidates exist.` : "";
    return {
        role: "user",
        content: [
            {
                type: "text",
                text: `The strict ${targetLabel} context-reduction requirement is not met yet; current validated reduction is ${currentReductionPercent}%.${remainingText} Continue removing low-value message entries or message content blocks using ${CONTEXT_DELETE_TOOL_NAME} or ${CONTEXT_GREP_DELETE_TOOL_NAME}. Use the focus query ${JSON.stringify(parameters.query)} to preserve relevant context. Call ${CONTEXT_COMPACTION_BUDGET_TOOL_NAME} to verify progress, and do not provide a final answer until the validated reduction is at least ${targetLabel}.`,
            },
        ],
        timestamp: Date.now(),
    };
}
function createContextCompactionAssistantMessage(model, content, stopReason, errorMessage) {
    return {
        role: "assistant",
        content,
        api: model.api,
        provider: model.provider,
        model: model.id,
        usage: {
            input: 0,
            output: 0,
            cacheRead: 0,
            cacheWrite: 0,
            totalTokens: 0,
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
        },
        stopReason,
        ...(errorMessage !== undefined ? { errorMessage } : {}),
        timestamp: Date.now(),
    };
}
function createContextCompactionStopStream(model, text) {
    const stream = createAssistantMessageEventStream();
    queueMicrotask(() => {
        const message = createContextCompactionAssistantMessage(model, [{ type: "text", text }], "stop");
        stream.push({ type: "done", reason: "stop", message });
        stream.end(message);
    });
    return stream;
}
function isContextCompactionOverflowError(model, errorMessage) {
    return isContextOverflow(createContextCompactionAssistantMessage(model, [], "error", errorMessage), model.contextWindow);
}
function feasibleAccepted(result, acceptanceTokenBudget) {
    return (acceptanceTokenBudget !== undefined &&
        result !== undefined &&
        result.deletedTargets.length > 0 &&
        result.stats.tokensAfter <= acceptanceTokenBudget);
}
function acceptedWithinRun(result, parameters, acceptanceTokenBudget) {
    if (acceptanceTokenBudget !== undefined)
        return feasibleAccepted(result, acceptanceTokenBudget);
    return contextCompactionTargetMet(result, parameters);
}
function targetMetAcceptedForLadder(run, parameters, ladder) {
    if (!hasMetContextCompactionTarget(run, parameters))
        return false;
    const overflowBudget = ladder?.criticalEvictionTokenBudget;
    return overflowBudget === undefined || run.validatedResult.stats.tokensAfter <= overflowBudget;
}
async function runContextDeletionAssistant(inputTranscript, model, apiKey, headers, signal, thinkingLevel = "off", parameters = getTranscriptCompactionParameters(inputTranscript), options = {}) {
    const transcript = { ...inputTranscript, parameters };
    const maxTokens = model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY;
    if (signal?.aborted)
        throw new Error("Context compaction failed: Request was aborted");
    const transcriptFile = writeContextCompactionTranscriptFile(transcript);
    const promptText = buildContextCompactionPrompt(transcript, transcriptFile.path, parameters) + (options.promptSuffix ?? "");
    const promptMessage = {
        role: "user",
        content: [{ type: "text", text: promptText }],
        timestamp: Date.now(),
    };
    const deletionTool = createContextDeletionTool(transcript, { contextWindow: model.contextWindow, ...parameters });
    let providerTurnCount = 0;
    const agent = new Agent({
        initialState: {
            systemPrompt: CONTEXT_COMPACTION_SYSTEM_PROMPT,
            model,
            thinkingLevel,
            tools: deletionTool.tools,
        },
        toolExecution: "parallel",
        streamFn: async (requestModel, context, streamOptions) => {
            const currentResult = deletionTool.getValidatedResult();
            if (acceptedWithinRun(currentResult, parameters, options.acceptanceTokenBudget)) {
                return createContextCompactionStopStream(requestModel, `Reached an acceptable context-reduction result (${currentResult.stats.percentReduction}%, ${currentResult.stats.tokensAfter} token(s) after deletion); using the validated deletions recorded so far.`);
            }
            if (providerTurnCount >= CONTEXT_COMPACTION_MAX_TURNS) {
                return createContextCompactionStopStream(requestModel, `Reached the context compaction turn cap (${CONTEXT_COMPACTION_MAX_TURNS}); using the validated deletions recorded so far.`);
            }
            providerTurnCount += 1;
            return streamSimple(requestModel, context, {
                ...streamOptions,
                maxTokens,
                apiKey,
                headers: headers ?? streamOptions?.headers,
            });
        },
    });
    let lastNudgedProgressKey;
    let nudgeCount = 0;
    const unsubscribeNudge = agent.subscribe((event, eventSignal) => {
        if (event.type !== "turn_end" || signal?.aborted || eventSignal.aborted)
            return;
        if (event.message.role !== "assistant")
            return;
        if (event.message.stopReason === "error" || event.message.stopReason === "aborted")
            return;
        if (event.message.content.some((content) => content.type === "toolCall"))
            return;
        if (nudgeCount >= CONTEXT_COMPACTION_MAX_PLANNER_NUDGES)
            return;
        const currentResult = deletionTool.getValidatedResult();
        if (acceptedWithinRun(currentResult, parameters, options.acceptanceTokenBudget))
            return;
        const progressKey = contextCompactionProgressKey(currentResult);
        if (progressKey === lastNudgedProgressKey)
            return;
        lastNudgedProgressKey = progressKey;
        nudgeCount += 1;
        agent.followUp(createContextCompactionTargetNudgeMessage(currentResult, parameters));
    });
    const abortOnSignal = () => agent.abort();
    signal?.addEventListener("abort", abortOnSignal, { once: true });
    try {
        await agent.prompt(promptMessage);
    }
    catch (error) {
        if (signal?.aborted)
            throw new Error("Context compaction failed: Request was aborted");
        const errorMessage = formatErrorMessage(error);
        const formattedErrorMessage = formatCopilotProviderError(model.provider, errorMessage);
        if (isContextCompactionOverflowError(model, errorMessage)) {
            return {
                validatedResult: deletionTool.getValidatedResult(),
                lastToolError: deletionTool.getLastError(),
                providerError: formattedErrorMessage,
                providerOverflow: true,
            };
        }
        throw new Error(`Context compaction failed: ${formattedErrorMessage}`);
    }
    finally {
        signal?.removeEventListener("abort", abortOnSignal);
        unsubscribeNudge();
        transcriptFile.cleanup();
    }
    if (signal?.aborted)
        throw new Error("Context compaction failed: Request was aborted");
    if (agent.state.errorMessage) {
        const formattedErrorMessage = formatCopilotProviderError(model.provider, agent.state.errorMessage);
        if (isContextCompactionOverflowError(model, agent.state.errorMessage)) {
            return {
                validatedResult: deletionTool.getValidatedResult(),
                lastToolError: deletionTool.getLastError(),
                providerError: formattedErrorMessage === agent.state.errorMessage ? undefined : formattedErrorMessage,
                providerOverflow: true,
            };
        }
        throw new Error(`Context compaction failed: ${formattedErrorMessage}`);
    }
    if (deletionTool.getCallCount() === 0) {
        throw new Error(`Context compaction did not call any transcript inspection, budget, or deletion tools (${CONTEXT_SEARCH_TRANSCRIPT_TOOL_NAME}, ${CONTEXT_READ_ENTRY_TOOL_NAME}, ${CONTEXT_COMPACTION_BUDGET_TOOL_NAME}, ${CONTEXT_DELETE_TOOL_NAME}, or ${CONTEXT_GREP_DELETE_TOOL_NAME})`);
    }
    return {
        validatedResult: deletionTool.getValidatedResult(),
        lastToolError: deletionTool.getLastError(),
        providerError: undefined,
        providerOverflow: false,
    };
}
function hasMetContextCompactionTarget(run, parameters) {
    return contextCompactionTargetMet(run.validatedResult, parameters);
}
function formatContextCompactionTargetFailureMessage(attempts, parameters) {
    const targetLabel = contextCompactionTargetLabel(parameters);
    if (attempts.length === 0)
        return `Context compaction did not meet the strict ${targetLabel} reduction requirement`;
    const attemptDetails = attempts
        .map((attempt) => {
        const reduction = contextCompactionProgressPercent(attempt.validatedResult);
        const deletionCount = attempt.validatedResult?.deletedTargets.length ?? 0;
        const tokensAfterText = attempt.validatedResult ? `; tokens after: ${attempt.validatedResult.stats.tokensAfter}` : "";
        const toolErrorText = attempt.lastToolError ? `; last deletion tool error: ${attempt.lastToolError}` : "";
        const providerErrorText = attempt.providerError ? `; provider error: ${attempt.providerError}` : "";
        return `attempt reached ${reduction}% with ${deletionCount} validated deletion target(s)${tokensAfterText}${toolErrorText}${providerErrorText}`;
    })
        .join("; ");
    return `Context compaction did not meet the strict ${targetLabel} reduction requirement; ${attemptDetails}`;
}
function runFailedAttempt(error) {
    return { validatedResult: undefined, lastToolError: undefined, providerError: error.message, providerOverflow: false };
}
function shouldRethrowPlannerError(error, signal, ladder) {
    if (signal?.aborted)
        return true;
    if (error.message === "Context compaction failed: Request was aborted")
        return true;
    return ladder?.criticalEvictionTokenBudget === undefined;
}
export async function contextCompact(preparation, model, apiKey, headers, signal, thinkingLevel = "off", ladder) {
    const parameters = normalizeContextCompactionParameters(preparation.parameters ?? preparation.transcript.parameters, preparation.parameters?.query ?? preparation.transcript.parameters?.query ?? CONTEXT_COMPACTION_AUTO_QUERY);
    const transcript = { ...preparation.transcript, parameters };
    const attempts = [];
    let skipCriticalPlanner = false;
    try {
        const standardRun = await runContextDeletionAssistant(transcript, model, apiKey, headers, signal, thinkingLevel, parameters);
        if (targetMetAcceptedForLadder(standardRun, parameters, ladder))
            return standardRun.validatedResult;
        if (feasibleAccepted(standardRun.validatedResult, ladder?.acceptanceTokenBudget))
            return standardRun.validatedResult;
        skipCriticalPlanner = standardRun.providerOverflow;
        attempts.push({ ...standardRun });
    }
    catch (error) {
        const formattedError = error instanceof Error ? error : new Error(formatErrorMessage(error));
        if (shouldRethrowPlannerError(formattedError, signal, ladder))
            throw formattedError;
        attempts.push(runFailedAttempt(formattedError));
    }
    if (ladder?.criticalEvictionTokenBudget === undefined) {
        throw new Error(formatContextCompactionTargetFailureMessage(attempts, parameters));
    }
    if (!skipCriticalPlanner) {
        try {
            const criticalParameters = criticalCompactionParameters(parameters);
            const criticalTranscript = relaxTranscriptForCriticalEviction(transcript);
            const criticalRun = await runContextDeletionAssistant(criticalTranscript, model, apiKey, headers, signal, thinkingLevel, criticalParameters, {
                promptSuffix: CONTEXT_COMPACTION_CRITICAL_OVERFLOW_PROMPT,
                acceptanceTokenBudget: ladder.criticalEvictionTokenBudget,
            });
            if (targetMetAcceptedForLadder(criticalRun, parameters, ladder))
                return criticalRun.validatedResult;
            if (feasibleAccepted(criticalRun.validatedResult, ladder.criticalEvictionTokenBudget))
                return criticalRun.validatedResult;
            attempts.push({ ...criticalRun });
        }
        catch (error) {
            const formattedError = error instanceof Error ? error : new Error(formatErrorMessage(error));
            if (signal?.aborted || formattedError.message === "Context compaction failed: Request was aborted")
                throw formattedError;
            attempts.push(runFailedAttempt(formattedError));
        }
    }
    try {
        return runDeterministicContextEviction(transcript, ladder.criticalEvictionTokenBudget);
    }
    catch (error) {
        const message = error instanceof Error ? error.message : formatErrorMessage(error);
        throw new Error(`${formatContextCompactionTargetFailureMessage(attempts, parameters)}; ${message}`);
    }
}
//# sourceMappingURL=context-compaction-runner.js.map