- Full IDE with terminal integration using xterm.js - Session management with local and web sessions - HTML preview functionality - Multi-terminal support with session picker Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
237 lines
5.4 KiB
JavaScript
237 lines
5.4 KiB
JavaScript
/**
|
|
* Tag Parser for Dyad-style XML-like tags
|
|
* Extracts operations from LLM responses
|
|
*/
|
|
|
|
/**
|
|
* Parse all dyad-write tags from response
|
|
* Format: <dyad-write path="file.ext">content</dyad-write>
|
|
*/
|
|
function getDyadWriteTags(response) {
|
|
const tags = [];
|
|
const regex = /<dyad-write\s+path="([^"]+)">([\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: <dyad-rename from="old.ext" to="new.ext">
|
|
*/
|
|
function getDyadRenameTags(response) {
|
|
const tags = [];
|
|
const regex = /<dyad-rename\s+from="([^"]+)"\s+to="([^"]+)">/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: <dyad-delete path="file.ext">
|
|
*/
|
|
function getDyadDeleteTags(response) {
|
|
const tags = [];
|
|
const regex = /<dyad-delete\s+path="([^"]+)">/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: <dyad-add-dependency packages="pkg1 pkg2">
|
|
*/
|
|
function getDyadAddDependencyTags(response) {
|
|
const tags = [];
|
|
const regex = /<dyad-add-dependency\s+packages="([^"]+)">/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: <dyad-command type="rebuild|restart|refresh">
|
|
*/
|
|
function getDyadCommandTags(response) {
|
|
const tags = [];
|
|
const regex = /<dyad-command\s+type="([^"]+)">/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: <dyad-chat-summary>summary text</dyad-chat-summary>
|
|
*/
|
|
function getDyadChatSummary(response) {
|
|
const match = response.match(/<dyad-chat-summary>([\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(/<dyad-write\s+path="[^"]+">[\s\S]*?<\/dyad-write>/g, '[File operation]');
|
|
|
|
// Remove dyad-rename tags
|
|
stripped = stripped.replace(/<dyad-rename\s+from="[^"]+"\s+to="[^"]+">/g, '[Rename operation]');
|
|
|
|
// Remove dyad-delete tags
|
|
stripped = stripped.replace(/<dyad-delete\s+path="[^"]+">/g, '[Delete operation]');
|
|
|
|
// Remove dyad-add-dependency tags
|
|
stripped = stripped.replace(/<dyad-add-dependency\s+packages="[^"]+">/g, '[Install packages]');
|
|
|
|
// Remove dyad-command tags
|
|
stripped = stripped.replace(/<dyad-command\s+type="[^"]+">/g, '[Command]');
|
|
|
|
// Remove dyad-chat-summary tags
|
|
stripped = stripped.replace(/<dyad-chat-summary>[\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
|
|
};
|