UNPKG

wowok_agent

Version:

Making It Easy for AI Agents to Communicate, Collaborate, Trade, and Trust.

402 lines (401 loc) 14.8 kB
import { GuardNodeSchema } from '../query/index.js'; import { CallGuard_DataSchema } from '../call/guard.js'; function detectFormat(text) { const trimmed = text.trim(); if (trimmed.startsWith('{') || trimmed.startsWith('[')) { return 'json'; } return 'markdown'; } function extractJsonFromMarkdown(markdown) { const codeBlockRegex = /```(?:json)?\s*\n([\s\S]*?)```/g; let match; let lastMatch = null; while ((match = codeBlockRegex.exec(markdown)) !== null) { const jsonContent = match[1].trim(); if (jsonContent.startsWith('{')) { const beforeMatch = markdown.substring(0, match.index); const lineOffset = (beforeMatch.match(/\n/g) || []).length + 1; lastMatch = { json: jsonContent, lineOffset }; } } return lastMatch; } function parseMarkdownToGuardData(markdown) { const errors = []; const jsonMatch = extractJsonFromMarkdown(markdown); if (jsonMatch) { return { data: jsonMatch.json, errors: [] }; } const result = {}; const descriptionMatch = markdown.match(/(?:^|\n)#+\s*(?:Description|描述)[^\n]*\n+([^\n#]+)/i); if (descriptionMatch) { result.description = descriptionMatch[1].trim(); } const tableMatch = markdown.match(/(?:^|\n)#+\s*(?:Table|数据表)[^\n]*\n+[\s\S]*?(?=(?:\n#+|$))/i); if (tableMatch) { const tableSection = tableMatch[0]; const tableRows = tableSection.match(/\|[^\n]+\|/g); if (tableRows && tableRows.length > 2) { const rows = tableRows.slice(2).map(row => { const cells = row.split('|').filter(c => c.trim()).map(c => c.trim()); const valueTypeCell = cells[2]; let value_type; const numericMatch = valueTypeCell.match(/\d+/); if (numericMatch) { value_type = parseInt(numericMatch[0], 10); } else { const typeNameMatch = valueTypeCell.match(/^([A-Za-z]+)/); if (typeNameMatch) { value_type = typeNameMatch[1]; } else { throw new Error(`Invalid value_type in table row: "${valueTypeCell}". Expected a number (0-19) or type name (e.g., "U64", "Address")`); } } return { identifier: parseInt(cells[0], 10), b_submission: cells[1].toLowerCase() === 'true', value_type, value: cells[3] === '-' ? undefined : cells[3], name: cells[4] === '-' ? undefined : cells[4] }; }); result.table = rows; } else { result.table = []; } } else { result.table = []; } const rootMatch = markdown.match(/(?:^|\n)#+\s*(?:Root|根节点)[^\n]*\n+([\s\S]*?)(?=(?:\n#+|$))/i); if (rootMatch) { const rootSection = rootMatch[1]; const rootJsonMatch = rootSection.match(/```(?:json)?\s*\n([\s\S]*?)```/); if (rootJsonMatch) { try { result.root = JSON.parse(rootJsonMatch[1]); } catch (e) { errors.push({ message: `Failed to parse root JSON: ${e.message}`, path: '/root', }); } } } const relyMatch = markdown.match(/(?:^|\n)#+\s*(?:Rely|依赖)[^\n]*\n+([\s\S]*?)(?=(?:\n#+|$))/i); if (relyMatch) { const relySection = relyMatch[1]; const logicMatch = relySection.match(/\*\*Logic:\*\*\s*(AND|OR)/i); const guardListMatches = relySection.match(/^\s*[-*]\s*(\S+)$/gm); if (logicMatch || guardListMatches) { const logic_and = logicMatch ? logicMatch[1].toUpperCase() === 'AND' : true; const guards = guardListMatches ? guardListMatches.map(g => g.replace(/^\s*[-*]\s*/, '').trim()) : []; result.rely = { guards, logic_or: !logic_and }; } else { const guardsMatch = relySection.match(/[-*]\s*guards?\s*:\s*\[([^\]]+)\]/i); const logicOrMatch = relySection.match(/[-*]\s*logic_or\s*:\s*(true|false)/i); if (guardsMatch || logicOrMatch) { result.rely = { guards: guardsMatch ? guardsMatch[1].split(',').map(g => g.trim().replace(/['"]/g, '')) : [], logic_or: logicOrMatch ? logicOrMatch[1].toLowerCase() === 'true' : undefined }; } } } return { data: result, errors }; } function parseJsonWithSourceMap(text, lineOffset = 0) { let pointers = {}; let data; try { const lines = text.split('\n'); const lineStarts = [0]; for (let i = 0; i < lines.length; i++) { lineStarts.push(lineStarts[i] + lines[i].length + 1); } data = JSON.parse(text); buildPointers(data, '', pointers, text, lineStarts, lineOffset); return { data, map: { pointers } }; } catch (e) { return null; } } function buildPointers(data, path, pointers, text, lineStarts, lineOffset) { if (typeof data !== 'object' || data === null) { return; } const pos = findValuePosition(text, path, lineStarts); if (pos !== undefined) { pointers[path] = { value: { line: pos.line + lineOffset, column: pos.column, pos: pos.pos } }; } if (Array.isArray(data)) { data.forEach((item, index) => { buildPointers(item, `${path}/${index}`, pointers, text, lineStarts, lineOffset); }); } else { Object.keys(data).forEach(key => { const escapedKey = key.replace(/~/g, '~0').replace(/\//g, '~1'); buildPointers(data[key], `${path}/${escapedKey}`, pointers, text, lineStarts, lineOffset); }); } } function findValuePosition(text, path, lineStarts) { if (path === '') { const match = text.match(/\S/); if (match && match.index !== undefined) { return getPosition(text, match.index, lineStarts); } return undefined; } const parts = path.split('/').slice(1); let searchPos = 0; let depth = 0; let inString = false; let escape = false; for (let i = 0; i < parts.length; i++) { const part = parts[i].replace(/~0/g, '~').replace(/~1/g, '/'); let found = false; while (searchPos < text.length) { const char = text[searchPos]; if (escape) { escape = false; searchPos++; continue; } if (char === '\\') { escape = true; searchPos++; continue; } if (char === '"') { inString = !inString; searchPos++; continue; } if (inString) { searchPos++; continue; } if (char === '{' || char === '[') { depth++; searchPos++; continue; } if (char === '}' || char === ']') { depth--; searchPos++; continue; } if (i < parts.length - 1 || !isNaN(Number(part))) { if (char === '"' && depth === i + 1) { const endQuote = text.indexOf('"', searchPos + 1); if (endQuote !== -1) { const key = text.slice(searchPos + 1, endQuote); if (key === part || (!isNaN(Number(part)) && key === parts[i - 1])) { searchPos = endQuote + 1; while (searchPos < text.length && (text[searchPos] === ' ' || text[searchPos] === ':' || text[searchPos] === '\n' || text[searchPos] === '\r' || text[searchPos] === '\t')) { searchPos++; } found = true; break; } } } if (!isNaN(Number(part)) && char === '[') { const arrayStart = searchPos; let arrayDepth = 1; let itemIndex = 0; let itemStart = searchPos + 1; for (let j = searchPos + 1; j < text.length; j++) { const c = text[j]; if (c === '[') arrayDepth++; if (c === ']') { arrayDepth--; if (arrayDepth === 0) break; } if (c === ',' && arrayDepth === 1) { if (itemIndex === Number(part)) { searchPos = itemStart; while (searchPos < text.length && (text[searchPos] === ' ' || text[searchPos] === '\n' || text[searchPos] === '\r' || text[searchPos] === '\t')) { searchPos++; } found = true; break; } itemIndex++; itemStart = j + 1; } } if (found) break; } } searchPos++; } } while (searchPos < text.length && (text[searchPos] === ' ' || text[searchPos] === '\n' || text[searchPos] === '\r' || text[searchPos] === '\t')) { searchPos++; } return getPosition(text, searchPos, lineStarts); } function getPosition(text, pos, lineStarts) { let line = 1; for (let i = 0; i < lineStarts.length; i++) { if (lineStarts[i] > pos) break; line = i + 1; } const column = pos - lineStarts[line - 1] + 1; return { line, column, pos }; } function formatZodPath(path) { return '/' + path.map(p => String(p).replace(/~/g, '~0').replace(/\//g, '~1')).join('/'); } export function parseGuardFromText(text) { const errors = []; const trimmed = text.trim(); const format = detectFormat(trimmed); let parsedData; let sourceMap = null; let lineOffset = 0; if (format === 'markdown') { const mdResult = parseMarkdownToGuardData(trimmed); if (mdResult.errors.length > 0) { return { success: false, errors: mdResult.errors }; } if (typeof mdResult.data === 'string') { const jsonMatch = extractJsonFromMarkdown(trimmed); if (jsonMatch) { lineOffset = jsonMatch.lineOffset; const parsed = parseJsonWithSourceMap(mdResult.data, lineOffset); if (parsed) { parsedData = parsed.data; sourceMap = parsed.map; } else { errors.push({ message: 'Failed to parse JSON from markdown code block', path: '/', }); return { success: false, errors }; } } else { parsedData = mdResult.data; } } else { parsedData = mdResult.data; } } else { try { const parsed = parseJsonWithSourceMap(trimmed, 0); if (parsed) { parsedData = parsed.data; sourceMap = parsed.map; } else { errors.push({ message: 'Failed to parse JSON with source mapping', path: '/', }); return { success: false, errors }; } } catch (e) { const jsonError = e; const lineMatch = jsonError.message.match(/position\s+(\d+)/); let line = 1; let column = 1; if (lineMatch) { const pos = parseInt(lineMatch[1], 10); const lines = trimmed.substring(0, pos).split('\n'); line = lines.length; column = lines[lines.length - 1].length + 1; } errors.push({ message: `JSON parse error: ${jsonError.message}`, path: '/', line, column, }); return { success: false, errors }; } } const result = CallGuard_DataSchema.safeParse(parsedData); if (result.success) { return { success: true, data: result.data, errors: [] }; } for (const issue of result.error.issues) { const path = formatZodPath(issue.path); let line; let column; if (sourceMap?.pointers[path]) { const pointer = sourceMap.pointers[path]; if (pointer.value) { line = pointer.value.line; column = pointer.value.column; } else if (pointer.key) { line = pointer.key.line; column = pointer.key.column; } } let message = issue.message; if (issue.code === 'invalid_union') { const unionIssue = issue; if (unionIssue.errors) { const unionErrors = unionIssue.errors.map((e) => e.issues?.map((i) => i.message).join('; ') || e.message).join(' | '); message = `${message}. Alternatives: ${unionErrors}`; } } errors.push({ message, path, line, column, }); } return { success: false, errors }; } export function formatGuardErrors(errors) { return errors.map(e => { let msg = `Path: ${e.path}`; if (e.line !== undefined && e.column !== undefined) { msg += ` (line ${e.line}, column ${e.column})`; } msg += `\n Error: ${e.message}`; return msg; }).join('\n\n'); } export function validateGuardNode(node) { const result = GuardNodeSchema.safeParse(node); if (result.success) { return { success: true, errors: [] }; } const errors = result.error.issues.map(issue => ({ message: issue.message, path: formatZodPath(issue.path), })); return { success: false, errors }; }