/**
 * ============================================================
 *  plugins/workflow-runner/src/jsonPayload.ts —— 从 LLM 文本里提取纯 JSON 负载
 * ------------------------------------------------------------
 *  根因（为什么需要这个文件）：
 *    "思考型"模型（MiniMax-M2/M3、DeepSeek-R1 系等）即使在 strict json_schema 下，
 *    仍会把 <think>…</think> 思维链**直接写进 content 字段**（不是 reasoning_content）。
 *    src/llm/client.ts 故意原样保留这段思维链（主循环下一轮工具调用要靠它接上下文，
 *    见 client.ts:200-205 注释），所以"剥离思维链"不能放进通用 transport——否则会
 *    破坏主循环。剥离必须发生在「契约要求纯 JSON」的边界，也就是 workflow 的
 *    output_schema 解析处。本模块就是那个边界的唯一实现，被两个解析点共享：
 *      - stepExecutors/llm.ts（llm + output_schema 单轮）
 *      - miniReAct.ts（agent 节点 / llm+context_files / onError:recover 三处子循环）
 *    放一处、两边 import，杜绝"只修一个解析点、另一个继续炸"的漂移。
 *
 *  处理顺序（保守、纯函数、可单测）：
 *    1. 去掉所有 <think>…</think> 块（多行、可多个、大小写不敏感）。
 *       **必须先做**：思维链里常出现 { } [ ]（模型在"想" JSON 长什么样），
 *       若不先剥，下面"找首个 JSON 起点"会错锚到思维链里的括号。
 *    2. 兜底：只有起始 <think>、收尾 </think> 被 length 截断 → 砍掉从 <think> 起的全部。
 *    3. 从剩余文本定位首个 { 或 [，配平括号截取到对应收尾。
 *       这一步天然容忍 ```json 代码围栏与 JSON 前后的散文（只取配平的那一段），
 *       所以不需要单独的"去围栏"正则（也就避免了误伤 JSON 字符串里的反引号）。
 *    4. 找不到任何 JSON 起点 → 原样返回 trim 后文本，交调用方 JSON.parse 报
 *       "honest error"（保留原文前若干字符便于排查）。
 * ============================================================
 */
/**
 * 从可能掺杂思维链 / 代码围栏 / 前后散文的文本里提取最可能的 JSON 负载字符串。
 * 不做 JSON.parse（解析与错误信息留给调用方，便于各自定制报错文案）。
 */
export function extractJsonPayload(raw) {
    // 1. 去成对的思维链块
    let s = raw.replace(/<think>[\s\S]*?<\/think>/gi, '');
    // 2. 截断兜底：有起始无收尾（被 max tokens 截断）→ 丢弃从 <think> 起的尾巴
    if (/<think>/i.test(s) && !/<\/think>/i.test(s)) {
        s = s.slice(0, s.search(/<think>/i));
    }
    s = s.trim();
    // 3. 定位首个 JSON 起点并配平截取
    const start = firstJsonStart(s);
    if (start === -1)
        return s; // 没有 JSON：原样返回，交调用方报错
    const end = matchBalanced(s, start);
    return end === -1 ? s.slice(start) : s.slice(start, end + 1);
}
/** 首个 { 或 [ 的下标（取更靠前者）；都没有返回 -1。 */
function firstJsonStart(s) {
    const obj = s.indexOf('{');
    const arr = s.indexOf('[');
    if (obj === -1)
        return arr;
    if (arr === -1)
        return obj;
    return Math.min(obj, arr);
}
/**
 * 从 start（{ 或 [）配平到对应的 } 或 ]，返回收尾下标；找不到返回 -1。
 * 跳过字符串字面量内部的括号与转义，避免被 JSON 值里的 } / ] 误导。
 */
function matchBalanced(s, start) {
    const open = s[start];
    const close = open === '{' ? '}' : ']';
    let depth = 0;
    let inStr = false;
    let esc = false;
    for (let i = start; i < s.length; i++) {
        const c = s[i];
        if (inStr) {
            if (esc)
                esc = false;
            else if (c === '\\')
                esc = true;
            else if (c === '"')
                inStr = false;
            continue;
        }
        if (c === '"')
            inStr = true;
        else if (c === open)
            depth++;
        else if (c === close) {
            depth--;
            if (depth === 0)
                return i;
        }
    }
    return -1;
}
