// 删掉：getRequestHeaders, extractMessageFromData, getStreamingReply, tryParseStreamingError, getEventSourceStream

import { eventSource, event_types, chat, name1, activateSendButtons, deactivateSendButtons, substituteParams } from "../../../../../script.js";
import { chat_completion_sources, oai_settings, promptManager, getChatCompletionModel } from "../../../../openai.js";
import { ChatCompletionService } from "../../../../custom-request.js";
import { getContext } from "../../../../st-context.js";
import { SlashCommandParser } from "../../../../slash-commands/SlashCommandParser.js";
import { SlashCommand } from "../../../../slash-commands/SlashCommand.js";
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from "../../../../slash-commands/SlashCommandArgument.js";
import { SECRET_KEYS, writeSecret } from "../../../../secrets.js";
import { power_user } from "../../../../power-user.js";
import { world_info } from "../../../../world-info.js";
import { xbLog, CacheRegistry } from "../core/debug-core.js";
import { getTrustedOrigin } from "../core/iframe-messaging.js";
import { replaceXbGetVarInString, replaceXbGetVarYamlInString } from "./variables/var-commands.js";

const EVT_DONE = 'xiaobaix_streaming_completed';

const PROXY_SUPPORTED = new Set([
    chat_completion_sources.OPENAI, chat_completion_sources.CLAUDE,
    chat_completion_sources.MAKERSUITE, chat_completion_sources.COHERE,
    chat_completion_sources.DEEPSEEK,
]);

class StreamingGeneration {
    constructor() {
        this.tempreply = '';
        this.isInitialized = false;
        this.isStreaming = false;
        this.sessions = new Map();
        this.lastSessionId = null;
        this.activeCount = 0;
        this._toggleBusy = false;
        this._toggleQueue = Promise.resolve();
        this.MAX_SESSIONS = 100;
    }

    init() {
        if (this.isInitialized) return;
        try { localStorage.removeItem('xbgen:lastToggleSnap'); } catch {}
        this.registerCommands();
        try { xbLog.info('streamingGeneration', 'init'); } catch {}
        this.isInitialized = true;
    }

    _getSlotId(id) {
        if (!id) return 1;
        const s = String(id).trim();
        const m = s.match(/^xb(\d+)$/i);
        if (m) {
            const n = +m[1];
            if (n >= 1 && n <= 100) return `xb${n}`;
        }
        const n = parseInt(s, 10);
        if (!isNaN(n) && n >= 1 && n <= 100) return n;
        if (s.length > 0 && s.length <= 50) return s;
        return 1;
    }

    _ensureSession(id, prompt) {
        const slotId = this._getSlotId(id);
        if (!this.sessions.has(slotId)) {
            if (this.sessions.size >= this.MAX_SESSIONS) this._cleanupOldestSessions();
            this.sessions.set(slotId, {
                id: slotId, text: '', isStreaming: false, prompt: prompt || '',
                updatedAt: Date.now(), abortController: null
            });
        }
        this.lastSessionId = slotId;
        return this.sessions.get(slotId);
    }

    _cleanupOldestSessions() {
        const keepCount = Math.max(10, this.MAX_SESSIONS - 10);
        const sorted = [...this.sessions.entries()].sort((a, b) => a[1].updatedAt - b[1].updatedAt);
        sorted.slice(0, Math.max(0, sorted.length - keepCount)).forEach(([sid, s]) => {
            try { s.abortController?.abort(); } catch {}
            this.sessions.delete(sid);
        });
    }

    updateTempReply(value, sessionId) {
        const text = String(value || '');
        if (sessionId !== undefined) {
            const sid = this._getSlotId(sessionId);
            const s = this.sessions.get(sid) || {
                id: sid, text: '', isStreaming: false, prompt: '',
                updatedAt: 0, abortController: null
            };
            s.text = text;
            s.updatedAt = Date.now();
            this.sessions.set(sid, s);
            this.lastSessionId = sid;
        }
        this.tempreply = text;
    }

    postToFrames(name, payload) {
        try {
            const frames = window?.frames;
            if (frames?.length) {
                const msg = { type: name, payload, from: 'xiaobaix' };
                const targetOrigin = getTrustedOrigin();
                let fail = 0;
                for (let i = 0; i < frames.length; i++) {
                    try { frames[i].postMessage(msg, targetOrigin); } catch { fail++; }
                }
                if (fail) {
                    try { xbLog.warn('streamingGeneration', `postToFrames fail=${fail} total=${frames.length} type=${name}`); } catch {}
                }
            }
        } catch {}
    }

    resolveCurrentApiAndModel(apiOptions = {}) {
        if (apiOptions.api && apiOptions.model) return apiOptions;
        const source = oai_settings?.chat_completion_source;
        const model = getChatCompletionModel();
        const map = {
            [chat_completion_sources.OPENAI]: 'openai',
            [chat_completion_sources.CLAUDE]: 'claude',
            [chat_completion_sources.MAKERSUITE]: 'gemini',
            [chat_completion_sources.COHERE]: 'cohere',
            [chat_completion_sources.DEEPSEEK]: 'deepseek',
            [chat_completion_sources.CUSTOM]: 'custom',
        };
        const api = map[source] || 'openai';
        return { api, model };
    }


        async callAPI(generateData, abortSignal, stream = true) {
            const messages = Array.isArray(generateData) ? generateData :
                (generateData?.prompt || generateData?.messages || generateData);   
            const baseOptions = (!Array.isArray(generateData) && generateData?.apiOptions) ? generateData.apiOptions : {};
            const opts = { ...baseOptions, ...this.resolveCurrentApiAndModel(baseOptions) };

            const modelLower = String(opts.model || '').toLowerCase();
            const isClaudeThinkingModel = 
                modelLower.includes('claude') && 
                modelLower.includes('thinking') && 
                !modelLower.includes('nothinking');

            if (isClaudeThinkingModel && Array.isArray(messages) && messages.length > 0) {
                const lastMsg = messages[messages.length - 1];
                if (lastMsg?.role === 'assistant') {
                    console.log('[xbgen] Claude Thinking 模型：移除 assistant prefill');
                    messages.pop();
                }
            } 

            const source = {
                openai: chat_completion_sources.OPENAI,
                claude: chat_completion_sources.CLAUDE,
                gemini: chat_completion_sources.MAKERSUITE,
                google: chat_completion_sources.MAKERSUITE,
                cohere: chat_completion_sources.COHERE,
                deepseek: chat_completion_sources.DEEPSEEK,
                custom: chat_completion_sources.CUSTOM,
            }[String(opts.api || '').toLowerCase()];            
            
            if (!source) {
                console.error('[xbgen:callAPI] 不支持的 api:', opts.api);
                try { xbLog.error('streamingGeneration', `unsupported api: ${opts.api}`, null); } catch {}
            }
            if (!source) throw new Error(`不支持的 api: ${opts.api}`);
            
            const model = String(opts.model || '').trim();
            const msgCount = Array.isArray(messages) ? messages.length : null;
            
            if (!model) {
                try { xbLog.error('streamingGeneration', 'missing model', null); } catch {}
            }
            if (!model) throw new Error('未检测到当前模型，请在聊天面板选择模型或在插件设置中为分析显式指定模型。');
            
            try {
                try {
                    if (xbLog.isEnabled?.()) {
                        xbLog.info('streamingGeneration', `callAPI stream=${!!stream} api=${String(opts.api || '')} model=${model} messages=${msgCount ?? '-'}`);
                    }
                } catch {}
                const provider = String(opts.api || '').toLowerCase();
                const reverseProxyConfigured = String(opts.apiurl || '').trim().length > 0;
                const pwd = String(opts.apipassword || '').trim();
                if (!reverseProxyConfigured && pwd) {
                    const providerToSecretKey = {
                        openai: SECRET_KEYS.OPENAI,
                        gemini: SECRET_KEYS.MAKERSUITE,
                        google: SECRET_KEYS.MAKERSUITE,
                        cohere: SECRET_KEYS.COHERE,
                        deepseek: SECRET_KEYS.DEEPSEEK,
                        custom: SECRET_KEYS.CUSTOM,
                    };
                    const secretKey = providerToSecretKey[provider];
                    if (secretKey) {
                        await writeSecret(secretKey, pwd, 'xbgen-inline');
                    }
                }
            } catch {}
            
            const num = (v) => {
                const n = Number(v);
                return Number.isFinite(n) ? n : undefined;
            };
            const isUnset = (k) => baseOptions?.[k] === '__unset__';
            const tUser = num(baseOptions?.temperature);
            const ppUser = num(baseOptions?.presence_penalty);
            const fpUser = num(baseOptions?.frequency_penalty);
            const tpUser = num(baseOptions?.top_p);
            const tkUser = num(baseOptions?.top_k);
            const mtUser = num(baseOptions?.max_tokens);
            const tUI = num(oai_settings?.temp_openai);
            const ppUI = num(oai_settings?.pres_pen_openai);
            const fpUI = num(oai_settings?.freq_pen_openai);
            const tpUI_OpenAI = num(oai_settings?.top_p_openai ?? oai_settings?.top_p);
            const mtUI_OpenAI = num(oai_settings?.openai_max_tokens ?? oai_settings?.max_tokens);
            const tpUI_Gemini = num(oai_settings?.makersuite_top_p ?? oai_settings?.top_p);
            const tkUI_Gemini = num(oai_settings?.makersuite_top_k ?? oai_settings?.top_k);
            const mtUI_Gemini = num(oai_settings?.makersuite_max_tokens ?? oai_settings?.max_output_tokens ?? oai_settings?.openai_max_tokens ?? oai_settings?.max_tokens);
            const effectiveTemperature = isUnset('temperature') ? undefined : (tUser ?? tUI);
            const effectivePresence = isUnset('presence_penalty') ? undefined : (ppUser ?? ppUI);
            const effectiveFrequency = isUnset('frequency_penalty') ? undefined : (fpUser ?? fpUI);
            const effectiveTopP = isUnset('top_p') ? undefined : (tpUser ?? (source === chat_completion_sources.MAKERSUITE ? tpUI_Gemini : tpUI_OpenAI));
            const effectiveTopK = isUnset('top_k') ? undefined : (tkUser ?? (source === chat_completion_sources.MAKERSUITE ? tkUI_Gemini : undefined));
            const effectiveMaxT = isUnset('max_tokens') ? undefined : (mtUser ?? (source === chat_completion_sources.MAKERSUITE ? (mtUI_Gemini ?? mtUI_OpenAI) : mtUI_OpenAI) ?? 4000);            
            
            const body = {
                messages, model, stream,
                chat_completion_source: source,
                temperature: effectiveTemperature,
                presence_penalty: effectivePresence,
                frequency_penalty: effectiveFrequency,
                top_p: effectiveTopP,
                max_tokens: effectiveMaxT,
                stop: Array.isArray(generateData?.stop) ? generateData.stop : undefined,
                use_makersuite_sysprompt: false,
                claude_use_sysprompt: oai_settings?.claude_use_sysprompt ?? false,
                custom_prompt_post_processing: undefined,
                // thinking 模型支持
                include_reasoning: oai_settings?.show_thoughts ?? true,
                reasoning_effort: oai_settings?.reasoning_effort || 'medium',
            };
            if (baseOptions?.enable_thinking !== undefined) body.enable_thinking = baseOptions.enable_thinking;
            if (baseOptions?.thinking_budget !== undefined) body.thinking_budget = baseOptions.thinking_budget;
            if (baseOptions?.min_p !== undefined) body.min_p = baseOptions.min_p;

            // Claude 专用：top_k
            if (source === chat_completion_sources.CLAUDE) {
                body.top_k = Number(oai_settings?.top_k_openai) || undefined;
            }
            
            if (source === chat_completion_sources.MAKERSUITE) {
                if (effectiveTopK !== undefined) body.top_k = effectiveTopK;
                body.max_output_tokens = effectiveMaxT;
            }
            const useNet = !!opts.enableNet;
            if (source === chat_completion_sources.MAKERSUITE && useNet) {
                body.tools = Array.isArray(body.tools) ? body.tools : [];
                if (!body.tools.some(t => t && t.google_search_retrieval)) {
                    body.tools.push({ google_search_retrieval: {} });
                }
                body.enable_web_search = true;
                body.makersuite_use_google_search = true;
            }
            let reverseProxy = String(opts.apiurl || oai_settings?.reverse_proxy || '').trim();
            let proxyPassword = String(oai_settings?.proxy_password || '').trim();
            const cmdApiUrl = String(opts.apiurl || '').trim();
            const cmdApiPwd = String(opts.apipassword || '').trim();
            if (cmdApiUrl) {
                if (cmdApiPwd) proxyPassword = cmdApiPwd;
            } else if (cmdApiPwd) {
                reverseProxy = '';
                proxyPassword = '';
            }
            if (PROXY_SUPPORTED.has(source) && reverseProxy) {
                body.reverse_proxy = reverseProxy.replace(/\/?$/, '');
                if (proxyPassword) body.proxy_password = proxyPassword;
            }
            if (source === chat_completion_sources.CUSTOM) {
                const customUrl = String(cmdApiUrl || oai_settings?.custom_url || '').trim();
                if (customUrl) {
                    body.custom_url = customUrl;
                } else {
                    throw new Error('未配置自定义后端URL，请在命令中提供 apiurl 或在设置中填写 custom_url');
                }
                if (oai_settings?.custom_include_headers) body.custom_include_headers = oai_settings.custom_include_headers;
                if (oai_settings?.custom_include_body) body.custom_include_body = oai_settings.custom_include_body;
                if (oai_settings?.custom_exclude_body) body.custom_exclude_body = oai_settings.custom_exclude_body;
            }
            
            
            const logSendRequestError = (err, streamMode) => {
                if (err?.name !== 'AbortError') {
                    const safeApiUrl = String(cmdApiUrl || reverseProxy || oai_settings?.custom_url || '').trim();
                    try {
                        xbLog.error('streamingGeneration', 'sendRequest failed', {
                            message: err?.message || String(err),
                            name: err?.name,
                            stream: !!streamMode,
                            api: String(opts.api || ''),
                            model,
                            msgCount,
                            apiurl: safeApiUrl,
                        });
                    } catch {}
                    console.error('[xbgen:callAPI] sendRequest failed:', err);
                }
            };

            if (stream) {
                const payload = ChatCompletionService.createRequestData(body);
                
                let streamFactory;
                try {
                    streamFactory = await ChatCompletionService.sendRequest(payload, false, abortSignal);
                } catch (err) {
                    logSendRequestError(err, true);
                    throw err;
                }
                
                const generator = (typeof streamFactory === 'function') ? streamFactory() : streamFactory;

                return (async function* () {
                    let last = '';
                    try {
                        for await (const item of (generator || [])) {
                            if (abortSignal?.aborted) {
                                return;
                            }

                            let accumulated = '';
                            if (typeof item === 'string') {
                                accumulated = item;
                            } else if (item && typeof item === 'object') {
                                // 尝试多种字段
                                accumulated = (typeof item.text === 'string' ? item.text : '') ||
                                              (typeof item.content === 'string' ? item.content : '') || '';
                                
                                // thinking 相关字段
                                if (!accumulated) {
                                    const thinking = item?.delta?.thinking || item?.thinking;
                                    if (typeof thinking === 'string') {
                                        accumulated = thinking;
                                    }
                                }
                                if (!accumulated) {
                                    const rc = item?.reasoning_content || item?.reasoning;
                                    if (typeof rc === 'string') {
                                        accumulated = rc;
                                    }
                                }
                                if (!accumulated) {
                                    const rc = item?.choices?.[0]?.delta?.reasoning_content;
                                    if (typeof rc === 'string') accumulated = rc;
                                }
                            }
                            
                            if (!accumulated) {
                                continue;
                            }

                            if (accumulated.startsWith(last)) {
                                last = accumulated;
                            } else {
                                last += accumulated;
                            }
                            yield last;
                        }
                    } catch (err) {
                        console.error('[xbgen:stream] 流式错误:', err);
                        console.error('[xbgen:stream] err.name:', err?.name);
                        console.error('[xbgen:stream] err.message:', err?.message);
                        if (err?.name === 'AbortError') return;
                        try { xbLog.error('streamingGeneration', 'Stream error', err); } catch {}
                        throw err;
                    }
                })();
            } else {
                const payload = ChatCompletionService.createRequestData(body);
                let extracted;
                try {
                    extracted = await ChatCompletionService.sendRequest(payload, false, abortSignal);
                } catch (err) {
                    logSendRequestError(err, false);
                    throw err;
                }

                let result = '';
                if (extracted && typeof extracted === 'object') {
                    const msg = extracted?.choices?.[0]?.message;
                    result = String(
                        msg?.content ??
                        msg?.reasoning_content ??
                        extracted?.choices?.[0]?.text ??
                        extracted?.content ??
                        extracted?.reasoning_content ??
                        ''
                    );
                } else {
                    result = String(extracted ?? '');
                }

                return result;
            }
        }


    async _emitPromptReady(chatArray) {
        try {
            if (Array.isArray(chatArray)) {
                const snapshot = this._cloneChat(chatArray);
                await eventSource?.emit?.(event_types.CHAT_COMPLETION_PROMPT_READY, { chat: snapshot, dryRun: false });
            }
        } catch {}
    }

    _cloneChat(chatArray) {
        try {
            if (typeof structuredClone === 'function') return structuredClone(chatArray);
        } catch {}
        try {
            return JSON.parse(JSON.stringify(chatArray));
        } catch {}
        try {
            return Array.isArray(chatArray)
                ? chatArray.map(m => (m && typeof m === 'object' ? { ...m } : m))
                : chatArray;
        } catch {
            return chatArray;
        }
    }

    async processGeneration(generateData, prompt, sessionId, stream = true) {
        const session = this._ensureSession(sessionId, prompt);
        const abortController = new AbortController();
        session.abortController = abortController;

        try {
            try { xbLog.info('streamingGeneration', `processGeneration start sid=${session.id} stream=${!!stream} promptLen=${String(prompt || '').length}`); } catch {}
            this.isStreaming = true;
            this.activeCount++;
            session.isStreaming = true;
            session.text = '';
            session.updatedAt = Date.now();
            this.tempreply = '';

            if (stream) {
                const generator = await this.callAPI(generateData, abortController.signal, true);
                for await (const chunk of generator) {
                    if (abortController.signal.aborted) {
                        break;
                    }
                    this.updateTempReply(chunk, session.id);
                }
            } else {
                const result = await this.callAPI(generateData, abortController.signal, false);
                this.updateTempReply(result, session.id);
            }

            const payload = { finalText: session.text, originalPrompt: prompt, sessionId: session.id };
            try { eventSource?.emit?.(EVT_DONE, payload); } catch { }
            this.postToFrames(EVT_DONE, payload);
            try { window?.postMessage?.({ type: EVT_DONE, payload, from: 'xiaobaix' }, getTrustedOrigin()); } catch { }

            try { xbLog.info('streamingGeneration', `processGeneration done sid=${session.id} outLen=${String(session.text || '').length}`); } catch {}
            return String(session.text || '');
        } catch (err) {
            if (err?.name === 'AbortError') {
                try { xbLog.warn('streamingGeneration', `processGeneration aborted sid=${session.id}`); } catch {}
                return String(session.text || '');
            }

            console.error('[StreamingGeneration] Generation error:', err);
            console.error('[StreamingGeneration] error.error =', err?.error);
            try { xbLog.error('streamingGeneration', `processGeneration error sid=${session.id}`, err); } catch {}

            let errorMessage = '生成失败';

            if (err && typeof err === 'object' && err.error && typeof err.error === 'object') {
                const detail = err.error;
                const rawMsg = String(detail.message || '').trim();
                const code = String(detail.code || '').trim().toLowerCase();

                if (
                    /input is too long/i.test(rawMsg) ||
                    /context length/i.test(rawMsg) ||
                    /maximum context length/i.test(rawMsg) ||
                    /too many tokens/i.test(rawMsg)
                ) {
                    errorMessage =
                        '输入过长：当前对话内容超过了所选模型或代理的上下文长度限制。\n' +
                        `原始信息：${rawMsg}`;
                } else if (
                    /quota/i.test(rawMsg) ||
                    /rate limit/i.test(rawMsg) ||
                    code === 'insufficient_quota'
                ) {
                    errorMessage =
                        '请求被配额或限流拒绝：当前 API 额度可能已用尽，或触发了限流。\n' +
                        `原始信息：${rawMsg || code}`;
                } else if (code === 'bad_request') {
                    errorMessage =
                        '请求被上游 API 以 Bad Request 拒绝。\n' +
                        '可能原因：参数格式不符合要求、模型名错误，或输入内容不被当前通道接受。\n\n' +
                        `原始信息：${rawMsg || code}`;
                } else {
                    errorMessage = rawMsg || code || JSON.stringify(detail);
                }
            } else if (err && typeof err === 'object' && err.message) {
                errorMessage = err.message;
            } else if (typeof err === 'string') {
                errorMessage = err;
            }

            throw new Error(errorMessage);
        } finally {
            session.isStreaming = false;
            this.activeCount = Math.max(0, this.activeCount - 1);
            this.isStreaming = this.activeCount > 0;
            try { session.abortController = null; } catch { }
        }
    }

    _normalize = (s) => String(s || '').replace(/[\r\t\u200B\u00A0]/g, '').replace(/\s+/g, ' ').replace(/^["'""'']+|["'""'']+$/g, '').trim();
    _stripNamePrefix = (s) => String(s || '').replace(/^\s*[^:]{1,32}:\s*/, '');
    _normStrip = (s) => this._normalize(this._stripNamePrefix(s));

    _parseCompositeParam(param) {
        const input = String(param || '').trim();
        if (!input) return [];

        try {
            const parsed = JSON.parse(input);
            if (Array.isArray(parsed)) {
                const normRole = (r) => {
                    const x = String(r || '').trim().toLowerCase();
                    if (x === 'sys' || x === 'system') return 'system';
                    if (x === 'assistant' || x === 'asst' || x === 'ai') return 'assistant';
                    if (x === 'user' || x === 'u') return 'user';
                    return '';
                };
                const result = parsed
                    .filter(m => m && typeof m === 'object')
                    .map(m => ({ role: normRole(m.role), content: String(m.content || '') }))
                    .filter(m => m.role);
                if (result.length > 0) {
                    return result;
                }
            }
        } catch {

        }

        const parts = [];
        let buf = '';
        let depth = 0;
        for (let i = 0; i < input.length; i++) {
            const ch = input[i];
            if (ch === '{') depth++;
            if (ch === '}') depth = Math.max(0, depth - 1);
            if (ch === ';' && depth === 0) {
                parts.push(buf);
                buf = '';
            } else {
                buf += ch;
            }
        }
        if (buf) parts.push(buf);

        const normRole = (r) => {
            const x = String(r || '').trim().toLowerCase();
            if (x === 'sys' || x === 'system') return 'system';
            if (x === 'assistant' || x === 'asst' || x === 'ai') return 'assistant';
            if (x === 'user' || x === 'u') return 'user';
            return '';
        };
        const extractValue = (v) => {
            let s = String(v || '').trim();
            if ((s.startsWith('{') && s.endsWith('}')) ||
                (s.startsWith('"') && s.endsWith('"')) ||
                (s.startsWith('\'') && s.endsWith('\''))) {
                s = s.slice(1, -1);
            }
            return s.trim();
        };

        const result = [];
        for (const seg of parts) {
            const idx = seg.indexOf('=');
            if (idx === -1) continue;
            const role = normRole(seg.slice(0, idx));
            if (!role) continue;
            const content = extractValue(seg.slice(idx + 1));
            if (content || content === '') result.push({ role, content });
        }
        return result;
    }

    _createIsFromChat() {
        const chatNorms = chat.map(m => this._normStrip(m?.mes)).filter(Boolean);
        const chatSet = new Set(chatNorms);
        return (content) => {
            const n = this._normStrip(content);
            if (!n || chatSet.has(n)) return !n ? false : true;
            for (const c of chatNorms) {
                const [a, b] = [n.length, c.length];
                const [minL, maxL] = [Math.min(a, b), Math.max(a, b)];
                if (minL < 20) continue;
                if (((a >= b && n.includes(c)) || (b >= a && c.includes(n))) && minL / maxL >= 0.8) return true;
            }
            return false;
        };
    }

    async _runToggleTask(task) {
        const prev = this._toggleQueue;
        let release;
        this._toggleQueue = new Promise(r => (release = r));
        await prev;
        try { return await task(); }
        finally { release(); }
    }

    async _withTemporaryPromptToggles(addonSet, fn) {
        return this._runToggleTask(async () => {
            const pm = promptManager;
            if (!pm || typeof pm.getPromptOrderForCharacter !== 'function') {
                return await fn();
            }

            // 记录原始状态
            const hadOwn = Object.prototype.hasOwnProperty.call(pm, 'getPromptOrderForCharacter');
            const original = pm.getPromptOrderForCharacter;

            const PRESET_EXCLUDES = new Set([
                'chatHistory',
                'worldInfoBefore', 'worldInfoAfter',
                'charDescription', 'charPersonality', 'scenario', 'personaDescription',
            ]);

            const wrapper = (...args) => {
                const list = original.call(pm, ...args) || [];

                const enableIds = new Set();

                if (addonSet.has('preset')) {
                    for (const e of list) {
                        if (e?.identifier && e.enabled && !PRESET_EXCLUDES.has(e.identifier)) {
                            enableIds.add(e.identifier);
                        }
                    }
                }

                if (addonSet.has('chatHistory')) enableIds.add('chatHistory');
                if (addonSet.has('worldInfo')) {
                    enableIds.add('worldInfoBefore');
                    enableIds.add('worldInfoAfter');
                }
                if (addonSet.has('charDescription')) enableIds.add('charDescription');
                if (addonSet.has('charPersonality')) enableIds.add('charPersonality');
                if (addonSet.has('scenario')) enableIds.add('scenario');
                if (addonSet.has('personaDescription')) enableIds.add('personaDescription');

                if (addonSet.has('worldInfo') && !addonSet.has('chatHistory')) {
                    enableIds.add('chatHistory');
                }

                return list.map(e => {
                    if (!e?.identifier) return e;
                    return { ...e, enabled: enableIds.has(e.identifier) };
                });
            };

            pm.getPromptOrderForCharacter = wrapper;

            try {
                return await fn();
            } finally {
                if (pm.getPromptOrderForCharacter === wrapper) {
                    if (hadOwn) {
                        pm.getPromptOrderForCharacter = original;
                    } else {

                        try {
                            delete pm.getPromptOrderForCharacter;
                        } catch {
                            pm.getPromptOrderForCharacter = original;
                        }
                    }
                }
            }
        });
    }

    async _captureWorldInfoText(prompt) {
        const addonSet = new Set(['worldInfo', 'chatHistory']);
        const context = getContext();
        let capturedData = null;
        const dataListener = (data) => {
            capturedData = (data && typeof data === 'object' && Array.isArray(data.prompt))
                ? { ...data, prompt: data.prompt.slice() }
                : (Array.isArray(data) ? data.slice() : data);
        };
        eventSource.on(event_types.GENERATE_AFTER_DATA, dataListener);
        const activatedUids = new Set();
        const wiListener = (payload) => {
            try {
                const list = Array.isArray(payload?.entries)
                    ? payload.entries
                    : (Array.isArray(payload) ? payload : (payload?.entry ? [payload.entry] : []));
                for (const it of list) {
                    const uid = it?.uid || it?.id || it?.entry?.uid || it?.entry?.id;
                    if (uid) activatedUids.add(uid);
                }
            } catch {}
        };
        eventSource.on(event_types.WORLD_INFO_ACTIVATED, wiListener);
        try {
            await this._withTemporaryPromptToggles(addonSet, async () => {
                await context.generate('normal', {
                    quiet_prompt: String(prompt || '').trim(),
                    quietToLoud: false,
                    skipWIAN: false,
                    force_name2: true,
                }, true);
            });
        } finally {
            eventSource.removeListener(event_types.GENERATE_AFTER_DATA, dataListener);
            eventSource.removeListener(event_types.WORLD_INFO_ACTIVATED, wiListener);
        }
        try {
            if (activatedUids.size > 0 && Array.isArray(world_info)) {
                const seen = new Set();
                const pieces = [];
                for (const wi of world_info) {
                    const uid = wi?.uid || wi?.id;
                    if (!uid || !activatedUids.has(uid) || seen.has(uid)) continue;
                    seen.add(uid);
                    const content = String(wi?.content || '').trim();
                    if (content) pieces.push(content);
                }
                const text = pieces.join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
                if (text) return text;
            }
        } catch {}
        let src = [];
        const cd = capturedData;
        if (Array.isArray(cd)) {
            src = cd.slice();
        } else if (cd && typeof cd === 'object' && Array.isArray(cd.prompt)) {
            src = cd.prompt.slice();
        }
        const isFromChat = this._createIsFromChat();
        const pieces = [];
        for (const m of src) {
            if (!m || typeof m.content !== 'string') continue;
            if (m.role === 'system') {
                pieces.push(m.content);
            } else if ((m.role === 'user' || m.role === 'assistant') && isFromChat(m.content)) {
                continue;
            }
        }
        let text = pieces.map(s => String(s || '').trim()).filter(Boolean).join('\n\n').replace(/\n{3,}/g, '\n\n').trim();
        text = text.replace(/\n{0,2}\s*\[Start a new Chat\]\s*\n?/ig, '\n');
        text = text.replace(/\n{3,}/g, '\n\n').trim();
        return text;
    }

    parseOpt(args, key) {
        const v = args?.[key];
        if (v === undefined) return undefined;
        const s = String(v).trim().toLowerCase();
        if (s === 'undefined' || s === 'none' || s === 'null' || s === 'off') return '__unset__';
        const n = Number(v);
        return Number.isFinite(n) ? n : undefined;
    }

    getActiveCharFields() {
        const ctx = getContext();
        const char = (ctx?.getCharacter?.(ctx?.characterId)) || (Array.isArray(ctx?.characters) ? ctx.characters[ctx.characterId] : null) || {};
        const data = char.data || char || {};
        const personaText =
            (typeof power_user?.persona_description === 'string' ? power_user.persona_description : '') ||
            String((ctx?.extensionSettings?.personas?.current?.description) || '').trim();
        const mesExamples =
            String(data.mes_example || data.mesExample || data.example_dialogs || '').trim();
        return {
            description: String(data.description || '').trim(),
            personality: String(data.personality || '').trim(),
            scenario: String(data.scenario || '').trim(),
            persona: String(personaText || '').trim(),
            mesExamples,
        };
    }

    _extractTextFromMessage(msg) {
        if (!msg) return '';
        if (typeof msg.mes === 'string') return msg.mes.replace(/\r\n/g, '\n');
        if (typeof msg.content === 'string') return msg.content.replace(/\r\n/g, '\n');
        if (Array.isArray(msg.content)) {
            return msg.content
                .filter(p => p && p.type === 'text' && typeof p.text === 'string')
                .map(p => p.text.replace(/\r\n/g, '\n')).join('\n');
        }
        return '';
    }

    _getLastMessagesSnapshot() {
        const ctx = getContext();
        const list = Array.isArray(ctx?.chat) ? ctx.chat : [];
        let lastMessage = '';
        let lastUserMessage = '';
        let lastCharMessage = '';
        for (let i = list.length - 1; i >= 0; i--) {
            const m = list[i];
            const text = this._extractTextFromMessage(m).trim();
            if (!lastMessage && text) lastMessage = text;
            if (!lastUserMessage && m?.is_user && text) lastUserMessage = text;
            if (!lastCharMessage && !m?.is_user && !m?.is_system && text) lastCharMessage = text;
            if (lastMessage && lastUserMessage && lastCharMessage) break;
        }
        return { lastMessage, lastUserMessage, lastCharMessage };
    }

    async expandInline(text) {
        let out = String(text ?? '');
        if (!out) return out;
        const f = this.getActiveCharFields();
        const dict = {
            '{{description}}': f.description,
            '{{personality}}': f.personality,
            '{{scenario}}': f.scenario,
            '{{persona}}': f.persona,
            '{{mesexamples}}': f.mesExamples,
        };
        for (const [k, v] of Object.entries(dict)) {
            if (!k) continue;
            const re = new RegExp(k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
            out = out.replace(re, v || '');
        }
        const ctx = getContext();
        out = String(out)
            .replace(/\{\{user\}\}/gi, String(ctx?.name1 || 'User'))
            .replace(/\{\{char\}\}/gi, String(ctx?.name2 || 'Assistant'))
            .replace(/\{\{newline\}\}/gi, '\n');
        out = out
            .replace(/<\s*user\s*>/gi, String(ctx?.name1 || 'User'))
            .replace(/<\s*(char|character)\s*>/gi, String(ctx?.name2 || 'Assistant'))
            .replace(/<\s*persona\s*>/gi, String(f.persona || ''));
        try {
            out = replaceXbGetVarInString(out);
            out = replaceXbGetVarYamlInString(out);
        } catch {}
        const snap = this._getLastMessagesSnapshot();
        const lastDict = {
            '{{lastmessage}}': snap.lastMessage,
            '{{lastusermessage}}': snap.lastUserMessage,
            '{{lastcharmessage}}': snap.lastCharMessage,
        };
        for (const [k, v] of Object.entries(lastDict)) {
            const re = new RegExp(k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
            out = out.replace(re, (m) => (v && v.length ? v : ''));
        }
        const expandVarMacros = async (s) => {
            if (typeof window?.STscript !== 'function') return s;
            let txt = String(s);
            const escapeForCmd = (v) => {
                const escaped = String(v).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
                return `"${escaped}"`;
            };
            const apply = async (macroRe, getCmdForRoot) => {
                const found = [];
                let m;
                macroRe.lastIndex = 0;
                while ((m = macroRe.exec(txt)) !== null) {
                    const full = m[0];
                    const path = m[1]?.trim();
                    if (!path) continue;
                    found.push({ full, path });
                }
                if (!found.length) return;
                const cache = new Map();
                const getRootAndTail = (p) => {
                    const idx = p.indexOf('.');
                    return idx === -1 ? [p, ''] : [p.slice(0, idx), p.slice(idx + 1)];
                };
                const dig = (val, tail) => {
                    if (!tail) return val;
                    const parts = tail.split('.').filter(Boolean);
                    let cur = val;
                    for (const key of parts) {
                        if (cur && typeof cur === 'object' && key in cur) cur = cur[key];
                        else return '';
                    }
                    return cur;
                };
                const roots = [...new Set(found.map(item => getRootAndTail(item.path)[0]))];
                await Promise.all(roots.map(async (root) => {
                    try {
                        const cmd = getCmdForRoot(root);
                        const result = await window.STscript(cmd);
                        let parsed = result;
                        try { parsed = JSON.parse(result); } catch {}
                        cache.set(root, parsed);
                    } catch {
                        cache.set(root, '');
                    }
                }));
                for (const item of found) {
                    const [root, tail] = getRootAndTail(item.path);
                    const rootVal = cache.get(root);
                    const val = tail ? dig(rootVal, tail) : rootVal;
                    const finalStr = typeof val === 'string' ? val : (val == null ? '' : JSON.stringify(val));
                    txt = txt.split(item.full).join(finalStr);
                }
            };
            await apply(
                /\{\{getvar::([\s\S]*?)\}\}/gi,
                (root) => `/getvar key=${escapeForCmd(root)}`
            );
              await apply(
                  /\{\{getglobalvar::([\s\S]*?)\}\}/gi,
                  (root) => `/getglobalvar ${escapeForCmd(root)}`
              );
              return txt;
          };
          out = await expandVarMacros(out);
        try { out = substituteParams(out); } catch {}
        return out;
    }

    async xbgenrawCommand(args, prompt) {
        const hasScaffolding = Boolean(String(
            args?.top || args?.top64 ||
            args?.topsys || args?.topuser || args?.topassistant ||
            args?.bottom || args?.bottom64 ||
            args?.bottomsys || args?.bottomuser || args?.bottomassistant ||
            args?.addon || ''
        ).trim());
        if (!prompt?.trim() && !hasScaffolding) return '';
        const role = ['user', 'system', 'assistant'].includes(args?.as) ? args.as : 'user';
        const sessionId = this._getSlotId(args?.id);
        const lockArg = String(args?.lock || '').toLowerCase();
        const lock = lockArg === 'on' || lockArg === 'true' || lockArg === '1';
        const apiOptions = {
            api: args?.api, apiurl: args?.apiurl,
            apipassword: args?.apipassword, model: args?.model,
            enableNet: ['on','true','1','yes'].includes(String(args?.net ?? '').toLowerCase()),
            top_p: this.parseOpt(args, 'top_p'),
            top_k: this.parseOpt(args, 'top_k'),
            max_tokens: this.parseOpt(args, 'max_tokens'),
            temperature: this.parseOpt(args, 'temperature'),
            presence_penalty: this.parseOpt(args, 'presence_penalty'),
            frequency_penalty: this.parseOpt(args, 'frequency_penalty'),
            enable_thinking: this.parseOpt(args, 'enable_thinking'),
            thinking_budget: this.parseOpt(args, 'thinking_budget'),
            min_p: this.parseOpt(args, 'min_p'),
        };
        let parsedStop;
        try {
            if (args?.stop) {
                const s = String(args.stop).trim();
                if (s) {
                    const j = JSON.parse(s);
                    parsedStop = Array.isArray(j) ? j : (typeof j === 'string' ? [j] : undefined);
                }
            }
        } catch {}
        const nonstream = String(args?.nonstream || '').toLowerCase() === 'true';
        const b64dUtf8 = (s) => {
            try {
                let str = String(s).trim().replace(/-/g, '+').replace(/_/g, '/');
                const pad = str.length % 4 ? '='.repeat(4 - (str.length % 4)) : '';
                str += pad;
                const bin = atob(str);
                const u8 = Uint8Array.from(bin, c => c.charCodeAt(0));
                return new TextDecoder().decode(u8);
            } catch { return ''; }
        };
        const topComposite = args?.top64 ? b64dUtf8(args.top64) : String(args?.top || '').trim();
        const bottomComposite = args?.bottom64 ? b64dUtf8(args.bottom64) : String(args?.bottom || '').trim();
        const createMsgs = (prefix) => {
            const msgs = [];
            ['sys', 'user', 'assistant'].forEach(r => {
                const content = String(args?.[`${prefix}${r === 'sys' ? 'sys' : r}`] || '').trim();
                if (content) msgs.push({ role: r === 'sys' ? 'system' : r, content });
            });
            return msgs;
        };
        const historyPlaceholderRegex = /\{\$history(\d{1,3})\}/ig;
        const resolveHistoryPlaceholder = async (text) => {
            if (!text || typeof text !== 'string') return text;
            const ctx = getContext();
            const chatArr = Array.isArray(ctx?.chat) ? ctx.chat : [];
            if (!chatArr.length) return text;
            const extractText = (msg) => {
                if (typeof msg?.mes === 'string') return msg.mes.replace(/\r\n/g, '\n');
                if (typeof msg?.content === 'string') return msg.content.replace(/\r\n/g, '\n');
                if (Array.isArray(msg?.content)) {
                    return msg.content
                        .filter(p => p && p.type === 'text' && typeof p.text === 'string')
                        .map(p => p.text.replace(/\r\n/g, '\n')).join('\n');
                }
                return '';
            };
            const replaceFn = (match, countStr) => {
                const count = Math.max(1, Math.min(200, Number(countStr)));
                const start = Math.max(0, chatArr.length - count);
                const lines = [];
                for (let i = start; i < chatArr.length; i++) {
                    const msg = chatArr[i];
                    const isUser = !!msg?.is_user;
                    const speaker = isUser
                        ? ((msg?.name && String(msg.name).trim()) || (ctx?.name1 && String(ctx.name1).trim()) || 'USER')
                        : ((msg?.name && String(msg.name).trim()) || (ctx?.name2 && String(ctx.name2).trim()) || 'ASSISTANT');
                    lines.push(`${speaker}：`);
                    const textContent = (extractText(msg) || '').trim();
                    if (textContent) lines.push(textContent);
                    lines.push('');
                }
                return lines.join('\n').replace(/\n{3,}/g, '\n\n').trim();
            };
            return text.replace(historyPlaceholderRegex, replaceFn);
        };
        const mapHistoryPlaceholders = async (messages) => {
            const out = [];
            for (const m of messages) {
                if (!m) continue;
                const content = await resolveHistoryPlaceholder(m.content);
                out.push({ ...m, content });
            }
            return out;
        };
        let topMsgs = []
            .concat(topComposite ? this._parseCompositeParam(topComposite) : [])
            .concat(createMsgs('top'));
        let bottomMsgs = []
            .concat(bottomComposite ? this._parseCompositeParam(bottomComposite) : [])
            .concat(createMsgs('bottom'));
        const expandSegmentInline = async (arr) => {
            for (const m of arr) {
                if (m && typeof m.content === 'string') {
                    const before = m.content;
                    const after = await this.expandInline(before);
                    m.content = after && after.length ? after : before;
                }
            }
        };

        await expandSegmentInline(topMsgs);

        await expandSegmentInline(bottomMsgs);

        topMsgs = await mapHistoryPlaceholders(topMsgs);
        bottomMsgs = await mapHistoryPlaceholders(bottomMsgs);

        if (typeof prompt === 'string' && prompt.trim()) {
            const afterP = await this.expandInline(prompt);
            const beforeP = await resolveHistoryPlaceholder(afterP);
            prompt = beforeP && beforeP.length ? beforeP : afterP;
        }
        try {
            const needsWI = [...topMsgs, ...bottomMsgs].some(m => m && typeof m.content === 'string' && m.content.includes('{$worldInfo}')) || (typeof prompt === 'string' && prompt.includes('{$worldInfo}'));
            if (needsWI) {
                const wiText = await this._captureWorldInfoText(prompt || '');
                const wiTrim = String(wiText || '').trim();
                if (wiTrim) {
                    const wiRegex = /\{\$worldInfo\}/ig;
                    const applyWI = (arr) => {
                        for (const m of arr) {
                            if (m && typeof m.content === 'string') {
                                m.content = m.content.replace(wiRegex, wiTrim);
                            }
                        }
                    };
                    applyWI(topMsgs);
                    applyWI(bottomMsgs);
                    if (typeof prompt === 'string') prompt = prompt.replace(wiRegex, wiTrim);
                }
            }
        } catch {}
        const addonSetStr = String(args?.addon || '').trim();
        const shouldUsePM = addonSetStr.length > 0;
        if (!shouldUsePM) {
            const messages = []
                .concat(topMsgs.filter(m => typeof m?.content === 'string' && m.content.trim().length))
                .concat(prompt && prompt.trim().length ? [{ role, content: prompt.trim() }] : [])
                .concat(bottomMsgs.filter(m => typeof m?.content === 'string' && m.content.trim().length));
            
            const common = { messages, apiOptions, stop: parsedStop };
            if (nonstream) {
                try { if (lock) deactivateSendButtons(); } catch {}
                try {
                    await this._emitPromptReady(messages);
                    const finalText = await this.processGeneration(common, prompt || '', sessionId, false);
                    return String(finalText ?? '');
                } finally {
                    try { if (lock) activateSendButtons(); } catch {}
                }
            } else {
                try { if (lock) deactivateSendButtons(); } catch {}
                await this._emitPromptReady(messages);
                const p = this.processGeneration(common, prompt || '', sessionId, true);
                p.finally(() => { try { if (lock) activateSendButtons(); } catch {} });
                p.catch(() => {});
                return String(sessionId);
            }
        }
        const addonSet = new Set(addonSetStr.split(',').map(s => s.trim()).filter(Boolean));
        const buildAddonFinalMessages = async () => {
            const context = getContext();
            let capturedData = null;
            const dataListener = (data) => {
                capturedData = (data && typeof data === 'object' && Array.isArray(data.prompt))
                    ? { ...data, prompt: data.prompt.slice() }
                    : (Array.isArray(data) ? data.slice() : data);
            };
            eventSource.on(event_types.GENERATE_AFTER_DATA, dataListener);
            const skipWIAN = addonSet.has('worldInfo') ? false : true;
            await this._withTemporaryPromptToggles(addonSet, async () => {
                const sandboxed = addonSet.has('worldInfo') && !addonSet.has('chatHistory');
                let chatBackup = null;
                if (sandboxed) {
                    try {
                        chatBackup = chat.slice();
                        chat.length = 0;
                        chat.push({ name: name1 || 'User', is_user: true, is_system: false, mes: '[hist]', send_date: new Date().toISOString() });
                    } catch {}
                }
                try {
                    await context.generate('normal', {
                        quiet_prompt: (prompt || '').trim(), quietToLoud: false,
                        skipWIAN, force_name2: true
                    }, true);
                } finally {
                    if (sandboxed && Array.isArray(chatBackup)) {
                        chat.length = 0;
                        chat.push(...chatBackup);
                    }
                }
            });
            eventSource.removeListener(event_types.GENERATE_AFTER_DATA, dataListener);
            let src = [];
            const cd = capturedData;
            if (Array.isArray(cd)) src = cd.slice();
            else if (cd && typeof cd === 'object' && Array.isArray(cd.prompt)) src = cd.prompt.slice();
            const sandboxedAfter = addonSet.has('worldInfo') && !addonSet.has('chatHistory');
            const isFromChat = this._createIsFromChat();
            const finalPromptMessages = src.filter(m => {
                if (!sandboxedAfter) return true;
                if (!m) return false;
                if (m.role === 'system') return true;
                if ((m.role === 'user' || m.role === 'assistant') && isFromChat(m.content)) return false;
                return true;
            });
            const norm = this._normStrip;
            const position = ['history', 'after_history', 'afterhistory', 'chathistory']
                .includes(String(args?.position || '').toLowerCase()) ? 'history' : 'bottom';
            const targetIdx = finalPromptMessages.findIndex(m => m && typeof m.content === 'string' && norm(m.content) === norm(prompt || ''));
            if (targetIdx !== -1) {
                finalPromptMessages.splice(targetIdx, 1);
            }
            if (prompt?.trim()) {
                const centerMsg = { role: (args?.as || 'assistant'), content: prompt.trim() };
                if (position === 'history') {
                    let lastHistoryIndex = -1;
                    const isFromChat2 = this._createIsFromChat();
                    for (let i = 0; i < finalPromptMessages.length; i++) {
                        const m = finalPromptMessages[i];
                        if (m && (m.role === 'user' || m.role === 'assistant') && isFromChat2(m.content)) {
                            lastHistoryIndex = i;
                        }
                    }
                    if (lastHistoryIndex >= 0) finalPromptMessages.splice(lastHistoryIndex + 1, 0, centerMsg);
                    else {
                        let lastSystemIndex = -1;
                        for (let i = 0; i < finalPromptMessages.length; i++) {
                            if (finalPromptMessages[i]?.role === 'system') lastSystemIndex = i;
                        }
                        if (lastSystemIndex >= 0) finalPromptMessages.splice(lastSystemIndex + 1, 0, centerMsg);
                        else finalPromptMessages.push(centerMsg);
                    }
                } else {
                    finalPromptMessages.push(centerMsg);
                }
            }
            const mergedOnce = ([]).concat(topMsgs).concat(finalPromptMessages).concat(bottomMsgs);
            const seenKey = new Set();
            const finalMessages = [];
            for (const m of mergedOnce) {
                if (!m || !m.content || !String(m.content).trim().length) continue;
                const key = `${m.role}:${this._normStrip(m.content)}`;
                if (seenKey.has(key)) continue;
                seenKey.add(key);
                finalMessages.push(m);
            }
            return finalMessages;
        };
        if (nonstream) {
            try { if (lock) deactivateSendButtons(); } catch {}
            try {
                const finalMessages = await buildAddonFinalMessages();
                const common = { messages: finalMessages, apiOptions, stop: parsedStop };
                await this._emitPromptReady(finalMessages);
                const finalText = await this.processGeneration(common, prompt || '', sessionId, false);
                return String(finalText ?? '');
            } finally {
                try { if (lock) activateSendButtons(); } catch {}
            }
        } else {
            (async () => {
                try {
                    try { if (lock) deactivateSendButtons(); } catch {}
                    const finalMessages = await buildAddonFinalMessages();
                    const common = { messages: finalMessages, apiOptions, stop: parsedStop };
                    await this._emitPromptReady(finalMessages);
                    await this.processGeneration(common, prompt || '', sessionId, true);
                } catch {} finally {
                    try { if (lock) activateSendButtons(); } catch {}
                }
            })();
            return String(sessionId);
        }
    }

    async xbgenCommand(args, prompt) {
        if (!prompt?.trim()) return '';
        const role = ['user', 'system', 'assistant'].includes(args?.as) ? args.as : 'system';
        const sessionId = this._getSlotId(args?.id);
        const lockArg = String(args?.lock || '').toLowerCase();
        const lock = lockArg === 'on' || lockArg === 'true' || lockArg === '1';
        const nonstream = String(args?.nonstream || '').toLowerCase() === 'true';
        const buildGenDataWithOptions = async () => {
            const context = getContext();
            const tempMessage = {
                name: role === 'user' ? (name1 || 'User') : 'System',
                is_user: role === 'user',
                is_system: role === 'system',
                mes: prompt.trim(),
                send_date: new Date().toISOString(),
            };
            const originalLength = chat.length;
            chat.push(tempMessage);
            let capturedData = null;
            const dataListener = (data) => {
                if (data?.prompt && Array.isArray(data.prompt)) {
                    let messages = [...data.prompt];
                    const promptText = prompt.trim();
                    for (let i = messages.length - 1; i >= 0; i--) {
                        const m = messages[i];
                        if (m.content === promptText &&
                            ((role !== 'system' && m.role === 'system') ||
                             (role === 'system' && m.role === 'user'))) {
                            messages.splice(i, 1);
                            break;
                        }
                    }
                    capturedData = { ...data, prompt: messages };
                } else {
                    capturedData = data;
                }
            };
            eventSource.on(event_types.GENERATE_AFTER_DATA, dataListener);
            try {
                await context.generate('normal', {
                    quiet_prompt: prompt.trim(), quietToLoud: false,
                    skipWIAN: false, force_name2: true
                }, true);
            } finally {
                eventSource.removeListener(event_types.GENERATE_AFTER_DATA, dataListener);
                chat.length = originalLength;
            }
            const apiOptions = {
                api: args?.api, apiurl: args?.apiurl,
                apipassword: args?.apipassword, model: args?.model,
                enableNet: ['on','true','1','yes'].includes(String(args?.net ?? '').toLowerCase()),
                top_p: this.parseOpt(args, 'top_p'),
                top_k: this.parseOpt(args, 'top_k'),
                max_tokens: this.parseOpt(args, 'max_tokens'),
                temperature: this.parseOpt(args, 'temperature'),
                presence_penalty: this.parseOpt(args, 'presence_penalty'),
                frequency_penalty: this.parseOpt(args, 'frequency_penalty'),
            };
            const cd = capturedData;
            let finalPromptMessages = [];
            if (cd && typeof cd === 'object' && Array.isArray(cd.prompt)) {
                finalPromptMessages = cd.prompt.slice();
            } else if (Array.isArray(cd)) {
                finalPromptMessages = cd.slice();
            }
            const norm = this._normStrip;
            const promptNorm = norm(prompt);
            for (let i = finalPromptMessages.length - 1; i >= 0; i--) {
                if (norm(finalPromptMessages[i]?.content) === promptNorm) {
                    finalPromptMessages.splice(i, 1);
                }
            }
            const messageToInsert = { role, content: prompt.trim() };
            const position = ['history', 'after_history', 'afterhistory', 'chathistory']
                .includes(String(args?.position || '').toLowerCase()) ? 'history' : 'bottom';
            if (position === 'history') {
                const isFromChat = this._createIsFromChat();
                let lastHistoryIndex = -1;
                for (let i = 0; i < finalPromptMessages.length; i++) {
                    const m = finalPromptMessages[i];
                    if (m && (m.role === 'user' || m.role === 'assistant') && isFromChat(m.content)) {
                        lastHistoryIndex = i;
                    }
                }
                if (lastHistoryIndex >= 0) {
                    finalPromptMessages.splice(lastHistoryIndex + 1, 0, messageToInsert);
                } else {
                    finalPromptMessages.push(messageToInsert);
                }
            } else {
                finalPromptMessages.push(messageToInsert);
            }
            const cd2 = capturedData;
            let dataWithOptions;
            if (cd2 && typeof cd2 === 'object' && !Array.isArray(cd2)) {
                dataWithOptions = Object.assign({}, cd2, { prompt: finalPromptMessages, apiOptions });
            } else {
                dataWithOptions = { messages: finalPromptMessages, apiOptions };
            }
            return dataWithOptions;
        };
        if (nonstream) {
            try { if (lock) deactivateSendButtons(); } catch {}
            try {
                const dataWithOptions = await buildGenDataWithOptions();
                const chatMsgs = Array.isArray(dataWithOptions?.prompt) ? dataWithOptions.prompt
                    : (Array.isArray(dataWithOptions?.messages) ? dataWithOptions.messages : []);
                await this._emitPromptReady(chatMsgs);
                const finalText = await this.processGeneration(dataWithOptions, prompt, sessionId, false);
                return String(finalText ?? '');
            } finally {
                try { if (lock) activateSendButtons(); } catch {}
            }
        }
        (async () => {
            try {
                try { if (lock) deactivateSendButtons(); } catch {}
                const dataWithOptions = await buildGenDataWithOptions();
                const chatMsgs = Array.isArray(dataWithOptions?.prompt) ? dataWithOptions.prompt
                    : (Array.isArray(dataWithOptions?.messages) ? dataWithOptions.messages : []);
                await this._emitPromptReady(chatMsgs);
                const finalText = await this.processGeneration(dataWithOptions, prompt, sessionId, true);
                try { if (args && args._scope) args._scope.pipe = String(finalText ?? ''); } catch {}
            } catch {}
            finally {
                try { if (lock) activateSendButtons(); } catch {}
            }
        })();
        return String(sessionId);
    }

    registerCommands() {
        const commonArgs = [
            { name: 'id', description: '会话ID', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'api', description: '后端: openai/claude/gemini/cohere/deepseek/custom', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'net', description: '联网 on/off', typeList: [ARGUMENT_TYPE.STRING], enumList: ['on','off'] },
            { name: 'apiurl', description: '自定义后端URL', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'apipassword', description: '后端密码', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'model', description: '模型名', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'position', description: '插入位置：bottom/history', typeList: [ARGUMENT_TYPE.STRING], enumList: ['bottom', 'history'] },
            { name: 'temperature', description: '温度', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'presence_penalty', description: '存在惩罚', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'frequency_penalty', description: '频率惩罚', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'top_p', description: 'Top P', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'top_k', description: 'Top K', typeList: [ARGUMENT_TYPE.STRING] },
            { name: 'max_tokens', description: '最大回复长度', typeList: [ARGUMENT_TYPE.STRING] },
        ];
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbgen',
            callback: (args, prompt) => this.xbgenCommand(args, prompt),
            namedArgumentList: [
                { name: 'as', description: '消息角色', typeList: [ARGUMENT_TYPE.STRING], defaultValue: 'system', enumList: ['user', 'system', 'assistant'] },
                { name: 'nonstream', description: '非流式：true/false', typeList: [ARGUMENT_TYPE.STRING], enumList: ['true', 'false'] },
                { name: 'lock', description: '生成时锁定输入 on/off', typeList: [ARGUMENT_TYPE.STRING], enumList: ['on', 'off'] },
                ...commonArgs
            ].map(SlashCommandNamedArgument.fromProps),
            unnamedArgumentList: [SlashCommandArgument.fromProps({
                description: '生成提示文本', typeList: [ARGUMENT_TYPE.STRING], isRequired: true
            })],
            helpString: '使用完整上下文进行流式生成',
            returns: 'session ID'
        }));
        SlashCommandParser.addCommandObject(SlashCommand.fromProps({
            name: 'xbgenraw',
            callback: (args, prompt) => this.xbgenrawCommand(args, prompt),
            namedArgumentList: [
                { name: 'as', description: '消息角色', typeList: [ARGUMENT_TYPE.STRING], defaultValue: 'user', enumList: ['user', 'system', 'assistant'] },
                { name: 'nonstream', description: '非流式：true/false', typeList: [ARGUMENT_TYPE.STRING], enumList: ['true', 'false'] },
                { name: 'lock', description: '生成时锁定输入 on/off', typeList: [ARGUMENT_TYPE.STRING], enumList: ['on', 'off'] },
                { name: 'addon', description: '附加上下文', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'topsys', description: '置顶 system', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'topuser', description: '置顶 user', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'topassistant', description: '置顶 assistant', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'bottomsys', description: '置底 system', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'bottomuser', description: '置底 user', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'bottomassistant', description: '置底 assistant', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'top', description: '复合置顶: assistant={A};user={B};sys={C}', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'bottom', description: '复合置底: assistant={C};sys={D1}', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'top64', description: '复合置顶(base64-url安全编码)', typeList: [ARGUMENT_TYPE.STRING] },
                { name: 'bottom64', description: '复合置底(base64-url安全编码)', typeList: [ARGUMENT_TYPE.STRING] },
                ...commonArgs
            ].map(SlashCommandNamedArgument.fromProps),
            unnamedArgumentList: [SlashCommandArgument.fromProps({
                description: '原始提示文本', typeList: [ARGUMENT_TYPE.STRING], isRequired: false
            })],
            helpString: '使用原始提示进行流式生成',
            returns: 'session ID'
        }));
    }

    getLastGeneration = (sessionId) => sessionId !== undefined ?
        (this.sessions.get(this._getSlotId(sessionId))?.text || '') : this.tempreply;

    getStatus = (sessionId) => {
        if (sessionId !== undefined) {
            const sid = this._getSlotId(sessionId);
            const s = this.sessions.get(sid);
            return s ? { isStreaming: !!s.isStreaming, text: s.text, sessionId: sid }
                     : { isStreaming: false, text: '', sessionId: sid };
        }
        return { isStreaming: !!this.isStreaming, text: this.tempreply };
    };

    startSession = (id, prompt) => this._ensureSession(id, prompt).id;
    getLastSessionId = () => this.lastSessionId;

    cancel(sessionId) {
        const s = this.sessions.get(this._getSlotId(sessionId));
        s?.abortController?.abort();
    }

    cleanup() {
        this.sessions.forEach(s => s.abortController?.abort());
        Object.assign(this, {
            sessions: new Map(), tempreply: '', lastSessionId: null,
            activeCount: 0, isInitialized: false, isStreaming: false
        });
    }
}

const streamingGeneration = new StreamingGeneration();

CacheRegistry.register('streamingGeneration', {
    name: '流式生成会话',
    getSize: () => streamingGeneration?.sessions?.size || 0,
    getBytes: () => {
        try {
            let bytes = String(streamingGeneration?.tempreply || '').length * 2; // UTF-16
            streamingGeneration?.sessions?.forEach?.((s) => {
                bytes += (String(s?.prompt || '').length + String(s?.text || '').length) * 2; // UTF-16
            });
            return bytes;
        } catch {
            return 0;
        }
    },
    clear: () => {
        try { streamingGeneration.cleanup(); } catch {}
    },
    getDetail: () => {
        try {
            const sessions = Array.from(streamingGeneration.sessions?.values?.() || []);
            return sessions.map(s => ({
                id: s?.id,
                isStreaming: !!s?.isStreaming,
                promptLen: String(s?.prompt || '').length,
                textLen: String(s?.text || '').length,
                updatedAt: s?.updatedAt || 0,
            }));
        } catch {
            return [];
        }
    },
});

export function initStreamingGeneration() {
    const w = window;
    if ((w)?.isXiaobaixEnabled === false) return;
    try { xbLog.info('streamingGeneration', 'initStreamingGeneration'); } catch {}
    streamingGeneration.init();
    (w)?.registerModuleCleanup?.('streamingGeneration', () => streamingGeneration.cleanup());
}

export { streamingGeneration };

if (typeof window !== 'undefined') {
    Object.assign(window, {
        xiaobaixStreamingGeneration: streamingGeneration,
        eventSource: (window)?.eventSource || eventSource
    });
}
