"""Robot Framework helper for actor compiler smoke tests. Provides a CLI interface for Robot to invoke the actor compiler on YAML-defined GRAPH actors and inspect compilation metadata. Usage: python robot/helper_actor_compiler.py compile python robot/helper_actor_compiler.py compile-fail python robot/helper_actor_compiler.py metadata """ from __future__ import annotations import json import sys from pathlib import Path _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from cleveragents.actor.compiler import compile_actor # noqa: E402 from cleveragents.actor.schema import ActorConfigSchema # noqa: E402 def main() -> int: """Entry point called by Robot Framework ``Run Process``.""" if len(sys.argv) < 3: print("Usage: helper_actor_compiler.py ") return 1 command = sys.argv[1] yaml_path = sys.argv[2] if command == "compile": try: config = ActorConfigSchema.from_yaml_file(yaml_path) compiled = compile_actor(config) print(f"actor-compiler-ok: {compiled.name}") print(f"nodes: {len(compiled.nodes)}") print(f"edges: {len(compiled.edges)}") print(f"entry: {compiled.entry_point}") return 0 except Exception as exc: print(f"actor-compiler-fail: {exc}") return 1 if command == "compile-fail": try: config = ActorConfigSchema.from_yaml_file(yaml_path) compile_actor(config) print("actor-compiler-unexpected-success") return 1 except Exception as exc: print(f"actor-compiler-expected-fail: {exc}") return 0 if command == "metadata": try: config = ActorConfigSchema.from_yaml_file(yaml_path) compiled = compile_actor(config) meta = compiled.metadata.model_dump(mode="json") print(f"actor-compiler-metadata: {json.dumps(meta)}") return 0 except Exception as exc: print(f"actor-compiler-fail: {exc}") return 1 if command == "cycle-detect": # Test cross-actor cycle detection using actor_ref top-level field. # Creates two mutually-referencing actors and verifies SubgraphCycleError # is raised, confirming the fix for issue #1431. from cleveragents.actor.compiler import SubgraphCycleError from cleveragents.actor.schema import ( ActorType, EdgeDefinition, NodeDefinition, NodeType, RouteDefinition, ) def _make_actor( name: str, subgraph_ref: str | None = None ) -> ActorConfigSchema: if subgraph_ref is not None: nodes = [ NodeDefinition( id="start", type=NodeType.AGENT, name="Start", description="Start", config={"agent": "default"}, ), NodeDefinition( id="sub", type=NodeType.SUBGRAPH, name="Sub", description="Subgraph", config={}, actor_ref=subgraph_ref, ), ] edges = [EdgeDefinition(from_node="start", to_node="sub")] entry, exits = "start", ["sub"] else: nodes = [ NodeDefinition( id="leaf", type=NodeType.AGENT, name="Leaf", description="Leaf", config={"agent": "default"}, ) ] edges = [] entry, exits = "leaf", ["leaf"] route = RouteDefinition( nodes=nodes, edges=edges, entry_node=entry, exit_nodes=exits ) return ActorConfigSchema( name=name, type=ActorType.GRAPH, description=f"Test actor {name}", model="gpt-4", route=route, ) actor_a = _make_actor("test/actor-a", subgraph_ref="test/actor-b") actor_b = _make_actor("test/actor-b", subgraph_ref="test/actor-a") registry = {"test/actor-a": actor_a, "test/actor-b": actor_b} try: compile_actor(actor_a, actor_resolver=registry.get) print( "actor-compiler-cycle-not-detected: FAIL — expected SubgraphCycleError" ) return 1 except SubgraphCycleError as exc: print(f"actor-compiler-cycle-detected: {exc}") return 0 except Exception as exc: print(f"actor-compiler-unexpected-error: {exc}") return 1 if command == "lsp-binding-field": # Regression test for issue #1432: _extract_lsp_bindings() must read # from node.lsp_binding (NodeLspBinding typed field), not only config dict. from cleveragents.actor.schema import ( ActorType, NodeDefinition, NodeLspBinding, NodeType, RouteDefinition, ) node = NodeDefinition( id="coder", type=NodeType.AGENT, name="Coder", description="Code writing node with typed LSP binding", config={"agent": "coder_agent"}, lsp_binding=NodeLspBinding( server="local/pyright", languages=["python"], auto=False, ), ) route = RouteDefinition( nodes=[node], edges=[], entry_node="coder", exit_nodes=["coder"], ) config = ActorConfigSchema( name="workflows/lsp_typed", type=ActorType.GRAPH, description="Test actor with typed lsp_binding field", model="gpt-4", route=route, ) try: compiled = compile_actor(config) bindings = compiled.metadata.lsp_bindings if len(bindings) == 1 and bindings[0].lsp_server_name == "local/pyright": print("actor-compiler-lsp-binding-ok") print(f"lsp-server: {bindings[0].lsp_server_name}") return 0 print(f"actor-compiler-lsp-binding-fail: got {bindings}") return 1 except Exception as exc: print(f"actor-compiler-fail: {exc}") return 1 print(f"Unknown command: {command}") return 1 if __name__ == "__main__": sys.exit(main())