"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
    for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuildInfo = exports.getBackendDevicesInfo = exports.loadLlamaModelInfo = exports.initLlama = exports.loadModel = exports.toggleNativeLog = exports.LlamaParallelAPI = void 0;
exports.addNativeLogListener = addNativeLogListener;
const binding_1 = require("./binding");
const version_1 = require("./version");
const parallel_1 = require("./parallel");
Object.defineProperty(exports, "LlamaParallelAPI", { enumerable: true, get: function () { return parallel_1.LlamaParallelAPI; } });
const utils_1 = require("./utils");
__exportStar(require("./binding"), exports);
const mods = {};
const logListeners = [];
const logCallback = (level, text) => {
    logListeners.forEach((listener) => listener(level, text));
};
let logEnabled = false;
const refreshNativeLogSetup = () => {
    Object.entries(mods).forEach(([, mod]) => {
        mod.LlamaContext.toggleNativeLog(logEnabled, logCallback);
    });
};
const toggleNativeLog = (enable) => __awaiter(void 0, void 0, void 0, function* () {
    logEnabled = enable;
    refreshNativeLogSetup();
});
exports.toggleNativeLog = toggleNativeLog;
function addNativeLogListener(listener) {
    logListeners.push(listener);
    return {
        remove: () => {
            logListeners.splice(logListeners.indexOf(listener), 1);
        },
    };
}
const getJsonSchema = (responseFormat) => {
    var _a;
    if ((responseFormat === null || responseFormat === void 0 ? void 0 : responseFormat.type) === 'json_schema') {
        return (_a = responseFormat.json_schema) === null || _a === void 0 ? void 0 : _a.schema;
    }
    if ((responseFormat === null || responseFormat === void 0 ? void 0 : responseFormat.type) === 'json_object') {
        return responseFormat.schema || {};
    }
    return null;
};
class LlamaContextWrapper {
    constructor(nativeCtx) {
        this.ctx = nativeCtx;
        this.parallel = new parallel_1.LlamaParallelAPI(nativeCtx);
    }
    getSystemInfo() {
        return this.ctx.getSystemInfo();
    }
    getModelInfo() {
        return this.ctx.getModelInfo();
    }
    getUsedDevices() {
        return this.ctx.getUsedDevices();
    }
    isJinjaSupported() {
        const { jinja } = this.ctx.getModelInfo().chatTemplates;
        return !!(jinja === null || jinja === void 0 ? void 0 : jinja.toolUse) || !!(jinja === null || jinja === void 0 ? void 0 : jinja.default);
    }
    isLlamaChatSupported() {
        return !!this.ctx.getModelInfo().chatTemplates.llamaChat;
    }
    getFormattedChat(messages, template, params) {
        var _a, _b, _c;
        const { messages: chat, has_media, media_paths } = (0, utils_1.formatMediaChat)(messages);
        const useJinja = this.isJinjaSupported() && ((_a = params === null || params === void 0 ? void 0 : params.jinja) !== null && _a !== void 0 ? _a : true);
        let tmpl;
        if (template)
            tmpl = template; // Force replace if provided
        const result = this.ctx.getFormattedChat(chat, tmpl, {
            jinja: useJinja,
            response_format: params === null || params === void 0 ? void 0 : params.response_format,
            tools: params === null || params === void 0 ? void 0 : params.tools,
            parallel_tool_calls: params === null || params === void 0 ? void 0 : params.parallel_tool_calls,
            tool_choice: params === null || params === void 0 ? void 0 : params.tool_choice,
            enable_thinking: (_b = params === null || params === void 0 ? void 0 : params.enable_thinking) !== null && _b !== void 0 ? _b : true,
            reasoning_format: (_c = params === null || params === void 0 ? void 0 : params.reasoning_format) !== null && _c !== void 0 ? _c : 'none',
            add_generation_prompt: params === null || params === void 0 ? void 0 : params.add_generation_prompt,
            now: params === null || params === void 0 ? void 0 : params.now,
            chat_template_kwargs: (params === null || params === void 0 ? void 0 : params.chat_template_kwargs)
                ? Object.entries(params.chat_template_kwargs).reduce((acc, [key, value]) => {
                    acc[key] = JSON.stringify(value); // Each value is a stringified JSON object
                    return acc;
                }, {})
                : undefined,
        });
        if (!useJinja) {
            return {
                type: 'llama-chat',
                prompt: result,
                has_media,
                media_paths,
            };
        }
        const jinjaResult = result;
        return Object.assign({ type: 'jinja', has_media,
            media_paths }, jinjaResult);
    }
    completion(options, callback) {
        const { messages, media_paths = options.media_paths } = (0, utils_1.formatMediaChat)(options.messages);
        return this.ctx.completion(Object.assign(Object.assign({}, options), { messages, media_paths: options.media_paths || media_paths }), callback || (() => { }));
    }
    stopCompletion() {
        return this.ctx.stopCompletion();
    }
    tokenize(text, { media_paths } = {}) {
        return this.ctx.tokenize(text, media_paths);
    }
    detokenize(tokens) {
        return this.ctx.detokenize(tokens);
    }
    embedding(text, params) {
        return this.ctx.embedding(text, params);
    }
    rerank(query, documents, params) {
        return this.ctx
            .rerank(query, documents, params)
            .then((results) => {
            // Sort by score descending and add document text for convenience
            return results
                .map((result) => (Object.assign(Object.assign({}, result), { document: documents[result.index] })))
                .sort((a, b) => b.score - a.score);
        });
    }
    saveSession(path) {
        return this.ctx.saveSession(path);
    }
    loadSession(path) {
        return this.ctx.loadSession(path);
    }
    release() {
        return this.ctx.release();
    }
    applyLoraAdapters(adapters) {
        return this.ctx.applyLoraAdapters(adapters);
    }
    removeLoraAdapters() {
        this.ctx.removeLoraAdapters();
    }
    getLoadedLoraAdapters() {
        return this.ctx.getLoadedLoraAdapters();
    }
    initMultimodal(options) {
        return this.ctx.initMultimodal(options);
    }
    isMultimodalEnabled() {
        return this.ctx.isMultimodalEnabled();
    }
    releaseMultimodal() {
        this.ctx.releaseMultimodal();
    }
    getMultimodalSupport() {
        return this.ctx.getMultimodalSupport();
    }
    initVocoder(options) {
        return this.ctx.initVocoder(options);
    }
    releaseVocoder() {
        this.ctx.releaseVocoder();
    }
    isVocoderEnabled() {
        return this.ctx.isVocoderEnabled();
    }
    getFormattedAudioCompletion(speaker, text) {
        return this.ctx.getFormattedAudioCompletion(speaker, text);
    }
    getAudioCompletionGuideTokens(text) {
        return this.ctx.getAudioCompletionGuideTokens(text);
    }
    decodeAudioTokens(tokens) {
        return this.ctx.decodeAudioTokens(tokens);
    }
    /**
     * Clear the KV and recurrent caches.
     * This is faster than recreating the context and useful for preventing
     * cache contamination between chat sessions.
     * @param clearData If true, also clears the cache data (default: false)
     */
    clearCache(clearData) {
        this.ctx.clearCache(clearData);
    }
    /**
     * Run a benchmark to measure model performance
     * @param pp Number of tokens to process for prompt processing benchmark
     * @param tg Number of tokens to generate for text generation benchmark
     * @param pl Parallel level (number of sequences)
     * @param nr Number of repetitions
     * @returns Benchmark results including timing and speed metrics
     */
    bench(pp, tg, pl, nr) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.ctx.bench(pp, tg, pl, nr);
        });
    }
}
const loadModel = (options, onProgress) => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b;
    const variant = (_a = options.lib_variant) !== null && _a !== void 0 ? _a : 'default';
    (_b = mods[variant]) !== null && _b !== void 0 ? _b : (mods[variant] = yield (0, binding_1.loadModule)(options.lib_variant));
    refreshNativeLogSetup();
    const { devices } = options;
    let filteredDevs = [];
    if (Array.isArray(devices)) {
        filteredDevs = [...devices];
        // Handle HTP* to use all HTP devices on Hexagon
        if (variant === 'snapdragon' && devices.includes('HTP*')) {
            const backendDevices = yield (0, exports.getBackendDevicesInfo)(variant);
            const htpDevices = backendDevices
                .filter((d) => d.deviceName.startsWith('HTP'))
                .map((d) => d.deviceName);
            filteredDevs = filteredDevs.reduce((acc, dev) => {
                if (dev.startsWith('HTP*')) {
                    acc.push(...htpDevices);
                }
                else if (!dev.startsWith('HTP')) {
                    acc.push(dev);
                }
                return acc;
            }, []);
        }
    }
    const nativeCtx = new mods[variant].LlamaContext(Object.assign(Object.assign({}, options), { devices: filteredDevs.length > 0 ? filteredDevs : undefined }), onProgress);
    return new LlamaContextWrapper(nativeCtx);
});
exports.loadModel = loadModel;
exports.initLlama = binding_1.loadModule;
const modelInfoSkip = [
    // Large fields
    'tokenizer.ggml.tokens',
    'tokenizer.ggml.token_type',
    'tokenizer.ggml.merges',
    'tokenizer.ggml.scores',
];
const loadLlamaModelInfo = (path) => __awaiter(void 0, void 0, void 0, function* () {
    var _a;
    const variant = 'default';
    (_a = mods[variant]) !== null && _a !== void 0 ? _a : (mods[variant] = yield (0, binding_1.loadModule)(variant));
    refreshNativeLogSetup();
    return mods[variant].LlamaContext.loadModelInfo(path, modelInfoSkip);
});
exports.loadLlamaModelInfo = loadLlamaModelInfo;
const getBackendDevicesInfo = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (variant = 'default') {
    var _a;
    (_a = mods[variant]) !== null && _a !== void 0 ? _a : (mods[variant] = yield (0, binding_1.loadModule)(variant));
    refreshNativeLogSetup();
    const jsonString = mods[variant].LlamaContext.getBackendDevicesInfo();
    return JSON.parse(jsonString);
});
exports.getBackendDevicesInfo = getBackendDevicesInfo;
exports.BuildInfo = {
    number: version_1.BUILD_NUMBER,
    commit: version_1.BUILD_COMMIT,
};
