"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToGenerativeAITools = exports.convertResponseContentToChatGenerationChunk = exports.mapGenerateContentResultToChatResult = exports.convertBaseMessagesToContent = exports.convertMessageContentToParts = exports.convertAuthorToRole = exports.getMessageAuthor = void 0;
const messages_1 = require("@langchain/core/messages");
const outputs_1 = require("@langchain/core/outputs");
const function_calling_1 = require("@langchain/core/utils/function_calling");
const base_1 = require("@langchain/core/language_models/base");
const uuid_1 = require("uuid");
const zod_to_genai_parameters_js_1 = require("./zod_to_genai_parameters.cjs");
function getMessageAuthor(message) {
    const type = message._getType();
    if (messages_1.ChatMessage.isInstance(message)) {
        return message.role;
    }
    if (type === "tool") {
        return type;
    }
    return message.name ?? type;
}
exports.getMessageAuthor = getMessageAuthor;
/**
 * Maps a message type to a Google Generative AI chat author.
 * @param message The message to map.
 * @param model The model to use for mapping.
 * @returns The message type mapped to a Google Generative AI chat author.
 */
function convertAuthorToRole(author) {
    switch (author) {
        /**
         *  Note: Gemini currently is not supporting system messages
         *  we will convert them to human messages and merge with following
         * */
        case "ai":
        case "model": // getMessageAuthor returns message.name. code ex.: return message.name ?? type;
            return "model";
        case "system":
            return "system";
        case "human":
            return "user";
        case "tool":
        case "function":
            return "function";
        default:
            throw new Error(`Unknown / unsupported author: ${author}`);
    }
}
exports.convertAuthorToRole = convertAuthorToRole;
function messageContentMedia(content) {
    if ("mimeType" in content && "data" in content) {
        return {
            inlineData: {
                mimeType: content.mimeType,
                data: content.data,
            },
        };
    }
    if ("mimeType" in content && "fileUri" in content) {
        return {
            fileData: {
                mimeType: content.mimeType,
                fileUri: content.fileUri,
            },
        };
    }
    throw new Error("Invalid media content");
}
function inferToolNameFromPreviousMessages(message, previousMessages) {
    return previousMessages
        .map((msg) => {
        if ((0, messages_1.isAIMessage)(msg)) {
            return msg.tool_calls ?? [];
        }
        return [];
    })
        .flat()
        .find((toolCall) => {
        return toolCall.id === message.tool_call_id;
    })?.name;
}
function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
    if (typeof message.content === "string" &&
        message.content !== "" &&
        !(0, messages_1.isToolMessage)(message)) {
        return [{ text: message.content }];
    }
    let functionCalls = [];
    let functionResponses = [];
    let messageParts = [];
    if ("tool_calls" in message &&
        Array.isArray(message.tool_calls) &&
        message.tool_calls.length > 0) {
        functionCalls = message.tool_calls.map((tc) => ({
            functionCall: {
                name: tc.name,
                args: tc.args,
            },
        }));
    }
    else if ((0, messages_1.isToolMessage)(message) && message.content) {
        const messageName = message.name ??
            inferToolNameFromPreviousMessages(message, previousMessages);
        if (messageName === undefined) {
            throw new Error(`Google requires a tool name for each tool call response, and we could not infer a called tool name for ToolMessage "${message.id}" from your passed messages. Please populate a "name" field on that ToolMessage explicitly.`);
        }
        functionResponses = [
            {
                functionResponse: {
                    name: messageName,
                    response: typeof message.content === "string"
                        ? { result: message.content }
                        : message.content,
                },
            },
        ];
    }
    else if (Array.isArray(message.content)) {
        messageParts = message.content.map((c) => {
            if (c.type === "text") {
                return {
                    text: c.text,
                };
            }
            else if (c.type === "executableCode") {
                return {
                    executableCode: c.executableCode,
                };
            }
            else if (c.type === "codeExecutionResult") {
                return {
                    codeExecutionResult: c.codeExecutionResult,
                };
            }
            if (c.type === "image_url") {
                if (!isMultimodalModel) {
                    throw new Error(`This model does not support images`);
                }
                let source;
                if (typeof c.image_url === "string") {
                    source = c.image_url;
                }
                else if (typeof c.image_url === "object" && "url" in c.image_url) {
                    source = c.image_url.url;
                }
                else {
                    throw new Error("Please provide image as base64 encoded data URL");
                }
                const [dm, data] = source.split(",");
                if (!dm.startsWith("data:")) {
                    throw new Error("Please provide image as base64 encoded data URL");
                }
                const [mimeType, encoding] = dm.replace(/^data:/, "").split(";");
                if (encoding !== "base64") {
                    throw new Error("Please provide image as base64 encoded data URL");
                }
                return {
                    inlineData: {
                        data,
                        mimeType,
                    },
                };
            }
            else if (c.type === "media") {
                return messageContentMedia(c);
            }
            else if (c.type === "tool_use") {
                return {
                    functionCall: {
                        name: c.name,
                        args: c.input,
                    },
                };
            }
            else if (c.type?.includes("/") &&
                // Ensure it's a single slash.
                c.type.split("/").length === 2 &&
                "data" in c &&
                typeof c.data === "string") {
                return {
                    inlineData: {
                        mimeType: c.type,
                        data: c.data,
                    },
                };
            }
            throw new Error(`Unknown content type ${c.type}`);
        });
    }
    return [...messageParts, ...functionCalls, ...functionResponses];
}
exports.convertMessageContentToParts = convertMessageContentToParts;
function convertBaseMessagesToContent(messages, isMultimodalModel, convertSystemMessageToHumanContent = false) {
    return messages.reduce((acc, message, index) => {
        if (!(0, messages_1.isBaseMessage)(message)) {
            throw new Error("Unsupported message input");
        }
        const author = getMessageAuthor(message);
        if (author === "system" && index !== 0) {
            throw new Error("System message should be the first one");
        }
        const role = convertAuthorToRole(author);
        const prevContent = acc.content[acc.content.length];
        if (!acc.mergeWithPreviousContent &&
            prevContent &&
            prevContent.role === role) {
            throw new Error("Google Generative AI requires alternate messages between authors");
        }
        const parts = convertMessageContentToParts(message, isMultimodalModel, messages.slice(0, index));
        if (acc.mergeWithPreviousContent) {
            const prevContent = acc.content[acc.content.length - 1];
            if (!prevContent) {
                throw new Error("There was a problem parsing your system message. Please try a prompt without one.");
            }
            prevContent.parts.push(...parts);
            return {
                mergeWithPreviousContent: false,
                content: acc.content,
            };
        }
        let actualRole = role;
        if (actualRole === "function" ||
            (actualRole === "system" && !convertSystemMessageToHumanContent)) {
            // GenerativeAI API will throw an error if the role is not "user" or "model."
            actualRole = "user";
        }
        const content = {
            role: actualRole,
            parts,
        };
        return {
            mergeWithPreviousContent: author === "system" && !convertSystemMessageToHumanContent,
            content: [...acc.content, content],
        };
    }, { content: [], mergeWithPreviousContent: false }).content;
}
exports.convertBaseMessagesToContent = convertBaseMessagesToContent;
function mapGenerateContentResultToChatResult(response, extra) {
    // if rejected or error, return empty generations with reason in filters
    if (!response.candidates ||
        response.candidates.length === 0 ||
        !response.candidates[0]) {
        return {
            generations: [],
            llmOutput: {
                filters: response.promptFeedback,
            },
        };
    }
    const functionCalls = response.functionCalls();
    const [candidate] = response.candidates;
    const { content: candidateContent, ...generationInfo } = candidate;
    let content;
    if (candidateContent?.parts.length === 1 && candidateContent.parts[0].text) {
        content = candidateContent.parts[0].text;
    }
    else {
        content = candidateContent.parts.map((p) => {
            if ("text" in p) {
                return {
                    type: "text",
                    text: p.text,
                };
            }
            else if ("executableCode" in p) {
                return {
                    type: "executableCode",
                    executableCode: p.executableCode,
                };
            }
            else if ("codeExecutionResult" in p) {
                return {
                    type: "codeExecutionResult",
                    codeExecutionResult: p.codeExecutionResult,
                };
            }
            return p;
        });
    }
    let text = "";
    if (typeof content === "string") {
        text = content;
    }
    else if ("text" in content[0]) {
        text = content[0].text;
    }
    const generation = {
        text,
        message: new messages_1.AIMessage({
            content,
            tool_calls: functionCalls?.map((fc) => {
                return {
                    ...fc,
                    type: "tool_call",
                    id: "id" in fc && typeof fc.id === "string" ? fc.id : (0, uuid_1.v4)(),
                };
            }),
            additional_kwargs: {
                ...generationInfo,
            },
            usage_metadata: extra?.usageMetadata,
        }),
        generationInfo,
    };
    return {
        generations: [generation],
        llmOutput: {
            tokenUsage: {
                promptTokens: extra?.usageMetadata?.input_tokens,
                completionTokens: extra?.usageMetadata?.output_tokens,
                totalTokens: extra?.usageMetadata?.total_tokens,
            },
        },
    };
}
exports.mapGenerateContentResultToChatResult = mapGenerateContentResultToChatResult;
function convertResponseContentToChatGenerationChunk(response, extra) {
    if (!response.candidates || response.candidates.length === 0) {
        return null;
    }
    const functionCalls = response.functionCalls();
    const [candidate] = response.candidates;
    const { content: candidateContent, ...generationInfo } = candidate;
    let content;
    // Checks if some parts do not have text. If false, it means that the content is a string.
    if (candidateContent?.parts &&
        candidateContent.parts.every((p) => "text" in p)) {
        content = candidateContent.parts.map((p) => p.text).join("");
    }
    else if (candidateContent.parts) {
        content = candidateContent.parts.map((p) => {
            if ("text" in p) {
                return {
                    type: "text",
                    text: p.text,
                };
            }
            else if ("executableCode" in p) {
                return {
                    type: "executableCode",
                    executableCode: p.executableCode,
                };
            }
            else if ("codeExecutionResult" in p) {
                return {
                    type: "codeExecutionResult",
                    codeExecutionResult: p.codeExecutionResult,
                };
            }
            return p;
        });
    }
    let text = "";
    if (content && typeof content === "string") {
        text = content;
    }
    else if (content && typeof content === "object" && "text" in content[0]) {
        text = content[0].text;
    }
    const toolCallChunks = [];
    if (functionCalls) {
        toolCallChunks.push(...functionCalls.map((fc) => ({
            ...fc,
            args: JSON.stringify(fc.args),
            index: extra.index,
            type: "tool_call_chunk",
            id: "id" in fc && typeof fc.id === "string" ? fc.id : (0, uuid_1.v4)(),
        })));
    }
    return new outputs_1.ChatGenerationChunk({
        text,
        message: new messages_1.AIMessageChunk({
            content: content || "",
            name: !candidateContent ? undefined : candidateContent.role,
            tool_call_chunks: toolCallChunks,
            // Each chunk can have unique "generationInfo", and merging strategy is unclear,
            // so leave blank for now.
            additional_kwargs: {},
            usage_metadata: extra.usageMetadata,
        }),
        generationInfo,
    });
}
exports.convertResponseContentToChatGenerationChunk = convertResponseContentToChatGenerationChunk;
function convertToGenerativeAITools(tools) {
    if (tools.every((tool) => "functionDeclarations" in tool &&
        Array.isArray(tool.functionDeclarations))) {
        return tools;
    }
    return [
        {
            functionDeclarations: tools.map((tool) => {
                if ((0, function_calling_1.isLangChainTool)(tool)) {
                    const jsonSchema = (0, zod_to_genai_parameters_js_1.schemaToGenerativeAIParameters)(tool.schema);
                    if (jsonSchema.type === "object" &&
                        "properties" in jsonSchema &&
                        Object.keys(jsonSchema.properties).length === 0) {
                        return {
                            name: tool.name,
                            description: tool.description,
                        };
                    }
                    return {
                        name: tool.name,
                        description: tool.description,
                        parameters: jsonSchema,
                    };
                }
                if ((0, base_1.isOpenAITool)(tool)) {
                    return {
                        name: tool.function.name,
                        description: tool.function.description ?? `A function available to call.`,
                        parameters: (0, zod_to_genai_parameters_js_1.jsonSchemaToGeminiParameters)(tool.function.parameters),
                    };
                }
                return tool;
            }),
        },
    ];
}
exports.convertToGenerativeAITools = convertToGenerativeAITools;
