"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseAgent = void 0;
const utils_1 = require("../../utils");
const EventService_1 = require("../../services/EventService");
const clients_1 = require("../../clients");
const ai_1 = require("../../ai");
const MessageProcessor_1 = require("../../services/MessageProcessor");
const contextLimits_1 = require("../../clients/contextLimits");
class BaseAgent {
    status = "not_started";
    lastHealthCheckTime = 0;
    provider = "openai";
    modelName = ai_1.Models.openai.GPT_4o;
    client = null;
    modelPreferences = [];
    currentModelPreferenceIndex = 0;
    easyFinalAnswer = false;
    requiredToolNames = ["finalAnswer"];
    maxTurns = null;
    maxSpend = null;
    maxRunTimeMs = null;
    startTimeMs = null;
    turnCount = 0;
    totalCostUsd = 0;
    currentThread = 0;
    totalInputTokens = 0;
    totalOutputTokens = 0;
    totalCacheReadTokens = 0;
    totalCacheWriteTokens = 0;
    compressThreshold = 30000;
    compressMinMessages = 30;
    threads = [];
    pendingUserMessages = [];
    pendingMessages = [];
    taskBreakdown = "";
    summaries = [];
    currentTaskId = null;
    agentEvents = new EventService_1.EventService();
    eventTypes = {
        newThread: "new_thread",
        threadUpdate: "thread_update",
        costUpdate: "cost_update",
        agentLog: "agent:log",
        toolCall: "tool:pre_call",
        toolUsed: "tool:post_call",
        agentStatus: "agent:status",
        notStarted: "not_started",
        inProgress: "in_progress",
        done: "done",
        pause: "pause",
        kill: "kill",
        unpause: "unpause",
        agentMsg: "agent:msg",
        userSay: "user:say",
        agentSay: "agent:say",
        agentNewTask: "agent:newTask",
        agentTaskComplete: "agent:taskComplete",
        tokenUsage: "agent:tokenUsage",
    };
    tools;
    events;
    messageProcessor;
    clientService;
    disabledTools = [];
    constructor(context) {
        this.tools = context.Tools;
        this.events = context.Events;
        this.messageProcessor = context.messageProcessor || new MessageProcessor_1.MessageProcessor();
        this.clientService = context.Clients || clients_1.Clients;
        if (!this.tools) {
            throw new Error("ToolsService is required for BaseAgent");
        }
        if (!this.events) {
            throw new Error("EventService is required for BaseAgent");
        }
        this.events.setListener({
            key: `agent:msg:${this.constructor.name}`,
            event: this.eventTypes.agentMsg,
        }, (eventData) => {
            if (this.status === this.eventTypes.inProgress ||
                this.status === this.eventTypes.pause) {
                const message = {
                    role: "user",
                    content: JSON.stringify(eventData),
                };
                this.addPendingMessage(message);
            }
        });
    }
    log(message, level = "info") {
        this.agentEvents.emit(this.eventTypes.agentLog, {
            agentName: this.name,
            message,
            level,
            timestamp: Date.now(),
            taskId: this.currentTaskId,
        });
    }
    setCompressThreshold(threshold) {
        this.compressThreshold = threshold;
    }
    getCompressThreshold() {
        if (this.compressThreshold !== contextLimits_1.DEFAULT_CONTEXT_LIMIT) {
            return this.compressThreshold;
        }
        const result = this.clientService.getContextLimit(this.getProvider(), this.getModel());
        const contextLimit = result?.contextLimit ?? contextLimits_1.DEFAULT_CONTEXT_LIMIT;
        const threshold = result?.threshold ?? contextLimit;
        return Math.floor(threshold * 0.85);
    }
    setMaxTurns(maxTurns) {
        this.maxTurns = maxTurns;
    }
    setMaxSpend(maxSpend) {
        this.maxSpend = maxSpend;
    }
    setMaxRunTime(maxRunTimeMs) {
        this.maxRunTimeMs = maxRunTimeMs;
    }
    newTask(taskId) {
        this.currentThread = 0;
        this.threads = [];
        this.taskBreakdown = "";
        this.summaries = [];
        this.totalCostUsd = 0;
        this.totalInputTokens = 0;
        this.totalOutputTokens = 0;
        this.totalCacheReadTokens = 0;
        this.totalCacheWriteTokens = 0;
        this.status = this.eventTypes.inProgress;
        this.turnCount = 0;
        this.startTimeMs = Date.now();
        this.currentTaskId = taskId || this.startTimeMs.toString();
        const id = taskId || this.startTimeMs.toString();
        this.events.emit(this.eventTypes.agentNewTask, {
            taskId: id,
        });
    }
    register() {
        this.events.registerAgent(this);
    }
    setModelPreferences(value) {
        this.modelPreferences = value;
        if (value.length) {
            this.updatePreferences(value[0]);
        }
    }
    updatePreferences(value) {
        this.setModel(value.model);
        this.setProvider(value.provider);
    }
    nextModel() {
        this.currentModelPreferenceIndex++;
        if (this.currentModelPreferenceIndex >= this.modelPreferences.length) {
            throw new Error("We have exhausted all model preferences.");
        }
        const nextModel = this.modelPreferences[this.currentModelPreferenceIndex];
        this.updatePreferences(nextModel);
    }
    getModel() {
        return this.modelName;
    }
    setModel(value) {
        this.modelName = value;
        this.client = null;
    }
    getProvider() {
        return this.provider;
    }
    setProvider(value) {
        this.provider = value;
        this.client = null;
    }
    getClient() {
        if (!this.client) {
            if (this.provider) {
                this.log(`Getting client for provider ${this.provider}`);
                const clientInfo = this.clientService.getClient(this.getProvider());
                if (clientInfo) {
                    this.client = clientInfo.client;
                }
            }
            if (!this.client) {
                this.log(`Getting client for model ${this.modelName}`);
                const clientInfo = this.clientService.getClient(undefined, this.getModel());
                if (clientInfo) {
                    this.client = clientInfo.client;
                }
            }
        }
        return this.client;
    }
    setClient(client) {
        this.client = client;
    }
    setEasyFinalAnswer(value) {
        this.easyFinalAnswer = value;
    }
    isTerminationResponse(content) {
        const trimmed = content.trim();
        const wordCount = trimmed.split(/\s+/).filter(Boolean).length;
        if (wordCount <= 3) {
            const terminationPattern = /^(done|complete|completed|finished|final\s*answer|task\s*complete|all\s*done|that'?s\s*(all|it)|ok(ay)?|yes)[.!]*$/i;
            if (terminationPattern.test(trimmed))
                return true;
        }
        const firstWords = trimmed.split(/\s+/).slice(0, 3).join(" ");
        const firstWordPattern = /^(task\s*(complete|completed|done|finished)|all\s*done|no\s*(further|more|additional|changes|action)|confirmed?\s*(complete|done|finished|one\s*last)|nothing\s*(more|further|else)|standing\s*by|everything\s*is|still\s*confirmed|acknowledged|done\s*and|complete\s*(and|\.)|completed\s*successfully|no\s*additional|verified\s*and)/i;
        if (firstWordPattern.test(firstWords))
            return true;
        if (this.easyFinalAnswer) {
            if (trimmed.startsWith("✅") || /^[\d\.\-\*]/.test(trimmed))
                return true;
        }
        try {
            const parsed = JSON.parse(trimmed);
            if (parsed && typeof parsed === "object") {
                if (typeof parsed.answer === "string")
                    return true;
                if (typeof parsed.finalAnswer === "string")
                    return true;
            }
        }
        catch (_) { }
        return false;
    }
    getEnabledTools() {
        return this.tools
            .getTools()
            .filter((t) => !this.disabledTools.includes(t.function.name));
    }
    getEnabledToolNames() {
        return this.getEnabledTools().map((t) => t.function.name);
    }
    disableTool(toolName) {
        this.disabledTools.push(toolName);
    }
    isToolEnabled(toolName) {
        return !!this.getEnabledTools().find((t) => t.function.name === toolName);
    }
    enableTool(toolName) {
        if (!this.isToolEnabled(toolName)) {
            this.disabledTools = this.disabledTools.filter((t) => t !== toolName);
        }
    }
    checkLimits() {
        if (this.maxTurns !== null && this.turnCount >= this.maxTurns) {
            this.log(`Turn limit reached: ${this.turnCount}/${this.maxTurns}`, "warn");
            return true;
        }
        if (this.maxSpend !== null && this.totalCostUsd >= this.maxSpend) {
            this.log(`Spend limit reached: $${this.totalCostUsd.toFixed(4)}/$${this.maxSpend.toFixed(4)}`, "warn");
            return true;
        }
        if (this.maxRunTimeMs !== null && this.startTimeMs !== null) {
            const currentRunTimeMs = this.runTime();
            if (currentRunTimeMs >= this.maxRunTimeMs) {
                this.log(`Runtime limit reached: ${currentRunTimeMs}ms/${this.maxRunTimeMs}ms`, "warn");
                return true;
            }
        }
        return false;
    }
    runTime() {
        if (this.startTimeMs) {
            return Date.now() - this.startTimeMs;
        }
        return 0;
    }
    shouldTerminateFromLimits() {
        return this.checkLimits();
    }
    getTurnCount() {
        return this.turnCount;
    }
    adjustTotalCostUsd(cost) {
        if (cost) {
            this.totalCostUsd += cost;
            this.agentEvents.emit(this.eventTypes.costUpdate, this.totalCostUsd);
        }
    }
    getTotalCostUsd() {
        return this.totalCostUsd;
    }
    adjustTokenUsage(usage) {
        if (!usage)
            return;
        const inputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0;
        const outputTokens = usage.output_tokens ?? usage.completion_tokens ?? 0;
        const cacheReadTokens = usage.cache_read_input_tokens ?? usage.cache_read_tokens ??
            usage.prompt_tokens_details?.cached_tokens ?? 0;
        const cacheWriteTokens = usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0;
        this.totalInputTokens += inputTokens;
        this.totalOutputTokens += outputTokens;
        this.totalCacheReadTokens += cacheReadTokens;
        this.totalCacheWriteTokens += cacheWriteTokens;
        this.agentEvents.emit(this.eventTypes.tokenUsage, {
            inputTokens,
            outputTokens,
            cacheReadTokens,
            cacheWriteTokens,
            totalInputTokens: this.totalInputTokens,
            totalOutputTokens: this.totalOutputTokens,
            totalCacheReadTokens: this.totalCacheReadTokens,
            totalCacheWriteTokens: this.totalCacheWriteTokens,
        });
    }
    getTokenUsage() {
        return {
            totalInputTokens: this.totalInputTokens,
            totalOutputTokens: this.totalOutputTokens,
            totalCacheReadTokens: this.totalCacheReadTokens,
            totalCacheWriteTokens: this.totalCacheWriteTokens,
        };
    }
    startNewThread(messages) {
        this.currentThread++;
        this.agentEvents.emit(this.eventTypes.newThread, messages);
        this.updateCurrentThread(messages);
    }
    updateCurrentThread(messages) {
        this.threads[this.currentThread] = messages;
        this.agentEvents.emit(this.eventTypes.threadUpdate, messages);
    }
    getThreads() {
        return this.threads;
    }
    getSummaries() {
        return this.summaries;
    }
    async processToolMessages(toolCall) {
        this.agentEvents.emit(this.eventTypes.toolCall, { toolCall });
        const { functionResp, toolMessages } = await this.tools.callTool(toolCall, this.getEnabledToolNames());
        this.agentEvents.emit(this.eventTypes.toolUsed, {
            toolCall,
            functionResp,
        });
        return toolMessages;
    }
    logMessages(messages) {
        for (const message of messages) {
            if (message.role === "assistant" && message.content) {
                this.agentEvents.emit(this.eventTypes.agentSay, {
                    message: message.content,
                });
            }
        }
    }
    formatInputContent(userInput) {
        return (0, utils_1.replaceEscapedNewLines)(userInput);
    }
    formatAiResponse(response) {
        return (0, utils_1.restoreEscapedNewLines)(response);
    }
    formatInputMessages(messages) {
        return messages.map((m) => ({
            ...m,
            content: typeof m.content === "string"
                ? this.formatInputContent(m.content)
                : m.content,
        }));
    }
    formatOutputMessages(messages) {
        return messages.map((m) => ({
            ...m,
            content: typeof m.content === "string"
                ? this.formatAiResponse(m.content)
                : m.content,
        }));
    }
    async healthCheck() {
        try {
            const canCallProvider = await this.getClient().createChatCompletion({
                messages: [{ role: "user", content: "Hello!" }],
                model: this.getModel(),
                max_tokens: 2,
            });
            return true;
        }
        catch (e) {
            this.log(String(e), "error");
            return false;
        }
    }
    async selectHealthyModel() {
        const currentTime = Date.now();
        if (currentTime - this.lastHealthCheckTime < 60 * 1000) {
            return;
        }
        let healthy = await this.healthCheck();
        this.lastHealthCheckTime = Date.now();
        while (!healthy) {
            this.nextModel();
            healthy = await this.healthCheck();
        }
        await this.healthCheck();
    }
    isRequiredToolMissing() {
        const requiredToolAvailable = this.getEnabledToolNames().some((enabled) => this.requiredToolNames.includes(enabled) ||
            this.requiredToolNames.includes((0, utils_1.mcpToolName)(enabled)) ||
            this.requiredToolNames.some((required) => enabled.endsWith(required)));
        if (requiredToolAvailable) {
            return false;
        }
        this.log(`Required tool: [${this.requiredToolNames}] not available, checking for finalAnswer. Enabled: ${this.getEnabledToolNames().join(", ")}`);
        const finalAnswer = "finalAnswer";
        const requiredFinalAnswer = this.requiredToolNames.includes(finalAnswer);
        const hasFinalAnswer = this.getEnabledToolNames().includes(finalAnswer);
        if (hasFinalAnswer && !requiredFinalAnswer) {
            this.log("Required tool not available, setting finalAnswer as required tool", "warn");
            this.requiredToolNames.push("finalAnswer");
            return false;
        }
        return true;
    }
    setNotHealthy() {
        this.lastHealthCheckTime = 0;
    }
    pause() {
        this.log("Pausing agent");
        this.agentEvents.emit(this.eventTypes.pause, this);
        this.status = this.eventTypes.pause;
    }
    unpause() {
        this.log("Unpausing agent");
        this.agentEvents.emit(this.eventTypes.unpause, this);
        this.status = this.eventTypes.inProgress;
    }
    async unpaused() {
        return new Promise((resolve) => {
            this.log("Waiting for agent to unpause");
            this.agentEvents.once(this.eventTypes.unpause, () => {
                this.log("Agent resumed");
                resolve(true);
            });
            this.agentEvents.once(this.eventTypes.done, () => {
                resolve(true);
            });
        });
    }
    async kill() {
        this.log("Killing agent");
        if (this.status === this.eventTypes.kill ||
            this.status === this.eventTypes.done) {
            this.log("Agent is already being killed or done, ignoring duplicate kill()", "warn");
            return;
        }
        this.agentEvents.emit(this.eventTypes.kill, this);
        this.status = this.eventTypes.kill;
        this.addPendingMessage({
            role: "user",
            content: `<Workflow>The user has requested the task to end, please call ${this.requiredToolNames} with a report of your ending state</Workflow>`,
        });
    }
    async call(userInput, _messages, retryCount = 0) {
        if (this.status === this.eventTypes.notStarted) {
            this.status = this.eventTypes.inProgress;
        }
        if (this.status === this.eventTypes.pause) {
            await this.unpaused();
        }
        await this.selectHealthyModel();
        this.turnCount++;
        if (this.shouldTerminateFromLimits()) {
            const currentRunTimeMs = this.startTimeMs
                ? Date.now() - this.startTimeMs
                : 0;
            const limitMsg = `Task terminated due to limits reached. Turn: ${this.turnCount}/${this.maxTurns || "unlimited"}, Cost: $${this.totalCostUsd.toFixed(4)}/${this.maxSpend ? "$" + this.maxSpend.toFixed(4) : "unlimited"}, Runtime: ${currentRunTimeMs}ms/${this.maxRunTimeMs ? this.maxRunTimeMs + "ms" : "unlimited"}`;
            this.status = this.eventTypes.done;
            this.agentEvents.emit(this.eventTypes.done, limitMsg);
            return limitMsg;
        }
        try {
            const model = this.getModel();
            let messages = _messages || (await this.getInitialMessages(userInput));
            if (!_messages) {
                messages = await this.messageProcessor.processMessages(messages, "initial_call");
            }
            if (this.pendingUserMessages.length) {
                messages.push(...this.pendingUserMessages);
                this.pendingUserMessages = [];
            }
            if (this.pendingMessages.length) {
                messages.push(...this.pendingMessages);
                this.pendingMessages = [];
            }
            messages = this.formatInputMessages(messages);
            this.updateCurrentThread(messages);
            const isMissingTool = this.isRequiredToolMissing();
            const startIndex = 0;
            const endIndex = messages.length;
            messages = await this.messageProcessor.processMessages(messages, "pre_call");
            const response = await this.getClient().createChatCompletion({
                model,
                messages,
                tools: this.getEnabledTools(),
                tool_choice: "auto",
                long_ttl_cache: this.runTime() > 300_000,
            });
            if (this.status === this.eventTypes.pause) {
                this.log("Agent was paused after completion, waiting before processing tool calls");
                await this.unpaused();
            }
            if (response?.usd_cost === undefined) {
                this.log(`Response cost is undefined: ${JSON.stringify(response, null, 2)}`, "warn");
                const error = response;
                if (error != null && "response" in error && "data" in error.response) {
                    this.log(`Response data: ${JSON.stringify(error.response.data, null, 2)}`, "warn");
                }
                if (!response?.choices) {
                    const errMsg = (error?.error?.message ?? error?.message) ||
                        JSON.stringify(response);
                    throw new Error(`AI response error: ${errMsg}`);
                }
            }
            this.adjustTotalCostUsd(response?.usd_cost);
            this.adjustTokenUsage(response?.usage);
            this.log("agent response cost: " + response?.usd_cost);
            for (const choice of response.choices) {
                messages.push(choice.message);
                messages = await this.messageProcessor.processMessages(messages, "post_call");
                const lastMessage = messages[messages.length - 1];
                this.logMessages([lastMessage]);
                const toolCalls = lastMessage.tool_calls;
                if (lastMessage.tool_calls) {
                    messages = await this.messageProcessor.processMessages(messages, "pre_tools");
                    this.updateCurrentThread(messages);
                    const truncationWarning = this.detectTruncatedToolCalls(toolCalls, response);
                    if (truncationWarning) {
                        messages.push(truncationWarning);
                        this.updateCurrentThread(messages);
                        return this.call(userInput, messages);
                    }
                    for (const toolCall of toolCalls) {
                        if (this.status === this.eventTypes.pause) {
                            this.log("Agent was paused before tool call, waiting before processing tool calls");
                            await this.unpaused();
                        }
                        const toolMessages = await this.processToolMessages(toolCall);
                        messages.push(...toolMessages);
                        const finalMessage = toolMessages.find((called) => this.requiredToolNames.includes(called.name) ||
                            this.requiredToolNames.includes((0, utils_1.mcpToolName)(called.name)) ||
                            this.requiredToolNames.some((required) => called.name.endsWith(required)));
                        if (finalMessage) {
                            if (this.pendingUserMessages.length > 0) {
                                this.log("finalAnswer called but pending user messages exist, continuing to respond to feedback");
                                messages.push(...this.pendingUserMessages);
                                this.pendingUserMessages = [];
                                this.updateCurrentThread(messages);
                                return this.call(userInput, messages);
                            }
                            this.events.emit(this.eventTypes.agentTaskComplete, {
                                taskId: this.currentTaskId ||
                                    this.startTimeMs?.toString() ||
                                    Date.now().toString(),
                                result: finalMessage.content || "Done",
                            });
                            const doneMsg = finalMessage.content || "Done";
                            this.updateCurrentThread(messages);
                            this.agentEvents.emit(this.eventTypes.done, doneMsg);
                            this.status = this.eventTypes.done;
                            return doneMsg;
                        }
                    }
                }
            }
            const newToolCalls = response.choices.flatMap((c) => c.message.tool_calls);
            if (newToolCalls && newToolCalls.length > 0) {
                messages = await this.messageProcessor.processMessages(messages, "post_tools");
            }
            const firstMessage = response.choices[0].message;
            if (response.choices.length === 1 &&
                firstMessage.content &&
                this.isTerminationResponse(firstMessage.content)) {
                this.log(`Termination word detected: "${firstMessage.content.trim()}", treating as finalAnswer`);
                this.status = this.eventTypes.done;
                this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
                return firstMessage.content;
            }
            if (response.choices.length === 1 &&
                firstMessage.content &&
                this.easyFinalAnswer) {
                this.status = this.eventTypes.done;
                this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
                return firstMessage.content;
            }
            if (isMissingTool) {
                const error = `Required tool: ${JSON.stringify(this.requiredToolNames)} not available, options are ${this.getEnabledToolNames().join(", ")}`;
                this.log(error, "error");
                this.status = this.eventTypes.done;
                this.agentEvents.emit(this.eventTypes.done, error);
                return error;
            }
            if (this.pendingUserMessages.length === 0 &&
                this.status === this.eventTypes.kill) {
                this.log("Agent killed, stopping execution");
                this.status = this.eventTypes.done;
                this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
                return firstMessage.content;
            }
            if (this.getMessagesLength(messages) > this.getCompressThreshold() &&
                messages.length > this.compressMinMessages) {
                const taskBreakdown = await this.getTaskBreakdown(messages);
                this.log(`Compressing messages: ${this.getMessagesLength(messages)} exceeds ${this.getCompressThreshold()}`);
                messages = await this.compressMessages(messages, startIndex, endIndex);
                this.startNewThread(messages);
            }
            if (["assistant", "tool"].includes(messages[messages.length - 1].role)) {
                const statusMessage = this.getStatusMessage();
                this.logStatus();
                const continuation = `<Workflow>
        workflow continues until you call one of ${JSON.stringify(this.requiredToolNames)}.\n
        ${statusMessage}
        </Workflow>`;
                messages.push({
                    role: "user",
                    content: continuation,
                });
            }
            this.updateCurrentThread(messages);
            return this.call(userInput, messages);
        }
        catch (e) {
            if (e.toString().includes("429")) {
                this.setNotHealthy();
                return this.call(userInput, _messages, retryCount);
            }
            const errorStr = e.toString();
            const isNonRetriable = errorStr.includes("401") ||
                errorStr.includes("403") ||
                errorStr.includes("404");
            const isRetriable = !isNonRetriable &&
                (errorStr.match(/5\d\d/) ||
                    errorStr.includes("Failed to get models") ||
                    errorStr.includes("timeout") ||
                    errorStr.includes("ECONNRESET") ||
                    errorStr.includes("ETIMEDOUT") ||
                    errorStr.includes("Invalid response format from MCP"));
            if (isRetriable && retryCount < 3) {
                const delay = 1000 * Math.pow(2, retryCount);
                this.log(`Agent request failed (attempt ${retryCount + 1}/3), retrying in ${delay}ms: ${e.message}`, "warn");
                await new Promise((resolve) => setTimeout(resolve, delay));
                return this.call(userInput, _messages, retryCount + 1);
            }
            this.log(`Agent failed: ${e}`, "error");
            if (e != null && typeof e === "object" && "response" in e && "data" in e.response) {
                this.log(`Error response data: ${JSON.stringify(e.response.data, null, 2)}`, "error");
            }
            this.agentEvents.emit(this.eventTypes.done, e.message);
            return e.message;
        }
    }
    getStatusMessage() {
        const baseMessage = `$${this.getTotalCostUsd().toPrecision(3)}\nElapsed: ${Math.floor(this.runTime() / 1000)}s\n`;
        const remainingTime = this.maxRunTimeMs && this.startTimeMs
            ? this.maxRunTimeMs - (Date.now() - this.startTimeMs)
            : null;
        const remainingTurns = this.maxTurns
            ? this.maxTurns - this.turnCount
            : null;
        const timeRemainingMsg = remainingTime
            ? `You have approximately ${Math.floor(remainingTime / 1000)} seconds remaining for this task. `
            : "";
        const turnsRemainingMsg = remainingTurns
            ? `You have ${remainingTurns} turns remaining. `
            : "";
        const remainingBudget = this.maxSpend
            ? this.maxSpend - this.totalCostUsd
            : null;
        const budgetRemainingMsg = remainingBudget
            ? `You have $${remainingBudget.toFixed(4)} remaining in your budget.`
            : "";
        const statusMessage = `${baseMessage}\n${timeRemainingMsg}\n${turnsRemainingMsg}\n${budgetRemainingMsg}`;
        return statusMessage;
    }
    logStatus() {
        const statusMessage = this.getStatusMessage();
        this.agentEvents.emit(this.eventTypes.agentStatus, {
            agentName: this.name,
            taskId: this.currentTaskId,
            statusMessage,
            details: {
                totalCostUsd: this.getTotalCostUsd(),
                elapsedMs: this.runTime(),
                remainingTimeMs: this.maxRunTimeMs && this.startTimeMs
                    ? this.maxRunTimeMs - (Date.now() - this.startTimeMs)
                    : undefined,
                remainingTurns: this.maxTurns
                    ? this.maxTurns - this.turnCount
                    : undefined,
                remainingBudget: this.maxSpend
                    ? this.maxSpend - this.totalCostUsd
                    : undefined,
            },
            timestamp: Date.now(),
        });
    }
    addPendingMessage(message) {
        if (this.status === this.eventTypes.done) {
            this.log("Agent is done, cannot take more messages", "warn");
        }
        else {
            const pendingMessages = this.pendingMessages.map((m) => m.content);
            if (pendingMessages.includes(message.content)) {
                return;
            }
            this.pendingMessages.push(message);
        }
    }
    addPendingUserMessage(message) {
        if (this.status === this.eventTypes.done) {
            this.log("Agent is done, cannot take more messages", "warn");
        }
        else {
            const pendingMessages = this.pendingUserMessages.map((m) => m.content);
            if (pendingMessages.includes(message.content)) {
                return;
            }
            this.pendingUserMessages.push(message);
        }
        this.events.emit(this.eventTypes.userSay, message.content);
    }
    getMessagesLength(messages) {
        return JSON.stringify(messages).split(" ").length;
    }
    detectTruncatedToolCalls(toolCalls, response) {
        const outputTokens = response?.usage?.completion_tokens || 0;
        const totalArgLength = toolCalls.reduce((sum, tc) => sum + (tc.function?.arguments?.length || 0), 0);
        const expectedArgChars = outputTokens * 4;
        const suspiciouslySmallArgs = outputTokens > 1000 && totalArgLength < expectedArgChars * 0.1;
        for (const toolCall of toolCalls) {
            const args = toolCall.function?.arguments || "";
            let isInvalidJson = false;
            try {
                JSON.parse(args);
            }
            catch {
                isInvalidJson = true;
            }
            if (isInvalidJson || args.trim() === "" || suspiciouslySmallArgs) {
                this.log(`Tool call '${toolCall.function?.name}' has malformed/truncated arguments — likely hit output token limit (outputTokens=${outputTokens}, argLength=${args.length})`, "warn");
                return {
                    role: "user",
                    content: "⚠️ Output limit warning: Your last tool call had incomplete or missing arguments, which usually means you exceeded the output token limit mid-response. The model reported " +
                        outputTokens +
                        " output tokens but only " +
                        totalArgLength +
                        " characters of tool call arguments were received. Please write smaller, more concise content in your tool calls. Aim for no more than 4000 tokens of output per response. Break large responses into smaller pieces if needed.",
                };
            }
        }
        return null;
    }
    async getTaskBreakdown(messages) {
        if (this.taskBreakdown) {
            return this.taskBreakdown;
        }
        const taskPrompt = `
    Analyze all previous messages.

    Generate a detailed task breakdown for this conversation, include a section for the following:
    1. Task List
    2. Completion Criteria - when the agent should stop

    Your output will be used to guide the work of the agent, and determine when we've accomplished the goal
    `;
        const model = this.getModel();
        const response = await this.getClient().createChatCompletion({
            model,
            messages: [
                ...messages,
                {
                    role: "user",
                    content: taskPrompt,
                },
            ],
            max_tokens: 2000,
        });
        this.adjustTotalCostUsd(response.usd_cost);
        this.taskBreakdown = response.choices[0].message.content;
        this.log(`task breakdown cost: ${response.usd_cost}`);
        return this.taskBreakdown;
    }
    async compressMessages(messages, startIndex, endIndex) {
        this.log(`Compressing messages from ${startIndex} to ${endIndex}, total messages: ${messages.length}`);
        const toCompress = messages.slice(startIndex, endIndex);
        const toCompressPrompt = `We are compressing our conversation to save memory.
    Please summarize the conversation so far, so that we may continue the original task with a smaller context

    Include the following sections:
    1. Initial Request - what this agent was originally tasked with.
    2. Progress - what has been tried so far,
    3. Next Steps - what we're about to do next to continue the user's original request.
    4. Tasks remaining - what tasks are left from the initial task breakdown.

    Our initial task breakdown: ${this.taskBreakdown}

    This summary will become the agent's only memory of the past, all other messages will be dropped:

      `;
        const model = this.getModel();
        const response = await this.getClient().createChatCompletion({
            model,
            messages: [
                ...messages,
                {
                    role: "user",
                    content: toCompressPrompt,
                },
            ],
        });
        this.adjustTotalCostUsd(response.usd_cost);
        const summaries = response.choices.map((c) => c.message.content);
        this.summaries.push(...summaries);
        const startMessages = [
            {
                role: "user",
                content: `
        Initial task breakdown:
        ${this.taskBreakdown}

        We have just compressed the conversation to save memory:
        ${JSON.stringify(summaries)}

        Please continue the task from where we left off
        `,
            },
        ];
        const systemMesasges = toCompress.filter((m) => m.role === "system");
        const newMessages = [
            ...systemMesasges,
            ...startMessages,
            ...messages.slice(endIndex),
        ];
        const oldLength = this.getMessagesLength(messages);
        const newLength = this.getMessagesLength(newMessages);
        const compressionRatio = (((oldLength - newLength) / oldLength) *
            100).toFixed(2);
        this.log(`compression cost: ${response.usd_cost}`);
        this.log(`Compressed messages from ${oldLength} to ${newLength}, ${compressionRatio}% reduction in size`);
        return newMessages;
    }
}
exports.BaseAgent = BaseAgent;
//# sourceMappingURL=base.js.map