UNPKG

@tencentdb-agent-memory/memory-tencentdb

Version:

Four-layer local memory system plugin for OpenClaw — auto-captures, structures, and profiles conversational knowledge using local LLM + SQLite vector search (L0→L1→L2→L3 pipeline)

86 lines (72 loc) 2.9 kB
/** * Tolerant JSON parsing utilities for LLM responses. * * LLMs often wrap JSON in markdown code fences, include trailing commas, * or prepend explanatory text. These utilities handle common deviations. */ /** * Extract JSON from LLM output — handles code fences, prefix text, etc. * Returns the parsed object/array, or null if parsing fails. */ export function extractJson<T = unknown>(raw: string): T | null { if (!raw || typeof raw !== "string") return null; const trimmed = raw.trim(); // Strategy 1: Direct parse (ideal case) const direct = tryParse<T>(trimmed); if (direct !== null) return direct; // Strategy 2: Extract from markdown code fence (```json ... ``` or ``` ... ```) const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)```/); if (fenceMatch) { const inner = fenceMatch[1].trim(); const parsed = tryParse<T>(inner); if (parsed !== null) return parsed; } // Strategy 3: Find first { to last } (or first [ to last ]) const firstBrace = trimmed.indexOf("{"); const lastBrace = trimmed.lastIndexOf("}"); if (firstBrace >= 0 && lastBrace > firstBrace) { const candidate = trimmed.slice(firstBrace, lastBrace + 1); const parsed = tryParse<T>(candidate); if (parsed !== null) return parsed; // Try with trailing comma fix const fixed = fixTrailingCommas(candidate); const parsedFixed = tryParse<T>(fixed); if (parsedFixed !== null) return parsedFixed; } const firstBracket = trimmed.indexOf("["); const lastBracket = trimmed.lastIndexOf("]"); if (firstBracket >= 0 && lastBracket > firstBracket) { const candidate = trimmed.slice(firstBracket, lastBracket + 1); const parsed = tryParse<T>(candidate); if (parsed !== null) return parsed; } // Strategy 4: Try fixing the entire string const fixed = fixTrailingCommas(trimmed); const parsedFixed = tryParse<T>(fixed); if (parsedFixed !== null) return parsedFixed; return null; } /** * Extract mermaid content from a code fence. * Returns the raw mermaid text (without fence markers). */ export function extractMermaidFromFence(text: string): string | null { if (!text) return null; const match = text.match(/```mermaid\s*\n?([\s\S]*?)```/); if (match) return match[1].trim(); // Fallback: if no fence, return as-is (might already be raw mermaid) if (text.includes("flowchart") || text.includes("graph")) return text.trim(); return null; } // ─── Internal Helpers ──────────────────────────────────────────────────────── function tryParse<T>(s: string): T | null { try { return JSON.parse(s) as T; } catch { return null; } } function fixTrailingCommas(s: string): string { // Remove trailing commas before } or ] return s.replace(/,\s*([}\]])/g, "$1"); }