"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstanceAiCompactionService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const di_1 = require("@n8n/di");
const instance_ai_1 = require("@n8n/instance-ai");
const context_limits_1 = require("../../modules/chat-hub/context-limits");
const typeorm_memory_storage_1 = require("./storage/typeorm-memory-storage");
const METADATA_KEY = 'instanceAiConversationSummary';
const DEFAULT_CONTEXT_WINDOW = 128_000;
function estimateTokens(text) {
    return Math.ceil(text.length / 4);
}
function getContextWindowForModel(modelId) {
    const raw = typeof modelId === 'string'
        ? modelId
        : 'specificationVersion' in modelId
            ? `${modelId.provider.split('.')[0]}/${modelId.modelId}`
            : modelId.id;
    const slashIndex = raw.indexOf('/');
    if (slashIndex < 0) {
        for (const providerModels of Object.values(context_limits_1.maxContextWindowTokens)) {
            if (providerModels[raw])
                return providerModels[raw];
            for (const [registryModel, tokens] of Object.entries(providerModels)) {
                if (tokens > 0 && registryModel.startsWith(raw))
                    return tokens;
            }
        }
        return DEFAULT_CONTEXT_WINDOW;
    }
    const provider = raw.slice(0, slashIndex);
    const model = raw.slice(slashIndex + 1);
    const providerModels = context_limits_1.maxContextWindowTokens[provider];
    if (!providerModels)
        return DEFAULT_CONTEXT_WINDOW;
    if (providerModels[model])
        return providerModels[model];
    for (const [registryModel, tokens] of Object.entries(providerModels)) {
        if (tokens > 0 && registryModel.startsWith(model))
            return tokens;
    }
    return DEFAULT_CONTEXT_WINDOW;
}
const FIXED_CONTEXT_OVERHEAD_TOKENS = 8_000;
let InstanceAiCompactionService = class InstanceAiCompactionService {
    constructor(logger, memoryStorage, globalConfig) {
        this.logger = logger;
        this.memoryStorage = memoryStorage;
        this.maxContextWindowTokensCap = globalConfig.instanceAi.maxContextWindowTokens;
    }
    async prepareCompactedContext(threadId, memory, modelId, lastMessages, compactionThreshold = 0.8) {
        try {
            const recentTail = lastMessages;
            const { messages: allMessages } = await this.memoryStorage.listMessages({
                threadId,
                perPage: false,
                orderBy: { field: 'createdAt', direction: 'ASC' },
            });
            if (allMessages.length <= recentTail) {
                return await this.getCachedSummaryBlock(threadId, memory);
            }
            const totalTokens = FIXED_CONTEXT_OVERHEAD_TOKENS +
                allMessages.reduce((sum, m) => sum + estimateTokens(this.extractRawText(m)), 0);
            const modelContextWindow = getContextWindowForModel(modelId);
            const contextWindow = this.maxContextWindowTokensCap > 0
                ? Math.min(modelContextWindow, this.maxContextWindowTokensCap)
                : modelContextWindow;
            const threshold = contextWindow * compactionThreshold;
            if (totalTokens < threshold) {
                return await this.getCachedSummaryBlock(threadId, memory);
            }
            const prefixEnd = allMessages.length - recentTail;
            const prefix = allMessages.slice(0, prefixEnd);
            const thread = await memory.getThreadById({ threadId });
            const existing = this.parseMetadata(thread?.metadata?.[METADATA_KEY]);
            let unsummarizedStart = 0;
            if (existing?.upToMessageId) {
                const idx = prefix.findIndex((m) => m.id === existing.upToMessageId);
                if (idx >= 0) {
                    unsummarizedStart = idx + 1;
                }
            }
            const unsummarizedSlice = prefix.slice(unsummarizedStart);
            const unsummarizedTokens = unsummarizedSlice.reduce((sum, m) => sum + estimateTokens(this.extractRawText(m)), 0);
            if (unsummarizedTokens < 500) {
                return await this.getCachedSummaryBlock(threadId, memory);
            }
            const messageBatch = this.extractHighSignalContent(unsummarizedSlice);
            if (messageBatch.length === 0) {
                return await this.getCachedSummaryBlock(threadId, memory);
            }
            const summary = await (0, instance_ai_1.generateCompactionSummary)(modelId, {
                previousSummary: existing?.summary ?? null,
                messageBatch,
            });
            const lastCompactedMessage = unsummarizedSlice[unsummarizedSlice.length - 1];
            const newMetadata = {
                version: (existing?.version ?? 0) + 1,
                upToMessageId: lastCompactedMessage.id,
                summary,
                updatedAt: new Date().toISOString(),
            };
            await this.saveMetadata(threadId, memory, newMetadata);
            return this.formatSummaryBlock(summary);
        }
        catch (error) {
            this.logger.warn('Conversation compaction failed, continuing without summary', {
                threadId,
                error: error instanceof Error ? error.message : String(error),
            });
            return null;
        }
    }
    async getCachedSummaryBlock(threadId, memory) {
        const thread = await memory.getThreadById({ threadId });
        const existing = this.parseMetadata(thread?.metadata?.[METADATA_KEY]);
        if (existing?.summary) {
            return this.formatSummaryBlock(existing.summary);
        }
        return null;
    }
    extractRawText(msg) {
        const content = msg.content;
        if (typeof content === 'string')
            return content;
        return JSON.stringify(content);
    }
    extractHighSignalContent(messages) {
        const result = [];
        for (const msg of messages) {
            if (msg.role !== 'user' && msg.role !== 'assistant')
                continue;
            const text = this.extractTextFromContent(msg.content);
            if (!text)
                continue;
            result.push({ role: msg.role, text });
        }
        return result;
    }
    extractTextFromContent(content) {
        if (typeof content === 'string')
            return content;
        const inner = content?.content;
        if (typeof inner === 'string')
            return inner;
        if (Array.isArray(inner)) {
            const textParts = [];
            for (const part of inner) {
                if (typeof part === 'string') {
                    textParts.push(part);
                }
                else if (isTextPart(part)) {
                    textParts.push(part.text);
                }
            }
            return textParts.join('\n');
        }
        return '';
    }
    formatSummaryBlock(summary) {
        return `<conversation-summary>\n${summary}\n</conversation-summary>`;
    }
    parseMetadata(raw) {
        if (!raw || typeof raw !== 'object')
            return null;
        const obj = raw;
        if (typeof obj.version === 'number' &&
            typeof obj.upToMessageId === 'string' &&
            typeof obj.summary === 'string' &&
            typeof obj.updatedAt === 'string') {
            return obj;
        }
        return null;
    }
    async saveMetadata(threadId, memory, metadata) {
        await (0, instance_ai_1.patchThread)(memory, {
            threadId,
            update: ({ metadata: currentMetadata }) => ({
                metadata: {
                    ...currentMetadata,
                    [METADATA_KEY]: metadata,
                },
            }),
        });
    }
};
exports.InstanceAiCompactionService = InstanceAiCompactionService;
exports.InstanceAiCompactionService = InstanceAiCompactionService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        typeorm_memory_storage_1.TypeORMMemoryStorage,
        config_1.GlobalConfig])
], InstanceAiCompactionService);
function isTextPart(part) {
    return (typeof part === 'object' &&
        part !== null &&
        'type' in part &&
        part.type === 'text' &&
        'text' in part &&
        typeof part.text === 'string');
}
//# sourceMappingURL=compaction.service.js.map