UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

284 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TEMPLATE_DATA_PATTERN = void 0; exports.resolveExpression = resolveExpression; exports.interpolateString = interpolateString; exports.interpolateObject = interpolateObject; exports.extractInterpolationExpressions = extractInterpolationExpressions; const JsonPath_1 = require("./JsonPath"); const Logger_1 = require("./Logger"); /** * Regular expression to match template JSON-path expressions inside of {{...}} */ exports.TEMPLATE_DATA_PATTERN = /\{\{([^}]+)\}\}/g; /** * Format a value to string with special handling for objects and arrays * @param value - The value to format * @returns A string representation of the value */ function formatValue(value) { if (value === undefined || value === null) { return ''; } // Handle arrays by joining their string representations if (Array.isArray(value)) { return value.map(formatValue).join(','); } // Handle objects with proper JSON formatting if (typeof value === 'object' && value !== null) { return JSON.stringify(value); } // Default case return String(value); } /** * Resolve a JSONPath expression against the context * @param expression - The JSONPath expression to evaluate * @param dataSource - The data context to evaluate against * @returns The resolved value or undefined if not found */ function resolveExpression(expression, dataSource) { try { // Handle expressions with wildcards or recursive searches that might return arrays const isWildcardOrRecursive = expression.includes('..') || expression.includes('*') || expression.includes('[:]'); const results = (0, JsonPath_1.queryJsonPath)(dataSource, expression); // Return the entire array for expressions that clearly want multiple values if (isWildcardOrRecursive && results.length > 1) { return results; } // Default case - return the first result if available if (results && results.length > 0) { return results[0]; } return undefined; } catch (error) { throw new Error(`Failed to evaluate JSONPath expression: ${expression}. Error: ${error}`); } } /** * Interpolate template strings using JSONPath expressions, handling nested expressions * @param template - The template string with {{...}} expressions * @param dataSource - The data context to interpolate from * @param recursionDepth - Current recursion depth (for loop detection) * @param maxRecursionDepth - Maximum allowed recursion depth * @param processed - Set of already processed templates (for loop detection) * @returns The interpolated string */ function interpolateString(template, dataSource, recursionDepth = 0, maxRecursionDepth = 10, processed = new Set()) { // Check for recursion depth limits if (recursionDepth > maxRecursionDepth) { Logger_1.appLogger.warn(`Maximum interpolation depth (${maxRecursionDepth}) exceeded for template: ${template.substring(0, 100)}${template.length > 100 ? '...' : ''}`); return template; } // Base case: no more expressions to interpolate if (!template.includes('{{')) { return template; } // Check if we've already processed this exact template (loop detection) if (processed.has(template)) { Logger_1.appLogger.warn(`Circular reference detected in template: ${template.substring(0, 100)}${template.length > 100 ? '...' : ''}`); return template; } // Add this template to the processed set processed.add(template); let result = template; const processedSegments = []; // [start, end, replacement] // Find and process expressions from outermost to innermost let i = 0; while (i < result.length) { // Find opening braces const openIndex = result.indexOf('{{', i); if (openIndex === -1) { break; } // Find matching closing braces with proper nesting let braceDepth = 1; let closeIndex = openIndex + 2; while (closeIndex < result.length && braceDepth > 0) { if (result.substr(closeIndex, 2) === '{{') { braceDepth++; closeIndex += 2; } else if (result.substr(closeIndex, 2) === '}}') { braceDepth--; closeIndex += 2; } else { closeIndex++; } } // If we found a complete expression if (braceDepth === 0) { const expressionContent = result .substring(openIndex + 2, closeIndex - 2) .trim(); try { // If the expression contains nested templates, process it first, passing along depth info const processedExpression = interpolateString(expressionContent, dataSource, recursionDepth + 1, maxRecursionDepth, new Set(processed)); // Then resolve the processed expression const value = resolveExpression(processedExpression, dataSource); // If the value is undefined, keep the original expression const replacement = value !== undefined ? formatValue(value) : `{{${expressionContent}}}`; // Store the replacement for later processedSegments.push([openIndex, closeIndex, replacement]); i = closeIndex; } catch (error) { Logger_1.appLogger.warn(`Error interpolating template: ${template}, expression: ${expressionContent}, error: ${error}`); i = closeIndex; } } else { // No matching closing braces, move past this opening brace i = openIndex + 2; } } // Apply replacements in reverse order (from end to start) to maintain correct indices for (let i = processedSegments.length - 1; i >= 0; i--) { const [start, end, replacement] = processedSegments[i]; result = result.substring(0, start) + replacement + result.substring(end); } // Recursively process the result again for any new expressions, but only if the result changed if (result.includes('{{') && result !== template) { return interpolateString(result, dataSource, recursionDepth + 1, maxRecursionDepth, processed); } return result; } /** * Interpolate the parameters object using JSONPath expressions. Any string values * and keys in the object will be replaced with the corresponding values from the data source. * Nested objects and arrays will be processed recursively. * * @param parameters - The original parameters object * @param dataSource - The data context to interpolate from * @returns A new parameters object with interpolated string values and keys */ function interpolateObject(parameters, dataSource, recursionDepth = 0, maxRecursionDepth = 10, processedObjects = new WeakSet()) { // Check for primitive values if (!parameters || typeof parameters !== 'object') { return parameters; } // Check for recursion depth limits if (recursionDepth > maxRecursionDepth) { Logger_1.appLogger.warn(`Maximum interpolation depth (${maxRecursionDepth}) exceeded for object`); return parameters; } // Check for circular references in objects if (processedObjects.has(parameters)) { Logger_1.appLogger.warn(`Circular reference detected in object`); return parameters; } // Add this object to the processed set processedObjects.add(parameters); // Create appropriate result container const result = Array.isArray(parameters) ? [] : {}; // Process each key-value pair if (Array.isArray(parameters)) { // Handle array case for (let i = 0; i < parameters.length; i++) { const value = parameters[i]; if (typeof value === 'string') { // Interpolate string values result[i] = interpolateString(value, dataSource, 0, maxRecursionDepth); } else if (typeof value === 'object' && value !== null) { // Recursively process nested objects result[i] = interpolateObject(value, dataSource, recursionDepth + 1, maxRecursionDepth, processedObjects); } else { // Keep non-string values as is result[i] = value; } } } else { // Handle object case for (const key in parameters) { const value = parameters[key]; // Interpolate the key if it's a string and contains a template pattern let processedKey = key; if (typeof key === 'string' && key.includes('{{')) { processedKey = interpolateString(key, dataSource, 0, maxRecursionDepth); } if (typeof value === 'string') { // Interpolate string values result[processedKey] = interpolateString(value, dataSource, 0, maxRecursionDepth); } else if (typeof value === 'object' && value !== null) { // Recursively process nested objects result[processedKey] = interpolateObject(value, dataSource, recursionDepth + 1, maxRecursionDepth, processedObjects); } else { // Keep non-string values as is result[processedKey] = value; } } } return result; } /** * Extracts interpolation expressions from a template string. * * @param template - The template string with {{...}} expressions * @returns An array of the extracted expressions without the {{ }} delimiters * * @example * // Returns ["env.USER_NAME"] * extractInterpolationExpressions("Hello {{env.USER_NAME}}, how are you?"); * * @example * // Returns ["env.API_KEY", "calls[0].result"] * extractInterpolationExpressions("Use {{env.API_KEY}} to access {{calls[0].result}}"); */ function extractInterpolationExpressions(template) { const expressions = []; // Extract expressions recursively function extractExpressions(text) { let i = 0; while (i < text.length) { // Find opening braces const openIndex = text.indexOf('{{', i); if (openIndex === -1) { break; } // Find matching closing braces let braceDepth = 1; let closeIndex = openIndex + 2; while (closeIndex < text.length && braceDepth > 0) { if (text.substr(closeIndex, 2) === '{{') { braceDepth++; closeIndex += 2; } else if (text.substr(closeIndex, 2) === '}}') { braceDepth--; closeIndex += 2; } else { closeIndex++; } } // If we found a complete expression if (braceDepth === 0) { // Extract the content between {{ and }} const expressionContent = text .substring(openIndex + 2, closeIndex - 2) .trim(); expressions.push(expressionContent); // Recursively look for nested expressions within this expression extractExpressions(expressionContent); i = closeIndex; } else { // No matching closing braces, move past this opening brace i = openIndex + 2; } } } extractExpressions(template); // Return unique expressions return [...new Set(expressions)]; } //# sourceMappingURL=TemplateInterpolator.js.map