"use strict";
// =============================================================================
// Pure Agent - PureAgent Class
// Ported from Hermes run_agent.py (AIAgent)
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.PureAgent = void 0;
const tool_registry_1 = require("./tool-registry");
const iteration_budget_1 = require("./iteration-budget");
const session_store_1 = require("./session-store");
const logger_1 = require("./logger");
const system_prompt_1 = require("./system-prompt");
const conversation_loop_1 = require("./conversation-loop");
const utils_1 = require("./utils");
const constants_1 = require("./constants");
const terminal_1 = require("./tools/terminal");
const file_tools_1 = require("./tools/file-tools");
const conv_files_1 = require("./conv-files");
// ---------------------------------------------------------------------------
// Discovery state
// ---------------------------------------------------------------------------
let _toolsDiscovered = false;
function ensureToolsDiscovered() {
    if (_toolsDiscovered)
        return;
    _toolsDiscovered = true;
    (0, tool_registry_1.discoverTools)();
}
// ---------------------------------------------------------------------------
// PureAgent class
// ---------------------------------------------------------------------------
class PureAgent {
    // Config (readonly after construction)
    baseUrl;
    apiKey;
    model;
    maxIterations;
    maxTokens;
    temperature;
    workingDir;
    contextWindow;
    apiMode;
    customHeaders;
    extraBody;
    // Fallback chain
    fallbacks;
    _fallbackIndex = 0;
    // Auth refresh
    refreshTokenCallback;
    // Optional system message
    systemMessage;
    // State
    sessionId;
    tools;
    validToolNames;
    _cachedSystemPrompt = null;
    _interruptRequested = false;
    _abortController;
    // Callbacks
    toolApprovalCallback;
    streamDeltaCallback;
    statusCallback;
    // Infrastructure
    _sessionStore;
    _logger;
    _closed = false;
    iterationBudget;
    // Token tracking for this session
    _totalInputTokens = 0;
    _totalOutputTokens = 0;
    _turnCount = 0;
    constructor(config) {
        // Ensure directories exist
        (0, constants_1.ensureDirectories)();
        // Ensure tools are discovered (once globally)
        ensureToolsDiscovered();
        // Config
        this.baseUrl = config.baseUrl.replace(/\/+$/, '');
        this.apiKey = config.apiKey;
        this.model = config.model || constants_1.DEFAULT_MODEL;
        this.maxIterations = config.maxIterations ?? constants_1.DEFAULT_MAX_ITERATIONS;
        this.maxTokens = config.maxTokens ?? constants_1.DEFAULT_MAX_TOKENS;
        this.temperature = config.temperature ?? constants_1.DEFAULT_TEMPERATURE;
        this.workingDir = config.workingDir || process.cwd();
        this.contextWindow = config.contextWindow || constants_1.DEFAULT_CONTEXT_WINDOW;
        this.customHeaders = config.customHeaders;
        this.extraBody = config.extraBody;
        this.systemMessage = config.systemMessage;
        this.fallbacks = config.fallbacks || [];
        this._fallbackIndex = 0;
        this.refreshTokenCallback = config.refreshTokenCallback;
        // Detect API mode
        this.apiMode = (0, utils_1.detectApiMode)(config.provider || '', this.baseUrl);
        // Session ID (allow caller to provide one for persistence)
        this.sessionId = config.sessionId || (0, utils_1.generateId)('session');
        // Abort controller
        this._abortController = new AbortController();
        // Iteration budget
        this.iterationBudget = new iteration_budget_1.IterationBudget(this.maxIterations);
        // Load tools
        this.tools = tool_registry_1.registry.getDefinitions();
        this.validToolNames = new Set(this.tools.map(t => t.function.name));
        // Callbacks
        this.toolApprovalCallback = config.toolApprovalCallback;
        this.streamDeltaCallback = config.streamDeltaCallback;
        this.statusCallback = config.statusCallback;
        // Infrastructure
        this._logger = (0, logger_1.getLogger)();
        this._logger.setSessionId(this.sessionId);
        this._sessionStore = (0, session_store_1.getSessionStore)();
        this._sessionStore.ensureSession(this.sessionId, 'pure-agent', this.model);
        this._logger.info(`PureAgent initialized: model=${this.model}, ` +
            `tools=${this.validToolNames.size}, ` +
            `apiMode=${this.apiMode}, ` +
            `baseUrl=${this.baseUrl}`);
    }
    // -------------------------------------------------------------------------
    // Run conversation (main entry point)
    // -------------------------------------------------------------------------
    async *runConversation(userMessage, options = {}) {
        this._turnCount++;
        this._abortController = new AbortController();
        this._interruptRequested = false;
        // Store user message — extract media to disk, store file references in DB
        let storedContent = userMessage;
        if (Array.isArray(userMessage) && userMessage.length > 0) {
            const processed = (0, conv_files_1.extractAndSaveMedia)(this.sessionId, userMessage);
            storedContent = processed.content;
        }
        const userMsgDbId = this._sessionStore.addMessage(this.sessionId, { role: 'user', content: storedContent });
        // Yield user message DB ID so frontend can track it for delete/retry
        yield { event: 'user_message', data: { id: String(userMsgDbId) } };
        // Run the conversation loop — store each round inline (matching 智控助手 pattern)
        let currentAssistantContent = '';
        let currentToolCalls;
        try {
            for await (const event of (0, conversation_loop_1.runConversation)(this, userMessage, options)) {
                // Accumulate streaming content
                if (event.event === 'content' && event.data.delta) {
                    currentAssistantContent += event.data.delta;
                }
                // When tool calls are issued, store the assistant message with tool_calls
                if (event.event === 'tool_calls' && event.data.calls) {
                    currentToolCalls = event.data.calls.map((tc) => ({
                        id: tc.id,
                        type: 'function',
                        function: { name: tc.name, arguments: typeof tc.arguments === 'string' ? tc.arguments : JSON.stringify(tc.arguments) },
                    }));
                    const msgId = this._sessionStore.addMessage(this.sessionId, {
                        role: 'assistant',
                        content: currentAssistantContent || null,
                        tool_calls: currentToolCalls,
                    });
                    // Inject backend message ID into the SSE event for frontend tracking
                    event.data.assistantMessageId = String(msgId);
                    // Reset for next round
                    currentAssistantContent = '';
                    currentToolCalls = undefined;
                }
                // When tool results arrive, store them
                if (event.event === 'tool_result') {
                    const toolMsgId = this._sessionStore.addMessage(this.sessionId, {
                        role: 'tool',
                        tool_call_id: event.data.tool_call_id,
                        content: event.data.result,
                    });
                    event.data.toolMessageId = String(toolMsgId);
                }
                // Done: store final assistant response (if not already stored via tool_calls)
                if (event.event === 'done') {
                    const finalContent = event.data.final_content || currentAssistantContent;
                    if (finalContent) {
                        const msgId = this._sessionStore.addMessage(this.sessionId, {
                            role: 'assistant',
                            content: finalContent,
                        });
                        event.data.assistantMessageId = String(msgId);
                    }
                }
                yield event;
            }
        }
        catch (err) {
            // On error, still store whatever we have
            if (currentAssistantContent) {
                this._sessionStore.addMessage(this.sessionId, {
                    role: 'assistant',
                    content: currentAssistantContent,
                });
            }
            throw err;
        }
    }
    // -------------------------------------------------------------------------
    // Interrupt
    // -------------------------------------------------------------------------
    interrupt() {
        this._interruptRequested = true;
        this._abortController.abort();
        this._logger.info('Interrupt requested');
    }
    // -------------------------------------------------------------------------
    // Close
    // -------------------------------------------------------------------------
    close() {
        if (this._closed)
            return;
        this._closed = true;
        // Abort any in-flight operations
        this._abortController.abort();
        // Cleanup background processes
        (0, terminal_1.cleanupAllBackground)();
        // Cleanup delegate sub-agents
        try {
            const { cleanupDelegateTool } = require('./delegate-tool');
            cleanupDelegateTool();
        }
        catch { /* not loaded */ }
        // Clear file ops cache
        (0, file_tools_1.clearFileOpsCache)(this.sessionId);
        // Clear logger session
        this._logger.clearSessionId();
        this._logger.info('PureAgent closed');
    }
    // -------------------------------------------------------------------------
    // Fallback provider
    // -------------------------------------------------------------------------
    get hasFallback() {
        return this._fallbackIndex < this.fallbacks.length;
    }
    tryActivateFallback() {
        if (!this.hasFallback)
            return false;
        const fallback = this.fallbacks[this._fallbackIndex];
        this._fallbackIndex++;
        // Update runtime properties
        this.baseUrl = fallback.baseUrl.replace(/\/+$/, '');
        this.apiKey = fallback.apiKey;
        if (fallback.model)
            this.model = fallback.model;
        if (fallback.customHeaders)
            this.customHeaders = fallback.customHeaders;
        // Reset abort controller for the new provider
        this._abortController = new AbortController();
        // Invalidate cached system prompt (model might have changed)
        this._cachedSystemPrompt = null;
        this._logger.info(`Switched to fallback provider ${this._fallbackIndex}/${this.fallbacks.length}: ${fallback.baseUrl}`);
        return true;
    }
    // -------------------------------------------------------------------------
    // Auth token refresh
    // -------------------------------------------------------------------------
    async tryRefreshAuth() {
        if (!this.refreshTokenCallback)
            return false;
        try {
            const result = await this.refreshTokenCallback();
            if (!result)
                return false;
            this.apiKey = result.apiKey;
            if (result.baseUrl)
                this.baseUrl = result.baseUrl.replace(/\/+$/, '');
            this._logger.info('Auth token refreshed successfully');
            return true;
        }
        catch (err) {
            this._logger.warn(`Auth refresh failed: ${err.message}`);
            return false;
        }
    }
    // -------------------------------------------------------------------------
    // Reset session state
    // -------------------------------------------------------------------------
    resetSessionState() {
        this._totalInputTokens = 0;
        this._totalOutputTokens = 0;
        this._turnCount = 0;
        (0, system_prompt_1.invalidateSystemPrompt)(this);
    }
    // -------------------------------------------------------------------------
    // Get session info
    // -------------------------------------------------------------------------
    getSessionInfo() {
        return {
            sessionId: this.sessionId,
            model: this.model,
            baseUrl: this.baseUrl,
            apiMode: this.apiMode,
            toolCount: this.validToolNames.size,
            tools: Array.from(this.validToolNames),
            workingDir: this.workingDir,
            turnCount: this._turnCount,
            totalInputTokens: this._totalInputTokens,
            totalOutputTokens: this._totalOutputTokens,
        };
    }
    // -------------------------------------------------------------------------
    // Get conversation history from store
    // -------------------------------------------------------------------------
    getConversationHistory() {
        return this._sessionStore.getMessages(this.sessionId);
    }
    // -------------------------------------------------------------------------
    // Static: create agent from existing conversation
    // -------------------------------------------------------------------------
    static fromConversation(sessionId, config) {
        const agent = new PureAgent(config);
        // Override session ID
        agent.sessionId = sessionId;
        return agent;
    }
}
exports.PureAgent = PureAgent;
//# sourceMappingURL=pure-agent.js.map