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