Skip to main content
Glama
outputParser.ts6.74 kB
import { ParsedOutput } from './types.js'; /** * HTML Output Parser for ServiceNow Background Scripts * * Parses HTML responses from ServiceNow background script execution to extract * script output and format it for consumption by AI agents. */ /** * Parse HTML response from ServiceNow background script execution * * Extracts script output from HTML response, focusing on: * - <pre> blocks (typical script output location) * - Console-like output areas * - Error messages and stack traces * * @param htmlResponse - The HTML response from ServiceNow * @returns Parsed output with both HTML and cleaned text */ export function parseScriptOutput(htmlResponse: string): ParsedOutput { if (!htmlResponse || typeof htmlResponse !== 'string') { return { html: '', text: '', }; } // Extract <pre> blocks (most common location for script output) const preBlocks = extractPreBlocks(htmlResponse); // Extract other potential output areas const otherOutput = extractOtherOutput(htmlResponse); // Combine and clean the output const combinedText = combineOutputs(preBlocks, otherOutput); // Ensure text is always a string, never null or undefined const safeText = combinedText != null && typeof combinedText === 'string' ? combinedText : ''; return { html: htmlResponse, text: safeText, }; } /** * Extract content from <pre> blocks * * @param html - HTML content * @returns Array of text content from <pre> blocks */ function extractPreBlocks(html: string): string[] { const preRegex = /<pre[^>]*>(.*?)<\/pre>/gis; const matches: string[] = []; let match; while ((match = preRegex.exec(html)) !== null) { if (match[1]) { matches.push(cleanHtmlContent(match[1])); } } return matches; } /** * Extract output from other common areas where script output might appear * * @param html - HTML content * @returns Array of text content from other output areas */ function extractOtherOutput(html: string): string[] { const outputs: string[] = []; // Look for divs with specific classes that might contain output const outputDivRegex = /<div[^>]*class=['"][^'"]*(?:output|result|console|log)[^'"]*['"][^>]*>(.*?)<\/div>/gis; let match; while ((match = outputDivRegex.exec(html)) !== null) { if (match[1]) { outputs.push(cleanHtmlContent(match[1])); } } // Look for textarea elements (sometimes used for output) const textareaRegex = /<textarea[^>]*>(.*?)<\/textarea>/gis; while ((match = textareaRegex.exec(html)) !== null) { if (match[1]) { outputs.push(cleanHtmlContent(match[1])); } } return outputs; } /** * Clean HTML content by removing tags and decoding entities * * @param content - HTML content to clean * @returns Cleaned text content */ function cleanHtmlContent(content: string): string { if (!content) return ''; // Decode HTML entities let cleaned = content .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&amp;/g, '&') .replace(/&quot;/g, '"') .replace(/&#39;/g, "'") .replace(/&nbsp;/g, ' '); // Remove HTML tags cleaned = cleaned.replace(/<[^>]*>/g, ''); // Clean up whitespace cleaned = cleaned .replace(/\s+/g, ' ') .replace(/\n\s*\n/g, '\n') .trim(); return cleaned; } /** * Combine multiple output sources into a single text string * * @param preBlocks - Content from <pre> blocks * @param otherOutput - Content from other output areas * @returns Combined and formatted output */ function combineOutputs(preBlocks: string[], otherOutput: string[]): string { const allOutputs = [...preBlocks, ...otherOutput]; // Filter out empty outputs and ensure all are strings const nonEmptyOutputs = allOutputs.filter(output => output != null && typeof output === 'string' && output.trim().length > 0 ); if (nonEmptyOutputs.length === 0) { return ''; } // If we have multiple outputs, separate them if (nonEmptyOutputs.length > 1) { return nonEmptyOutputs.join('\n\n---\n\n'); } // Ensure we always return a string, never null const result = nonEmptyOutputs[0]; return result != null && typeof result === 'string' ? result : ''; } /** * Extract error information from HTML response * * @param html - HTML content * @returns Error message if found, null otherwise */ export function extractErrorMessage(html: string): string | null { if (!html) return null; // Look for common error patterns const errorPatterns = [ /<div[^>]*class=['"][^'"]*error[^'"]*['"][^>]*>(.*?)<\/div>/i, /<span[^>]*class=['"][^'"]*error[^'"]*['"][^>]*>(.*?)<\/span>/i, /Error:\s*(.*?)(?:\n|$)/i, /Exception:\s*(.*?)(?:\n|$)/i, ]; for (const pattern of errorPatterns) { const match = pattern.exec(html); if (match && match[1]) { const errorText = cleanHtmlContent(match[1]); if (errorText.trim().length > 0) { return errorText.trim(); } } } return null; } /** * Check if the HTML response indicates a successful execution * * @param html - HTML content * @returns True if execution appears successful */ export function isSuccessfulExecution(html: string): boolean { if (!html) return false; // Look for success indicators const successPatterns = [ /success/i, /completed/i, /executed/i, /finished/i, ]; // Look for error indicators const errorPatterns = [ /error/i, /exception/i, /failed/i, /timeout/i, ]; const hasSuccess = successPatterns.some(pattern => pattern.test(html)); const hasError = errorPatterns.some(pattern => pattern.test(html)); // If we have explicit success indicators and no errors, consider it successful if (hasSuccess && !hasError) { return true; } // If we have error indicators, consider it failed if (hasError) { return false; } // Default to successful if we have any content return html.trim().length > 0; } /** * Get metadata about the parsed output * * @param parsedOutput - Parsed output from parseScriptOutput * @returns Metadata about the output */ export function getOutputMetadata(parsedOutput: ParsedOutput): { hasOutput: boolean; textLength: number; htmlLength: number; hasErrors: boolean; isSuccessful: boolean; } { const hasOutput = parsedOutput.text.trim().length > 0; const textLength = parsedOutput.text.length; const htmlLength = parsedOutput.html.length; const hasErrors = extractErrorMessage(parsedOutput.html) !== null; const isSuccessful = isSuccessfulExecution(parsedOutput.html); return { hasOutput, textLength, htmlLength, hasErrors, isSuccessful, }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ClearSkye/SkyeNet-MCP-ACE'

If you have feedback or need assistance with the MCP directory API, please join our Discord server