UNPKG

@vfarcic/dot-ai

Version:

AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance

144 lines (143 loc) 5.49 kB
"use strict"; /** * Shared Visualization Utilities (PRD #320) * * Common utilities for visualization support across all MCP tools. * Provides session metadata interfaces, URL generation, and prompt selection. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TOOL_SESSION_PREFIXES = exports.VISUALIZATION_PREFIX = void 0; exports.getPromptForTool = getPromptForTool; exports.getVisualizationUrl = getVisualizationUrl; exports.extractPrefixFromSessionId = extractPrefixFromSessionId; exports.getToolNameFromPrefix = getToolNameFromPrefix; exports.parseVisualizationResponse = parseVisualizationResponse; /** * Visualization mode prefix - when present in intent, return visualization data directly * Used by tools to detect when caller wants visualization output instead of summary */ exports.VISUALIZATION_PREFIX = '[visualization]'; /** * Get the prompt file name for visualization * All tools use the unified visualize.md template (PRD #320) * * @param toolName - Name of the tool (unused, kept for API compatibility) * @returns Prompt file name (without .md extension) */ function getPromptForTool(_toolName) { return 'visualize'; } /** * Get visualization URL if WEB_UI_BASE_URL is configured * Feature toggle - only returns URL when env var is set * * @param sessionIds - Single session ID or array of session IDs to include in URL * @returns Visualization URL or undefined if not configured */ function getVisualizationUrl(sessionIds) { const baseUrl = process.env.WEB_UI_BASE_URL; if (!baseUrl) { return undefined; } // Remove trailing slash if present, then append /v/{sessionId(s)} const normalizedBaseUrl = baseUrl.replace(/\/$/, ''); // Join multiple session IDs with + separator const sessionPath = Array.isArray(sessionIds) ? sessionIds.join('+') : sessionIds; return `${normalizedBaseUrl}/v/${sessionPath}`; } /** * Extract the session prefix from a session ID * Session IDs are formatted: {prefix}-{timestamp}-{uuid} * * @param sessionId - Full session ID (e.g., 'qry-1704067200000-a1b2c3d4') * @returns Session prefix (e.g., 'qry') */ function extractPrefixFromSessionId(sessionId) { const parts = sessionId.split('-'); // Return first part as prefix, default to 'qry' if invalid return parts[0] || 'qry'; } /** * Session prefixes used by each tool * Useful for documentation and validation */ exports.TOOL_SESSION_PREFIXES = { query: 'qry', recommend: 'sol', // "solution" - recommend tool provides solutions remediate: 'rem', operate: 'opr', version: 'ver', projectSetup: 'prj', }; /** * Get the tool name from a session prefix * Reverse lookup from prefix to tool name * * @param prefix - Session prefix (e.g., 'qry') * @returns Tool name or undefined if not recognized */ function getToolNameFromPrefix(prefix) { for (const [tool, toolPrefix] of Object.entries(exports.TOOL_SESSION_PREFIXES)) { if (toolPrefix === prefix) { return tool; } } return undefined; } /** * Parse AI response into VisualizationResponse * Extracts JSON from AI response, validates structure, normalizes insights * * @param aiResponse - Raw AI response string * @param toolsUsed - Optional array of tools used during generation * @returns Parsed VisualizationResponse * @throws Error if parsing or validation fails */ function parseVisualizationResponse(aiResponse, toolsUsed) { // Extract JSON from response - it may have text before/after the JSON block let jsonContent = aiResponse.trim(); // Find JSON block in markdown code fence const jsonBlockMatch = jsonContent.match(/```(?:json)?\s*([\s\S]*?)```/); if (jsonBlockMatch) { jsonContent = jsonBlockMatch[1].trim(); } else if (!jsonContent.startsWith('{')) { // Try to find raw JSON object if no code fence const jsonStart = jsonContent.indexOf('{'); const jsonEnd = jsonContent.lastIndexOf('}'); if (jsonStart !== -1 && jsonEnd !== -1) { jsonContent = jsonContent.substring(jsonStart, jsonEnd + 1); } } const parsed = JSON.parse(jsonContent); // Validate required fields if (!parsed.title || !Array.isArray(parsed.visualizations) || !Array.isArray(parsed.insights)) { throw new Error('Invalid visualization response structure'); } // Validate each visualization has required fields for (const viz of parsed.visualizations) { if (!viz.id || !viz.label || !viz.type || viz.content === undefined) { throw new Error(`Invalid visualization: missing required fields in ${JSON.stringify(viz)}`); } if (!['mermaid', 'cards', 'code', 'table', 'diff', 'bar-chart'].includes(viz.type)) { throw new Error(`Invalid visualization type: ${viz.type}`); } } // Normalize insights to strings if they are objects const normalizedInsights = parsed.insights.map((insight) => { if (typeof insight === 'string') { return insight; } // Convert object insights to string format if (insight.title && insight.description) { const severity = insight.severity ? ` [${insight.severity}]` : ''; return `${insight.title}${severity}: ${insight.description}`; } return String(insight); }); return { ...parsed, insights: normalizedInsights, ...(toolsUsed && { toolsUsed }) }; }