"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageGeminiSafetyHandler = exports.DefaultGeminiSafetyHandler = void 0;
exports.normalizeSpeechConfig = normalizeSpeechConfig;
exports.getGeminiAPI = getGeminiAPI;
exports.validateGeminiParams = validateGeminiParams;
exports.isModelGemini = isModelGemini;
exports.isModelGemma = isModelGemma;
const uuid_1 = require("uuid");
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 stream_1 = require("@langchain/core/utils/stream");
const types_js_1 = require("../types.cjs");
const safety_js_1 = require("./safety.cjs");
const zod_to_gemini_parameters_js_1 = require("./zod_to_gemini_parameters.cjs");
class DefaultGeminiSafetyHandler {
    constructor(settings) {
        Object.defineProperty(this, "errorFinish", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: ["SAFETY", "RECITATION", "OTHER"]
        });
        this.errorFinish = settings?.errorFinish ?? this.errorFinish;
    }
    handleDataPromptFeedback(response, data) {
        // Check to see if our prompt was blocked in the first place
        const promptFeedback = data?.promptFeedback;
        const blockReason = promptFeedback?.blockReason;
        if (blockReason) {
            throw new safety_js_1.GoogleAISafetyError(response, `Prompt blocked: ${blockReason}`);
        }
        return data;
    }
    handleDataFinishReason(response, data) {
        const firstCandidate = data?.candidates?.[0];
        const finishReason = firstCandidate?.finishReason;
        if (this.errorFinish.includes(finishReason)) {
            throw new safety_js_1.GoogleAISafetyError(response, `Finish reason: ${finishReason}`);
        }
        return data;
    }
    handleData(response, data) {
        let ret = data;
        ret = this.handleDataPromptFeedback(response, ret);
        ret = this.handleDataFinishReason(response, ret);
        return ret;
    }
    handle(response) {
        let newdata;
        if ("nextChunk" in response.data) {
            // TODO: This is a stream. How to handle?
            newdata = response.data;
        }
        else if (Array.isArray(response.data)) {
            // If it is an array, try to handle every item in the array
            try {
                newdata = response.data.map((item) => this.handleData(response, item));
            }
            catch (xx) {
                // eslint-disable-next-line no-instanceof/no-instanceof
                if (xx instanceof safety_js_1.GoogleAISafetyError) {
                    throw new safety_js_1.GoogleAISafetyError(response, xx.message);
                }
                else {
                    throw xx;
                }
            }
        }
        else {
            const data = response.data;
            newdata = this.handleData(response, data);
        }
        return {
            ...response,
            data: newdata,
        };
    }
}
exports.DefaultGeminiSafetyHandler = DefaultGeminiSafetyHandler;
class MessageGeminiSafetyHandler extends DefaultGeminiSafetyHandler {
    constructor(settings) {
        super(settings);
        Object.defineProperty(this, "msg", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: ""
        });
        Object.defineProperty(this, "forceNewMessage", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        this.msg = settings?.msg ?? this.msg;
        this.forceNewMessage = settings?.forceNewMessage ?? this.forceNewMessage;
    }
    setMessage(data) {
        const ret = data;
        if (this.forceNewMessage ||
            !data?.candidates?.[0]?.content?.parts?.length) {
            ret.candidates = data.candidates ?? [];
            ret.candidates[0] = data.candidates[0] ?? {};
            ret.candidates[0].content = data.candidates[0].content ?? {};
            ret.candidates[0].content = {
                role: "model",
                parts: [{ text: this.msg }],
            };
        }
        return ret;
    }
    handleData(response, data) {
        try {
            return super.handleData(response, data);
        }
        catch (xx) {
            return this.setMessage(data);
        }
    }
}
exports.MessageGeminiSafetyHandler = MessageGeminiSafetyHandler;
const extractMimeType = (str) => {
    if (str.startsWith("data:")) {
        return {
            mimeType: str.split(":")[1].split(";")[0],
            data: str.split(",")[1],
        };
    }
    return null;
};
function normalizeSpeechConfig(config) {
    function isSpeechConfig(config) {
        return (typeof config === "object" &&
            (Object.hasOwn(config, "voiceConfig") ||
                Object.hasOwn(config, "multiSpeakerVoiceConfig")));
    }
    function hasLanguage(config) {
        return typeof config === "object" && Object.hasOwn(config, "languageCode");
    }
    function hasVoice(config) {
        return Object.hasOwn(config, "voice");
    }
    if (typeof config === "undefined") {
        return undefined;
    }
    // If this is already a GoogleSpeechConfig, just return it
    if (isSpeechConfig(config)) {
        return config;
    }
    let languageCode;
    let voice;
    if (hasLanguage(config)) {
        languageCode = config.languageCode;
        voice = hasVoice(config) ? config.voice : config.voices;
    }
    else {
        languageCode = undefined;
        voice = config;
    }
    let ret;
    if (typeof voice === "string") {
        // They just provided the prebuilt voice configuration name. Use it.
        ret = {
            voiceConfig: {
                prebuiltVoiceConfig: {
                    voiceName: voice,
                },
            },
        };
    }
    else {
        // This is multi-speaker, so we have speaker/name pairs
        // If we have just one (why?), turn it into an array for the moment
        const voices = Array.isArray(voice)
            ? voice
            : [voice];
        // Go through all the speaker/name pairs and turn this into the voice config array
        const speakerVoiceConfigs = voices.map((v) => ({
            speaker: v.speaker,
            voiceConfig: {
                prebuiltVoiceConfig: {
                    voiceName: v.name,
                },
            },
        }));
        // Create the multi-speaker voice configuration
        ret = {
            multiSpeakerVoiceConfig: {
                speakerVoiceConfigs,
            },
        };
    }
    if (languageCode) {
        ret.languageCode = languageCode;
    }
    return ret;
}
function getGeminiAPI(config) {
    function messageContentText(content) {
        if (content?.text && content?.text.length > 0) {
            return {
                text: content.text,
            };
        }
        else {
            return null;
        }
    }
    function messageContentImageUrlData(content) {
        const url = typeof content.image_url === "string"
            ? content.image_url
            : content.image_url.url;
        if (!url) {
            throw new Error("Missing Image URL");
        }
        const mimeTypeAndData = extractMimeType(url);
        if (mimeTypeAndData) {
            return {
                inlineData: mimeTypeAndData,
            };
        }
        else {
            // FIXME - need some way to get mime type
            return {
                fileData: {
                    mimeType: "image/png",
                    fileUri: url,
                },
            };
        }
    }
    function messageContentImageUrl(content) {
        const ret = messageContentImageUrlData(content);
        supplementVideoMetadata(content, ret);
        return ret;
    }
    async function blobToFileData(blob) {
        return {
            fileData: {
                fileUri: blob.path,
                mimeType: blob.mimetype,
            },
        };
    }
    async function fileUriContentToBlob(uri) {
        return config?.mediaManager?.getMediaBlob(uri);
    }
    async function messageContentMediaData(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    content) {
        if ("mimeType" in content && "data" in content) {
            return {
                inlineData: {
                    mimeType: content.mimeType,
                    data: content.data,
                },
            };
        }
        else if ("mimeType" in content && "fileUri" in content) {
            return {
                fileData: {
                    mimeType: content.mimeType,
                    fileUri: content.fileUri,
                },
            };
        }
        else {
            const uri = content.fileUri;
            const blob = await fileUriContentToBlob(uri);
            if (blob) {
                return await blobToFileData(blob);
            }
        }
        throw new Error(`Invalid media content: ${JSON.stringify(content, null, 1)}`);
    }
    function supplementVideoMetadata(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    content, ret) {
        // Add videoMetadata if defined
        if ("videoMetadata" in content && typeof ret === "object") {
            // eslint-disable-next-line no-param-reassign
            ret.videoMetadata = content.videoMetadata;
        }
        return ret;
    }
    async function messageContentMedia(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    content) {
        const ret = await messageContentMediaData(content);
        supplementVideoMetadata(content, ret);
        return ret;
    }
    function messageContentReasoning(content) {
        if (content?.reasoning && content?.reasoning.length > 0) {
            return {
                text: content.reasoning,
                thought: true,
            };
        }
        else {
            return null;
        }
    }
    const standardContentBlockConverter = {
        providerName: "Google Gemini",
        fromStandardTextBlock(block) {
            return {
                text: block.text,
            };
        },
        fromStandardImageBlock(block) {
            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 (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 (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}`);
        },
    };
    async function messageContentComplexToPart(content) {
        switch (content.type) {
            case "text":
                if ("text" in content) {
                    return messageContentText(content);
                }
                break;
            case "image_url":
                if ("image_url" in content) {
                    // Type guard for MessageContentImageUrl
                    return messageContentImageUrl(content);
                }
                break;
            case "media":
                return await messageContentMedia(content);
            case "reasoning":
                return messageContentReasoning(content);
            default:
                throw new Error(`Unsupported type "${content.type}" received while converting message to message parts: ${JSON.stringify(content)}`);
        }
        throw new Error(`Cannot coerce "${content.type}" message part into a string.`);
    }
    async function messageContentComplexToParts(content) {
        const contents = content.map((m) => (0, messages_1.isDataContentBlock)(m)
            ? (0, messages_1.convertToProviderContentBlock)(m, standardContentBlockConverter)
            : messageContentComplexToPart(m));
        return Promise.all(contents);
    }
    async function messageContentToParts(content) {
        // Convert a string to a text type MessageContent if needed
        const messageContent = typeof content === "string"
            ? [
                {
                    type: "text",
                    text: content,
                },
            ]
            : content;
        // Get all of the parts, even those that don't correctly resolve
        const allParts = await messageContentComplexToParts(messageContent);
        // Remove any invalid parts
        const parts = allParts.reduce((acc, val) => {
            if (val) {
                return [...acc, val];
            }
            else {
                return acc;
            }
        }, []);
        return parts;
    }
    function messageToolCallsToParts(toolCalls) {
        if (!toolCalls || toolCalls.length === 0) {
            return [];
        }
        return toolCalls.map((tool) => {
            let args = {};
            if (tool?.function?.arguments) {
                const argStr = tool.function.arguments;
                args = JSON.parse(argStr);
            }
            return {
                functionCall: {
                    name: tool.function.name,
                    args,
                },
            };
        });
    }
    function messageKwargsToParts(kwargs) {
        const ret = [];
        if (kwargs?.tool_calls) {
            ret.push(...messageToolCallsToParts(kwargs.tool_calls));
        }
        return ret;
    }
    async function roleMessageToContent(role, message) {
        const contentParts = await messageContentToParts(message.content);
        let toolParts;
        if ((0, messages_1.isAIMessage)(message) && !!message.tool_calls?.length) {
            toolParts = message.tool_calls.map((toolCall) => ({
                functionCall: {
                    name: toolCall.name,
                    args: toolCall.args,
                },
            }));
        }
        else {
            toolParts = messageKwargsToParts(message.additional_kwargs);
        }
        const parts = [...contentParts, ...toolParts];
        const signatures = message?.additional_kwargs?.signatures ?? [];
        if (signatures.length === parts.length) {
            for (let co = 0; co < signatures.length; co += 1) {
                const signature = signatures[co];
                if (signature && signature.length > 0) {
                    parts[co].thoughtSignature = signature;
                }
            }
        }
        return [
            {
                role,
                parts,
            },
        ];
    }
    async function systemMessageToContent(message) {
        return config?.useSystemInstruction
            ? roleMessageToContent("system", message)
            : [
                ...(await roleMessageToContent("user", message)),
                ...(await roleMessageToContent("model", new messages_1.AIMessage("Ok"))),
            ];
    }
    function toolMessageToContent(message, prevMessage) {
        const contentStr = typeof message.content === "string"
            ? message.content
            : message.content.reduce((acc, content) => {
                if (content.type === "text") {
                    return acc + content.text;
                }
                else {
                    return acc;
                }
            }, "");
        // Hacky :(
        const responseName = ((0, messages_1.isAIMessage)(prevMessage) && !!prevMessage.tool_calls?.length
            ? prevMessage.tool_calls[0].name
            : prevMessage.name) ?? message.tool_call_id;
        try {
            const content = JSON.parse(contentStr);
            return [
                {
                    role: "function",
                    parts: [
                        {
                            functionResponse: {
                                name: responseName,
                                response: { content },
                            },
                        },
                    ],
                },
            ];
        }
        catch (_) {
            return [
                {
                    role: "function",
                    parts: [
                        {
                            functionResponse: {
                                name: responseName,
                                response: { content: contentStr },
                            },
                        },
                    ],
                },
            ];
        }
    }
    async function baseMessageToContent(message, prevMessage) {
        const type = message._getType();
        switch (type) {
            case "system":
                return systemMessageToContent(message);
            case "human":
                return roleMessageToContent("user", message);
            case "ai":
                return roleMessageToContent("model", message);
            case "tool":
                if (!prevMessage) {
                    throw new Error("Tool messages cannot be the first message passed to the model.");
                }
                return toolMessageToContent(message, prevMessage);
            default:
                console.log(`Unsupported message type: ${type}`);
                return [];
        }
    }
    function thoughtPartToMessageContent(part) {
        return {
            type: "reasoning",
            reasoning: part.text,
        };
    }
    function textPartToMessageContent(part) {
        return {
            type: "text",
            text: part.text,
        };
    }
    function inlineDataPartToMessageContentImage(part) {
        return {
            type: "image_url",
            image_url: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`,
        };
    }
    function inlineDataPartToMessageContentMedia(part
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ) {
        return {
            type: "media",
            mimeType: part.inlineData.mimeType,
            data: part.inlineData.data,
        };
    }
    function inlineDataPartToMessageContent(part
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ) {
        const mimeType = part?.inlineData?.mimeType ?? "";
        if (mimeType.startsWith("image")) {
            return inlineDataPartToMessageContentImage(part);
        }
        else {
            return inlineDataPartToMessageContentMedia(part);
        }
    }
    function fileDataPartToMessageContent(part) {
        return {
            type: "image_url",
            image_url: part.fileData.fileUri,
        };
    }
    function partsToMessageContent(parts) {
        return parts
            .map((part) => {
            if (part === undefined || part === null) {
                return null;
            }
            else if (part.thought) {
                return thoughtPartToMessageContent(part);
            }
            else if ("text" in part) {
                return textPartToMessageContent(part);
            }
            else if ("inlineData" in part) {
                return inlineDataPartToMessageContent(part);
            }
            else if ("fileData" in part) {
                return fileDataPartToMessageContent(part);
            }
            else {
                return null;
            }
        })
            .reduce((acc, content) => {
            if (content) {
                acc.push(content);
            }
            return acc;
        }, []);
    }
    function toolRawToTool(raw) {
        return {
            id: raw.id,
            type: raw.type,
            function: {
                name: raw.function.name,
                arguments: JSON.stringify(raw.function.arguments),
            },
        };
    }
    function functionCallPartToToolRaw(part) {
        return {
            id: (0, uuid_1.v4)().replace(/-/g, ""),
            type: "function",
            function: {
                name: part.functionCall.name,
                arguments: part.functionCall.args ?? {},
            },
        };
    }
    function partsToToolsRaw(parts) {
        return parts
            .map((part) => {
            if (part === undefined || part === null) {
                return null;
            }
            else if ("functionCall" in part) {
                return functionCallPartToToolRaw(part);
            }
            else {
                return null;
            }
        })
            .reduce((acc, content) => {
            if (content) {
                acc.push(content);
            }
            return acc;
        }, []);
    }
    function toolsRawToTools(raws) {
        return raws.map((raw) => toolRawToTool(raw));
    }
    function responseToGenerateContentResponseData(response) {
        if ("nextChunk" in response.data) {
            throw new Error("Cannot convert Stream to GenerateContentResponseData");
        }
        else if (Array.isArray(response.data)) {
            // Collapse the array of response data as if it was a single one
            return response.data.reduce((acc, val) => {
                // Add all the parts
                // FIXME: Handle other candidates?
                const valParts = val?.candidates?.[0]?.content?.parts ?? [];
                acc.candidates[0].content.parts.push(...valParts);
                // FIXME: Merge promptFeedback and safety settings
                acc.promptFeedback = val.promptFeedback;
                return acc;
            });
        }
        else {
            return response.data;
        }
    }
    function responseToParts(response) {
        const responseData = responseToGenerateContentResponseData(response);
        const parts = responseData?.candidates?.[0]?.content?.parts ?? [];
        return parts;
    }
    function partToText(part) {
        return "text" in part ? part.text : "";
    }
    function responseToString(response) {
        const parts = responseToParts(response);
        const ret = parts.reduce((acc, part) => {
            const val = partToText(part);
            return acc + val;
        }, "");
        return ret;
    }
    function safeResponseTo(response, responseTo) {
        const safetyHandler = config?.safetyHandler ?? new DefaultGeminiSafetyHandler();
        try {
            const safeResponse = safetyHandler.handle(response);
            return responseTo(safeResponse);
        }
        catch (xx) {
            // eslint-disable-next-line no-instanceof/no-instanceof
            if (xx instanceof safety_js_1.GoogleAISafetyError) {
                const ret = responseTo(xx.response);
                xx.reply = ret;
            }
            throw xx;
        }
    }
    function safeResponseToString(response) {
        return safeResponseTo(response, responseToString);
    }
    function logprobResultToLogprob(result) {
        const token = result?.token;
        const logprob = result?.logProbability;
        const encoder = new TextEncoder();
        const bytes = Array.from(encoder.encode(token));
        return {
            token,
            logprob,
            bytes,
        };
    }
    function candidateToLogprobs(candidate) {
        const logprobs = candidate?.logprobsResult;
        const chosenTokens = logprobs?.chosenCandidates ?? [];
        const topTokens = logprobs?.topCandidates ?? [];
        const content = [];
        for (let co = 0; co < chosenTokens.length; co += 1) {
            const chosen = chosenTokens[co];
            const top = topTokens[co]?.candidates ?? [];
            const logprob = logprobResultToLogprob(chosen);
            logprob.top_logprobs = top.map((l) => logprobResultToLogprob(l));
            content.push(logprob);
        }
        return {
            content,
        };
    }
    function candidateToUrlContextMetadata(candidate) {
        const retrieval = candidate?.urlRetrievalMetadata?.urlRetrievalContexts ?? [];
        const context = candidate?.urlContextMetadata?.urlMetadata ?? [];
        const all = [...retrieval, ...context];
        if (all.length === 0) {
            return undefined;
        }
        else {
            return {
                urlMetadata: all,
            };
        }
    }
    function addModalityCounts(modalityTokenCounts, details) {
        modalityTokenCounts?.forEach((modalityTokenCount) => {
            const { modality, tokenCount } = modalityTokenCount;
            const modalityLc = modality.toLowerCase();
            const currentCount = details[modalityLc] ?? 0;
            // eslint-disable-next-line no-param-reassign
            details[modalityLc] = currentCount + tokenCount;
        });
    }
    function responseToUsageMetadata(response) {
        if ("usageMetadata" in response.data) {
            const data = response?.data;
            const usageMetadata = data?.usageMetadata;
            const input_tokens = usageMetadata.promptTokenCount ?? 0;
            const candidatesTokenCount = usageMetadata.candidatesTokenCount ?? 0;
            const thoughtsTokenCount = usageMetadata.thoughtsTokenCount ?? 0;
            const output_tokens = candidatesTokenCount + thoughtsTokenCount;
            const total_tokens = usageMetadata.totalTokenCount ?? input_tokens + output_tokens;
            const input_token_details = {};
            addModalityCounts(usageMetadata.promptTokensDetails, input_token_details);
            if (typeof usageMetadata?.cachedContentTokenCount === "number") {
                input_token_details.cache_read = usageMetadata.cachedContentTokenCount;
            }
            const output_token_details = {};
            addModalityCounts(usageMetadata?.candidatesTokensDetails, output_token_details);
            if (typeof usageMetadata?.thoughtsTokenCount === "number") {
                output_token_details.reasoning = usageMetadata.thoughtsTokenCount;
            }
            const ret = {
                input_tokens,
                output_tokens,
                total_tokens,
                input_token_details,
                output_token_details,
            };
            return ret;
        }
        return undefined;
    }
    function responseToGenerationInfo(response) {
        const data = 
        // eslint-disable-next-line no-nested-ternary
        Array.isArray(response.data) && response.data[0]
            ? response.data[0]
            : response.data &&
                response.data.candidates
                ? response.data
                : undefined;
        if (!data) {
            return {};
        }
        const finish_reason = data.candidates[0]?.finishReason;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const ret = {
            safety_ratings: data.candidates[0]?.safetyRatings?.map((rating) => ({
                category: rating.category,
                probability: rating.probability,
                probability_score: rating.probabilityScore,
                severity: rating.severity,
                severity_score: rating.severityScore,
            })),
            citation_metadata: data.candidates[0]?.citationMetadata,
            grounding_metadata: data.candidates[0]?.groundingMetadata,
            finish_reason,
            finish_message: data.candidates[0]?.finishMessage,
            url_context_metadata: candidateToUrlContextMetadata(data.candidates[0]),
            avgLogprobs: data.candidates[0]?.avgLogprobs,
            logprobs: candidateToLogprobs(data.candidates[0]),
        };
        // Only add the usage_metadata on the last chunk
        // sent while streaming (see issue 8102).
        if (typeof finish_reason === "string") {
            ret.usage_metadata = responseToUsageMetadata(response);
        }
        return ret;
    }
    function responseToChatGeneration(response) {
        return new outputs_1.ChatGenerationChunk({
            text: responseToString(response),
            message: partToMessageChunk(responseToParts(response)[0]),
            generationInfo: responseToGenerationInfo(response),
        });
    }
    function safeResponseToChatGeneration(response) {
        return safeResponseTo(response, responseToChatGeneration);
    }
    function chunkToString(chunk) {
        if (chunk === null) {
            return "";
        }
        else if (typeof chunk.content === "string") {
            return chunk.content;
        }
        else if (chunk.content.length === 0) {
            return "";
        }
        else if (chunk.content[0].type === "text") {
            return chunk.content[0].text;
        }
        else {
            throw new Error(`Unexpected chunk: ${chunk}`);
        }
    }
    function partToMessageChunk(part) {
        const fields = partsToBaseMessageChunkFields([part]);
        if (typeof fields.content === "string") {
            return new messages_1.AIMessageChunk(fields);
        }
        else if (fields.content.every((item) => item.type === "text")) {
            const newContent = fields.content
                .map((item) => ("text" in item ? item.text : ""))
                .join("");
            return new messages_1.AIMessageChunk({
                ...fields,
                content: newContent,
            });
        }
        return new messages_1.AIMessageChunk(fields);
    }
    function partToChatGeneration(part) {
        const message = partToMessageChunk(part);
        const text = partToText(part);
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const generationInfo = {};
        return new outputs_1.ChatGenerationChunk({
            text,
            message,
            generationInfo,
        });
    }
    function groundingSupportByPart(groundingSupports) {
        const ret = [];
        if (!groundingSupports || groundingSupports.length === 0) {
            return [];
        }
        groundingSupports?.forEach((groundingSupport) => {
            const segment = groundingSupport?.segment;
            const partIndex = segment?.partIndex ?? 0;
            if (ret[partIndex]) {
                ret[partIndex].push(groundingSupport);
            }
            else {
                ret[partIndex] = [groundingSupport];
            }
        });
        return ret;
    }
    function responseToGroundedChatGenerations(response) {
        const parts = responseToParts(response);
        if (parts.length === 0) {
            return [];
        }
        // Citation and grounding information connected to each part / ChatGeneration
        // to make sure they are available in downstream filters.
        const candidate = response?.data
            ?.candidates?.[0];
        const groundingMetadata = candidate?.groundingMetadata;
        const citationMetadata = candidate?.citationMetadata;
        const groundingParts = groundingSupportByPart(groundingMetadata?.groundingSupports);
        const ret = parts.map((part, index) => {
            const gen = partToChatGeneration(part);
            if (!gen.generationInfo) {
                gen.generationInfo = {};
            }
            if (groundingMetadata) {
                gen.generationInfo.groundingMetadata = groundingMetadata;
                const groundingPart = groundingParts[index];
                if (groundingPart) {
                    gen.generationInfo.groundingSupport = groundingPart;
                }
            }
            if (citationMetadata) {
                gen.generationInfo.citationMetadata = citationMetadata;
            }
            return gen;
        });
        return ret;
    }
    function combineContent(gen, forceComplex = false) {
        const allString = gen.every((item) => typeof item.message.content === "string");
        if (allString && !forceComplex) {
            // Everything is a string, and we don't want to force it to return
            // MessageContentComplex[], so concatenate the content into one string
            return gen.map((item) => item.message.content).join("");
        }
        else {
            // We either have complex types, or we want to force them, so turn
            // it into an array of complex types.
            const ret = [];
            gen.forEach((item) => {
                if (typeof item.message.content === "string") {
                    // If this is a string, turn it into a text type
                    ret.push({
                        type: "text",
                        text: item.message.content,
                    });
                }
                else {
                    // Otherwise, add all the complex types to what we're returning
                    item.message.content.forEach((c) => {
                        ret.push(c);
                    });
                }
            });
            return ret;
        }
    }
    function combineText(gen) {
        return gen.map((item) => item.text ?? "").join("");
    }
    /*
     * We don't really need the entire AIMessageChunk here, but it is
     * a conventient way to combine all the Tool Calling information.
     */
    function combineToolCalls(gen) {
        let ret = new messages_1.AIMessageChunk("");
        gen.forEach((item) => {
            const message = item?.message;
            ret = (0, stream_1.concat)(ret, message);
        });
        return ret;
    }
    function combineAdditionalKwargs(gen) {
        const ret = {};
        gen.forEach((item) => {
            const message = item?.message;
            const kwargs = message?.additional_kwargs ?? {};
            const keys = Object.keys(kwargs);
            keys.forEach((key) => {
                const value = kwargs[key];
                if (Object.hasOwn(ret, key) &&
                    Array.isArray(ret[key]) &&
                    Array.isArray(value)) {
                    ret[key].push(...value);
                }
                else {
                    ret[key] = value;
                }
            });
        });
        return ret;
    }
    function combineGenerations(generations, response) {
        const gen = splitGenerationTypes(generations, response);
        const combinedContent = combineContent(gen.content);
        const combinedText = combineText(gen.content);
        const combinedToolCalls = combineToolCalls(gen.content);
        const kwargs = combineAdditionalKwargs(gen.content);
        const lastContent = gen.content[gen.content.length - 1];
        // Add usage metadata
        const usage_metadata = responseToUsageMetadata(response);
        // Add thinking / reasoning
        // if (gen.reasoning && gen.reasoning.length > 0) {
        //   kwargs.reasoning_content = combineContent(gen.reasoning, true);
        // }
        // Build the message and the generation chunk to return
        const message = new messages_1.AIMessageChunk({
            content: combinedContent,
            additional_kwargs: kwargs,
            usage_metadata,
            tool_calls: combinedToolCalls.tool_calls,
            invalid_tool_calls: combinedToolCalls.invalid_tool_calls,
        });
        return [
            new outputs_1.ChatGenerationChunk({
                message,
                text: combinedText,
                generationInfo: lastContent.generationInfo,
            }),
        ];
    }
    function splitGenerationTypes(generations, _response) {
        const content = [];
        const reasoning = [];
        generations.forEach((gen) => {
            if (gen?.generationInfo?.thought) {
                reasoning.push(gen);
            }
            else {
                content.push(gen);
            }
        });
        return {
            content,
            reasoning,
        };
    }
    /**
     * Although this returns an array, only the first (or maybe last)
     * element in the array is used. So we need to combine them into
     * just one element that contains everything we need.
     * @param response
     */
    function responseToChatGenerations(response) {
        const generations = responseToGroundedChatGenerations(response);
        if (generations.length === 0) {
            return [];
        }
        const ret = combineGenerations(generations, response);
        // Add logprobs information to the message
        const candidate = response?.data
            ?.candidates?.[0];
        const avgLogprobs = candidate?.avgLogprobs;
        const logprobs = candidateToLogprobs(candidate);
        if (logprobs) {
            ret[0].message.response_metadata = {
                ...ret[0].message.response_metadata,
                logprobs,
                avgLogprobs,
            };
        }
        return ret;
    }
    function responseToBaseMessageFields(response) {
        const parts = responseToParts(response);
        return partsToBaseMessageChunkFields(parts);
    }
    function partsToSignatures(parts) {
        return parts.map((part) => part?.thoughtSignature ?? "");
    }
    function partsToBaseMessageChunkFields(parts) {
        const fields = {
            content: partsToMessageContent(parts),
            tool_call_chunks: [],
            tool_calls: [],
            invalid_tool_calls: [],
        };
        fields.additional_kwargs = {};
        const rawTools = partsToToolsRaw(parts);
        if (rawTools.length > 0) {
            const tools = toolsRawToTools(rawTools);
            for (const tool of tools) {
                fields.tool_call_chunks?.push({
                    name: tool.function.name,
                    args: tool.function.arguments,
                    id: tool.id,
                    type: "tool_call_chunk",
                });
                try {
                    fields.tool_calls?.push({
                        name: tool.function.name,
                        args: JSON.parse(tool.function.arguments),
                        id: tool.id,
                    });
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                }
                catch (e) {
                    fields.invalid_tool_calls?.push({
                        name: tool.function.name,
                        args: tool.function.arguments,
                        id: tool.id,
                        error: e.message,
                        type: "invalid_tool_call",
                    });
                }
            }
            fields.additional_kwargs.tool_calls = tools;
        }
        fields.additional_kwargs.signatures = partsToSignatures(parts);
        return fields;
    }
    function responseToBaseMessage(response) {
        const fields = responseToBaseMessageFields(response);
        return new messages_1.AIMessage(fields);
    }
    function safeResponseToBaseMessage(response) {
        return safeResponseTo(response, responseToBaseMessage);
    }
    function responseToChatResult(response) {
        const generations = responseToChatGenerations(response);
        return {
            generations,
            llmOutput: responseToGenerationInfo(response),
        };
    }
    function safeResponseToChatResult(response) {
        return safeResponseTo(response, responseToChatResult);
    }
    function inputType(input) {
        if (typeof input === "string") {
            return "MessageContent";
        }
        else {
            const firstItem = input[0];
            if (Object.hasOwn(firstItem, "content")) {
                return "BaseMessageArray";
            }
            else {
                return "MessageContent";
            }
        }
    }
    async function formatMessageContents(input, _parameters) {
        const parts = await messageContentToParts(input);
        const contents = [
            {
                role: "user", // Required by Vertex AI
                parts,
            },
        ];
        return contents;
    }
    async function formatBaseMessageContents(input, _parameters) {
        const inputPromises = input.map((msg, i) => baseMessageToContent(msg, input[i - 1]));
        const inputs = await Promise.all(inputPromises);
        return inputs.reduce((acc, cur) => {
            // Filter out the system content
            if (cur.every((content) => content.role === "system")) {
                return acc;
            }
            // Combine adjacent function messages
            if (cur[0]?.role === "function" &&
                acc.length > 0 &&
                acc[acc.length - 1].role === "function") {
                acc[acc.length - 1].parts = [
                    ...acc[acc.length - 1].parts,
                    ...cur[0].parts,
                ];
            }
            else {
                acc.push(...cur);
            }
            return acc;
        }, []);
    }
    async function formatContents(input, parameters) {
        const it = inputType(input);
        switch (it) {
            case "MessageContent":
                return formatMessageContents(input, parameters);
            case "BaseMessageArray":
                return formatBaseMessageContents(input, parameters);
            default:
                throw new Error(`Unknown input type "${it}": ${input}`);
        }
    }
    function formatGenerationConfig(parameters) {
        const ret = {
            temperature: parameters.temperature,
            topK: parameters.topK,
            topP: parameters.topP,
            seed: parameters.seed,
            presencePenalty: parameters.presencePenalty,
            frequencyPenalty: parameters.frequencyPenalty,
            maxOutputTokens: parameters.maxOutputTokens,
            stopSequences: parameters.stopSequences,
            responseMimeType: parameters.responseMimeType,
            responseModalities: parameters.responseModalities,
            speechConfig: normalizeSpeechConfig(parameters.speechConfig),
        };
        // Add the logprobs if explicitly set
        if (typeof parameters.logprobs !== "undefined") {
            ret.responseLogprobs = parameters.logprobs;
            if (parameters.logprobs &&
                typeof parameters.topLogprobs !== "undefined") {
                ret.logprobs = parameters.topLogprobs;
            }
        }
        // Add thinking configuration if explicitly set
        // Note that you cannot have thinkingBudget set to 0 and includeThoughts true
        if (typeof parameters.maxReasoningTokens !== "undefined") {
            const includeThoughts = parameters.maxReasoningTokens !== 0;
            ret.thinkingConfig = {
                thinkingBudget: parameters.maxReasoningTokens,
                includeThoughts,
            };
        }
        // Remove any undefined properties, so we don't send them
        let attribute;
        for (attribute in ret) {
            if (ret[attribute] === undefined) {
                delete ret[attribute];
            }
        }
        return ret;
    }
    function formatSafetySettings(parameters) {
        return parameters.safetySettings ?? [];
    }
    async function formatBaseMessageSystemInstruction(input) {
        let ret = {};
        for (let index = 0; index < input.length; index += 1) {
            const message = input[index];
            if (message._getType() === "system") {
                // For system types, we only want it if it is the first message,
                // if it appears anywhere else, it should be an error.
                if (index === 0) {
                    // eslint-disable-next-line prefer-destructuring
                    ret = (await baseMessageToContent(message, undefined))[0];
                }
                else {
                    throw new Error("System messages are only permitted as the first passed message.");
                }
            }
        }
        return ret;
    }
    async function formatSystemInstruction(input) {
        if (!config?.useSystemInstruction) {
            return {};
        }
        const it = inputType(input);
        switch (it) {
            case "BaseMessageArray":
                return formatBaseMessageSystemInstruction(input);
            default:
                return {};
        }
    }
    function structuredToolToFunctionDeclaration(tool) {
        const jsonSchema = (0, zod_to_gemini_parameters_js_1.schemaToGeminiParameters)(tool.schema);
        return {
            name: tool.name,
            description: tool.description ?? `A function available to call.`,
            parameters: jsonSchema,
        };
    }
    function searchToolName(tool) {
        for (const name of types_js_1.GeminiSearchToolAttributes) {
            if (name in tool) {
                return name;
            }
        }
        return undefined;
    }
    function cleanGeminiTool(tool) {
        const orig = searchToolName(tool);
        const adj = config?.googleSearchToolAdjustment;
        if (orig && adj && adj !== orig) {
            return {
                [adj]: {},
            };
        }
        else {
            return tool;
        }
    }
    function formatTools(parameters) {
        const tools = parameters?.tools;
        if (!tools || tools.length === 0) {
            return [];
        }
        // Group all LangChain tools into a single functionDeclarations array.
        // Gemini Tools may be normalized to different tool names
        const langChainTools = [];
        const otherTools = [];
        tools.forEach((tool) => {
            if ((0, function_calling_1.isLangChainTool)(tool)) {
                langChainTools.push(tool);
            }
            else {
                otherTools.push(cleanGeminiTool(tool));
            }
        });
        const result = [...otherTools];
        if (langChainTools.length > 0) {
            result.push({
                functionDeclarations: langChainTools.map(structuredToolToFunctionDeclaration),
            });
        }
        return result;
    }
    function formatToolConfig(parameters) {
        if (!parameters.tool_choice || typeof parameters.tool_choice !== "string") {
            return undefined;
        }
        if (["auto", "any", "none"].includes(parameters.tool_choice)) {
            return {
                functionCallingConfig: {
                    mode: parameters.tool_choice,
                    allowedFunctionNames: parameters.allowed_function_names,
                },
            };
        }
        // force tool choice to be a single function name in case of structured output
        return {
            functionCallingConfig: {
                mode: "any",
                allowedFunctionNames: [parameters.tool_choice],
            },
        };
    }
    async function formatData(input, parameters) {
        const typedInput = input;
        const contents = await formatContents(typedInput, parameters);
        const generationConfig = formatGenerationConfig(parameters);
        const tools = formatTools(parameters);
        const toolConfig = formatToolConfig(parameters);
        const safetySettings = formatSafetySettings(parameters);
        const systemInstruction = await formatSystemInstruction(typedInput);
        const ret = {
            contents,
            generationConfig,
        };
        if (tools && tools.length) {
            ret.tools = tools;
        }
        if (toolConfig) {
            ret.toolConfig = toolConfig;
        }
        if (safetySettings && safetySettings.length) {
            ret.safetySettings = safetySettings;
        }
        if (systemInstruction?.role &&
            systemInstruction?.parts &&
            systemInstruction?.parts?.length) {
            ret.systemInstruction = systemInstruction;
        }
        if (parameters.cachedContent) {
            ret.cachedContent = parameters.cachedContent;
        }
        return ret;
    }
    return {
        messageContentToParts,
        baseMessageToContent,
        responseToString: safeResponseToString,
        responseToChatGeneration: safeResponseToChatGeneration,
        chunkToString,
        responseToBaseMessage: safeResponseToBaseMessage,
        responseToChatResult: safeResponseToChatResult,
        formatData,
    };
}
function validateGeminiParams(params) {
    if (params.maxOutputTokens && params.maxOutputTokens < 0) {
        throw new Error("`maxOutputTokens` must be a positive integer");
    }
    if (typeof params.maxReasoningTokens !== "undefined") {
        if (typeof params.maxOutputTokens !== "undefined") {
            if (params.maxReasoningTokens >= params.maxOutputTokens) {
                throw new Error("`maxOutputTokens` must be greater than `maxReasoningTokens`");
            }
        }
    }
    if (params.temperature &&
        (params.temperature < 0 || params.temperature > 2)) {
        throw new Error("`temperature` must be in the range of [0.0,2.0]");
    }
    if (params.topP && (params.topP < 0 || params.topP > 1)) {
        throw new Error("`topP` must be in the range of [0.0,1.0]");
    }
    if (params.topK && params.topK < 0) {
        throw new Error("`topK` must be a positive integer");
    }
}
function isModelGemini(modelName) {
    return modelName.toLowerCase().startsWith("gemini");
}
function isModelGemma(modelName) {
    return modelName.toLowerCase().startsWith("gemma");
}
