import { AIMessage, HumanMessage, SystemMessage, } from "@langchain/core/messages";
import { getEmbeddingContextSize, getModelContextSize, } from "@langchain/core/language_models/base";
import { LLMChain } from "../../chains/llm_chain.js";
import { AutoGPTOutputParser } from "./output_parser.js";
import { AutoGPTPrompt } from "./prompt.js";
// import { HumanInputRun } from "./tools/human/tool"; // TODO
import { FINISH_NAME } from "./schema.js";
import { TokenTextSplitter } from "../../text_splitter.js";
/**
 * Class representing the AutoGPT concept with LangChain primitives. It is
 * designed to be used with a set of tools such as a search tool,
 * write-file tool, and a read-file tool.
 * @example
 * ```typescript
 * const autogpt = AutoGPT.fromLLMAndTools(
 *   new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 }),
 *   [
 *     new ReadFileTool({ store: new InMemoryFileStore() }),
 *     new WriteFileTool({ store: new InMemoryFileStore() }),
 *     new SerpAPI("YOUR_SERPAPI_API_KEY", {
 *       location: "San Francisco,California,United States",
 *       hl: "en",
 *       gl: "us",
 *     }),
 *   ],
 *   {
 *     memory: new MemoryVectorStore(new OpenAIEmbeddings()).asRetriever(),
 *     aiName: "Tom",
 *     aiRole: "Assistant",
 *   },
 * );
 * const result = await autogpt.run(["write a weather report for SF today"]);
 * ```
 */
export class AutoGPT {
    constructor({ aiName, memory, chain, outputParser, tools, feedbackTool, maxIterations, }) {
        Object.defineProperty(this, "aiName", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "memory", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "fullMessageHistory", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "nextActionCount", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "chain", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "outputParser", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "tools", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "feedbackTool", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "maxIterations", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        // Currently not generic enough to support any text splitter.
        Object.defineProperty(this, "textSplitter", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.aiName = aiName;
        this.memory = memory;
        this.fullMessageHistory = [];
        this.nextActionCount = 0;
        this.chain = chain;
        this.outputParser = outputParser;
        this.tools = tools;
        this.feedbackTool = feedbackTool;
        this.maxIterations = maxIterations;
        const chunkSize = getEmbeddingContextSize("modelName" in memory.vectorStore.embeddings
            ? memory.vectorStore.embeddings.modelName
            : undefined);
        this.textSplitter = new TokenTextSplitter({
            chunkSize,
            chunkOverlap: Math.round(chunkSize / 10),
        });
    }
    /**
     * Creates a new AutoGPT instance from a given LLM and a set of tools.
     * @param llm A BaseChatModel object.
     * @param tools An array of ObjectTool objects.
     * @param options.aiName The name of the AI.
     * @param options.aiRole The role of the AI.
     * @param options.memory A VectorStoreRetriever object that represents the memory of the AI.
     * @param options.maxIterations The maximum number of iterations the AI can perform.
     * @param options.outputParser An AutoGPTOutputParser object that parses the output of the AI.
     * @returns A new instance of the AutoGPT class.
     */
    static fromLLMAndTools(llm, tools, { aiName, aiRole, memory, maxIterations = 100, 
    // humanInTheLoop = false,
    outputParser = new AutoGPTOutputParser(), }) {
        const prompt = new AutoGPTPrompt({
            aiName,
            aiRole,
            tools,
            tokenCounter: llm.getNumTokens.bind(llm),
            sendTokenLimit: getModelContextSize("modelName" in llm ? llm.modelName : "gpt2"),
        });
        // const feedbackTool = humanInTheLoop ? new HumanInputRun() : null;
        const chain = new LLMChain({ llm, prompt });
        return new AutoGPT({
            aiName,
            memory,
            chain,
            outputParser,
            tools,
            // feedbackTool,
            maxIterations,
        });
    }
    /**
     * Runs the AI with a given set of goals.
     * @param goals An array of strings representing the goals.
     * @returns A string representing the result of the run or undefined if the maximum number of iterations is reached without a result.
     */
    async run(goals) {
        const user_input = "Determine which next command to use, and respond using the format specified above:";
        let loopCount = 0;
        while (loopCount < this.maxIterations) {
            loopCount += 1;
            const { text: assistantReply } = await this.chain.call({
                goals,
                user_input,
                memory: this.memory,
                messages: this.fullMessageHistory,
            });
            // Print the assistant reply
            console.log(assistantReply);
            this.fullMessageHistory.push(new HumanMessage(user_input));
            this.fullMessageHistory.push(new AIMessage(assistantReply));
            const action = await this.outputParser.parse(assistantReply);
            const tools = this.tools.reduce((acc, tool) => ({ ...acc, [tool.name]: tool }), {});
            if (action.name === FINISH_NAME) {
                return action.args.response;
            }
            let result;
            if (action.name in tools) {
                const tool = tools[action.name];
                let observation;
                try {
                    observation = await tool.call(action.args);
                }
                catch (e) {
                    observation = `Error in args: ${e}`;
                }
                result = `Command ${tool.name} returned: ${observation}`;
            }
            else if (action.name === "ERROR") {
                result = `Error: ${action.args}. `;
            }
            else {
                result = `Unknown command '${action.name}'. Please refer to the 'COMMANDS' list for available commands and only respond in the specified JSON format.`;
            }
            let memoryToAdd = `Assistant Reply: ${assistantReply}\nResult: ${result} `;
            if (this.feedbackTool) {
                const feedback = `\n${await this.feedbackTool.call("Input: ")}`;
                if (feedback === "q" || feedback === "stop") {
                    console.log("EXITING");
                    return "EXITING";
                }
                memoryToAdd += feedback;
            }
            const documents = await this.textSplitter.createDocuments([memoryToAdd]);
            await this.memory.addDocuments(documents);
            this.fullMessageHistory.push(new SystemMessage(result));
        }
        return undefined;
    }
}
