// MiniCPM5 native XML uses Python literals for typed parameters. No eval/Function. export function parseMiniCpmParamValue(input, schema = {}) { const raw = String(input ?? ''); const cdata = /^\s*\s*$/.exec(raw); let decoded=cdata ? cdata[1] : raw; if(!cdata) { if(raw.includes('<')) throw Error('Malformed XML parameter: use CDATA for literal markup'); decoded=raw.replace(/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);/gi,(_,entity)=>{ if(entity.startsWith('#')) { const n=entity[1].toLowerCase()==='x'?parseInt(entity.slice(2),16):parseInt(entity.slice(1),10);return String.fromCodePoint(n); } return {amp:'&',lt:'<',gt:'>',quot:'"',apos:"'"}[entity]; }); if(/&(?!(?:#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);)/i.test(raw)) throw Error('Unescaped XML ampersand'); } if(schema.type==='string') return decoded; const text = decoded.trim(); if (!text) return ''; let i = 0; const ws = () => { while (i < text.length && /\s/u.test(text[i])) i++; }; const take = char => { ws(); if (text[i++] !== char) throw Error('Invalid literal'); }; function value(depth = 0) { if (depth > 32) throw Error('Literal nesting limit'); ws(); const c = text[i]; if (c === "'" || c === '"') { const quote = text[i++]; let result = ''; while (i < text.length) { const ch = text[i++]; if (ch === quote) return result; if (ch !== '\\') { result += ch; continue; } const e = text[i++]; const simple = { '\\':'\\', "'":"'", '"':'"', n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', '/':'/' }; if (Object.hasOwn(simple,e)) { result += simple[e]; continue; } const width = e === 'u' ? 4 : e === 'x' ? 2 : 0; const hex = text.slice(i,i+width); if (!width || !new RegExp(`^[a-fA-F0-9]{${width}}$`).test(hex)) throw Error('Invalid escape'); result += String.fromCharCode(parseInt(hex,16)); i += width; } throw Error('Unterminated string'); } if (c === '[' || c === '{') { i++; const array = c === '['; const end = array ? ']' : '}'; const result = array ? [] : Object.create(null); ws(); if (text[i] === end) { i++; return array ? result : {...result}; } while (i < text.length) { if (array) result.push(value(depth+1)); else { ws(); if (!['"',"'"].includes(text[i])) throw Error('Expected string key'); const key = value(depth+1); if (Object.hasOwn(result,key)) throw Error('Duplicate key'); take(':'); result[key] = value(depth+1); } ws(); if (text[i] === end) { i++; return array ? result : {...result}; } take(','); ws(); if (text[i] === end) { i++; return array ? result : {...result}; } } throw Error('Unterminated container'); } const scalar = /^(True|False|None|true|false|null)(?![\w])/u.exec(text.slice(i)); if (scalar) { i += scalar[0].length; return {True:true,False:false,None:null,true:true,false:false,null:null}[scalar[0]]; } const number = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/u.exec(text.slice(i)); if (number) { i += number[0].length; const result=Number(number[0]); if(Number.isFinite(result)) return result; } throw Error('Not a literal'); } try { const result=value(); ws(); if(i===text.length) return result; } catch { /* native strings are also unquoted */ } return text; } // Strict standalone adapter. Keep downstream tool schema/approval validation in place. export function parseMiniCpmToolCalls(text, toolsOrNames) { const tools=Array.isArray(toolsOrNames)?toolsOrNames:[]; const allowedNames=Array.isArray(toolsOrNames)?new Set(tools.map(t=>t.function.name)):toolsOrNames; const schemas=new Map(tools.map(t=>[t.function.name,t.function.parameters || {}])); const source=String(text ?? '').trim(); if (!source || source.length>10000 || !(allowedNames instanceof Set)) return []; // Native MiniCPM may explain an error before emitting the XML tool call. // Accept that prose, but never skip over a malformed XML call or parameter. const start=source.indexOf('([\s\S]*?)<\/function>/gy; const params=/([\s\S]*?)<\/param>/gy; while(cursor