"""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"