"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); }
};
var ChatService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChatService = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const llm_provider_interface_1 = require("../llm-provider/llm-provider.interface");
const events_gateway_1 = require("../events/events.gateway");
const llm_responses_service_1 = require("../llm-responses/llm-responses.service");
const application_state_service_1 = require("../application-state/application-state.service");
const message_bus_service_1 = require("../message-bus/message-bus.service");
const sessions_service_1 = require("../sessions/sessions.service");
const system_prompts_service_1 = require("../system-prompts/system-prompts.service");
const tool_schema_service_1 = require("../llm-orchestration/tool-schema.service");
const history_compression_service_1 = require("../llm-orchestration/history-compression.service");
const sub_agents_service_1 = require("../sub-agents/sub-agents.service");
const mcp_service_1 = require("../mcp/mcp.service");
const session_input_context_service_1 = require("../session-inputs/session-input-context.service");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const session_input_entity_1 = require("../core-entities/session-input.entity");
const ai_action_entity_1 = require("../core-entities/ai-action.entity");
const utils_1 = require("../utils");
function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
function isRetryableError(error, _retryAttempt) {
    const errorMessage = error.message?.toLowerCase() || '';
    const errorCode = error.code;
    const status = error.status;
    if (error.name === 'AbortError' ||
        error.code === 'ABORT_ERR' ||
        error.message === 'This operation was aborted') {
        return false;
    }
    const networkErrors = [
        'ECONNRESET',
        'ETIMEDOUT',
        'ENOTFOUND',
        'ENETUNREACH',
        'ECONNREFUSED',
    ];
    if (errorCode && networkErrors.includes(errorCode)) {
        return true;
    }
    if (status === 429 ||
        errorMessage.includes('rate limit') ||
        errorMessage.includes('too many requests')) {
        return true;
    }
    if (status >= 500 && status < 600) {
        return true;
    }
    if (errorMessage.includes('empty response')) {
        return true;
    }
    if (errorMessage.includes('timeout')) {
        return true;
    }
    return false;
}
let ChatService = ChatService_1 = class ChatService {
    constructor(llmProvider, eventsGateway, llmResponsesService, applicationStateService, messageBusService, sessionsService, systemPromptsService, toolSchemaService, historyCompressionService, subAgentsService, moduleRef, handlerRegistry, sessionInputsRepository, aiActionsRepository) {
        this.llmProvider = llmProvider;
        this.eventsGateway = eventsGateway;
        this.llmResponsesService = llmResponsesService;
        this.applicationStateService = applicationStateService;
        this.messageBusService = messageBusService;
        this.sessionsService = sessionsService;
        this.systemPromptsService = systemPromptsService;
        this.toolSchemaService = toolSchemaService;
        this.historyCompressionService = historyCompressionService;
        this.subAgentsService = subAgentsService;
        this.moduleRef = moduleRef;
        this.handlerRegistry = handlerRegistry;
        this.sessionInputsRepository = sessionInputsRepository;
        this.aiActionsRepository = aiActionsRepository;
        this.logger = new common_1.Logger(ChatService_1.name);
        this.activeRequests = new Map();
        this.syncPromises = new Map();
    }
    resolveSyncPromise(sessionId, result) {
        const promise = this.syncPromises.get(sessionId);
        if (promise) {
            this.syncPromises.delete(sessionId);
            this.logger.log(`Resolving sync promise for session ${sessionId}. Success: ${result.success}`);
            promise.resolve(result);
        }
    }
    rejectSyncPromise(sessionId, error) {
        const promise = this.syncPromises.get(sessionId);
        if (promise) {
            this.syncPromises.delete(sessionId);
            this.logger.log(`Rejecting sync promise for session ${sessionId}. Error: ${error.message}`);
            promise.reject(error);
        }
    }
    getSyncPromise(sessionId) {
        return this.syncPromises.get(sessionId);
    }
    async runAgentSync(agentName, prompt, parentSessionId, options) {
        this.logger.log(`runAgentSync called for "${agentName}" from parent session ${parentSessionId}`);
        const subAgent = await this.subAgentsService.getAgentByName(agentName);
        if (!subAgent) {
            const availableAgents = (await this.subAgentsService.findActive())
                .map((a) => a.name)
                .join(', ');
            return {
                content: `Sub-agent "${agentName}" not found or not active. Available: ${availableAgents}`,
                success: false,
                iterations: 0,
                childSessionId: '',
            };
        }
        let childSessionId;
        const sessionInputContextService = this.moduleRef.get(session_input_context_service_1.SessionInputContextService, { strict: false });
        if (options?.sessionId) {
            const existingSession = await this.sessionsService.findOne(options.sessionId);
            if (!existingSession) {
                return {
                    content: `Session "${options.sessionId}" not found. Cannot reuse a non-existent session.`,
                    success: false,
                    iterations: 0,
                    childSessionId: options.sessionId,
                };
            }
            childSessionId = existingSession.id;
            this.logger.log(`Reusing existing session ${childSessionId} for sub-agent "${agentName}"`);
        }
        else {
            const childSession = await this.sessionsService.create({
                session_title: `Sub-Agent: ${agentName}`,
                system_prompt_id: subAgent.system_prompt_id || undefined,
                default_initial_context_template_id: subAgent.context_template_id || undefined,
                default_followup_context_template_id: subAgent.followup_context_template_id || undefined,
                model_id: subAgent.model_id || undefined,
                sub_agent_id: subAgent.id,
                parent_session_id: parentSessionId,
            });
            childSessionId = childSession.id;
            this.logger.log(`Created child session ${childSessionId} for sub-agent "${agentName}"`);
        }
        let initialUserMessage;
        let generatedContextString = null;
        if (subAgent.context_template_id) {
            try {
                const contextTemplate = subAgent.contextTemplate || null;
                const generatedContext = await sessionInputContextService.generateContext({
                    user_prompt: prompt,
                    context_template_id: subAgent.context_template_id,
                    execution_strategy: 'auto_apply',
                }, contextTemplate);
                initialUserMessage = generatedContext;
                generatedContextString = generatedContext;
                this.logger.log(`Sub-agent "${agentName}" initial prompt rendered through context template.`);
            }
            catch (error) {
                this.logger.error(`Failed to render context template for sub-agent "${agentName}": ${error.message}. Falling back to raw prompt.`, error.stack);
                initialUserMessage = prompt;
            }
        }
        else {
            initialUserMessage = prompt;
        }
        const sessionInput = this.sessionInputsRepository.create({
            session: { id: childSessionId },
            role: 'user',
            user_prompt: prompt,
            generated_context_string: generatedContextString,
            execution_strategy: 'auto_apply',
            context_template_id: subAgent.context_template_id || undefined,
            ...(subAgent.context_template_id
                ? { contextTemplate: { id: subAgent.context_template_id } }
                : {}),
        });
        await this.sessionInputsRepository.save(sessionInput);
        this.eventsGateway.sendToAll('refresh-ui', {
            sessionId: childSessionId,
        });
        this.eventsGateway.sendToAll('refresh-ui', {
            sessionId: parentSessionId,
        });
        const syncPromise = new Promise((resolve, reject) => {
            this.syncPromises.set(childSessionId, { resolve, reject });
        });
        this.sendMessage(childSessionId, generatedContextString || initialUserMessage).catch((err) => {
            this.logger.error(`runAgentSync: sendMessage failed for session ${childSessionId}: ${err.message}`, err.stack);
            this.rejectSyncPromise(childSessionId, err);
        });
        return syncPromise;
    }
    async callWithRetry(request, sessionId, pendingInputId) {
        const retryEnabled = await this.applicationStateService.getLlmRetryEnabled();
        const maxAttempts = await this.applicationStateService.getLlmRetryMaxAttempts();
        let lastError;
        let attempt = 1;
        while (attempt <= maxAttempts) {
            try {
                if (attempt > 1) {
                    this.logger.log(`Retry attempt ${attempt}/${maxAttempts} for session ${sessionId}`);
                    this.eventsGateway.sendToAll('llm-retry-attempt', {
                        sessionInputId: pendingInputId,
                        sessionId,
                        attempt,
                        maxAttempts,
                    });
                }
                return await this.llmProvider.generateContent(request);
            }
            catch (error) {
                lastError = error;
                const shouldRetry = retryEnabled &&
                    attempt < maxAttempts &&
                    isRetryableError(error, attempt);
                if (!shouldRetry) {
                    throw error;
                }
                const backoffMs = Math.pow(2, attempt - 1) * 1000;
                this.logger.log(`LLM call failed (attempt ${attempt}/${maxAttempts}): ${error.message}. Retrying in ${backoffMs}ms...`);
                await delay(backoffMs);
                attempt++;
            }
        }
        throw lastError;
    }
    async getHistory(sessionId) {
        const inputs = await this.sessionInputsRepository.find({
            where: { session_id: sessionId, is_discarded: false },
            order: { sequence_number: 'ASC' },
        });
        const modelInputIdsWithToolCalls = [];
        for (const input of inputs) {
            if (input.role === 'model' && input.tool_calls) {
                modelInputIdsWithToolCalls.push(input.id);
            }
        }
        const actionsByInputId = new Map();
        if (modelInputIdsWithToolCalls.length > 0) {
            const allActions = await this.aiActionsRepository.find({
                where: { input_id: (0, typeorm_2.In)(modelInputIdsWithToolCalls) },
                relations: ['executionLogs'],
            });
            for (const action of allActions) {
                const existing = actionsByInputId.get(action.input_id) || [];
                existing.push(action);
                actionsByInputId.set(action.input_id, existing);
            }
        }
        const history = [];
        for (const input of inputs) {
            if (input.role === 'user' && input.generated_context_string) {
                history.push({
                    role: 'user',
                    parts: [{ text: input.generated_context_string }],
                    shortId: (0, utils_1.toShortId)(input.id),
                });
            }
            else if (input.role === 'model') {
                const content = {
                    role: 'model',
                    parts: [{ text: input.raw_llm_response || '' }],
                    shortId: (0, utils_1.toShortId)(input.id),
                };
                if (input.thoughts) {
                    content.thoughts = input.thoughts;
                }
                if (input.tool_calls) {
                    try {
                        content.tool_calls = JSON.parse(input.tool_calls);
                    }
                    catch (e) {
                        this.logger.error(`Failed to parse tool_calls for input ${input.id}: ${e.message}`);
                    }
                }
                history.push(content);
                if (content.tool_calls && content.tool_calls.length > 0) {
                    const toolActions = actionsByInputId.get(input.id) || [];
                    const actionsByToolCallId = new Map();
                    for (const action of toolActions) {
                        if (action.tool_call_id) {
                            actionsByToolCallId.set(action.tool_call_id, action);
                        }
                    }
                    for (const toolCall of content.tool_calls) {
                        const action = actionsByToolCallId.get(toolCall.id);
                        if (!action)
                            continue;
                        const output = action.executionLogs?.[0]?.output || '';
                        const errorMessage = action.executionLogs?.[0]?.error_message || '';
                        let toolResult;
                        if (output && errorMessage) {
                            toolResult = `${output}\n\nError: ${errorMessage}`;
                        }
                        else if (errorMessage) {
                            toolResult = `Error: ${errorMessage}`;
                        }
                        else if (output) {
                            toolResult = output;
                        }
                        else {
                            const statusText = action.status || 'unknown';
                            toolResult = `Tool ${action.action_type} executed with status: ${statusText}. No output available.`;
                        }
                        history.push({
                            role: 'tool',
                            tool_call_id: action.tool_call_id,
                            tool_name: action.action_type,
                            parts: [{ text: toolResult }],
                        });
                    }
                }
            }
        }
        this.logger.log(`Reconstructed history for session ${sessionId} with ${history.length} turns.`);
        return this.historyCompressionService.compress(history);
    }
    async sendMessage(sessionId, message, existingInputId) {
        const history = await this.getHistory(sessionId);
        try {
            const session = await this.sessionsService.findOne(sessionId);
            const isStreaming = await this.applicationStateService.getStreamingEnabled();
            const isSubAgentSession = !!session.sub_agent_id;
            let subAgent = null;
            let systemPromptToUse = session.systemPrompt;
            let systemPromptId = '';
            if (isSubAgentSession) {
                subAgent = await this.subAgentsService.findOne(session.sub_agent_id);
                if (subAgent && subAgent.systemPrompt) {
                    systemPromptToUse = subAgent.systemPrompt;
                }
                this.logger.log(`Session ${sessionId} is a sub-agent session (agent: ${subAgent?.name})`);
            }
            let finalSystemInstruction = '';
            if (systemPromptToUse) {
                systemPromptId = systemPromptToUse.id;
                this.logger.log(`Lazy rendering system prompt ${systemPromptId} for turn in session ${sessionId}`);
                const rendered = await this.systemPromptsService.findOneWithSession(systemPromptId, session.id, session.session_title);
                finalSystemInstruction = rendered.prompt_content;
            }
            else {
                this.logger.log(`No system prompt found for session ${sessionId}. Fetching default.`);
                const defaultPrompt = await this.systemPromptsService.findDefaultWithSession(session.id, session.session_title);
                finalSystemInstruction = defaultPrompt?.prompt_content || '';
                systemPromptId = defaultPrompt?.id || '';
            }
            const executionStrategy = await this.applicationStateService.getExecutionStrategy();
            const isToolCapable = session.model_id?.includes('/');
            let tools;
            if (isToolCapable && systemPromptId) {
                if (isSubAgentSession && subAgent) {
                    const handlerRegistry = this.handlerRegistry;
                    const mcpService = this.moduleRef.get(mcp_service_1.McpService, {
                        strict: false,
                    });
                    if (subAgent.enabled_tools === null) {
                        tools =
                            this.toolSchemaService.generateToolDefinitions(handlerRegistry);
                        this.logger.log(`Sub-agent "${subAgent.name}" using ALL built-in tools.`);
                    }
                    else if (subAgent.enabled_tools.length > 0) {
                        const filteredHandlers = new Map();
                        for (const toolName of subAgent.enabled_tools) {
                            const handler = handlerRegistry.get(toolName);
                            if (handler) {
                                filteredHandlers.set(toolName, handler);
                            }
                        }
                        tools =
                            this.toolSchemaService.generateToolDefinitions(filteredHandlers);
                        this.logger.log(`Sub-agent "${subAgent.name}" using ${filteredHandlers.size}/${subAgent.enabled_tools.length} filtered built-in tools.`);
                    }
                    if (subAgent.enabled_mcp_tools !== null &&
                        subAgent.enabled_mcp_tools !== undefined) {
                        try {
                            const allMcpTools = await mcpService.getActiveMcpToolDefinitions();
                            if (allMcpTools.length > 0) {
                                let enabledMcpToolNames;
                                if (subAgent.enabled_mcp_tools === 'all') {
                                    enabledMcpToolNames = allMcpTools.map((t) => t.function.name);
                                }
                                else {
                                    const parsed = mcpService.parseEnabledMcpTools(subAgent.enabled_mcp_tools);
                                    if (parsed === 'all') {
                                        enabledMcpToolNames = allMcpTools.map((t) => t.function.name);
                                    }
                                    else {
                                        const parsedMcpTools = parsed || [];
                                        const allToolNames = allMcpTools.map((t) => t.function.name);
                                        enabledMcpToolNames = mcpService.resolveMcpToolNames(parsedMcpTools, allToolNames);
                                    }
                                }
                                const filteredMcpTools = allMcpTools.filter((tool) => enabledMcpToolNames.includes(tool.function.name));
                                if (filteredMcpTools.length > 0) {
                                    tools = [...(tools || []), ...filteredMcpTools];
                                }
                            }
                        }
                        catch (error) {
                            this.logger.warn(`Failed to load MCP tools for sub-agent "${subAgent.name}": ${error.message}`);
                        }
                    }
                }
                else {
                    tools = await this.toolSchemaService.generateToolDefinitionsForPrompt(this.handlerRegistry, systemPromptId);
                }
                this.logger.log(`Generated ${tools?.length || 0} tools for session ${sessionId}${isSubAgentSession ? ' (sub-agent)' : ''}`);
            }
            let pendingInput;
            if (existingInputId) {
                pendingInput = await this.sessionInputsRepository.findOneBy({
                    id: existingInputId,
                });
                if (!pendingInput) {
                    throw new Error(`Existing SessionInput with ID "${existingInputId}" not found for regeneration.`);
                }
                pendingInput.raw_llm_response = null;
                pendingInput.thoughts = null;
                pendingInput.tool_calls = null;
                pendingInput.llm_response_explanation = null;
                pendingInput.error_message = null;
                pendingInput.input_token_count = null;
                pendingInput.output_token_count = null;
                pendingInput.cached_token_count = null;
                pendingInput = await this.sessionInputsRepository.save(pendingInput);
            }
            else {
                pendingInput = await this.llmResponsesService.createPendingAiInput(sessionId, executionStrategy);
            }
            this.eventsGateway.sendToAll('llm-generation-started', {
                sessionInputId: pendingInput.id,
                sessionId: session.id,
                isStreaming: isStreaming,
            });
            await this.sessionsService.update(session.id, { is_running: true });
            const abortController = new AbortController();
            this.activeRequests.set(sessionId, {
                abortController,
                pendingInputId: pendingInput.id,
            });
            const onToken = isStreaming
                ? (chunk, isFirst, thoughtChunk) => {
                    if (isFirst) {
                        this.eventsGateway.sendToAll('llm-generation-in-progress', {
                            sessionInputId: pendingInput.id,
                        });
                    }
                    if (chunk) {
                        this.eventsGateway.sendToAll('llm-stream-chunk', {
                            sessionInputId: pendingInput.id,
                            sessionId: session.id,
                            chunk,
                            isFirst,
                        });
                    }
                    if (thoughtChunk) {
                        this.eventsGateway.sendToAll('llm-thought-chunk', {
                            sessionInputId: pendingInput.id,
                            sessionId: session.id,
                            chunk: thoughtChunk,
                            isFirst,
                        });
                    }
                }
                : undefined;
            const generationConfig = {};
            if (session.reasoning_effort) {
                generationConfig.reasoning = { effort: session.reasoning_effort };
            }
            const llm_response = await this.callWithRetry({
                prompt: message,
                systemInstruction: finalSystemInstruction,
                history: history,
                modelId: session.model_id,
                generationConfig,
                tools,
                onToken,
                abortController,
            }, sessionId, pendingInput.id);
            await this.llmResponsesService.finalizePendingAiInput(pendingInput.id, {
                raw_llm_response: llm_response.text,
                execution_strategy: executionStrategy,
                input_token_count: llm_response.usage?.inputTokens,
                output_token_count: llm_response.usage?.outputTokens,
                cached_token_count: llm_response.usage?.cachedTokens,
                tool_calls: llm_response.tool_calls,
                thoughts: llm_response.thoughts,
            });
            this.eventsGateway.sendToAll('llm-generation-ended', {
                sessionInputId: pendingInput.id,
                sessionId: session.id,
            });
            await this.sessionsService.update(session.id, { is_running: false });
            this.eventsGateway.sendToAll('refresh-ui', { sessionId });
            this.activeRequests.delete(sessionId);
        }
        catch (error) {
            const pendingInputId = this.activeRequests.get(sessionId)?.pendingInputId || 'unknown';
            this.activeRequests.delete(sessionId);
            const isAbortError = error.name === 'AbortError' ||
                error.code === 'ABORT_ERR' ||
                error.message === 'This operation was aborted';
            if (isAbortError) {
                this.logger.log(`Request aborted for session ${sessionId} (expected operation)`);
                this.eventsGateway.sendToAll('llm-generation-ended', {
                    sessionInputId: pendingInputId,
                    sessionId: sessionId,
                });
                await this.sessionsService.update(sessionId, { is_running: false });
                return;
            }
            await this.llmResponsesService.updatePendingAiInputWithError(pendingInputId, error.message);
            this.eventsGateway.sendToAll('llm-generation-ended', {
                sessionInputId: pendingInputId,
                sessionId: sessionId,
                error: error.message,
            });
            await this.sessionsService.update(sessionId, { is_running: false });
            this.logger.error(`Error in sendMessage for session ${sessionId}: ${error.message}`, error.stack);
            this.rejectSyncPromise(sessionId, error);
            this.eventsGateway.sendToAll('refresh-ui', { sessionId });
            throw error;
        }
    }
    cancelRequest(sessionId) {
        const state = this.activeRequests.get(sessionId);
        if (!state) {
            this.logger.warn(`No active request to cancel for session ${sessionId}`);
            return false;
        }
        try {
            state.abortController.abort();
            this.logger.log(`Request aborted for session ${sessionId}`);
            this.activeRequests.delete(sessionId);
            this.eventsGateway.sendToAll('refresh-ui', { sessionId });
            this.sessionsService
                .update(sessionId, { is_running: false })
                .catch((err) => {
                this.logger.warn(`Failed to reset is_running for session ${sessionId}: ${err.message}`);
            });
            return true;
        }
        catch (error) {
            this.logger.error(`Failed to cancel request for session ${sessionId}: ${error.message}`);
            return false;
        }
    }
    abortStreaming(sessionId) {
        return this.cancelRequest(sessionId);
    }
};
exports.ChatService = ChatService;
exports.ChatService = ChatService = ChatService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Inject)(llm_provider_interface_1.LLM_PROVIDER)),
    __param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => llm_responses_service_1.LlmResponsesService))),
    __param(5, (0, common_1.Inject)((0, common_1.forwardRef)(() => sessions_service_1.SessionsService))),
    __param(6, (0, common_1.Inject)((0, common_1.forwardRef)(() => system_prompts_service_1.SystemPromptsService))),
    __param(11, (0, common_1.Inject)('ACTION_HANDLER_REGISTRY')),
    __param(12, (0, typeorm_1.InjectRepository)(session_input_entity_1.SessionInput)),
    __param(13, (0, typeorm_1.InjectRepository)(ai_action_entity_1.AIAction)),
    __metadata("design:paramtypes", [Object, events_gateway_1.EventsGateway,
        llm_responses_service_1.LlmResponsesService,
        application_state_service_1.ApplicationStateService,
        message_bus_service_1.MessageBusService,
        sessions_service_1.SessionsService,
        system_prompts_service_1.SystemPromptsService,
        tool_schema_service_1.ToolSchemaService,
        history_compression_service_1.HistoryCompressionService,
        sub_agents_service_1.SubAgentsService,
        core_1.ModuleRef,
        Map,
        typeorm_2.Repository,
        typeorm_2.Repository])
], ChatService);
//# sourceMappingURL=chat.service.js.map