"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunnableWithMessageHistory = void 0;
const index_js_1 = require("../messages/index.cjs");
const base_js_1 = require("./base.cjs");
const passthrough_js_1 = require("./passthrough.cjs");
/**
 * Wraps a LCEL chain and manages history. It appends input messages
 * and chain outputs as history, and adds the current history messages to
 * the chain input.
 * @example
 * ```typescript
 * // yarn add @langchain/anthropic @langchain/community @upstash/redis
 *
 * import {
 *   ChatPromptTemplate,
 *   MessagesPlaceholder,
 * } from "@langchain/core/prompts";
 * import { ChatAnthropic } from "@langchain/anthropic";
 * import { UpstashRedisChatMessageHistory } from "@langchain/community/stores/message/upstash_redis";
 * // For demos, you can also use an in-memory store:
 * // import { ChatMessageHistory } from "langchain/stores/message/in_memory";
 *
 * const prompt = ChatPromptTemplate.fromMessages([
 *   ["system", "You're an assistant who's good at {ability}"],
 *   new MessagesPlaceholder("history"),
 *   ["human", "{question}"],
 * ]);
 *
 * const chain = prompt.pipe(new ChatAnthropic({}));
 *
 * const chainWithHistory = new RunnableWithMessageHistory({
 *   runnable: chain,
 *   getMessageHistory: (sessionId) =>
 *     new UpstashRedisChatMessageHistory({
 *       sessionId,
 *       config: {
 *         url: process.env.UPSTASH_REDIS_REST_URL!,
 *         token: process.env.UPSTASH_REDIS_REST_TOKEN!,
 *       },
 *     }),
 *   inputMessagesKey: "question",
 *   historyMessagesKey: "history",
 * });
 *
 * const result = await chainWithHistory.invoke(
 *   {
 *     ability: "math",
 *     question: "What does cosine mean?",
 *   },
 *   {
 *     configurable: {
 *       sessionId: "some_string_identifying_a_user",
 *     },
 *   }
 * );
 *
 * const result2 = await chainWithHistory.invoke(
 *   {
 *     ability: "math",
 *     question: "What's its inverse?",
 *   },
 *   {
 *     configurable: {
 *       sessionId: "some_string_identifying_a_user",
 *     },
 *   }
 * );
 * ```
 */
class RunnableWithMessageHistory extends base_js_1.RunnableBinding {
    constructor(fields) {
        let historyChain = base_js_1.RunnableLambda.from((input, options) => this._enterHistory(input, options ?? {})).withConfig({ runName: "loadHistory" });
        const messagesKey = fields.historyMessagesKey ?? fields.inputMessagesKey;
        if (messagesKey) {
            historyChain = passthrough_js_1.RunnablePassthrough.assign({
                [messagesKey]: historyChain,
            }).withConfig({ runName: "insertHistory" });
        }
        const bound = historyChain
            .pipe(fields.runnable.withListeners({
            onEnd: (run, config) => this._exitHistory(run, config ?? {}),
        }))
            .withConfig({ runName: "RunnableWithMessageHistory" });
        const config = fields.config ?? {};
        super({
            ...fields,
            config,
            bound,
        });
        Object.defineProperty(this, "runnable", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "inputMessagesKey", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "outputMessagesKey", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "historyMessagesKey", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "getMessageHistory", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.runnable = fields.runnable;
        this.getMessageHistory = fields.getMessageHistory;
        this.inputMessagesKey = fields.inputMessagesKey;
        this.outputMessagesKey = fields.outputMessagesKey;
        this.historyMessagesKey = fields.historyMessagesKey;
    }
    _getInputMessages(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    inputValue) {
        let parsedInputValue;
        if (typeof inputValue === "object" &&
            !Array.isArray(inputValue) &&
            !(0, index_js_1.isBaseMessage)(inputValue)) {
            let key;
            if (this.inputMessagesKey) {
                key = this.inputMessagesKey;
            }
            else if (Object.keys(inputValue).length === 1) {
                key = Object.keys(inputValue)[0];
            }
            else {
                key = "input";
            }
            if (Array.isArray(inputValue[key]) && Array.isArray(inputValue[key][0])) {
                parsedInputValue = inputValue[key][0];
            }
            else {
                parsedInputValue = inputValue[key];
            }
        }
        else {
            parsedInputValue = inputValue;
        }
        if (typeof parsedInputValue === "string") {
            return [new index_js_1.HumanMessage(parsedInputValue)];
        }
        else if (Array.isArray(parsedInputValue)) {
            return parsedInputValue;
        }
        else if ((0, index_js_1.isBaseMessage)(parsedInputValue)) {
            return [parsedInputValue];
        }
        else {
            throw new Error(`Expected a string, BaseMessage, or array of BaseMessages.\nGot ${JSON.stringify(parsedInputValue, null, 2)}`);
        }
    }
    _getOutputMessages(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    outputValue) {
        let parsedOutputValue;
        if (!Array.isArray(outputValue) &&
            !(0, index_js_1.isBaseMessage)(outputValue) &&
            typeof outputValue !== "string") {
            let key;
            if (this.outputMessagesKey !== undefined) {
                key = this.outputMessagesKey;
            }
            else if (Object.keys(outputValue).length === 1) {
                key = Object.keys(outputValue)[0];
            }
            else {
                key = "output";
            }
            // If you are wrapping a chat model directly
            // The output is actually this weird generations object
            if (outputValue.generations !== undefined) {
                parsedOutputValue = outputValue.generations[0][0].message;
            }
            else {
                parsedOutputValue = outputValue[key];
            }
        }
        else {
            parsedOutputValue = outputValue;
        }
        if (typeof parsedOutputValue === "string") {
            return [new index_js_1.AIMessage(parsedOutputValue)];
        }
        else if (Array.isArray(parsedOutputValue)) {
            return parsedOutputValue;
        }
        else if ((0, index_js_1.isBaseMessage)(parsedOutputValue)) {
            return [parsedOutputValue];
        }
        else {
            throw new Error(`Expected a string, BaseMessage, or array of BaseMessages. Received: ${JSON.stringify(parsedOutputValue, null, 2)}`);
        }
    }
    async _enterHistory(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    input, kwargs) {
        const history = kwargs?.configurable?.messageHistory;
        const messages = await history.getMessages();
        if (this.historyMessagesKey === undefined) {
            return messages.concat(this._getInputMessages(input));
        }
        return messages;
    }
    async _exitHistory(run, config) {
        const history = config.configurable?.messageHistory;
        // Get input messages
        let inputs;
        // Chat model inputs are nested arrays
        if (Array.isArray(run.inputs) && Array.isArray(run.inputs[0])) {
            inputs = run.inputs[0];
        }
        else {
            inputs = run.inputs;
        }
        let inputMessages = this._getInputMessages(inputs);
        // If historic messages were prepended to the input messages, remove them to
        // avoid adding duplicate messages to history.
        if (this.historyMessagesKey === undefined) {
            const existingMessages = await history.getMessages();
            inputMessages = inputMessages.slice(existingMessages.length);
        }
        // Get output messages
        const outputValue = run.outputs;
        if (!outputValue) {
            throw new Error(`Output values from 'Run' undefined. Run: ${JSON.stringify(run, null, 2)}`);
        }
        const outputMessages = this._getOutputMessages(outputValue);
        await history.addMessages([...inputMessages, ...outputMessages]);
    }
    async _mergeConfig(...configs) {
        const config = await super._mergeConfig(...configs);
        // Extract sessionId
        if (!config.configurable || !config.configurable.sessionId) {
            const exampleInput = {
                [this.inputMessagesKey ?? "input"]: "foo",
            };
            const exampleConfig = { configurable: { sessionId: "123" } };
            throw new Error(`sessionId is required. Pass it in as part of the config argument to .invoke() or .stream()\n` +
                `eg. chain.invoke(${JSON.stringify(exampleInput)}, ${JSON.stringify(exampleConfig)})`);
        }
        // attach messageHistory
        const { sessionId } = config.configurable;
        config.configurable.messageHistory = await this.getMessageHistory(sessionId);
        return config;
    }
}
exports.RunnableWithMessageHistory = RunnableWithMessageHistory;
