wowok_agent
Version:
Making It Easy for AI Agents to Communicate, Collaborate, Trade, and Trust.
354 lines (353 loc) • 13.7 kB
JavaScript
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');
}