/** * Tag Parser for Dyad-style XML-like tags * Extracts operations from LLM responses */ /** * Parse all dyad-write tags from response * Format: content */ function getDyadWriteTags(response) { const tags = []; const regex = /([\s\S]*?)<\/dyad-write>/g; let match; while ((match = regex.exec(response)) !== null) { tags.push({ type: 'write', path: match[1], content: match[2].trim() }); } return tags; } /** * Parse all dyad-rename tags from response * Format: */ function getDyadRenameTags(response) { const tags = []; const regex = //g; let match; while ((match = regex.exec(response)) !== null) { tags.push({ type: 'rename', from: match[1], to: match[2] }); } return tags; } /** * Parse all dyad-delete tags from response * Format: */ function getDyadDeleteTags(response) { const tags = []; const regex = //g; let match; while ((match = regex.exec(response)) !== null) { tags.push({ type: 'delete', path: match[1] }); } return tags; } /** * Parse all dyad-add-dependency tags from response * Format: */ function getDyadAddDependencyTags(response) { const tags = []; const regex = //g; let match; while ((match = regex.exec(response)) !== null) { tags.push({ type: 'add-dependency', packages: match[1].split(' ').filter(p => p.trim()) }); } return tags; } /** * Parse all dyad-command tags from response * Format: */ function getDyadCommandTags(response) { const tags = []; const regex = //g; let match; while ((match = regex.exec(response)) !== null) { tags.push({ type: 'command', command: match[1] }); } return tags; } /** * Parse all dyad-chat-summary tags from response * Format: summary text */ function getDyadChatSummary(response) { const match = response.match(/([\s\S]*?)<\/dyad-chat-summary>/); if (match) { return { type: 'chat-summary', summary: match[1].trim() }; } return null; } /** * Extract all tags from response in order * Returns structured object with all tag types */ function extractAllTags(response) { return { writes: getDyadWriteTags(response), renames: getDyadRenameTags(response), deletes: getDyadDeleteTags(response), dependencies: getDyadAddDependencyTags(response), commands: getDyadCommandTags(response), chatSummary: getDyadChatSummary(response) }; } /** * Check if response contains any actionable tags */ function hasActionableTags(response) { const tags = extractAllTags(response); return ( tags.writes.length > 0 || tags.renames.length > 0 || tags.deletes.length > 0 || tags.dependencies.length > 0 || tags.commands.length > 0 ); } /** * Strip all tags from response for display purposes */ function stripTags(response) { let stripped = response; // Remove dyad-write tags stripped = stripped.replace(/[\s\S]*?<\/dyad-write>/g, '[File operation]'); // Remove dyad-rename tags stripped = stripped.replace(//g, '[Rename operation]'); // Remove dyad-delete tags stripped = stripped.replace(//g, '[Delete operation]'); // Remove dyad-add-dependency tags stripped = stripped.replace(//g, '[Install packages]'); // Remove dyad-command tags stripped = stripped.replace(//g, '[Command]'); // Remove dyad-chat-summary tags stripped = stripped.replace(/[\s\S]*?<\/dyad-chat-summary>/g, ''); return stripped; } /** * Generate a summary of operations for approval UI */ function generateOperationSummary(tags) { const operations = []; tags.deletes.forEach(tag => { operations.push({ type: 'delete', description: `Delete ${tag.path}`, tag }); }); tags.renames.forEach(tag => { operations.push({ type: 'rename', description: `Rename ${tag.from} → ${tag.to}`, tag }); }); tags.writes.forEach(tag => { operations.push({ type: 'write', description: `Create/update ${tag.path}`, tag }); }); tags.dependencies.forEach(tag => { operations.push({ type: 'install', description: `Install packages: ${tag.packages.join(', ')}`, tag }); }); tags.commands.forEach(tag => { operations.push({ type: 'command', description: `Execute command: ${tag.command}`, tag }); }); return operations; } module.exports = { getDyadWriteTags, getDyadRenameTags, getDyadDeleteTags, getDyadAddDependencyTags, getDyadCommandTags, getDyadChatSummary, extractAllTags, hasActionableTags, stripTags, generateOperationSummary };