"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DiscardMessagesHandler = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const session_inputs_service_1 = require("../../session-inputs/session-inputs.service");
const core_entities_1 = require("../../core-entities");
const history_compression_service_1 = require("../history-compression.service");
const get_messages_handler_1 = require("./get-messages.handler");
const utils_1 = require("../../utils");
let DiscardMessagesHandler = class DiscardMessagesHandler {
    constructor(sessionInputsService, sessionInputsRepository, historyCompressionService) {
        this.sessionInputsService = sessionInputsService;
        this.sessionInputsRepository = sessionInputsRepository;
        this.historyCompressionService = historyCompressionService;
        this.toolName = 'discard_messages';
    }
    getMetadata() {
        return {
            name: this.toolName,
            description: 'Discard messages from the LLM context window to extend session life. Discarded messages are excluded from future LLM calls but preserved in history. After discarding, a follow-up turn starts automatically so you can continue working. Call get_messages first to identify short_ids, then call discard_messages with IDs to remove.',
            arguments: [
                {
                    name: 'input_ids',
                    type: 'string',
                    description: 'Comma-separated list of short_ids to discard (e.g., "a1b90,cd3ef"). Use get_messages to find short_ids.',
                    required: true,
                },
                {
                    name: 'reason',
                    type: 'string',
                    description: 'Brief explanation (2-3 sentences max) of what is being discarded, what is kept, and why.',
                    required: true,
                },
            ],
        };
    }
    getDefinition() {
        return `## discard_messages

Discard messages from the LLM context window to extend session life. Discarded messages are excluded from future LLM calls but preserved in history. After discarding, a follow-up turn starts automatically with reduced context so you can continue your task. Call \`get_messages\` first to identify short_ids, then call \`discard_messages\` with the IDs to remove.

**Important:** Check \`total_est_tokens\`, \`target_tokens\`, and \`floor_tokens\` from \`get_messages\` output. Cherry-pick individual messages until \`total_est_tokens\` is near \`target_tokens\`. **Never discard below \`floor_tokens\`.**

### Parameters:
- \`input_ids\` (string, required): Comma-separated list of short_ids to discard (e.g., "a1b90,cd3ef,ghi45"). Use \`get_messages\` to find short_ids.
- \`reason\` (string, required): Brief explanation (2-3 sentences max) of what is being discarded, what is kept, and why.

### Returns:
JSON object with:
- \`affected\`: Number of messages discarded
- \`short_ids\`: List of short_ids that were discarded
- \`reason\`: Your reason for discarding
- \`discarded_tokens\`: Estimated tokens freed by discarding
- \`new_total_tokens\`: Estimated total tokens remaining after discarding
- \`target_tokens\`: Target token count (${get_messages_handler_1.CONTEXT_TARGET_TOKENS})
- \`floor_tokens\`: Minimum token count (${get_messages_handler_1.CONTEXT_FLOOR_TOKENS})
- \`auto_discarded_get_messages\`: Number of get_messages calls that were auto-discarded

### Example:
\`\`\`typescript
tool: discard_messages
args: {
  input_ids: "a1b90,cd3ef,ghi45",
  reason: "Discarding old request_context calls and completed run_command outputs to free up context window. Keeping recent user prompts and code changes."
}
\`\`\``;
    }
    async estimateTokens(inputs) {
        if (inputs.length === 0) {
            return { totalTokens: 0, perInputTokens: [] };
        }
        const { history, turnToInputIndex } = (0, get_messages_handler_1.buildHistory)(inputs);
        const compressedHistory = await this.historyCompressionService.compress(history);
        const compressedCharSizes = new Array(inputs.length).fill(0);
        for (let i = 0; i < compressedHistory.length; i++) {
            const turnCharSize = (0, get_messages_handler_1.computeTurnCharSize)(compressedHistory[i]);
            const inputIdx = turnToInputIndex[i];
            if (inputIdx >= 0 && inputIdx < inputs.length) {
                compressedCharSizes[inputIdx] += turnCharSize;
            }
        }
        let ratio = get_messages_handler_1.DEFAULT_CHARS_PER_TOKEN;
        let ratioInputIndex = -1;
        for (let i = inputs.length - 1; i >= 0; i--) {
            if (inputs[i].role === 'model' &&
                inputs[i].input_token_count != null &&
                inputs[i].input_token_count > 0) {
                ratioInputIndex = i;
                break;
            }
        }
        if (ratioInputIndex >= 0) {
            let cumulativeChars = 0;
            for (let i = 0; i <= ratioInputIndex; i++) {
                cumulativeChars += compressedCharSizes[i];
            }
            if (cumulativeChars > 0) {
                ratio = cumulativeChars / inputs[ratioInputIndex].input_token_count;
            }
        }
        const perInputTokens = compressedCharSizes.map((cs) => cs > 0 ? Math.round(cs / ratio) : 0);
        const totalTokens = perInputTokens.reduce((sum, t) => sum + t, 0);
        return { totalTokens, perInputTokens };
    }
    async execute(args, context) {
        try {
            const { input_ids, reason } = args;
            if (!input_ids ||
                typeof input_ids !== 'string' ||
                input_ids.trim() === '') {
                return {
                    status: 'FAILURE',
                    summary: 'Failed to discard messages',
                    error_message: 'input_ids is required and must be a non-empty comma-separated string of short_ids',
                    execution_log: {
                        output: '',
                        error_message: 'input_ids is required and must be a non-empty comma-separated string of short_ids',
                    },
                };
            }
            if (!reason || typeof reason !== 'string' || reason.trim() === '') {
                return {
                    status: 'FAILURE',
                    summary: 'Failed to discard messages',
                    error_message: 'reason is required and must be a non-empty string explaining what is discarded, what is kept, and why',
                    execution_log: {
                        output: '',
                        error_message: 'reason is required and must be a non-empty string explaining what is discarded, what is kept, and why',
                    },
                };
            }
            const shortIds = input_ids
                .split(',')
                .map((id) => id.trim())
                .filter((id) => id.length > 0);
            if (shortIds.length === 0) {
                return {
                    status: 'FAILURE',
                    summary: 'Failed to discard messages',
                    error_message: 'No valid IDs provided after parsing input_ids',
                    execution_log: {
                        output: '',
                        error_message: 'No valid IDs provided after parsing input_ids',
                    },
                };
            }
            const allInputs = await this.sessionInputsService.findAllBySessionId(context.session_id);
            const shortToFull = new Map();
            for (const inp of allInputs) {
                shortToFull.set((0, utils_1.toShortId)(inp.id), inp.id);
            }
            const resolvedIds = [];
            const unresolved = [];
            for (const shortId of shortIds) {
                const fullId = shortToFull.get(shortId);
                if (fullId) {
                    resolvedIds.push(fullId);
                }
                else {
                    unresolved.push(shortId);
                }
            }
            if (unresolved.length > 0) {
                return {
                    status: 'FAILURE',
                    summary: 'Failed to discard messages',
                    error_message: `Could not resolve short_ids: ${unresolved.join(', ')}. Use get_messages to find valid short_ids.`,
                    execution_log: {
                        output: '',
                        error_message: `Could not resolve short_ids: ${unresolved.join(', ')}`,
                    },
                };
            }
            const getMessagesInputIds = allInputs
                .filter((inp) => inp.role === 'model' &&
                inp.aiActions &&
                inp.aiActions.length > 0 &&
                inp.aiActions.every((a) => a.action_type === 'get_messages'))
                .map((inp) => inp.id)
                .filter((id) => !resolvedIds.includes(id));
            const nonDiscardedInputs = allInputs.filter((inp) => !inp.is_discarded);
            const resolvedIdSet = new Set(resolvedIds);
            const autoDiscardIdSet = new Set(getMessagesInputIds);
            const inputsBeingDiscarded = nonDiscardedInputs.filter((inp) => resolvedIdSet.has(inp.id) || autoDiscardIdSet.has(inp.id));
            const inputsForEstimation = await this.sessionInputsRepository.find({
                where: {
                    session: { id: context.session_id },
                    is_discarded: false,
                },
                relations: ['aiActions', 'aiActions.executionLogs'],
                order: { sequence_number: 'ASC' },
            });
            const { totalTokens: oldTotalTokens, perInputTokens } = await this.estimateTokens(inputsForEstimation);
            const idToIndex = new Map();
            for (let i = 0; i < inputsForEstimation.length; i++) {
                idToIndex.set(inputsForEstimation[i].id, i);
            }
            let discardedTokens = 0;
            for (const inp of inputsBeingDiscarded) {
                const idx = idToIndex.get(inp.id);
                if (idx !== undefined) {
                    discardedTokens += perInputTokens[idx];
                }
            }
            const newTotalTokens = oldTotalTokens - discardedTokens;
            const result = await this.sessionInputsService.toggleDiscardedBatch(context.session_id, {
                input_ids: resolvedIds,
                is_discarded: true,
            });
            let autoDiscardedCount = 0;
            const _autoDiscardedTokens = 0;
            if (getMessagesInputIds.length > 0) {
                const autoResult = await this.sessionInputsService.toggleDiscardedBatch(context.session_id, {
                    input_ids: getMessagesInputIds,
                    is_discarded: true,
                });
                autoDiscardedCount = autoResult.affected;
            }
            const totalDiscardedTokens = discardedTokens;
            const finalNewTotal = newTotalTokens;
            const summary = autoDiscardedCount > 0
                ? `Discarded ${result.affected} message(s) + ${autoDiscardedCount} get_messages call(s). ~${totalDiscardedTokens} tokens freed. New total: ~${finalNewTotal} tokens.`
                : `Discarded ${result.affected} message(s). ~${totalDiscardedTokens} tokens freed. New total: ~${finalNewTotal} tokens.`;
            return {
                status: 'SUCCESS',
                summary,
                execution_log: {
                    output: JSON.stringify({
                        affected: result.affected,
                        short_ids: shortIds,
                        reason: reason.trim(),
                        discarded_tokens: totalDiscardedTokens,
                        new_total_tokens: finalNewTotal,
                        target_tokens: get_messages_handler_1.CONTEXT_TARGET_TOKENS,
                        floor_tokens: get_messages_handler_1.CONTEXT_FLOOR_TOKENS,
                        auto_discarded_get_messages: autoDiscardedCount,
                    }),
                },
                persisted_args: {
                    arguments: JSON.stringify({
                        input_ids: shortIds,
                        reason: reason.trim(),
                    }),
                },
            };
        }
        catch (error) {
            return {
                status: 'FAILURE',
                summary: 'Failed to discard messages',
                error_message: error.message,
                execution_log: {
                    output: '',
                    error_message: error.message,
                },
            };
        }
    }
};
exports.DiscardMessagesHandler = DiscardMessagesHandler;
exports.DiscardMessagesHandler = DiscardMessagesHandler = __decorate([
    (0, common_1.Injectable)(),
    __param(1, (0, typeorm_1.InjectRepository)(core_entities_1.SessionInput)),
    __metadata("design:paramtypes", [session_inputs_service_1.SessionInputsService,
        typeorm_2.Repository,
        history_compression_service_1.HistoryCompressionService])
], DiscardMessagesHandler);
//# sourceMappingURL=discard-messages.handler.js.map