import { PromptTemplate } from "@langchain/core/prompts";
import { LLMChain } from "../../chains/llm_chain.js";
import { BaseChain } from "../../chains/base.js";
/**
 * Implementation of a generative agent that can learn and form new memories over
 * time. It extends the BaseChain class, which is a generic
 * sequence of calls to components, including other chains.
 * @example
 * ```typescript
 * const tommie: GenerativeAgent = new GenerativeAgent(
 *   new OpenAI({ temperature: 0.9, maxTokens: 1500 }),
 *   new GenerativeAgentMemory(
 *     new ChatOpenAI({ model: "gpt-4o-mini" }),
 *     new TimeWeightedVectorStoreRetriever({
 *       vectorStore: new MemoryVectorStore(new OpenAIEmbeddings()),
 *       otherScoreKeys: ["importance"],
 *       k: 15,
 *     }),
 *     { reflectionThreshold: 8 },
 *   ),
 *   {
 *     name: "Tommie",
 *     age: 25,
 *     traits: "anxious, likes design, talkative",
 *     status: "looking for a job",
 *   },
 * );
 *
 * await tommie.addMemory(
 *   "Tommie remembers his dog, Bruno, from when he was a kid",
 *   new Date(),
 * );
 * const summary = await tommie.getSummary({ forceRefresh: true });
 * const response = await tommie.generateDialogueResponse(
 *   "USER says Hello Tommie, how are you today?",
 * );
 * ```
 */
export class GenerativeAgent extends BaseChain {
    static lc_name() {
        return "GenerativeAgent";
    }
    // TODO: Add support for daily summaries
    // private dailySummaries: string[] = []; // summary of the events in the plan that the agent took.
    _chainType() {
        return "generative_agent_executor";
    }
    get inputKeys() {
        return ["observation", "suffix", "now"];
    }
    get outputKeys() {
        return ["output", "continue_dialogue"];
    }
    constructor(llm, longTermMemory, config) {
        super();
        // a character with memory and innate characterisitics
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // the character's name
        Object.defineProperty(this, "age", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // the optional age of the character
        Object.defineProperty(this, "traits", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // permanent traits to ascribe to the character
        Object.defineProperty(this, "status", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // the traits of the character you wish not to change
        Object.defineProperty(this, "longTermMemory", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "llm", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // the underlying language model
        Object.defineProperty(this, "verbose", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // false
        Object.defineProperty(this, "summary", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // stateful self-summary generated via reflection on the character's memory.
        Object.defineProperty(this, "summaryRefreshSeconds", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 3600
        });
        Object.defineProperty(this, "lastRefreshed", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        }); // the last time the character's summary was regenerated
        this.llm = llm;
        this.longTermMemory = longTermMemory;
        this.name = config.name;
        this.age = config.age;
        this.traits = config.traits;
        this.status = config.status;
        this.verbose = config.verbose ?? this.verbose;
        this.summary = "";
        this.summaryRefreshSeconds =
            config.summaryRefreshSeconds ?? this.summaryRefreshSeconds;
        this.lastRefreshed = new Date();
        // this.dailySummaries = config.dailySummaries ?? this.dailySummaries;
    }
    // LLM methods
    /**
     * Parses a newline-separated string into a list of strings.
     * @param text The string to parse.
     * @returns An array of strings parsed from the input text.
     */
    parseList(text) {
        // parse a newline-seperated string into a list of strings
        const lines = text.trim().split("\n");
        const result = lines.map((line) => line.replace(/^\s*\d+\.\s*/, "").trim());
        return result;
    }
    /**
     * Creates a new LLMChain with the given prompt and the agent's language
     * model, verbosity, output key, and memory.
     * @param prompt The prompt to use for the LLMChain.
     * @returns A new LLMChain instance.
     */
    chain(prompt) {
        const chain = new LLMChain({
            llm: this.llm,
            prompt,
            verbose: this.verbose,
            outputKey: "output", // new
            memory: this.longTermMemory,
        });
        return chain;
    }
    /**
     * Extracts the observed entity from the given observation.
     * @param observation The observation to extract the entity from.
     * @param runManager Optional CallbackManagerForChainRun instance.
     * @returns The extracted entity as a string.
     */
    async getEntityFromObservations(observation, runManager) {
        const prompt = PromptTemplate.fromTemplate("What is the observed entity in the following observation? {observation}" +
            "\nEntity=");
        const result = await this.chain(prompt).call({
            observation,
        }, runManager?.getChild("entity_extractor"));
        return result.output;
    }
    /**
     * Extracts the action of the given entity from the given observation.
     * @param observation The observation to extract the action from.
     * @param entityName The name of the entity to extract the action for.
     * @param runManager Optional CallbackManagerForChainRun instance.
     * @returns The extracted action as a string.
     */
    async getEntityAction(observation, entityName, runManager) {
        const prompt = PromptTemplate.fromTemplate("What is the {entity} doing in the following observation? {observation}" +
            "\nThe {entity} is");
        const result = await this.chain(prompt).call({
            entity: entityName,
            observation,
        }, runManager?.getChild("entity_action_extractor"));
        const trimmedResult = result.output.trim();
        return trimmedResult;
    }
    /**
     * Summarizes memories that are most relevant to an observation.
     * @param observation The observation to summarize related memories for.
     * @param runManager Optional CallbackManagerForChainRun instance.
     * @returns The summarized memories as a string.
     */
    async summarizeRelatedMemories(observation, runManager) {
        // summarize memories that are most relevant to an observation
        const prompt = PromptTemplate.fromTemplate(`
{q1}?
Context from memory:
{relevant_memories}
Relevant context:`);
        const entityName = await this.getEntityFromObservations(observation, runManager);
        const entityAction = await this.getEntityAction(observation, entityName, runManager);
        const q1 = `What is the relationship between ${this.name} and ${entityName}`;
        const q2 = `${entityName} is ${entityAction}`;
        const response = await this.chain(prompt).call({
            q1,
            queries: [q1, q2],
        }, runManager?.getChild("entity_relationships"));
        return response.output.trim(); // added output
    }
    async _call(values, runManager) {
        const { observation, suffix, now } = values;
        // react to a given observation or dialogue act
        const prompt = PromptTemplate.fromTemplate(`{agent_summary_description}` +
            `\nIt is {current_time}.` +
            `\n{agent_name}'s status: {agent_status}` +
            `\nSummary of relevant context from {agent_name}'s memory:` +
            "\n{relevant_memories}" +
            `\nMost recent observations: {most_recent_memories}` +
            `\nObservation: {observation}` +
            `\n\n${suffix}`);
        const agentSummaryDescription = await this.getSummary({}, runManager); // now = now in param
        const relevantMemoriesStr = await this.summarizeRelatedMemories(observation, runManager);
        const currentTime = (now || new Date()).toLocaleString("en-US", {
            month: "long",
            day: "numeric",
            year: "numeric",
            hour: "numeric",
            minute: "numeric",
            hour12: true,
        });
        const chainInputs = {
            agent_summary_description: agentSummaryDescription,
            current_time: currentTime,
            agent_name: this.name,
            observation,
            agent_status: this.status,
            most_recent_memories: "",
        };
        chainInputs[this.longTermMemory.getRelevantMemoriesKey()] =
            relevantMemoriesStr;
        const consumedTokens = await this.llm.getNumTokens(await prompt.format({ ...chainInputs }));
        chainInputs[this.longTermMemory.getMostRecentMemoriesTokenKey()] =
            consumedTokens;
        const response = await this.chain(prompt).call(chainInputs, runManager?.getChild("reaction_from_summary"));
        const rawOutput = response.output;
        let output = rawOutput;
        let continue_dialogue = false;
        if (rawOutput.includes("REACT:")) {
            const reaction = this._cleanResponse(rawOutput.split("REACT:").pop());
            await this.addMemory(`${this.name} observed ${observation} and reacted by ${reaction}`, now, {}, runManager?.getChild("memory"));
            output = `${reaction}`;
            continue_dialogue = false;
        }
        else if (rawOutput.includes("SAY:")) {
            const saidValue = this._cleanResponse(rawOutput.split("SAY:").pop());
            await this.addMemory(`${this.name} observed ${observation} and said ${saidValue}`, now, {}, runManager?.getChild("memory"));
            output = `${this.name} said ${saidValue}`;
            continue_dialogue = true;
        }
        else if (rawOutput.includes("GOODBYE:")) {
            const farewell = this._cleanResponse(rawOutput.split("GOODBYE:").pop() ?? "");
            await this.addMemory(`${this.name} observed ${observation} and said ${farewell}`, now, {}, runManager?.getChild("memory"));
            output = `${this.name} said ${farewell}`;
            continue_dialogue = false;
        }
        return { output, continue_dialogue };
    }
    _cleanResponse(text) {
        if (text === undefined) {
            return "";
        }
        const regex = new RegExp(`^${this.name} `);
        return text.replace(regex, "").trim();
    }
    /**
     * Generates a reaction to the given observation.
     * @param observation The observation to generate a reaction for.
     * @param now Optional current date.
     * @returns A boolean indicating whether to continue the dialogue and the output string.
     */
    async generateReaction(observation, now) {
        const callToActionTemplate = `Should {agent_name} react to the observation, and if so,` +
            ` what would be an appropriate reaction? Respond in one line.` +
            ` If the action is to engage in dialogue, write:\nSAY: "what to say"` +
            ` \notherwise, write:\nREACT: {agent_name}'s reaction (if anything).` +
            ` \nEither do nothing, react, or say something but not both.\n\n`;
        const { output, continue_dialogue } = await this.call({
            observation,
            suffix: callToActionTemplate,
            now,
        });
        return [continue_dialogue, output];
    }
    /**
     * Generates a dialogue response to the given observation.
     * @param observation The observation to generate a dialogue response for.
     * @param now Optional current date.
     * @returns A boolean indicating whether to continue the dialogue and the output string.
     */
    async generateDialogueResponse(observation, now) {
        const callToActionTemplate = `What would ${this.name} say? To end the conversation, write: GOODBYE: "what to say". Otherwise to continue the conversation, write: SAY: "what to say next"\n\n`;
        const { output, continue_dialogue } = await this.call({
            observation,
            suffix: callToActionTemplate,
            now,
        });
        return [continue_dialogue, output];
    }
    // Agent stateful' summary methods
    // Each dialog or response prompt includes a header
    // summarizing the agent's self-description. This is
    // updated periodically through probing it's memories
    /**
     * Gets the agent's summary, which includes the agent's name, age, traits,
     * and a summary of the agent's core characteristics. The summary is
     * updated periodically through probing the agent's memories.
     * @param config Optional configuration object with current date and a boolean to force refresh.
     * @param runManager Optional CallbackManagerForChainRun instance.
     * @returns The agent's summary as a string.
     */
    async getSummary(config, runManager) {
        const { now = new Date(), forceRefresh = false } = config ?? {};
        const sinceRefresh = Math.floor((now.getTime() - this.lastRefreshed.getTime()) / 1000);
        if (!this.summary ||
            sinceRefresh >= this.summaryRefreshSeconds ||
            forceRefresh) {
            this.summary = await this.computeAgentSummary(runManager);
            this.lastRefreshed = now;
        }
        let age;
        if (this.age) {
            age = this.age;
        }
        else {
            age = "N/A";
        }
        return `Name: ${this.name} (age: ${age})
Innate traits: ${this.traits}
${this.summary}`;
    }
    /**
     * Computes the agent's summary by summarizing the agent's core
     * characteristics given the agent's relevant memories.
     * @param runManager Optional CallbackManagerForChainRun instance.
     * @returns The computed summary as a string.
     */
    async computeAgentSummary(runManager) {
        const prompt = PromptTemplate.fromTemplate("How would you summarize {name}'s core characteristics given the following statements:\n" +
            "----------" +
            "{relevant_memories}" +
            "----------" +
            "Do not embellish." +
            "\n\nSummary: ");
        // the agent seeks to think about their core characterisitics
        const result = await this.chain(prompt).call({
            name: this.name,
            queries: [`${this.name}'s core characteristics`],
        }, runManager?.getChild("compute_agent_summary"));
        return result.output.trim();
    }
    /**
     * Returns a full header of the agent's status, summary, and current time.
     * @param config Optional configuration object with current date and a boolean to force refresh.
     * @returns The full header as a string.
     */
    getFullHeader(config = {}) {
        const { now = new Date(), forceRefresh = false } = config;
        // return a full header of the agent's status, summary, and current time.
        const summary = this.getSummary({ now, forceRefresh });
        const currentTimeString = now.toLocaleString("en-US", {
            month: "long",
            day: "numeric",
            year: "numeric",
            hour: "numeric",
            minute: "numeric",
            hour12: true,
        });
        return `${summary}\nIt is ${currentTimeString}.\n${this.name}'s status: ${this.status}`;
    }
    /**
     * Adds a memory to the agent's long-term memory.
     * @param memoryContent The content of the memory to add.
     * @param now Optional current date.
     * @param metadata Optional metadata for the memory.
     * @param callbacks Optional Callbacks instance.
     * @returns The result of adding the memory to the agent's long-term memory.
     */
    async addMemory(memoryContent, now, metadata, callbacks) {
        return this.longTermMemory.addMemory(memoryContent, now, metadata, callbacks);
    }
}
