"""Generate stub Flow XML for .agent file action targets."""
from __future__ import annotations
# Mapping from .agent types to Flow variable dataTypes
_TYPE_MAP = {
"string": "String",
"number": "Number",
"boolean": "Boolean",
"date": "Date",
"datetime": "DateTime",
"id": "String",
"object": "Apex",
}
# Mapping from complex_data_type_name to Flow variable dataTypes (for action I/O)
_COMPLEX_TYPE_MAP = {
"lightning__integerType": "Number",
"lightning__numberType": "Number",
"lightning__doubleType": "Number",
"lightning__currencyType": "Currency",
"lightning__dateTimeStringType": "DateTime",
"lightning__recordInfoType": "SObject",
"lightning__objectType": "Apex",
"lightning__listType": "Apex",
"lightning__textType": "String",
}
API_VERSION = "63.0"
def generate_flow_xml(
api_name: str,
inputs: list[dict] | None = None,
outputs: list[dict] | None = None,
process_type: str = "AutoLaunchedFlow",
) -> str:
"""Generate a stub Flow XML with matching input/output variables.
Produces a minimal .flow-meta.xml that:
- Declares input variables matching action inputs
- Declares output variables matching action outputs
- Merges bidirectional variables (isInput=true, isOutput=true)
- Uses Active status so flows are immediately callable
- Has a single Assignment element as placeholder logic
Args:
api_name: The flow API name.
inputs: Action input definitions (list of dicts with 'name', 'type' keys).
outputs: Action output definitions (list of dicts with 'name', 'type' keys).
process_type: Flow process type (default: AutoLaunchedFlow).
Returns:
Flow XML string.
"""
inputs = inputs or []
outputs = outputs or []
input_names = {inp["name"] for inp in inputs}
bidirectional_names = input_names & {out["name"] for out in outputs}
lines = [
'',
'',
f' {API_VERSION}',
f' ',
f' {process_type}',
' Active',
' {!$Flow.CurrentDateTime}',
]
# Input variables
for inp in inputs:
flow_type = _COMPLEX_TYPE_MAP.get(inp.get("complex_data_type_name", ""), _TYPE_MAP.get(inp.get("type", "string"), "String"))
is_output = inp["name"] in bidirectional_names
lines.extend([
' ',
f' {inp["name"]}',
f' {flow_type}',
])
if flow_type == "Number":
lines.append(f' {_infer_scale(inp["name"])}')
lines.extend([
' false',
' true',
f' {"true" if is_output else "false"}',
])
if inp.get("description"):
lines.append(f' {_escape_xml(inp["description"])}')
lines.append(' ')
# Output-only variables
for out in outputs:
if out["name"] in bidirectional_names:
continue
flow_type = _COMPLEX_TYPE_MAP.get(out.get("complex_data_type_name", ""), _TYPE_MAP.get(out.get("type", "string"), "String"))
lines.extend([
' ',
f' {out["name"]}',
f' {flow_type}',
])
if flow_type == "Number":
lines.append(f' {_infer_scale(out["name"])}')
lines.extend([
' false',
' false',
' true',
])
if out.get("description"):
lines.append(f' {_escape_xml(out["description"])}')
lines.append(' ')
# Placeholder variable if no outputs
if not outputs:
lines.extend([
' ',
' placeholder_result',
' String',
' false',
' false',
' true',
' ',
])
# Placeholder assignment
lines.extend([
' ',
' Placeholder_Assignment',
' ',
' 176',
' 158',
])
for out in outputs:
flow_type = _COMPLEX_TYPE_MAP.get(out.get("complex_data_type_name", ""), _TYPE_MAP.get(out.get("type", "string"), "String"))
lines.extend([
' ',
f' {out["name"]}',
' Assign',
f' {_default_value_element_by_flow_type(flow_type)}',
' ',
])
if not outputs:
lines.extend([
' ',
' placeholder_result',
' Assign',
' TODO',
' ',
])
lines.extend([
' ',
' ',
' 50',
' 0',
' ',
' Placeholder_Assignment',
' ',
' ',
'',
])
return "\n".join(lines) + "\n"
def _infer_scale(name: str) -> int:
"""Infer decimal scale from variable name. Currency/amount/price → 2, else 0."""
currency_hints = {"balance", "amount", "price", "cost", "total", "credit", "fee", "rate", "pct", "percent", "utilization"}
lower = name.lower()
for hint in currency_hints:
if hint in lower:
return 2
return 0
def _escape_xml(text: str) -> str:
"""Escape XML special characters."""
return (
text
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace('"', """)
.replace("'", "'")
)
def _default_value_element(type_name: str) -> str:
"""Return a type-appropriate XML value element for a placeholder assignment."""
if type_name == "boolean":
return "false"
if type_name == "number":
return "0"
if type_name == "date":
return "2000-01-01"
if type_name == "datetime":
return "2000-01-01T00:00:00Z"
return "TODO"
def _default_value_element_by_flow_type(flow_type: str) -> str:
"""Return a type-appropriate XML value element based on resolved Flow dataType."""
if flow_type == "Boolean":
return "false"
if flow_type in ("Number", "Currency"):
return "0"
if flow_type == "Date":
return "2000-01-01"
if flow_type == "DateTime":
return "2000-01-01T00:00:00Z"
return "TODO"