"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpstashRatelimitError = exports.UpstashRatelimitHandler = void 0;
const base_1 = require("@langchain/core/callbacks/base");
/**
 * Upstash Ratelimit Error
 *
 * Raised when the rate limit is reached in `UpstashRatelimitHandler`.
 */
class UpstashRatelimitError extends Error {
    /**
     * @param message - Error message
     * @param type - The kind of limit which was reached. One of "token" or "request"
     * @param limit - The limit which was reached. Passed when type is request
     * @param reset - Unix timestamp in milliseconds when the limits are reset. Passed when type is request
     */
    constructor(message, type, limit, reset) {
        super(message);
        Object.defineProperty(this, "type", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "limit", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "reset", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.type = type;
        this.limit = limit;
        this.reset = reset;
    }
}
exports.UpstashRatelimitError = UpstashRatelimitError;
/**
 * Callback to handle rate limiting based on the number of requests
 * or the number of tokens in the input.
 *
 * It uses Upstash Ratelimit to track the rate limit which utilizes
 * Upstash Redis to track the state.
 *
 * Should not be passed to the chain when initializing the chain.
 * This is because the handler has a state which should be fresh
 * every time invoke is called. Instead, initialize and pass a handler
 * every time you invoke.
 */
class UpstashRatelimitHandler extends base_1.BaseCallbackHandler {
    /**
     * @param identifier - The identifier to rate limit, like a user ID or an IP address
     * @param options - Ratelimit options
     */
    constructor(identifier, options) {
        super();
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: "UpstashRatelimit"
        });
        Object.defineProperty(this, "raiseError", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: true
        });
        Object.defineProperty(this, "_checked", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "identifier", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "tokenRatelimit", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "requestRatelimit", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "includeOutputTokens", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "llmOutputTokenUsageField", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "llmOutputTotalTokenField", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "llmOutputPromptTokenField", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        if (!options.tokenRatelimit && !options.requestRatelimit) {
            throw new Error("You must pass at least one of tokenRatelimit or requestRatelimit.");
        }
        this.identifier = identifier;
        this.tokenRatelimit = options.tokenRatelimit;
        this.requestRatelimit = options.requestRatelimit;
        this.includeOutputTokens = options.includeOutputTokens ?? false;
        this.llmOutputTokenUsageField =
            options.llmOutputTokenUsageField ?? "tokenUsage";
        this.llmOutputTotalTokenField =
            options.llmOutputTotalTokenField ?? "totalTokens";
        this.llmOutputPromptTokenField =
            options.llmOutputPromptTokenField ?? "promptTokens";
        this.awaitHandlers = true;
    }
    /**
     * Run when the chain starts running.
     *
     * This method is called multiple times during a chain execution.
     * To ensure it only runs once, it checks and updates a `_checked` state.
     *
     * @param _chain - Serialized chain
     * @param _inputs - Chain input values
     * @throws UpstashRatelimitError - If the request rate limit is reached
     */
    async handleChainStart(_chain, _inputs) {
        if (this.requestRatelimit && !this._checked) {
            const response = await this.requestRatelimit.limit(this.identifier);
            if (!response.success) {
                throw new UpstashRatelimitError("Request limit reached!", "request", response.limit, response.reset);
            }
            this._checked = true;
        }
    }
    /**
     * Run when the LLM starts running.
     *
     * @param _llm - Serialized LLM
     * @param _prompts - Prompts passed to the LLM
     * @throws UpstashRatelimitError - If the token rate limit is reached
     */
    async handleLLMStart(_llm, _prompts, _runId, _parentRunId, _extraParams, _tags, _metadata, _name) {
        if (this.tokenRatelimit) {
            const result = await this.tokenRatelimit.getRemaining(this.identifier);
            // result of getRemaining was changed from a number to an object in v2.0.0.
            // we check to make sure that it works with versions before & after:
            const remaining = typeof result === "number" ? result : result.remaining;
            if (remaining <= 0) {
                throw new UpstashRatelimitError("Token limit reached!", "token");
            }
        }
    }
    /**
     * Run when the LLM ends running.
     *
     * If the `includeOutputTokens` is set to true, the number of tokens
     * in the LLM completion are counted for rate limiting.
     *
     * @param output - LLM result output
     * @throws Error - If the LLM response does not include required token usage information
     */
    async handleLLMEnd(output, _runId, _parentRunId, _tags) {
        if (this.tokenRatelimit) {
            const llmOutput = output.llmOutput || {};
            try {
                const tokenUsage = llmOutput[this.llmOutputTokenUsageField];
                const tokenCount = this.includeOutputTokens
                    ? tokenUsage[this.llmOutputTotalTokenField]
                    : tokenUsage[this.llmOutputPromptTokenField];
                if (tokenCount !== undefined) {
                    await this.tokenRatelimit.limit(this.identifier, {
                        rate: tokenCount,
                    });
                }
                else {
                    throw new Error("tokenCount not found in llm output");
                }
            }
            catch (error) {
                // eslint-disable-next-line no-instanceof/no-instanceof
                if (error instanceof UpstashRatelimitError) {
                    throw error;
                }
                console.error(`Failed to log token usage for Upstash rate limit. It could be because the LLM returns the token usage in a different format than expected. See UpstashRatelimitHandler parameters. Got error: ${error}`);
            }
        }
    }
    /**
     * Creates a new UpstashRatelimitHandler object with the same
     * ratelimit configurations but with a new identifier if it's
     * provided.
     *
     * Also resets the state of the handler.
     *
     * @param identifier - Optional new identifier to use for the new handler instance
     * @returns New UpstashRatelimitHandler instance
     */
    reset(identifier) {
        return new UpstashRatelimitHandler(identifier ?? this.identifier, {
            tokenRatelimit: this.tokenRatelimit,
            requestRatelimit: this.requestRatelimit,
            includeOutputTokens: this.includeOutputTokens,
        });
    }
}
exports.UpstashRatelimitHandler = UpstashRatelimitHandler;
