donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
284 lines • 11.9 kB
JavaScript
;
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