"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.shouldUseMimoReasoningContentCompat = shouldUseMimoReasoningContentCompat;
exports.normalizeMimoChatRequestPayload = normalizeMimoChatRequestPayload;
exports.normalizeMimoChatCompletionPayload = normalizeMimoChatCompletionPayload;
exports.normalizeMimoSseText = normalizeMimoSseText;
exports.createMimoReasoningContentFetch = createMimoReasoningContentFetch;
const util_1 = require("util");
const CHAT_COMPLETIONS_PATH = /\/chat\/completions(?:[?#]|$)/;
const MIMO_BASE_URL_PATTERN = /xiaomimimo\.com/i;
const MIMO_MODEL_PATTERN = /\bmimo-v/i;
function isRecord(value) {
    return !!value && typeof value === 'object' && !Array.isArray(value);
}
function hasNonEmptyString(value) {
    return typeof value === 'string' && value.length > 0;
}
function hasToolCalls(message) {
    return Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
}
function hasContent(message) {
    const content = message.content;
    return Array.isArray(content)
        ? content.length > 0
        : content !== undefined && content !== null && content !== '';
}
function mergePreviousAssistantMessage(previous, message) {
    let merged = false;
    if (!hasNonEmptyString(message.reasoning_content) && hasNonEmptyString(previous.reasoning_content)) {
        message.reasoning_content = previous.reasoning_content;
        merged = true;
    }
    if (!hasContent(message) && hasContent(previous)) {
        message.content = previous.content;
        merged = true;
    }
    else if (typeof previous.content === 'string' && typeof message.content === 'string') {
        message.content = `${previous.content}\n${message.content}`;
        merged = true;
    }
    return merged || !hasContent(previous);
}
function summarizeContent(content) {
    if (typeof content === 'string')
        return { contentType: 'string', contentLength: content.length };
    if (Array.isArray(content))
        return { contentType: 'array', contentParts: content.length };
    if (content === null)
        return { contentType: 'null' };
    return { contentType: typeof content };
}
function summarizeMimoChatRequestPayload(payload) {
    if (!isRecord(payload))
        return undefined;
    return {
        model: payload.model,
        stream: payload.stream,
        toolCount: Array.isArray(payload.tools) ? payload.tools.length : 0,
        messages: Array.isArray(payload.messages)
            ? payload.messages.map((message, index) => {
                if (!isRecord(message))
                    return { index, type: typeof message };
                return {
                    index,
                    role: message.role,
                    ...summarizeContent(message.content),
                    toolCallCount: Array.isArray(message.tool_calls) ? message.tool_calls.length : 0,
                    hasReasoning: hasNonEmptyString(message.reasoning),
                    reasoningLength: hasNonEmptyString(message.reasoning) ? message.reasoning.length : 0,
                    hasReasoningContent: hasNonEmptyString(message.reasoning_content),
                    reasoningContentLength: hasNonEmptyString(message.reasoning_content)
                        ? message.reasoning_content.length
                        : 0,
                    toolCallId: typeof message.tool_call_id === 'string' ? message.tool_call_id : undefined,
                };
            })
            : undefined,
    };
}
function getRequestUrl(input) {
    if (typeof input === 'string')
        return input;
    if (input instanceof URL)
        return input.toString();
    const maybeUrl = input?.url;
    return typeof maybeUrl === 'string' ? maybeUrl : '';
}
function headersWithoutContentLength(headers) {
    if (!headers)
        return headers;
    const HeadersCtor = globalThis.Headers;
    if (!HeadersCtor)
        return headers;
    const copied = new HeadersCtor(headers);
    copied.delete('content-length');
    return copied;
}
function isRequestInput(input) {
    const RequestCtor = globalThis.Request;
    return !!RequestCtor && input instanceof RequestCtor;
}
function shouldUseMimoReasoningContentCompat(config) {
    if (config.protocol !== 'chat_completions')
        return false;
    const baseURL = config.baseURL || '';
    const models = `${config.model || ''} ${config.lightModel || ''}`;
    return MIMO_BASE_URL_PATTERN.test(baseURL) || MIMO_MODEL_PATTERN.test(models);
}
function normalizeMimoChatRequestPayload(payload) {
    if (!isRecord(payload) || !Array.isArray(payload.messages))
        return false;
    let changed = false;
    const normalizedMessages = [];
    for (const message of payload.messages) {
        if (!isRecord(message) || message.role !== 'assistant') {
            normalizedMessages.push(message);
            continue;
        }
        const reasoning = hasNonEmptyString(message.reasoning)
            ? message.reasoning
            : hasNonEmptyString(message.reasoning_content)
                ? message.reasoning_content
                : undefined;
        if (message.reasoning_content !== reasoning) {
            if (reasoning) {
                message.reasoning_content = reasoning;
                changed = true;
            }
        }
        if ('reasoning' in message) {
            delete message.reasoning;
            changed = true;
        }
        const previous = normalizedMessages[normalizedMessages.length - 1];
        if (isRecord(previous) && previous.role === 'assistant' && !hasToolCalls(previous)) {
            if (mergePreviousAssistantMessage(previous, message)) {
                normalizedMessages.pop();
                changed = true;
            }
        }
        normalizedMessages.push(message);
    }
    if (normalizedMessages.length !== payload.messages.length) {
        payload.messages = normalizedMessages;
        changed = true;
    }
    return changed;
}
function normalizeMimoChatCompletionPayload(payload) {
    if (!isRecord(payload) || !Array.isArray(payload.choices))
        return false;
    let changed = false;
    for (const choice of payload.choices) {
        if (!isRecord(choice))
            continue;
        for (const key of ['delta', 'message']) {
            const message = choice[key];
            if (!isRecord(message))
                continue;
            if (hasNonEmptyString(message.reasoning_content) && message.reasoning !== message.reasoning_content) {
                message.reasoning = message.reasoning_content;
                changed = true;
            }
        }
    }
    return changed;
}
function decodeRequestBody(body) {
    if (typeof body === 'string')
        return body;
    if (body instanceof Uint8Array)
        return new util_1.TextDecoder().decode(body);
    if (body instanceof ArrayBuffer)
        return new util_1.TextDecoder().decode(new Uint8Array(body));
    if (ArrayBuffer.isView(body)) {
        return new util_1.TextDecoder().decode(new Uint8Array(body.buffer, body.byteOffset, body.byteLength));
    }
    return undefined;
}
function normalizeRequestBody(body) {
    const text = decodeRequestBody(body);
    if (text === undefined)
        return { body, changed: false };
    const trimmed = text.trim();
    if (!trimmed.startsWith('{'))
        return { body, changed: false };
    try {
        const payload = JSON.parse(text);
        const changed = normalizeMimoChatRequestPayload(payload);
        const summary = summarizeMimoChatRequestPayload(payload);
        return changed
            ? { body: JSON.stringify(payload), changed, summary }
            : { body, changed: false, summary };
    }
    catch {
        return { body, changed: false };
    }
}
async function normalizeRequestInputBody(input) {
    if (input.bodyUsed)
        return { input, changed: false };
    try {
        const body = await input.clone().text();
        const normalized = normalizeRequestBody(body);
        if (!normalized.changed)
            return { input, changed: false, summary: normalized.summary };
        const RequestCtor = globalThis.Request;
        return {
            input: new RequestCtor(input, {
                body: normalized.body,
                headers: headersWithoutContentLength(input.headers),
            }),
            changed: true,
            summary: normalized.summary,
        };
    }
    catch {
        return { input, changed: false };
    }
}
async function normalizeFetchRequest(input, init) {
    const isChatCompletions = CHAT_COMPLETIONS_PATH.test(getRequestUrl(input));
    if (!isChatCompletions)
        return { input, init, isChatCompletions };
    const normalized = normalizeRequestBody(init?.body);
    if (!normalized.changed) {
        if (isRequestInput(input) && !init?.body) {
            const normalizedInput = await normalizeRequestInputBody(input);
            if (normalizedInput.changed) {
                return {
                    input: normalizedInput.input,
                    init: init
                        ? {
                            ...init,
                            headers: headersWithoutContentLength(init.headers),
                        }
                        : init,
                    isChatCompletions,
                    summary: normalizedInput.summary,
                };
            }
        }
        return { input, init, isChatCompletions, summary: normalized.summary };
    }
    return {
        input,
        init: {
            ...init,
            body: normalized.body,
            headers: headersWithoutContentLength(init?.headers),
        },
        isChatCompletions,
        summary: normalized.summary,
    };
}
function transformSseLine(line) {
    if (!line.startsWith('data:'))
        return line;
    const prefixMatch = line.match(/^data:\s*/);
    const prefix = prefixMatch?.[0] ?? 'data: ';
    const data = line.slice(prefix.length);
    if (!data || data.trim() === '[DONE]')
        return line;
    try {
        const payload = JSON.parse(data);
        return normalizeMimoChatCompletionPayload(payload)
            ? `${prefix}${JSON.stringify(payload)}`
            : line;
    }
    catch {
        return line;
    }
}
function normalizeMimoSseText(text) {
    return text
        .split('\n')
        .map((line) => transformSseLine(line))
        .join('\n');
}
function createNormalizedSseBody(body) {
    const TransformStreamCtor = globalThis.TransformStream;
    if (!body || !TransformStreamCtor)
        return undefined;
    const decoder = new util_1.TextDecoder();
    const encoder = new util_1.TextEncoder();
    let pending = '';
    return body.pipeThrough(new TransformStreamCtor({
        transform(chunk, controller) {
            pending += decoder.decode(chunk, { stream: true });
            const lines = pending.split('\n');
            pending = lines.pop() ?? '';
            if (lines.length > 0) {
                controller.enqueue(encoder.encode(`${normalizeMimoSseText(lines.join('\n'))}\n`));
            }
        },
        flush(controller) {
            pending += decoder.decode();
            if (pending) {
                controller.enqueue(encoder.encode(normalizeMimoSseText(pending)));
            }
        },
    }));
}
async function normalizeFetchResponse(response) {
    const contentType = response.headers.get('content-type') || '';
    const ResponseCtor = globalThis.Response;
    if (!ResponseCtor)
        return response;
    const headers = headersWithoutContentLength(response.headers);
    if (contentType.includes('text/event-stream')) {
        const body = createNormalizedSseBody(response.body);
        return body
            ? new ResponseCtor(body, {
                status: response.status,
                statusText: response.statusText,
                headers,
            })
            : response;
    }
    if (!contentType.includes('application/json'))
        return response;
    const text = await response.text();
    try {
        const payload = JSON.parse(text);
        const changed = normalizeMimoChatCompletionPayload(payload);
        return new ResponseCtor(changed ? JSON.stringify(payload) : text, {
            status: response.status,
            statusText: response.statusText,
            headers,
        });
    }
    catch {
        return new ResponseCtor(text, {
            status: response.status,
            statusText: response.statusText,
            headers,
        });
    }
}
function createMimoReasoningContentFetch(baseFetch = fetch) {
    return (async (input, init) => {
        const normalizedRequest = await normalizeFetchRequest(input, init);
        const response = await baseFetch(normalizedRequest.input, normalizedRequest.init);
        if (normalizedRequest.isChatCompletions && response.status >= 400) {
            let responsePreview;
            try {
                responsePreview = (await response.clone().text()).slice(0, 500);
            }
            catch {
                responsePreview = undefined;
            }
            console.warn('[MiMoCompat] Chat completions request failed', {
                status: response.status,
                statusText: response.statusText,
                request: normalizedRequest.summary,
                responsePreview,
            });
        }
        return normalizedRequest.isChatCompletions
            ? normalizeFetchResponse(response)
            : response;
    });
}
//# sourceMappingURL=mimoReasoningCompat.js.map