/** * Pulls the actual HTML document out of an agent's possibly chatty response. * Agents sometimes wrap output in ```html ... ``` fences or prepend explanation. */ export function extractHtml(streamed: string): string { if (!streamed) return ""; // 1. Strip leading ```html fence (and trailing ```) const fence = streamed.match(/```(?:html|HTML)?\s*([\s\S]*?)```/); if (fence) { const inner = fence[1].trim(); if (inner.startsWith("<")) return inner; } // 2. Find const doctypeStart = streamed.search(/"); if (closeIdx !== -1) { return streamed.slice(doctypeStart, closeIdx + "".length); } // streaming, partial — return from doctype to end return streamed.slice(doctypeStart); } // 3. Find ... const htmlStart = streamed.search(/]/i); if (htmlStart !== -1) { const closeIdx = streamed.lastIndexOf(""); if (closeIdx !== -1) { return streamed.slice(htmlStart, closeIdx + "".length); } return streamed.slice(htmlStart); } // 4. If it begins with < (root element), trust it if (streamed.trimStart().startsWith("<")) { return streamed; } // 5. Wrap whatever we got in a minimal scaffold so something renders return `
${escape(
    streamed,
  )}
`; } function escape(s: string): string { return s .replace(/&/g, "&") .replace(//g, ">"); } /** * For previews while the stream is still arriving — make sure we always * produce a closing so the iframe can render incrementally. */ export function previewHtml(streamed: string): string { const html = extractHtml(streamed); if (!html) return ""; if (/<\/html>/i.test(html)) return html; return html + "\n\n"; }