Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion runner/arazzo_runner/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,10 @@ async def handle_execute_operation(runner: ArazzoRunner | None, args: argparse.N
if isinstance(result, dict) and 'headers' in result:
result = dict(result) # Make a shallow copy to avoid mutating originals
result.pop('headers')
logger.info(f"Operation Result: {json.dumps(result, indent=2)}")
try:
logger.info(f"Operation Result: {json.dumps(result, indent=2)}")
except Exception as e:
logger.info(f"Operation Result: {str(result)}")
# Determine exit code based on HTTP status (e.g., 2xx is success)
status_code = result.get("status_code", 500)
sys.exit(0 if 200 <= status_code < 300 else 1)
Expand Down
124 changes: 113 additions & 11 deletions runner/arazzo_runner/evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
Expression Evaluator for Arazzo Runner

This module provides functions for evaluating runtime expressions used in Arazzo workflows.
Supports regex-based transformations through x-transform configurations.
"""

import logging
import re
from typing import Any
from typing import Any, Dict, List, Union

from .models import ExecutionState

Expand Down Expand Up @@ -151,6 +152,79 @@ def handle_array_access(expression: str, state: ExecutionState) -> Any | None:

return None

@staticmethod
def apply_regex_transforms(value: Any, transforms: List[Dict[str, Any]]) -> Any:
"""
Apply regex transformations to a value. Much simpler approach.

Args:
value: Input value to transform
transforms: List of transform configs with 'pattern' and 'result'

Returns:
Transformed value
"""
if not transforms:
return value

current_value = str(value) if value is not None else ""

for transform in transforms:
if transform.get("type") != "regex":
continue

pattern = transform.get("pattern")
result_template = transform.get("result")

if not pattern or not result_template:
continue

try:
match = re.search(pattern, current_value)
if match:
result = result_template

# First, handle escaping by temporarily replacing escaped sequences
# \\1 -> literal \1, \\<name> -> literal \<name>
escape_map = {}

# Handle escaped backslashes (\\1 -> \1)
escape_counter = 0
while f"\\\\{escape_counter + 1}" in result:
escape_counter += 1
placeholder = f"__ESCAPED_BACKSLASH_{escape_counter}__"
result = result.replace(f"\\\\{escape_counter}", placeholder)
escape_map[placeholder] = f"\\{escape_counter}"

# Handle escaped named groups (\\<name> -> \<name>)
import re as re_module
escaped_named_pattern = r'\\\\<([^>]+)>'
escaped_named_matches = re_module.findall(escaped_named_pattern, result)
for i, name in enumerate(escaped_named_matches):
placeholder = f"__ESCAPED_NAMED_{i}__"
result = result.replace(f"\\\\<{name}>", placeholder)
escape_map[placeholder] = f"\\<{name}>"

# Replace named groups: \<name> -> matched value
for name, value in match.groupdict().items():
if value is not None:
result = result.replace(f"\\<{name}>", value)

# Replace numbered groups: \1, \2, etc.
for i, group in enumerate(match.groups(), 1):
if group is not None:
result = result.replace(f"\\{i}", group)

# Restore escaped sequences
for placeholder, literal in escape_map.items():
result = result.replace(placeholder, literal)

current_value = result
except re.error:
continue # Skip invalid patterns

return current_value

@staticmethod
def evaluate_expression(
expression: str,
Expand Down Expand Up @@ -531,7 +605,7 @@ def replace_expr(match):
def process_object_expressions(
obj: dict, state: ExecutionState, source_descriptions: dict[str, Any] = None
) -> dict:
"""Process dictionary values, evaluating any expressions"""
"""Process dictionary values, evaluating expressions and applying regex transforms"""
if not isinstance(obj, dict):
return obj

Expand All @@ -543,10 +617,23 @@ def process_object_expressions(
value, state, source_descriptions
)
elif isinstance(value, dict):
# Process nested dictionary
result[key] = ExpressionEvaluator.process_object_expressions(
value, state, source_descriptions
)
# Check if this is a parameter with x-transform
if "value" in value and "x-transform" in value:
# Evaluate the base value first
base_value = value.get("value")
if isinstance(base_value, str) and base_value.startswith("$"):
base_value = ExpressionEvaluator.evaluate_expression(
base_value, state, source_descriptions
)
# Apply transforms
result[key] = ExpressionEvaluator.apply_regex_transforms(
base_value, value.get("x-transform", [])
)
else:
# Process nested dictionary
result[key] = ExpressionEvaluator.process_object_expressions(
value, state, source_descriptions
)
elif isinstance(value, list):
# Process nested list
result[key] = ExpressionEvaluator.process_array_expressions(
Expand All @@ -560,7 +647,7 @@ def process_object_expressions(
def process_array_expressions(
arr: list, state: ExecutionState, source_descriptions: dict[str, Any] = None
) -> list:
"""Process list values, evaluating any expressions"""
"""Process list values, evaluating expressions and applying regex transforms"""
if not isinstance(arr, list):
return arr

Expand All @@ -572,10 +659,25 @@ def process_array_expressions(
ExpressionEvaluator.evaluate_expression(item, state, source_descriptions)
)
elif isinstance(item, dict):
# Process nested dictionary
result.append(
ExpressionEvaluator.process_object_expressions(item, state, source_descriptions)
)
# Check if this is a parameter with x-transform
if "value" in item and "x-transform" in item:
# Evaluate the base value first
base_value = item.get("value")
if isinstance(base_value, str) and base_value.startswith("$"):
base_value = ExpressionEvaluator.evaluate_expression(
base_value, state, source_descriptions
)
# Apply transforms
result.append(
ExpressionEvaluator.apply_regex_transforms(
base_value, item.get("x-transform", [])
)
)
else:
# Process nested dictionary
result.append(
ExpressionEvaluator.process_object_expressions(item, state, source_descriptions)
)
elif isinstance(item, list):
# Process nested list
result.append(
Expand Down
Loading
Loading