"use strict";
// =============================================================================
// Pure Agent - Express Routes
// Routes LLM calls through the protocol-proxy system (same as 智控助手)
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupPureAgentRoutes = setupPureAgentRoutes;
exports.cleanupPureAgent = cleanupPureAgent;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const constants_1 = require("./constants");
const pure_agent_1 = require("./pure-agent");
const session_store_1 = require("./session-store");
const logger_1 = require("./logger");
const utils_1 = require("./utils");
const constants_2 = require("./constants");
const scheduler_1 = require("./cron/scheduler");
const multimodal_1 = require("./tools/multimodal");
const conv_files_1 = require("./conv-files");
// ---------------------------------------------------------------------------
// Active agents and pending approvals
// ---------------------------------------------------------------------------
const _activeAgents = new Map();
const _pendingApprovals = new Map();
const _activeStreams = new Set();
let _deps = null;
// ---------------------------------------------------------------------------
// SSE helper
// ---------------------------------------------------------------------------
function sendSSE(res, event, data) {
    try {
        res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
    }
    catch { /* connection already closed */ }
}
// ---------------------------------------------------------------------------
// Setup routes
// ---------------------------------------------------------------------------
function setupPureAgentRoutes(app, deps) {
    const logger = (0, logger_1.getLogger)();
    const store = (0, session_store_1.getSessionStore)();
    _deps = deps || null;
    // =========================================================================
    // GET /api/pure-agent/proxies -- Proxy tree for model selection
    // =========================================================================
    app.get('/api/pure-agent/proxies', (_req, res) => {
        if (!_deps)
            return res.json({ proxies: [] });
        try {
            const proxies = _deps.configStore.getProxies();
            const allProviders = _deps.configStore.getProviders();
            const tree = proxies
                .filter((p) => p.enabled !== false)
                .map((proxy) => {
                const targetProviderIds = new Set((proxy.targets || []).map((t) => t.providerId).filter(Boolean));
                const providers = (targetProviderIds.size > 0
                    ? allProviders.filter((p) => targetProviderIds.has(p.id))
                    : allProviders).map((p) => ({
                    id: p.id, name: p.name, protocol: p.protocol, adapter: p.adapter || '',
                    models: (p.models || []).map((m) => ({
                        name: typeof m === 'string' ? m : m.name,
                        contextLength: typeof m === 'object' ? m.contextLength : undefined,
                    })),
                }));
                return { id: proxy.id, name: proxy.name, port: proxy.port, defaultModel: proxy.defaultModel || '', providers };
            });
            res.json({ proxies: tree });
        }
        catch (err) {
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // POST /api/pure-agent/chat -- SSE chat endpoint (via proxy)
    // =========================================================================
    app.post('/api/pure-agent/chat', async (req, res) => {
        const { conversationId: requestedConvId, message, proxyId, providerId, model, maxIterations, maxTokens, temperature, workingDir, systemMessage, contextWindow, } = req.body;
        if (!message)
            return res.status(400).json({ error: 'Missing required field: message' });
        if (!proxyId)
            return res.status(400).json({ error: 'Missing required field: proxyId' });
        if (!_deps)
            return res.status(500).json({ error: 'Server not configured' });
        const proxy = _deps.configStore.getProxyById(proxyId);
        if (!proxy)
            return res.status(404).json({ error: 'Proxy not found' });
        // Build proxy URL and headers
        const proxyUrl = `http://localhost:${proxy.port}/v1/chat/completions`;
        const proxyHeaders = { 'Content-Type': 'application/json' };
        if (proxy.requireAuth && proxy.authToken) {
            proxyHeaders['Authorization'] = `Bearer ${proxy.authToken}`;
        }
        const effectiveModel = model || proxy.defaultModel || 'gpt-4o';
        // Provider-specific routing via x-pp-* headers
        if (providerId) {
            proxyHeaders['x-pp-provider-id'] = providerId;
            proxyHeaders['x-pp-model'] = effectiveModel;
            const provider = _deps.configStore.getProviderById(providerId);
            if (provider) {
                proxyHeaders['x-pp-provider-url'] = provider.url;
                proxyHeaders['x-pp-provider-protocol'] = provider.protocol;
                if (provider.adapter)
                    proxyHeaders['x-pp-provider-adapter'] = provider.adapter;
                if (Array.isArray(provider.capabilities))
                    proxyHeaders['x-pp-provider-capabilities'] = JSON.stringify(provider.capabilities);
                const enabledKeys = (provider.apiKeys || []).filter((k) => k.enabled !== false);
                if (enabledKeys.length > 0) {
                    proxyHeaders['x-pp-provider-keys'] = JSON.stringify(enabledKeys.map((k) => ({ key: k.key, enabled: k.enabled, alias: encodeURIComponent(k.alias || '') })));
                }
                proxyHeaders['x-pp-provider-name'] = encodeURIComponent(provider.name);
            }
        }
        else if (model) {
            proxyHeaders['x-pp-model'] = model;
        }
        const conversationId = requestedConvId || `conv_${(0, utils_1.generateId)()}`;
        if (_activeStreams.has(conversationId)) {
            return res.status(409).json({ error: 'A request is already in progress' });
        }
        _activeStreams.add(conversationId);
        // SSE headers
        res.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'X-Accel-Buffering': 'no',
        });
        // Flush headers immediately
        res.flushHeaders();
        sendSSE(res, 'conversation', { id: conversationId });
        store.ensureSession(conversationId, 'pure-agent', effectiveModel);
        const config = {
            baseUrl: proxyUrl,
            apiKey: '',
            sessionId: conversationId,
            model: effectiveModel,
            maxIterations: maxIterations || 30,
            maxTokens: maxTokens || 4096,
            temperature: temperature ?? 0.7,
            workingDir: workingDir || process.cwd(),
            systemMessage,
            customHeaders: proxyHeaders,
            contextWindow: contextWindow || 128000,
            streamDeltaCallback: (delta) => sendSSE(res, 'content', { delta }),
            toolApprovalCallback: async (name, args) => requestApproval(res, name, args),
        };
        const history = store.getMessages(conversationId);
        // Set cron agent config (so cron jobs can use the same API credentials)
        (0, scheduler_1.setCronAgentConfig)(config);
        // Always create a fresh agent with latest config
        const existingAgent = _activeAgents.get(conversationId);
        if (existingAgent)
            existingAgent.close();
        const agent = new pure_agent_1.PureAgent(config);
        _activeAgents.set(conversationId, agent);
        // Track whether streaming has started (to avoid false abort from close event)
        let streaming = false;
        let aborted = false;
        // Only treat close as abort AFTER streaming has started
        req.on('close', () => {
            if (streaming) {
                logger.info(`[PureAgent] Client disconnected during stream: conv=${conversationId}`);
                aborted = true;
                agent.interrupt();
            }
        });
        logger.info(`[PureAgent] Chat start: conv=${conversationId}, model=${effectiveModel}, proxy=${proxy.name}(:${proxy.port})`);
        // Pass full content as-is (base64 stays in DB, matching 智控助手 behavior)
        const userMessageContent = message;
        try {
            for await (const event of agent.runConversation(userMessageContent, {
                conversationHistory: history.slice(0, -1),
                systemMessage,
            })) {
                streaming = true;
                if (aborted) {
                    logger.info(`[PureAgent] Aborted during iteration`);
                    break;
                }
                logger.info(`[PureAgent] SSE event: ${event.event}`);
                sendSSE(res, event.event, event.data);
                if (event.event === 'done' || event.event === 'error')
                    break;
            }
            logger.info(`[PureAgent] Chat end: conv=${conversationId}, aborted=${aborted}`);
        }
        catch (err) {
            logger.error(`[PureAgent] Chat error: ${(0, utils_1.sanitizeError)(err)}`);
            if (!aborted)
                sendSSE(res, 'error', { message: (0, utils_1.sanitizeError)(err) });
        }
        finally {
            _activeStreams.delete(conversationId);
            if (!res.writableEnded)
                res.end();
        }
    });
    // =========================================================================
    // GET /api/pure-agent/conversations
    // =========================================================================
    app.get('/api/pure-agent/conversations', (_req, res) => {
        try {
            const sessions = store.listSessions();
            res.json({
                conversations: sessions.map(s => ({
                    id: s.sessionId, model: s.model, createdAt: s.createdAt, updatedAt: s.updatedAt,
                    messageCount: store.messageCount(s.sessionId),
                })),
            });
        }
        catch (err) {
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // GET /api/pure-agent/conversations/:id/messages
    // =========================================================================
    app.get('/api/pure-agent/conversations/:id/messages', (req, res) => {
        logger.info(`[PureAgent] GET messages: conv=${req.params.id}`);
        try {
            const session = store.getSession(req.params.id);
            if (!session) {
                logger.info(`[PureAgent] Session not found: ${req.params.id}`);
                return res.status(404).json({ error: 'Conversation not found' });
            }
            const messages = store.getMessages(req.params.id);
            logger.info(`[PureAgent] Returning ${messages.length} messages for ${req.params.id}`);
            res.json({ id: session.sessionId, model: session.model, messages });
        }
        catch (err) {
            logger.error(`[PureAgent] GET messages error: ${(0, utils_1.sanitizeError)(err)}`);
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // DELETE /api/pure-agent/conversations/:id/messages/:msgId
    // Delete a message and all messages after it (for Q&A pair deletion)
    // =========================================================================
    app.delete('/api/pure-agent/conversations/:id/messages/:msgId', (req, res) => {
        try {
            const { id, msgId } = req.params;
            const keepUser = req.query.keep_user === 'true'; // For retry: keep the user message
            const session = store.getSession(id);
            if (!session) {
                logger.warn(`[PureAgent] DELETE messages: conversation ${id} not found`);
                return res.status(404).json({ error: 'Conversation not found' });
            }
            const messages = store.getMessages(id);
            const msgIdNum = parseInt(msgId, 10);
            // Find the message index
            const idx = messages.findIndex(m => parseInt(m.id || '0', 10) === msgIdNum);
            if (idx < 0) {
                logger.warn(`[PureAgent] DELETE messages: message ${msgId} not found in ${id} (available: ${messages.map(m => m.id).join(',')})`);
                return res.status(404).json({ error: 'Message not found' });
            }
            let startIdx = idx;
            if (!keepUser) {
                // Default: find the start of the Q&A pair (go back to user message)
                while (startIdx > 0 && messages[startIdx].role !== 'user')
                    startIdx--;
                if (messages[startIdx].role !== 'user')
                    startIdx = idx;
            }
            else {
                // Retry mode: start AFTER the user message (keep user message)
                if (messages[startIdx].role === 'user')
                    startIdx = idx + 1;
            }
            // Delete from startIdx to end
            const removed = messages.slice(startIdx);
            const remaining = messages.slice(0, startIdx);
            store.replaceMessages(id, remaining);
            logger.info(`[PureAgent] Deleted ${removed.length} messages from ${id} (from msg ${msgId})`);
            res.json({ success: true, removedCount: removed.length });
        }
        catch (err) {
            logger.error(`[PureAgent] Delete message error: ${(0, utils_1.sanitizeError)(err)}`);
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // DELETE /api/pure-agent/conversations/:id
    // =========================================================================
    app.delete('/api/pure-agent/conversations/:id', (req, res) => {
        try {
            const convId = req.params.id;
            const deleted = store.deleteSession(convId);
            if (!deleted)
                return res.status(404).json({ error: 'Conversation not found' });
            const agent = _activeAgents.get(convId);
            if (agent) {
                agent.close();
                _activeAgents.delete(convId);
            }
            // Clean up associated files
            (0, conv_files_1.cleanupConvFiles)(convId);
            res.json({ success: true });
        }
        catch (err) {
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // DELETE /api/pure-agent/conversations
    // =========================================================================
    app.delete('/api/pure-agent/conversations', (_req, res) => {
        try {
            const sessions = store.listSessions();
            for (const s of sessions) {
                store.deleteSession(s.sessionId);
                (0, conv_files_1.cleanupConvFiles)(s.sessionId);
            }
            for (const [, agent] of _activeAgents)
                agent.close();
            _activeAgents.clear();
            res.json({ success: true });
        }
        catch (err) {
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // POST /api/pure-agent/approve
    // =========================================================================
    app.post('/api/pure-agent/approve', (req, res) => {
        const { id, approved } = req.body;
        if (!id)
            return res.status(400).json({ error: 'Missing id' });
        const pending = _pendingApprovals.get(id);
        if (!pending)
            return res.status(404).json({ error: 'No pending approval' });
        clearTimeout(pending.timeout);
        _pendingApprovals.delete(id);
        pending.resolve(approved !== false);
        res.json({ ok: true });
    });
    // =========================================================================
    // POST /api/pure-agent/interrupt
    // =========================================================================
    app.post('/api/pure-agent/interrupt', (req, res) => {
        const { conversationId } = req.body;
        if (!conversationId)
            return res.status(400).json({ error: 'Missing conversationId' });
        const agent = _activeAgents.get(conversationId);
        if (!agent)
            return res.status(404).json({ error: 'No active agent' });
        agent.interrupt();
        res.json({ ok: true });
    });
    // =========================================================================
    // GET /api/pure-agent/status
    // =========================================================================
    app.get('/api/pure-agent/status', (_req, res) => {
        res.json({
            activeAgents: _activeAgents.size, activeStreams: _activeStreams.size,
            pendingApprovals: _pendingApprovals.size, totalSessions: store.sessionCount(),
        });
    });
    // =========================================================================
    // GET /api/pure-agent/search
    // =========================================================================
    app.get('/api/pure-agent/search', (req, res) => {
        const { q, limit } = req.query;
        if (!q)
            return res.status(400).json({ error: 'Missing q' });
        try {
            res.json({ results: store.searchMessages(q, parseInt(limit, 10) || 20) });
        }
        catch (err) {
            res.status(500).json({ error: (0, utils_1.sanitizeError)(err) });
        }
    });
    // =========================================================================
    // GET /api/pure-agent/conv-files/:convId/:filename -- Static file serving
    // =========================================================================
    const convFilesDir = path.join((0, constants_1.getPureAgentHome)(), 'conv-files');
    app.get('/api/pure-agent/conv-files/:convId/:filename', (req, res) => {
        const filePath = path.join(convFilesDir, req.params.convId, req.params.filename);
        // Security: ensure path is within conv-files directory
        const resolved = path.resolve(filePath);
        if (!resolved.startsWith(path.resolve(convFilesDir))) {
            return res.status(403).json({ error: 'Forbidden' });
        }
        if (!fs.existsSync(resolved)) {
            return res.status(404).json({ error: 'File not found' });
        }
        res.sendFile(resolved);
    });
    // Wire up multimodal services getter (for generate_image/video/tts/music)
    if (_deps?.configStore?.getMultimodalServices) {
        (0, multimodal_1.setMultimodalServicesGetter)(() => _deps.configStore.getMultimodalServices());
    }
    // Start cron ticker (checks every 60 seconds)
    (0, scheduler_1.startCronTicker)(60000);
    logger.info('Pure Agent routes registered (with cron ticker)');
}
// ---------------------------------------------------------------------------
// Tool approval
// ---------------------------------------------------------------------------
function requestApproval(res, toolName, toolArgs) {
    return new Promise((resolve) => {
        const id = `approval_${(0, utils_1.generateId)()}`;
        sendSSE(res, 'tool_approval', { id, name: toolName, arguments: toolArgs });
        const timeout = setTimeout(() => { _pendingApprovals.delete(id); resolve(false); }, constants_2.TOOL_APPROVAL_TIMEOUT_MS);
        _pendingApprovals.set(id, { resolve, timeout });
    });
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
function cleanupPureAgent() {
    for (const [, agent] of _activeAgents)
        agent.close();
    _activeAgents.clear();
    for (const [, pending] of _pendingApprovals) {
        clearTimeout(pending.timeout);
        pending.resolve(false);
    }
    _pendingApprovals.clear();
}
//# sourceMappingURL=routes.js.map