UNPKG

wowok_agent

Version:

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

354 lines (353 loc) 13.7 kB
import { NodeSchema } from '../call/machine.js'; import { MachineNodeSchema } from '../query/index.js'; import { writeFileSync } from 'fs'; 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('[') || jsonContent.startsWith('{')) { const beforeMatch = markdown.substring(0, match.index); const lineOffset = (beforeMatch.match(/\n/g) || []).length + 1; lastMatch = { json: jsonContent, lineOffset }; } } return lastMatch; } function parseMarkdownToNodeData(markdown) { const errors = []; const jsonMatch = extractJsonFromMarkdown(markdown); if (jsonMatch) { try { const data = JSON.parse(jsonMatch.json); return { data, errors: [] }; } catch (e) { errors.push({ message: `Failed to parse JSON from markdown: ${e.message}`, path: '/', }); return { data: null, errors }; } } const result = { op: 'add' }; const nodes = []; const opMatch = markdown.match(/(?:^|\n)#+\s*(?:Operation|操作类型)[^\n]*\n+[-*]\s*op\s*:\s*(\w+)/i); if (opMatch) { result.op = opMatch[1].toLowerCase(); } const replaceMatch = markdown.match(/(?:^|\n)#+\s*(?:Options|选项)[^\n]*\n+[-*]\s*bReplace\s*:\s*(true|false)/i); if (replaceMatch) { result.bReplace = replaceMatch[1].toLowerCase() === 'true'; } const nodeSections = markdown.split(/(?=^##\s*(?:Node|节点)[:\s])/m); for (const section of nodeSections.slice(1)) { const nodeNameMatch = section.match(/^##\s*(?:Node|节点)[:\s]+(.+?)(?:\n|$)/im); if (!nodeNameMatch) continue; const nodeName = nodeNameMatch[1].trim(); const node = { name: nodeName, pairs: [] }; const pairSections = section.split(/(?=^###\s*(?:Pair|节点对|Previous Node)[:\s])/m); for (const pairSection of pairSections.slice(1)) { const prevNodeMatch = pairSection.match(/(?:^|\n)[-*]\s*prev_node\s*[::]\s*(.+?)(?:\n|$)/im) || pairSection.match(/^###\s*(?:Pair|节点对|Previous Node)[:\s]+(.+?)(?:\n|$)/im); const thresholdMatch = pairSection.match(/(?:^|\n)[-*]\s*threshold\s*[::]\s*(\d+)/i); if (!prevNodeMatch) continue; const pair = { prev_node: prevNodeMatch[1].trim(), threshold: thresholdMatch ? parseInt(thresholdMatch[1], 10) : 0, forwards: [] }; const tableRows = pairSection.match(/\|[^\n]+\|/g); if (tableRows && tableRows.length > 2) { for (const row of tableRows.slice(2)) { const cells = row.split('|').filter(c => c.trim()).map(c => c.trim()); if (cells.length >= 2) { const forward = { name: cells[0], weight: parseInt(cells[1], 10) || 0 }; if (cells.length >= 3 && cells[2] && cells[2] !== '-') { forward.namedOperator = cells[2]; } if (cells.length >= 4 && cells[3] && cells[3] !== '-') { forward.permissionIndex = parseInt(cells[3], 10); } if (cells.length >= 5 && cells[4] && cells[4] !== '-') { forward.guard = cells[4]; } pair.forwards.push(forward); } } } const forwardMatches = pairSection.matchAll(/(?:^|\n)[-*]\s*(?:forward)?\s*[::]?\s*\n?([\s\S]*?)(?=(?:\n[-*]\s*(?:forward|prev_node|threshold))|\n###|\n##|$)/gi); for (const forwardMatch of forwardMatches) { const forwardSection = forwardMatch[1]; const nameMatch = forwardSection.match(/\s*name\s*[::]\s*(.+?)(?:\n|$)/im); const weightMatch = forwardSection.match(/\s*weight\s*[::]\s*(\d+)/i); if (nameMatch && weightMatch) { const forward = { name: nameMatch[1].trim(), weight: parseInt(weightMatch[1], 10) }; const namedOperatorMatch = forwardSection.match(/\s*namedOperator\s*[::]\s*(.+?)(?:\n|$)/im); const permissionIndexMatch = forwardSection.match(/\s*permissionIndex\s*[::]\s*(\d+)/i); const guardMatch = forwardSection.match(/\s*guard\s*[::]\s*(.+?)(?:\n|$)/im); if (namedOperatorMatch) { forward.namedOperator = namedOperatorMatch[1].trim(); } if (permissionIndexMatch) { forward.permissionIndex = parseInt(permissionIndexMatch[1], 10); } if (guardMatch) { forward.guard = guardMatch[1].trim(); } pair.forwards.push(forward); } } node.pairs.push(pair); } nodes.push(node); } if (nodes.length > 0) { result.nodes = nodes; } return { data: result, errors }; } function formatZodPath(path) { return '/' + path.map(p => String(p).replace(/~/g, '~0').replace(/\//g, '~1')).join('/'); } function parseJsonWithErrors(text) { const errors = []; try { const data = JSON.parse(text); return { data, 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 = text.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 { data: null, errors }; } } function validateWithZod(schema, data, basePath = '') { const errors = []; const result = schema.safeParse(data); if (result.success) { return { success: true, data: result.data, errors: [] }; } for (const issue of result.error.issues) { const path = basePath + formatZodPath(issue.path); errors.push({ message: issue.message, path, }); } return { success: false, errors }; } export function parseNodeFromText(text) { const trimmed = text.trim(); const format = detectFormat(trimmed); let parsedData; let errors = []; if (format === 'markdown') { const mdResult = parseMarkdownToNodeData(trimmed); if (mdResult.errors.length > 0) { return { success: false, errors: mdResult.errors }; } parsedData = mdResult.data; } else { const jsonResult = parseJsonWithErrors(trimmed); if (jsonResult.errors.length > 0) { return { success: false, errors: jsonResult.errors }; } parsedData = jsonResult.data; } const validation = validateWithZod(NodeSchema, parsedData); return { success: validation.success, data: validation.data, errors: validation.errors, }; } export function parseMachineNodesFromText(text) { const trimmed = text.trim(); const format = detectFormat(trimmed); let parsedData; let errors = []; if (format === 'markdown') { const mdResult = parseMarkdownToNodeData(trimmed); if (mdResult.errors.length > 0) { return { success: false, errors: mdResult.errors }; } if (typeof mdResult.data === 'object' && mdResult.data !== null && 'nodes' in mdResult.data) { parsedData = mdResult.data.nodes; } else { parsedData = mdResult.data; } } else { const jsonResult = parseJsonWithErrors(trimmed); if (jsonResult.errors.length > 0) { return { success: false, errors: jsonResult.errors }; } parsedData = jsonResult.data; } if (!Array.isArray(parsedData)) { if (typeof parsedData === 'object' && parsedData !== null && 'op' in parsedData) { return { success: false, errors: [{ message: 'Expected JSON array of nodes for json_or_markdown mode. Use schema mode for operations with "op" field.', path: '/', }] }; } return { success: false, errors: [{ message: 'Expected JSON array of nodes', path: '/', }] }; } const nodes = []; for (let i = 0; i < parsedData.length; i++) { const nodeData = parsedData[i]; const validation = validateWithZod(MachineNodeSchema, nodeData, `/${i}`); if (validation.success && validation.data) { nodes.push(validation.data); } else { errors.push(...validation.errors); } } if (errors.length > 0) { return { success: false, errors }; } return { success: true, data: nodes, errors: [] }; } export function formatNodeErrors(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 validateMachineNode(node) { return validateWithZod(MachineNodeSchema, node); } export function validateMachineNodePair(pair) { const result = MachineNodeSchema.shape.pairs.element.safeParse(pair); 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 }; } export function validateMachineForward(forward) { const result = MachineNodeSchema.shape.pairs.element.shape.forwards.element.safeParse(forward); 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 }; } export function machineNodesToJson(nodes, options = {}) { const { machineId, includeComments = true } = options; let result = ''; if (includeComments && machineId) { result += `// Machine ID: ${machineId}\n`; } result += JSON.stringify(nodes, null, 2); return result; } export function machineNodesToMarkdown(nodes, options = {}) { const { machineName, machineAddress, machineId, includeComments = true } = options; let md = `# Machine Node Definition\n\n`; if (machineName) { md += `**Machine:** ${machineName}\n\n`; } if (machineAddress) { md += `**Address:** ${machineAddress}\n\n`; } if (machineId) { md += `**ID:** ${machineId}\n\n`; } md += `**Total Nodes:** ${nodes.length}\n\n`; md += `---\n\n`; for (const node of nodes) { md += `## Node: ${node.name}\n\n`; if (includeComments) { md += `<!-- Node definition with pairs and forwards -->\n\n`; } for (const pair of node.pairs) { md += `### Pair: ${pair.prev_node}\n\n`; md += `- **prev_node**: ${pair.prev_node}\n`; md += `- **threshold**: ${pair.threshold}\n\n`; if (pair.forwards && pair.forwards.length > 0) { md += `#### Forwards\n\n`; md += `| name | weight | namedOperator | permissionIndex | guard |\n`; md += `|------|--------|---------------|-----------------|-------|\n`; for (const forward of pair.forwards) { const namedOp = forward.namedOperator || ''; const permIdx = forward.permissionIndex?.toString() || ''; const guard = forward.guard?.guard || ''; md += `| ${forward.name} | ${forward.weight} | ${namedOp} | ${permIdx} | ${guard} |\n`; } md += `\n`; } } md += `---\n\n`; } md += `## JSON Definition\n\n`; if (includeComments) { md += `<!-- You can also use the JSON format below -->\n\n`; } md += `\`\`\`json\n`; if (includeComments && machineId) { md += `// Machine ID: ${machineId}\n`; } md += JSON.stringify(nodes, null, 2); md += `\n\`\`\`\n`; return md; } export function saveMachineNodesToFile(nodes, filePath, format = 'json', options = {}) { const content = format === 'json' ? machineNodesToJson(nodes, options) : machineNodesToMarkdown(nodes, options); writeFileSync(filePath, content, 'utf-8'); }