"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMessageAuthor = getMessageAuthor;
exports.convertAuthorToRole = convertAuthorToRole;
exports.convertMessageContentToParts = convertMessageContentToParts;
exports.convertBaseMessagesToContent = convertBaseMessagesToContent;
exports.mapGenerateContentResultToChatResult = mapGenerateContentResultToChatResult;
exports.convertResponseContentToChatGenerationChunk = convertResponseContentToChatGenerationChunk;
exports.convertToGenerativeAITools = convertToGenerativeAITools;
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;
}
/**
 * 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 "supervisor":
        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}`);
    }
}
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 _getStandardContentBlockConverter(isMultimodalModel) {
    const standardContentBlockConverter = {
        providerName: "Google Gemini",
        fromStandardTextBlock(block) {
            return {
                text: block.text,
            };
        },
        fromStandardImageBlock(block) {
            if (!isMultimodalModel) {
                throw new Error("This model does not support images");
            }
            if (block.source_type === "url") {
                const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
                if (data) {
                    return {
                        inlineData: {
                            mimeType: data.mime_type,
                            data: data.data,
                        },
                    };
                }
                else {
                    return {
                        fileData: {
                            mimeType: block.mime_type ?? "",
                            fileUri: block.url,
                        },
                    };
                }
            }
            if (block.source_type === "base64") {
                return {
                    inlineData: {
                        mimeType: block.mime_type ?? "",
                        data: block.data,
                    },
                };
            }
            throw new Error(`Unsupported source type: ${block.source_type}`);
        },
        fromStandardAudioBlock(block) {
            if (!isMultimodalModel) {
                throw new Error("This model does not support audio");
            }
            if (block.source_type === "url") {
                const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
                if (data) {
                    return {
                        inlineData: {
                            mimeType: data.mime_type,
                            data: data.data,
                        },
                    };
                }
                else {
                    return {
                        fileData: {
                            mimeType: block.mime_type ?? "",
                            fileUri: block.url,
                        },
                    };
                }
            }
            if (block.source_type === "base64") {
                return {
                    inlineData: {
                        mimeType: block.mime_type ?? "",
                        data: block.data,
                    },
                };
            }
            throw new Error(`Unsupported source type: ${block.source_type}`);
        },
        fromStandardFileBlock(block) {
            if (!isMultimodalModel) {
                throw new Error("This model does not support files");
            }
            if (block.source_type === "text") {
                return {
                    text: block.text,
                };
            }
            if (block.source_type === "url") {
                const data = (0, messages_1.parseBase64DataUrl)({ dataUrl: block.url });
                if (data) {
                    return {
                        inlineData: {
                            mimeType: data.mime_type,
                            data: data.data,
                        },
                    };
                }
                else {
                    return {
                        fileData: {
                            mimeType: block.mime_type ?? "",
                            fileUri: block.url,
                        },
                    };
                }
            }
            if (block.source_type === "base64") {
                return {
                    inlineData: {
                        mimeType: block.mime_type ?? "",
                        data: block.data,
                    },
                };
            }
            throw new Error(`Unsupported source type: ${block.source_type}`);
        },
    };
    return standardContentBlockConverter;
}
function _convertLangChainContentToPart(content, isMultimodalModel) {
    if ((0, messages_1.isDataContentBlock)(content)) {
        return (0, messages_1.convertToProviderContentBlock)(content, _getStandardContentBlockConverter(isMultimodalModel));
    }
    if (content.type === "text") {
        return { text: content.text };
    }
    else if (content.type === "executableCode") {
        return { executableCode: content.executableCode };
    }
    else if (content.type === "codeExecutionResult") {
        return { codeExecutionResult: content.codeExecutionResult };
    }
    else if (content.type === "image_url") {
        if (!isMultimodalModel) {
            throw new Error(`This model does not support images`);
        }
        let source;
        if (typeof content.image_url === "string") {
            source = content.image_url;
        }
        else if (typeof content.image_url === "object" &&
            "url" in content.image_url) {
            source = content.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 (content.type === "media") {
        return messageContentMedia(content);
    }
    else if (content.type === "tool_use") {
        return {
            functionCall: {
                name: content.name,
                args: content.input,
            },
        };
    }
    else if (content.type?.includes("/") &&
        // Ensure it's a single slash.
        content.type.split("/").length === 2 &&
        "data" in content &&
        typeof content.data === "string") {
        return {
            inlineData: {
                mimeType: content.type,
                data: content.data,
            },
        };
    }
    else if ("functionCall" in content) {
        // No action needed here — function calls will be added later from message.tool_calls
        return undefined;
    }
    else {
        if ("type" in content) {
            throw new Error(`Unknown content type ${content.type}`);
        }
        else {
            throw new Error(`Unknown content ${JSON.stringify(content)}`);
        }
    }
}
function convertMessageContentToParts(message, isMultimodalModel, previousMessages) {
    if ((0, messages_1.isToolMessage)(message)) {
        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.`);
        }
        const result = Array.isArray(message.content)
            ? message.content
                .map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
                .filter((p) => p !== undefined)
            : message.content;
        if (message.status === "error") {
            return [
                {
                    functionResponse: {
                        name: messageName,
                        // The API expects an object with an `error` field if the function call fails.
                        // `error` must be a valid object (not a string or array), so we wrap `message.content` here
                        response: { error: { details: result } },
                    },
                },
            ];
        }
        return [
            {
                functionResponse: {
                    name: messageName,
                    // again, can't have a string or array value for `response`, so we wrap it as an object here
                    response: { result },
                },
            },
        ];
    }
    let functionCalls = [];
    const messageParts = [];
    if (typeof message.content === "string" && message.content) {
        messageParts.push({ text: message.content });
    }
    if (Array.isArray(message.content)) {
        messageParts.push(...message.content
            .map((c) => _convertLangChainContentToPart(c, isMultimodalModel))
            .filter((p) => p !== undefined));
    }
    if ((0, messages_1.isAIMessage)(message) && message.tool_calls?.length) {
        functionCalls = message.tool_calls.map((tc) => {
            return {
                functionCall: {
                    name: tc.name,
                    args: tc.args,
                },
            };
        });
    }
    return [...messageParts, ...functionCalls];
}
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;
}
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 (Array.isArray(candidateContent?.parts) &&
        candidateContent.parts.length === 1 &&
        candidateContent.parts[0].text) {
        content = candidateContent.parts[0].text;
    }
    else if (Array.isArray(candidateContent?.parts) &&
        candidateContent.parts.length > 0) {
        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;
        });
    }
    else {
        // no content returned - likely due to abnormal stop reason, e.g. malformed function call
        content = [];
    }
    let text = "";
    if (typeof content === "string") {
        text = content;
    }
    else if (Array.isArray(content) && content.length > 0) {
        const block = content.find((b) => "text" in b);
        text = block?.text ?? text;
    }
    const generation = {
        text,
        message: new messages_1.AIMessage({
            content: 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,
            },
        },
    };
}
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 (Array.isArray(candidateContent?.parts) &&
        candidateContent.parts.every((p) => "text" in p)) {
        content = candidateContent.parts.map((p) => p.text).join("");
    }
    else if (Array.isArray(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;
        });
    }
    else {
        // no content returned - likely due to abnormal stop reason, e.g. malformed function call
        content = [];
    }
    let text = "";
    if (content && typeof content === "string") {
        text = content;
    }
    else if (Array.isArray(content)) {
        const block = content.find((b) => "text" in b);
        text = block?.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,
    });
}
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;
            }),
        },
    ];
}
