"use strict";
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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginAgent = exports.PluginAgentToolGroups = void 0;
const path_1 = __importDefault(require("path"));
const promises_1 = require("node:fs/promises");
const js_sha256_1 = require("js-sha256");
const cliconfig_1 = require("../../database/cliconfig");
const compiler_1 = require("../compiler");
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const chalk_1 = __importDefault(require("chalk"));
const baseAgent_1 = require("./baseAgent");
const projectrootpath_1 = require("../../projectrootpath");
const logger_1 = require("../../utils/logger");
const tokenCounter_1 = require("../tokenCounter");
const tools_1 = require("./mcp-servers/tools");
const supabase_1 = require("./mcp-servers/supabase");
const Sentry = __importStar(require("@sentry/node"));
exports.PluginAgentToolGroups = [
    "graphqlSchema",
    "projectCodeRead",
    "projectCodeWrite",
    "codeExecution",
    // "dashboardRead",
    "supabaseReadTools",
    "batchedPluginAgentTools"
];
class PluginAgent extends baseAgent_1.BaseAgent {
    constructor() {
        super();
        this.initialize().catch((err) => {
            logger_1.logger.error("Failed to initialize PluginAgent", err);
        });
    }
    /**
     * Replaces the old 'initialize' logic. Sets up the claude client and the MCP clients.
     * The user can add more logic for supabase, with the commented code or additional servers.
     */
    initialize() {
        const _super = Object.create(null, {
            initialize: { get: () => super.initialize }
        });
        return __awaiter(this, void 0, void 0, function* () {
            _super.initialize.call(this);
            // Set up GraphQL MCP if not connected yet:
            if (!this.mcp) {
                this.mcp = {
                    client: new index_js_1.Client({ name: "plugin-agent-client", version: "1.0.0" }),
                };
                yield this.connectToMCPServer(path_1.default.resolve(projectrootpath_1.nodejsRoot, "controllers", "agents", "mcp-servers", "index.ts"), this.mcp, [
                    "pluginAgent", // name of the mcp server. 
                    ...exports.PluginAgentToolGroups
                ]);
            }
        });
    }
    /**
     * After generation is complete, we finalize the code, parse out the code blocks,
     * then compile them with the old logic from "prompt()" function.
     */
    finalProcessingAndCompilation(openedAppRepoPath) {
        return __awaiter(this, void 0, void 0, function* () {
            logger_1.logger.info("compiling all plugins");
            yield new Promise((resolve) => {
                setTimeout(() => {
                    resolve({});
                }, 2000);
            });
            // Now compile
            const pluginsDir = path_1.default.resolve(openedAppRepoPath, "remoteCode/plugins");
            const entries = yield (0, promises_1.readdir)(pluginsDir, { withFileTypes: true });
            const plugins = entries
                .filter((it) => it.isDirectory())
                .map((it) => it.name);
            const compiledDirectoryName = (0, js_sha256_1.sha256)(plugins.join("_")).toString();
            return yield (0, compiler_1.compileMultiplePlugins)(path_1.default.resolve(openedAppRepoPath, "remoteCode"), plugins, compiledDirectoryName);
        });
    }
    /**
     * The main driver method that replaces the old 'prompt(...)' function. It orchestrates
     * the sub-steps using the class methods above.
     */
    runPrompt(appId, assetName, userPrompt, req, res, model, provider, user, temp_id) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c;
            const chalkColor = (0, baseAgent_1.cycleColor)();
            logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + "runPrompt started with userPrompt: " +
                chalk_1.default.gray(JSON.stringify(userPrompt)));
            let requestDropped = false;
            req.socket.on("close", () => {
                requestDropped = true;
                logger_1.logger.info(chalkColor(`[PluginAgent][${user}][${appId}] request was closed. Tool loop will be abandoned if its still running.`));
            });
            // Prepare prompt data
            const finalizedPrompt = this.prepareUserPrompt(userPrompt, assetName);
            // ensure anthropic is ready
            if (!this.claude) {
                logger_1.logger.info(chalkColor(`[PluginAgent][${user}][${appId}]`) + " no anthropic client found!");
                return {
                    tokenCounterInstance: null,
                    message: "No anthropic client found!",
                };
            }
            if (!this.openai) {
                logger_1.logger.info(`[PluginAgent][${user}][${appId}] no openai client found!`);
                return {
                    tokenCounterInstance: null,
                    message: "No openai client found!",
                };
            }
            if (!this.google) {
                logger_1.logger.info(chalkColor(`[PluginAgent][${user}]`) + " no google client found!");
                return {
                    tokenCounterInstance: null,
                    message: "No google client found!",
                };
            }
            if (!this.amazon) {
                logger_1.logger.info(chalkColor(`[PluginAgent][${user}]`) + " no google client found!");
                return {
                    tokenCounterInstance: null,
                    message: "No amazon client found!",
                };
            }
            // get the open app
            const openedApp = yield (0, cliconfig_1.getOpenApp)(appId);
            if (!openedApp) {
                logger_1.logger.info(chalkColor(`[PluginAgent][${user}][${appId}]`) + " no app found for appId:" + appId);
                return {
                    tokenCounterInstance: null,
                    message: "App not found!",
                };
            }
            // we will build a file for writing
            const chatAnchor = path_1.default.resolve(openedApp.repoPath, "remoteCode", "plugins", assetName, "source", "component.jsx");
            logger_1.logger.info(chalkColor(`[DevAgent][${user}[${appId}]]`) + " Getting chat for: " + chatAnchor);
            const { chat, messages, systemPrompt } = yield this.retrieveOrCreateChat(chatAnchor, model, "widget", provider, finalizedPrompt, path_1.default.resolve(projectrootpath_1.nodejsRoot, provider === "amazon" ? "systemPrompts/tokenoptimized/latest.html" : "systemPrompts/latest.html"), appId, user, temp_id);
            logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + " Chat retrieved. Truncating to fit within limit.");
            // const slicedMessages = messages.length <= 10 ? messages : messages.slice(messages.length - 10)
            const slicedMessages = messages;
            // Now ensure we are under token budget
            let finalMessages = yield this.fitHistoryWithinTokenBudget(slicedMessages, systemPrompt, model, provider, { pluginName: assetName, pluginsDir: path_1.default.resolve(openedApp.repoPath, "remoteCode", "plugins") }, user);
            logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + " Starting chat with " + finalMessages.length + " messages after fitting.");
            const tokenCounterInstance = new tokenCounter_1.TokenCounter([], "widget", model, provider);
            try {
                let toolRun = false;
                do {
                    logger_1.logger.info(chalkColor(`[PluginAgent][${user}][${appId}]`) + " triggering completion");
                    //Putting the input message in chat history message
                    tokenCounterInstance.appendInMemoryInputMessages(finalMessages);
                    let llmsdk;
                    if (provider === "claude") {
                        llmsdk = this.claude;
                    }
                    else if (provider === "google") {
                        llmsdk = this.openai;
                    }
                    else if (provider === "amazon") {
                        llmsdk = this.amazon;
                    }
                    else {
                        llmsdk = this.openai;
                    }
                    const ingestedResponse = yield this.ingestResponseAndStream(llmsdk, systemPrompt, model, finalMessages, res, tokenCounterInstance, false);
                    logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + " completion finished");
                    if ((ingestedResponse === null || ingestedResponse === void 0 ? void 0 : ingestedResponse.messageText) &&
                        ingestedResponse.messageText.trim()) {
                        //Pushing to chat history array
                        tokenCounterInstance.appendInMemoryOutputMessages([
                            {
                                role: "assistant",
                                tool_call_id: null,
                                tool_name: null,
                                content_type: "text",
                                content: ingestedResponse === null || ingestedResponse === void 0 ? void 0 : ingestedResponse.messageText,
                                temp_id: ingestedResponse.uuid
                            },
                        ]);
                        yield (0, cliconfig_1.persistChatMessage)(chat.id, null, null, "text", "assistant", ingestedResponse.messageText, ingestedResponse.uuid);
                        finalMessages.push({
                            role: "assistant",
                            tool_call_id: null,
                            tool_name: null,
                            content_type: "text",
                            content: ingestedResponse.messageText,
                            temp_id: ingestedResponse.uuid
                        });
                    }
                    logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + " Added model response to chat history");
                    if ((ingestedResponse === null || ingestedResponse === void 0 ? void 0 : ingestedResponse.status) === "MaxTokensUsed") {
                        logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + " MaxTokensUsed error encountered! Will truncate and wait 20sec before continuing.");
                        res.write("\nClaude used max tokens. We'll wait a sec and prompt to continue.\n");
                        yield new Promise((resolve) => setTimeout(resolve, 20000));
                        finalMessages.push({
                            role: "user",
                            tool_call_id: null,
                            tool_name: null,
                            content_type: "text",
                            content: `You stopped in the middle of generation. Please continue. If you were in the middle of calling the tool apply_file_ops
                then you can try splitting the ops across multiple tool calls to avoid running out of tokens.`,
                            temp_id: "none"
                        });
                        finalMessages = yield this.fitHistoryWithinTokenBudget(finalMessages, systemPrompt, model, provider, { pluginName: assetName, pluginsDir: path_1.default.resolve(openedApp.repoPath, "remoteCode", "plugins") }, user);
                        logger_1.logger.info(chalkColor(`[DevAgent][${user}][${appId}]`) + " retrying after delay because max tokens were used");
                        continue; // back up and call again
                    }
                    // handle tool calls
                    toolRun = !!(ingestedResponse === null || ingestedResponse === void 0 ? void 0 : ingestedResponse.toolInput);
                    if (toolRun && (ingestedResponse === null || ingestedResponse === void 0 ? void 0 : ingestedResponse.toolInput)) {
                        //Putting the tool call in chat history message
                        //Putting the tool call output in chat history array
                        tokenCounterInstance.appendInMemoryOutputMessages((_a = ingestedResponse.toolInput) === null || _a === void 0 ? void 0 : _a.flatMap((toolcall) => [
                            {
                                role: "assistant",
                                tool_call_id: toolcall.id,
                                tool_name: toolcall.tool,
                                content_type: "tool_use",
                                content: toolcall.partialInput || "",
                                temp_id: ingestedResponse.uuid
                            },
                            {
                                role: "user",
                                tool_call_id: toolcall.id,
                                tool_name: toolcall.tool,
                                content_type: "tool_result",
                                content: "",
                                temp_id: ingestedResponse.uuid
                            },
                        ]));
                        logger_1.logger.info(chalkColor(`[PluginAgent][${user}]`) + "tool call detected");
                        const pluginsDir = path_1.default.resolve(openedApp.repoPath, "remoteCode", "plugins");
                        const assetRoot = path_1.default.resolve(pluginsDir, assetName);
                        try {
                            let nextStep = "ContinueChat";
                            let supabaseCreds = null;
                            for (let toolcall of ingestedResponse.toolInput) {
                                logger_1.logger.info("Running tool: " + chalk_1.default.green(toolcall.tool));
                                if (toolcall.tool.startsWith("supabase_") && !supabaseCreds) {
                                    supabaseCreds = yield supabase_1.supabaseConfigManager.getCreds(appId);
                                }
                                let toolResult = yield this.handleToolRun(toolcall, finalMessages, {
                                    plugin_root: assetRoot,
                                    plugins_dir: pluginsDir,
                                    app_id: appId,
                                    supabase_creds: supabaseCreds
                                }, ingestedResponse.uuid, chat.id);
                                if (toolcall.tool === "compile_generated_code") {
                                    res.write("streamItemStart:" + JSON.stringify({ type: "soft_refresh" }) + ":streamItemEnd");
                                }
                                // If even one tool requires user feedback then we stop
                                if (nextStep !== "WaitForUserInput") {
                                    nextStep = toolResult;
                                }
                            }
                            logger_1.logger.info("Next step after tool run: " + chalk_1.default.green(nextStep));
                            if (nextStep === "WaitForUserInput") {
                                toolRun = false;
                            }
                            else if (nextStep === "ContinueChat") {
                                toolRun = true;
                            }
                        }
                        catch (err) {
                            logger_1.logger.error(chalk_1.default.red("Error in handleToolRun: "), err);
                            finalMessages.push({
                                role: "user",
                                tool_call_id: null,
                                tool_name: null,
                                content_type: "text",
                                content: `There was an error when performing the tool call for \
                        ${JSON.stringify(ingestedResponse.toolInput)}. If you were running \
                        file ops try doing one file op per tool call to prevent \
                        output token limit exceeded errors. Here is the error: ` +
                                    (err === null || err === void 0 ? void 0 : err.toString()),
                                temp_id: "none"
                            });
                            continue;
                        }
                    }
                    // Update the current code
                    if ((finalMessages.length > 0) && finalMessages[0].content.startsWith(baseAgent_1.INSERTED_CODE_PREFIX)) {
                        try {
                            const formattedFileContents = yield (0, tools_1.readAllPluginFilesByPluginName)(path_1.default.resolve(openedApp.repoPath, "remoteCode", "plugins"), assetName);
                            const existingCode = baseAgent_1.INSERTED_CODE_PREFIX + '\n' + formattedFileContents.content[0].text;
                            finalMessages[0].content = existingCode;
                        }
                        catch (err) {
                            let errorMessage;
                            if (err && err.toString) {
                                errorMessage = err.toString();
                                logger_1.logger.error(chalk_1.default.red("Error in reading the contents of plugin code files") + errorMessage);
                            }
                            else {
                                console.error(chalk_1.default.red("Error in reading the contents of plugin code files"), err);
                            }
                        }
                    }
                    if (!toolRun) {
                        try {
                            logger_1.logger.info("Running compilation before exiting plugin agent!");
                            const compilationResult = yield this.finalProcessingAndCompilation(openedApp.repoPath);
                            if (compilationResult.errors) {
                                toolRun = true;
                                finalMessages.push({
                                    role: "user",
                                    content_type: "text",
                                    content: "The generated code failed to compile. The errors are: " + JSON.stringify(compilationResult.errors),
                                    tool_call_id: null,
                                    tool_name: null,
                                    temp_id: null
                                });
                            }
                            else {
                                logger_1.logger.info(compilationResult);
                            }
                        }
                        catch (err) {
                            logger_1.logger.error("Failure during compilation: " + err);
                        }
                    }
                } while (toolRun && !requestDropped);
            }
            catch (err) {
                console.error(chalk_1.default.red(chalkColor(`[DevAgent][${user}][${appId}]`) + ` Failure during ${provider} conversation:`, (_b = err === null || err === void 0 ? void 0 : err.error) === null || _b === void 0 ? void 0 : _b.error), err);
                res.write("streamItemStart:" +
                    JSON.stringify({
                        type: "error",
                        details: ((_c = err === null || err === void 0 ? void 0 : err.error) === null || _c === void 0 ? void 0 : _c.error) || "unexpected error " + err,
                    }) +
                    ":streamItemEnd");
                if (err && process.env.ENABLE_SENTRY) {
                    Sentry.captureException(err);
                }
            }
            // compile
            logger_1.logger.info("Preparing to compile generated code");
            const compilationResult = yield this.finalProcessingAndCompilation(openedApp.repoPath);
            if (compilationResult.errors) {
                res.write("streamItemStart:" +
                    JSON.stringify({
                        type: "compile_failed",
                        details: compilationResult,
                    }, null, 2) +
                    ":streamItemEnd");
            }
            else {
                res.write("streamItemStart:" +
                    JSON.stringify({
                        type: "compile_successful",
                        details: compilationResult,
                    }, null, 2) +
                    ":streamItemEnd");
            }
            res.end();
            return {
                tokenCounterInstance,
                message: "Success",
            };
        });
    }
}
exports.PluginAgent = PluginAgent;
