diff --git a/benchmarks/acp_facade_bench.py b/benchmarks/a2a_facade_bench.py similarity index 72% rename from benchmarks/acp_facade_bench.py rename to benchmarks/a2a_facade_bench.py index 4069fde8a..53ba04261 100644 --- a/benchmarks/acp_facade_bench.py +++ b/benchmarks/a2a_facade_bench.py @@ -1,9 +1,9 @@ -"""ASV benchmarks for ACP facade dispatch, version negotiation, and event queue. +"""ASV benchmarks for A2A facade dispatch, version negotiation, and event queue. Measures the performance of: -- AcpLocalFacade dispatch overhead per operation -- AcpVersionNegotiator negotiation throughput -- AcpEventQueue publish and get_events throughput +- A2aLocalFacade dispatch overhead per operation +- A2aVersionNegotiator negotiation throughput +- A2aEventQueue publish and get_events throughput """ from __future__ import annotations @@ -23,10 +23,10 @@ import cleveragents # noqa: E402 importlib.reload(cleveragents) -from cleveragents.acp.events import AcpEventQueue # noqa: E402 -from cleveragents.acp.facade import AcpLocalFacade # noqa: E402 -from cleveragents.acp.models import AcpEvent, AcpRequest # noqa: E402 -from cleveragents.acp.versioning import AcpVersionNegotiator # noqa: E402 +from cleveragents.a2a.events import A2aEventQueue # noqa: E402 +from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402 +from cleveragents.a2a.models import A2aEvent, A2aRequest # noqa: E402 +from cleveragents.a2a.versioning import A2aVersionNegotiator # noqa: E402 # --------------------------------------------------------------------------- # Facade dispatch benchmarks @@ -34,17 +34,17 @@ from cleveragents.acp.versioning import AcpVersionNegotiator # noqa: E402 class FacadeDispatchSuite: - """Benchmark AcpLocalFacade.dispatch() overhead.""" + """Benchmark A2aLocalFacade.dispatch() overhead.""" timeout = 60 def setup(self) -> None: - self.facade = AcpLocalFacade() - self.session_req = AcpRequest(operation="session.create") - self.plan_req = AcpRequest( + self.facade = A2aLocalFacade() + self.session_req = A2aRequest(operation="session.create") + self.plan_req = A2aRequest( operation="plan.execute", params={"plan_id": "BENCH001"} ) - self.context_req = AcpRequest(operation="context.get") + self.context_req = A2aRequest(operation="context.get") def time_dispatch_session_create(self) -> None: self.facade.dispatch(self.session_req) @@ -57,7 +57,7 @@ class FacadeDispatchSuite: def time_dispatch_all_operations(self) -> None: for op in self.facade.list_operations(): - req = AcpRequest(operation=op) + req = A2aRequest(operation=op) self.facade.dispatch(req) def time_list_operations(self) -> None: @@ -70,12 +70,12 @@ class FacadeDispatchSuite: class VersionNegotiationSuite: - """Benchmark AcpVersionNegotiator throughput.""" + """Benchmark A2aVersionNegotiator throughput.""" timeout = 60 def setup(self) -> None: - self.negotiator = AcpVersionNegotiator() + self.negotiator = A2aVersionNegotiator() def time_negotiate_supported(self) -> None: self.negotiator.negotiate("1.0") @@ -96,13 +96,13 @@ class VersionNegotiationSuite: class EventQueueSuite: - """Benchmark AcpEventQueue throughput.""" + """Benchmark A2aEventQueue throughput.""" timeout = 60 def setup(self) -> None: - self.queue = AcpEventQueue() - self.event = AcpEvent(event_type="bench.event") + self.queue = A2aEventQueue() + self.event = A2aEvent(event_type="bench.event") def time_publish_single(self) -> None: self.queue.publish(self.event) @@ -118,7 +118,7 @@ class EventQueueSuite: self.queue.subscribe_local(lambda e: None) def time_publish_with_subscriber(self) -> None: - q = AcpEventQueue() + q = A2aEventQueue() q.subscribe_local(lambda e: None) for _ in range(100): q.publish(self.event) diff --git a/benchmarks/m6_acceptance_bench.py b/benchmarks/m6_acceptance_bench.py index 0640daf1c..86c070ef8 100644 --- a/benchmarks/m6_acceptance_bench.py +++ b/benchmarks/m6_acceptance_bench.py @@ -1,10 +1,10 @@ """ASV benchmarks for M6 autonomy acceptance suite runtime. Measures the performance of: -- ACP local facade dispatch operations +- A2A local facade dispatch operations - Automation guard evaluation - AutomationProfileService resolution precedence -- ACP event queue publish/subscribe +- A2A event queue publish/subscribe - Fixture loading overhead """ @@ -25,10 +25,10 @@ import cleveragents # noqa: E402 importlib.reload(cleveragents) -from cleveragents.acp.events import AcpEventQueue # noqa: E402 -from cleveragents.acp.facade import AcpLocalFacade # noqa: E402 -from cleveragents.acp.models import AcpEvent, AcpRequest # noqa: E402 -from cleveragents.acp.versioning import AcpVersionNegotiator # noqa: E402 +from cleveragents.a2a.events import A2aEventQueue # noqa: E402 +from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402 +from cleveragents.a2a.models import A2aEvent, A2aRequest # noqa: E402 +from cleveragents.a2a.versioning import A2aVersionNegotiator # noqa: E402 from cleveragents.application.services.automation_profile_service import ( # noqa: E402 AutomationProfileService, ) @@ -43,23 +43,23 @@ _FIXTURES_DIR = Path(__file__).resolve().parents[1] / "features" / "fixtures" / class M6FacadeDispatchSuite: - """Benchmark ACP local facade dispatch operations.""" + """Benchmark A2A local facade dispatch operations.""" def setup(self) -> None: - self._facade = AcpLocalFacade() + self._facade = A2aLocalFacade() def time_session_create(self) -> None: """Benchmark session.create dispatch.""" - self._facade.dispatch(AcpRequest(operation="session.create", params={})) + self._facade.dispatch(A2aRequest(operation="session.create", params={})) def time_plan_create(self) -> None: """Benchmark plan.create dispatch.""" - self._facade.dispatch(AcpRequest(operation="plan.create", params={})) + self._facade.dispatch(A2aRequest(operation="plan.create", params={})) def time_plan_execute(self) -> None: """Benchmark plan.execute dispatch.""" self._facade.dispatch( - AcpRequest( + A2aRequest( operation="plan.execute", params={"plan_id": "01M6SM0KE00000000000000001"}, ) @@ -170,16 +170,16 @@ class M6ProfileResolutionSuite: ) def time_version_negotiation(self) -> None: - """Benchmark ACP version negotiation.""" - negotiator = AcpVersionNegotiator() + """Benchmark A2A version negotiation.""" + negotiator = A2aVersionNegotiator() negotiator.negotiate("1.0") class M6EventQueueSuite: - """Benchmark ACP event queue operations.""" + """Benchmark A2A event queue operations.""" def setup(self) -> None: - self._queue = AcpEventQueue() + self._queue = A2aEventQueue() def teardown(self) -> None: if not self._queue.is_closed: @@ -187,13 +187,13 @@ class M6EventQueueSuite: def time_publish_event(self) -> None: """Benchmark publishing a single event.""" - self._queue.publish(AcpEvent(event_type="plan.progress", data={"step": 1})) + self._queue.publish(A2aEvent(event_type="plan.progress", data={"step": 1})) def time_subscribe_and_publish(self) -> None: """Benchmark subscribe + publish cycle.""" - q = AcpEventQueue() + q = A2aEventQueue() sub_id = q.subscribe_local(lambda _e: None) - q.publish(AcpEvent(event_type="test.event", data={})) + q.publish(A2aEvent(event_type="test.event", data={})) q.unsubscribe(sub_id) q.close() @@ -205,9 +205,9 @@ class M6EventQueueSuite: class M6FixtureLoadSuite: """Benchmark loading M6 fixture files.""" - def time_load_acp_facade_flows(self) -> None: - """Benchmark loading acp_facade_flows.json.""" - with open(_FIXTURES_DIR / "acp_facade_flows.json") as f: + def time_load_a2a_facade_flows(self) -> None: + """Benchmark loading a2a_facade_flows.json.""" + with open(_FIXTURES_DIR / "a2a_facade_flows.json") as f: json.load(f) def time_load_autonomy_guardrails(self) -> None: @@ -223,7 +223,7 @@ class M6FixtureLoadSuite: def time_load_all_fixtures(self) -> None: """Benchmark loading all M6 fixture files.""" for fname in ( - "acp_facade_flows.json", + "a2a_facade_flows.json", "autonomy_guardrails.json", "automation_profiles.json", ): diff --git a/benchmarks/server_stub_bench.py b/benchmarks/server_stub_bench.py index 320084d09..441332428 100644 --- a/benchmarks/server_stub_bench.py +++ b/benchmarks/server_stub_bench.py @@ -23,12 +23,12 @@ import cleveragents # noqa: E402 importlib.reload(cleveragents) -from cleveragents.acp.clients import ( # noqa: E402 +from cleveragents.a2a.clients import ( # noqa: E402 StubAuthClient, StubRemoteExecutionClient, StubServerClient, ) -from cleveragents.acp.server_config import ServerConnectionConfig # noqa: E402 +from cleveragents.a2a.server_config import ServerConnectionConfig # noqa: E402 from cleveragents.application.services.config_service import _REGISTRY # noqa: E402 # --------------------------------------------------------------------------- diff --git a/features/acp_clients_coverage_boost.feature b/features/a2a_clients_coverage_boost.feature similarity index 96% rename from features/acp_clients_coverage_boost.feature rename to features/a2a_clients_coverage_boost.feature index 9d2deddc6..d7a7faced 100644 --- a/features/acp_clients_coverage_boost.feature +++ b/features/a2a_clients_coverage_boost.feature @@ -1,8 +1,8 @@ -@phase2 @acp @coverage-boost -Feature: ACP clients coverage boost +@phase2 @a2a @coverage-boost +Feature: A2A clients coverage boost As a developer I want complete coverage of Protocol default bodies and stub validation branches - So that all code paths in cleveragents.acp.clients are exercised + So that all code paths in cleveragents.a2a.clients are exercised # ----------------------------------------------------------------------- # Protocol default method bodies (the `...` expressions) diff --git a/features/acp_facade_coverage_boost.feature b/features/a2a_facade_coverage_boost.feature similarity index 95% rename from features/acp_facade_coverage_boost.feature rename to features/a2a_facade_coverage_boost.feature index 88c1763b3..f38f7d4d0 100644 --- a/features/acp_facade_coverage_boost.feature +++ b/features/a2a_facade_coverage_boost.feature @@ -1,5 +1,5 @@ -Feature: ACP local facade coverage boost — uncovered validation paths - As a developer maintaining the ACP local facade +Feature: A2A local facade coverage boost — uncovered validation paths + As a developer maintaining the A2A local facade I want all error-handling and validation paths exercised So that edge-case regressions are caught early @@ -7,8 +7,8 @@ Feature: ACP local facade coverage boost — uncovered validation paths # Constructor validation (line 82) # --------------------------------------------------------------- - Scenario: Constructing AcpLocalFacade with a non-dict raises TypeError - When I try to create an AcpLocalFacade with a non-dict services argument + Scenario: Constructing A2aLocalFacade with a non-dict raises TypeError + When I try to create an A2aLocalFacade with a non-dict services argument Then a TypeError should be raised with message "services must be a dict or None" # --------------------------------------------------------------- diff --git a/features/acp_facade_wiring.feature b/features/a2a_facade_wiring.feature similarity index 82% rename from features/acp_facade_wiring.feature rename to features/a2a_facade_wiring.feature index 69c9f9062..e5b8d0589 100644 --- a/features/acp_facade_wiring.feature +++ b/features/a2a_facade_wiring.feature @@ -1,5 +1,5 @@ -Feature: ACP local facade wiring to live services - As a client of the ACP local facade +Feature: A2A local facade wiring to live services + As a client of the A2A local facade I want operations to route to real application services So that the facade delegates to live implementations @@ -8,20 +8,20 @@ Feature: ACP local facade wiring to live services # --------------------------------------------------------------- Scenario: session.create delegates to SessionService - Given a wired AcpLocalFacade with a mock SessionService + Given a wired A2aLocalFacade with a mock SessionService When I dispatch wired operation "session.create" with params {"actor_name": "local/test-actor"} Then the wired response status should be "ok" And wired response data key "session_id" equals "MOCK-SESSION-001" And wired response data key "status" equals "created" Scenario: session.close delegates to SessionService - Given a wired AcpLocalFacade with a mock SessionService + Given a wired A2aLocalFacade with a mock SessionService When I dispatch wired operation "session.close" with params {"session_id": "MOCK-SESSION-001"} Then the wired response status should be "ok" And wired response data key "status" equals "closed" Scenario: session.close without session_id returns error - Given a wired AcpLocalFacade with a mock SessionService + Given a wired A2aLocalFacade with a mock SessionService When I dispatch wired operation "session.close" with params {} Then the wired response status should be "error" @@ -30,38 +30,38 @@ Feature: ACP local facade wiring to live services # --------------------------------------------------------------- Scenario: plan.create delegates to PlanLifecycleService - Given a wired AcpLocalFacade with a mock PlanLifecycleService + Given a wired A2aLocalFacade with a mock PlanLifecycleService When I dispatch wired operation "plan.create" with params {"action_name": "local/test-action"} Then the wired response status should be "ok" And wired response data key "plan_id" equals "MOCK-PLAN-001" And wired response data key "status" equals "created" Scenario: plan.create without action_name returns error - Given a wired AcpLocalFacade with a mock PlanLifecycleService + Given a wired A2aLocalFacade with a mock PlanLifecycleService When I dispatch wired operation "plan.create" with params {} Then the wired response status should be "error" Scenario: plan.execute delegates to PlanLifecycleService - Given a wired AcpLocalFacade with a mock PlanLifecycleService + Given a wired A2aLocalFacade with a mock PlanLifecycleService When I dispatch wired operation "plan.execute" with params {"plan_id": "MOCK-PLAN-001"} Then the wired response status should be "ok" And wired response data key "plan_id" equals "MOCK-PLAN-001" Scenario: plan.status delegates to PlanLifecycleService - Given a wired AcpLocalFacade with a mock PlanLifecycleService + Given a wired A2aLocalFacade with a mock PlanLifecycleService When I dispatch wired operation "plan.status" with params {"plan_id": "MOCK-PLAN-001"} Then the wired response status should be "ok" And wired response data key "plan_id" equals "MOCK-PLAN-001" And wired response data key "phase" equals "strategize" Scenario: plan.diff delegates to PlanLifecycleService - Given a wired AcpLocalFacade with a mock PlanLifecycleService + Given a wired A2aLocalFacade with a mock PlanLifecycleService When I dispatch wired operation "plan.diff" with params {"plan_id": "MOCK-PLAN-001"} Then the wired response status should be "ok" And wired response data key "plan_id" equals "MOCK-PLAN-001" Scenario: plan.apply delegates to PlanLifecycleService - Given a wired AcpLocalFacade with a mock PlanLifecycleService + Given a wired A2aLocalFacade with a mock PlanLifecycleService When I dispatch wired operation "plan.apply" with params {"plan_id": "MOCK-PLAN-001"} Then the wired response status should be "ok" And wired response data key "plan_id" equals "MOCK-PLAN-001" @@ -71,13 +71,13 @@ Feature: ACP local facade wiring to live services # --------------------------------------------------------------- Scenario: registry.list_tools delegates to ToolRegistry - Given a wired AcpLocalFacade with a mock ToolRegistry + Given a wired A2aLocalFacade with a mock ToolRegistry When I dispatch wired operation "registry.list_tools" with params {} Then the wired response status should be "ok" And wired response data should contain tools list with 2 items Scenario: registry.list_resources delegates to ResourceRegistryService - Given a wired AcpLocalFacade with a mock ResourceRegistryService + Given a wired A2aLocalFacade with a mock ResourceRegistryService When I dispatch wired operation "registry.list_resources" with params {} Then the wired response status should be "ok" And wired response data should contain resources list with 1 items @@ -87,7 +87,7 @@ Feature: ACP local facade wiring to live services # --------------------------------------------------------------- Scenario: context.get returns stub pending ACMS pipeline - Given a wired AcpLocalFacade with no services + Given a wired A2aLocalFacade with no services When I dispatch wired operation "context.get" with params {} Then the wired response status should be "ok" And wired response data key "stub" equals "True" @@ -96,8 +96,8 @@ Feature: ACP local facade wiring to live services # Event wiring # --------------------------------------------------------------- - Scenario: event.subscribe delegates to AcpEventQueue - Given a wired AcpLocalFacade with a mock AcpEventQueue + Scenario: event.subscribe delegates to A2aEventQueue + Given a wired A2aLocalFacade with a mock A2aEventQueue When I dispatch wired operation "event.subscribe" with params {} Then the wired response status should be "ok" And wired response data key "status" equals "subscribed" @@ -108,25 +108,25 @@ Feature: ACP local facade wiring to live services # --------------------------------------------------------------- Scenario: NOT_FOUND error code for missing resources - Given a wired AcpLocalFacade with a raising SessionService for not-found + Given a wired A2aLocalFacade with a raising SessionService for not-found When I dispatch wired operation "session.close" with params {"session_id": "nonexistent"} Then the wired response status should be "error" And wired response error code should be "NOT_FOUND" Scenario: VALIDATION_ERROR code for validation failures - Given a wired AcpLocalFacade with a raising service for validation-error + Given a wired A2aLocalFacade with a raising service for validation-error When I dispatch wired operation "plan.create" with params {"action_name": "bad"} Then the wired response status should be "error" And wired response error code should be "VALIDATION_ERROR" Scenario: PLAN_ERROR code for plan failures - Given a wired AcpLocalFacade with a raising service for plan-error + Given a wired A2aLocalFacade with a raising service for plan-error When I dispatch wired operation "plan.execute" with params {"plan_id": "P1"} Then the wired response status should be "error" And wired response error code should be "PLAN_ERROR" Scenario: INVALID_STATE code for business rule violations - Given a wired AcpLocalFacade with a raising service for invalid-state + Given a wired A2aLocalFacade with a raising service for invalid-state When I dispatch wired operation "plan.apply" with params {"plan_id": "P1"} Then the wired response status should be "error" And wired response error code should be "INVALID_STATE" @@ -136,24 +136,24 @@ Feature: ACP local facade wiring to live services # --------------------------------------------------------------- Scenario: session.create stub when no service wired - Given a wired AcpLocalFacade with no services + Given a wired A2aLocalFacade with no services When I dispatch wired operation "session.create" with params {} Then the wired response status should be "ok" And wired response data key "status" equals "created" Scenario: plan.create stub when no service wired - Given a wired AcpLocalFacade with no services + Given a wired A2aLocalFacade with no services When I dispatch wired operation "plan.create" with params {} Then the wired response status should be "ok" And wired response data key "status" equals "created" Scenario: registry.list_tools stub when no service wired - Given a wired AcpLocalFacade with no services + Given a wired A2aLocalFacade with no services When I dispatch wired operation "registry.list_tools" with params {} Then the wired response status should be "ok" Scenario: event.subscribe stub when no service wired - Given a wired AcpLocalFacade with no services + Given a wired A2aLocalFacade with no services When I dispatch wired operation "event.subscribe" with params {} Then the wired response status should be "ok" And wired response data key "status" equals "subscribed" diff --git a/features/consolidated_misc.feature b/features/consolidated_misc.feature index c3ec7a533..a31120631 100644 --- a/features/consolidated_misc.feature +++ b/features/consolidated_misc.feature @@ -1,39 +1,39 @@ Feature: Consolidated Misc - Combined scenarios from: acp_facade, agents_base_uncovered_lines, bridge_coverage, coverage_improvements, m6_autonomy_acceptance, scale_test, container_coverage_r2, lsp_registry, memory_service_coverage, migration_runner_coverage, system_coverage_boost, yaml_engine_direct_coverage + Combined scenarios from: a2a_facade, agents_base_uncovered_lines, bridge_coverage, coverage_improvements, m6_autonomy_acceptance, scale_test, container_coverage_r2, lsp_registry, memory_service_coverage, migration_runner_coverage, system_coverage_boost, yaml_engine_direct_coverage # ============================================================ - # Originally from: acp_facade.feature - # Feature: ACP Local Facade and Server Stubs + # Originally from: a2a_facade.feature + # Feature: A2A Local Facade and Server Stubs # ============================================================ Scenario: Create facade with no services - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services Then the facade should be created successfully Scenario: Create facade with services dict - Given a new AcpLocalFacade with services {"session": "mock_session"} + Given a new A2aLocalFacade with services {"session": "mock_session"} Then the facade should be created successfully Scenario: Register a service on the facade - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I register a service named "planner" on the facade Then the service should be registered successfully Scenario: Register service with empty name raises error - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I try to register a service with an empty name - Then an ACP ValueError should be raised + Then an A2A ValueError should be raised # ----------------------------------------------------------------------- - # AcpLocalFacade — dispatch for each supported operation + # A2aLocalFacade — dispatch for each supported operation # ----------------------------------------------------------------------- Scenario: Dispatch session.create returns session_id and status - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "session.create" with params {} Then the response status should be "ok" And response data includes key "session_id" @@ -41,14 +41,14 @@ Feature: Consolidated Misc Scenario: Dispatch session.close returns status closed - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "session.close" with params {} Then the response status should be "ok" And response data key "status" equals "closed" Scenario: Dispatch plan.create returns plan_id and status - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "plan.create" with params {} Then the response status should be "ok" And response data includes key "plan_id" @@ -56,7 +56,7 @@ Feature: Consolidated Misc Scenario: Dispatch plan.execute returns plan_id and status queued - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "plan.execute" with params {"plan_id": "PLAN001"} Then the response status should be "ok" And response data key "plan_id" equals "PLAN001" @@ -64,7 +64,7 @@ Feature: Consolidated Misc Scenario: Dispatch plan.status returns plan_id and phase - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "plan.status" with params {"plan_id": "PLAN001"} Then the response status should be "ok" And response data key "plan_id" equals "PLAN001" @@ -72,7 +72,7 @@ Feature: Consolidated Misc Scenario: Dispatch plan.diff returns plan_id and empty changes - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "plan.diff" with params {"plan_id": "PLAN001"} Then the response status should be "ok" And response data key "plan_id" equals "PLAN001" @@ -80,7 +80,7 @@ Feature: Consolidated Misc Scenario: Dispatch plan.apply returns plan_id and status applied - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "plan.apply" with params {"plan_id": "PLAN001"} Then the response status should be "ok" And response data key "plan_id" equals "PLAN001" @@ -88,51 +88,51 @@ Feature: Consolidated Misc Scenario: Dispatch registry.list_tools returns empty tools - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "registry.list_tools" with params {} Then the response status should be "ok" And response data includes key "tools" Scenario: Dispatch registry.list_resources returns empty resources - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "registry.list_resources" with params {} Then the response status should be "ok" And response data includes key "resources" Scenario: Dispatch context.get returns empty context - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "context.get" with params {} Then the response status should be "ok" And response data includes key "context" Scenario: Dispatch event.subscribe returns subscription_id and status - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I dispatch operation "event.subscribe" with params {} Then the response status should be "ok" And response data includes key "subscription_id" And response data key "status" equals "subscribed" # ----------------------------------------------------------------------- - # AcpLocalFacade — unknown operation + # A2aLocalFacade — unknown operation # ----------------------------------------------------------------------- - Scenario: Dispatch unknown operation raises AcpOperationNotFoundError - Given a new AcpLocalFacade with no services + Scenario: Dispatch unknown operation raises A2aOperationNotFoundError + Given a new A2aLocalFacade with no services When I dispatch an unknown operation "does.not.exist" - Then an AcpOperationNotFoundError should be raised + Then an A2aOperationNotFoundError should be raised And the error operation attribute should be "does.not.exist" # ----------------------------------------------------------------------- - # AcpLocalFacade — list_operations + # A2aLocalFacade — list_operations # ----------------------------------------------------------------------- Scenario: list_operations returns all supported operations - Given a new AcpLocalFacade with no services + Given a new A2aLocalFacade with no services When I call list_operations on the facade Then the operations list should contain "session.create" And the operations list should contain "plan.create" @@ -143,161 +143,161 @@ Feature: Consolidated Misc And the operations list should have 11 items # ----------------------------------------------------------------------- - # AcpHttpTransport — all stubs raise AcpNotAvailableError + # A2aHttpTransport — all stubs raise A2aNotAvailableError # ----------------------------------------------------------------------- - Scenario: Transport send raises AcpNotAvailableError - Given a new AcpHttpTransport + Scenario: Transport send raises A2aNotAvailableError + Given a new A2aHttpTransport When I try to send a request via the transport - Then an AcpNotAvailableError should be raised + Then an A2aNotAvailableError should be raised - Scenario: Transport connect raises AcpNotAvailableError - Given a new AcpHttpTransport + Scenario: Transport connect raises A2aNotAvailableError + Given a new A2aHttpTransport When I try to connect via the transport to "http://localhost:8080" - Then an AcpNotAvailableError should be raised + Then an A2aNotAvailableError should be raised - Scenario: Transport disconnect raises AcpNotAvailableError - Given a new AcpHttpTransport + Scenario: Transport disconnect raises A2aNotAvailableError + Given a new A2aHttpTransport When I try to disconnect the transport - Then an AcpNotAvailableError should be raised + Then an A2aNotAvailableError should be raised Scenario: Transport is_connected returns False - Given a new AcpHttpTransport + Given a new A2aHttpTransport Then the transport should not be connected # ----------------------------------------------------------------------- - # AcpEventQueue — local mode works + # A2aEventQueue — local mode works # ----------------------------------------------------------------------- Scenario: Publish event to local queue - Given a new AcpEventQueue + Given a new A2aEventQueue When I publish an event with type "plan.started" Then the event queue should have 1 event Scenario: Subscribe locally and receive event - Given a new AcpEventQueue + Given a new A2aEventQueue And I subscribe locally with a callback When I publish an event with type "plan.completed" Then the callback should have been called with event type "plan.completed" Scenario: Unsubscribe removes subscription - Given a new AcpEventQueue + Given a new A2aEventQueue And I subscribe locally with a callback When I unsubscribe using the subscription id Then the unsubscribe should return True Scenario: Unsubscribe non-existent returns False - Given a new AcpEventQueue + Given a new A2aEventQueue When I unsubscribe using a non-existent subscription id Then the unsubscribe should return False Scenario: Get events respects limit - Given a new AcpEventQueue + Given a new A2aEventQueue When I publish 5 events And I get events with limit 3 Then I should receive 3 events # ----------------------------------------------------------------------- - # AcpEventQueue — remote stub raises + # A2aEventQueue — remote stub raises # ----------------------------------------------------------------------- - Scenario: Remote subscribe raises AcpNotAvailableError - Given a new AcpEventQueue + Scenario: Remote subscribe raises A2aNotAvailableError + Given a new A2aEventQueue When I try to subscribe remotely to "http://remote:9090/events" - Then an AcpNotAvailableError should be raised + Then an A2aNotAvailableError should be raised # ----------------------------------------------------------------------- - # AcpVersionNegotiator + # A2aVersionNegotiator # ----------------------------------------------------------------------- Scenario: Negotiate supported version succeeds - Given a new AcpVersionNegotiator + Given a new A2aVersionNegotiator When I negotiate version "1.0" Then the negotiated version should be "1.0" Scenario: Negotiate unsupported version raises error - Given a new AcpVersionNegotiator + Given a new A2aVersionNegotiator When I try to negotiate version "2.0" - Then an AcpVersionMismatchError should be raised + Then an A2aVersionMismatchError should be raised And the error requested_version should be "2.0" Scenario: is_supported returns True for valid version - Given a new AcpVersionNegotiator + Given a new A2aVersionNegotiator Then version "1.0" should be supported Scenario: is_supported returns False for invalid version - Given a new AcpVersionNegotiator + Given a new A2aVersionNegotiator Then version "99.0" should not be supported Scenario: get_current returns current version - Given a new AcpVersionNegotiator + Given a new A2aVersionNegotiator Then the current version should be "1.0" # ----------------------------------------------------------------------- - # AcpRequest model validation + # A2aRequest model validation # ----------------------------------------------------------------------- - Scenario: AcpRequest with valid operation succeeds - When I create an AcpRequest with operation "session.create" + Scenario: A2aRequest with valid operation succeeds + When I create an A2aRequest with operation "session.create" Then the request should have a non-empty request_id - And the request acp_version should be "1.0" + And the request a2a_version should be "1.0" - Scenario: AcpRequest with empty operation fails validation - When I try to create an AcpRequest with empty operation + Scenario: A2aRequest with empty operation fails validation + When I try to create an A2aRequest with empty operation Then a validation error should be raised # ----------------------------------------------------------------------- - # AcpResponse model validation + # A2aResponse model validation # ----------------------------------------------------------------------- - Scenario: AcpResponse with valid status ok succeeds - When I create an AcpResponse with status "ok" and request_id "REQ001" + Scenario: A2aResponse with valid status ok succeeds + When I create an A2aResponse with status "ok" and request_id "REQ001" Then the response should be valid - Scenario: AcpResponse with invalid status fails validation - When I try to create an AcpResponse with status "maybe" + Scenario: A2aResponse with invalid status fails validation + When I try to create an A2aResponse with status "maybe" Then a validation error should be raised # ----------------------------------------------------------------------- - # AcpErrorDetail model validation + # A2aErrorDetail model validation # ----------------------------------------------------------------------- - Scenario: AcpErrorDetail with valid fields succeeds - When I create an AcpErrorDetail with code "NOT_FOUND" and message "gone" + Scenario: A2aErrorDetail with valid fields succeeds + When I create an A2aErrorDetail with code "NOT_FOUND" and message "gone" Then the error detail should be valid - Scenario: AcpErrorDetail with empty code fails validation - When I try to create an AcpErrorDetail with empty code + Scenario: A2aErrorDetail with empty code fails validation + When I try to create an A2aErrorDetail with empty code Then a validation error should be raised # ----------------------------------------------------------------------- - # AcpEvent model construction + # A2aEvent model construction # ----------------------------------------------------------------------- - Scenario: AcpEvent auto-generates event_id and timestamp - When I create an AcpEvent with type "plan.progress" + Scenario: A2aEvent auto-generates event_id and timestamp + When I create an A2aEvent with type "plan.progress" Then the event should have a non-empty event_id And the event should have a non-empty timestamp @@ -306,24 +306,24 @@ Feature: Consolidated Misc # ----------------------------------------------------------------------- - Scenario: AcpError is a CleverAgentsError - Then AcpError should be a subclass of CleverAgentsError + Scenario: A2aError is a CleverAgentsError + Then A2aError should be a subclass of CleverAgentsError - Scenario: AcpNotAvailableError is an AcpError - Then AcpNotAvailableError should be a subclass of AcpError + Scenario: A2aNotAvailableError is an A2aError + Then A2aNotAvailableError should be a subclass of A2aError - Scenario: AcpVersionMismatchError is an AcpError - Then AcpVersionMismatchError should be a subclass of AcpError + Scenario: A2aVersionMismatchError is an A2aError + Then A2aVersionMismatchError should be a subclass of A2aError - Scenario: AcpOperationNotFoundError is an AcpError - Then AcpOperationNotFoundError should be a subclass of AcpError + Scenario: A2aOperationNotFoundError is an A2aError + Then A2aOperationNotFoundError should be a subclass of A2aError - Scenario: AcpNotAvailableError has default message - When I create an AcpNotAvailableError with default message + Scenario: A2aNotAvailableError has default message + When I create an A2aNotAvailableError with default message Then the error message should contain "not available in local mode" @@ -485,17 +485,17 @@ Feature: Consolidated Misc # Feature: M6 autonomy acceptance smoke tests # ============================================================ - Scenario: M6 smoke load ACP facade flows fixture + Scenario: M6 smoke load A2A facade flows fixture Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke load the ACP facade flows fixture + And a m6 smoke A2A local facade + When I m6 smoke load the A2A facade flows fixture Then the m6 smoke facade fixture should have a session lifecycle entry And the m6 smoke facade fixture should have a plan lifecycle entry Scenario: M6 smoke load autonomy guardrails fixture Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke load the autonomy guardrails fixture Then the m6 smoke guardrails fixture should have a denylist entry And the m6 smoke guardrails fixture should have an allowlist entry @@ -504,237 +504,237 @@ Feature: Consolidated Misc Scenario: M6 smoke load automation profiles fixture Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke load the automation profiles fixture Then the m6 smoke profiles fixture should list all 8 built-in names And the m6 smoke profiles fixture should have a custom profile entry - # --- ACP facade session operations --- + # --- A2A facade session operations --- - Scenario: M6 smoke ACP session create returns session id + Scenario: M6 smoke A2A session create returns session id Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "session.create" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "session_id" And the m6 smoke response data should contain key "status" - Scenario: M6 smoke ACP session close returns closed status + Scenario: M6 smoke A2A session close returns closed status Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "session.close" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data "status" should equal "closed" - # --- ACP facade plan operations --- + # --- A2A facade plan operations --- - Scenario: M6 smoke ACP plan create returns plan id + Scenario: M6 smoke A2A plan create returns plan id Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "plan.create" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "plan_id" - Scenario: M6 smoke ACP plan execute queues plan + Scenario: M6 smoke A2A plan execute queues plan Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "plan.execute" with params {"plan_id": "01M6SM0KE00000000000000001"} Then the m6 smoke response status should be "ok" And the m6 smoke response data "status" should equal "queued" - Scenario: M6 smoke ACP plan status returns phase + Scenario: M6 smoke A2A plan status returns phase Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "plan.status" with params {"plan_id": "01M6SM0KE00000000000000001"} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "phase" - Scenario: M6 smoke ACP plan diff returns changes list + Scenario: M6 smoke A2A plan diff returns changes list Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "plan.diff" with params {"plan_id": "01M6SM0KE00000000000000001"} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "changes" - Scenario: M6 smoke ACP plan apply returns applied status + Scenario: M6 smoke A2A plan apply returns applied status Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "plan.apply" with params {"plan_id": "01M6SM0KE00000000000000001"} Then the m6 smoke response status should be "ok" And the m6 smoke response data "status" should equal "applied" - # --- ACP facade registry and context operations --- + # --- A2A facade registry and context operations --- - Scenario: M6 smoke ACP registry list tools returns empty list + Scenario: M6 smoke A2A registry list tools returns empty list Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "registry.list_tools" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "tools" - Scenario: M6 smoke ACP registry list resources returns empty list + Scenario: M6 smoke A2A registry list resources returns empty list Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "registry.list_resources" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "resources" - Scenario: M6 smoke ACP context get returns context dict + Scenario: M6 smoke A2A context get returns context dict Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "context.get" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "context" - Scenario: M6 smoke ACP event subscribe returns subscription id + Scenario: M6 smoke A2A event subscribe returns subscription id Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch "event.subscribe" with params {} Then the m6 smoke response status should be "ok" And the m6 smoke response data should contain key "subscription_id" - # --- ACP facade error handling --- + # --- A2A facade error handling --- - Scenario: M6 smoke ACP unknown operation raises error + Scenario: M6 smoke A2A unknown operation raises error Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke dispatch unknown operation "nonexistent.op" - Then the m6 smoke facade should raise AcpOperationNotFoundError + Then the m6 smoke facade should raise A2aOperationNotFoundError - Scenario: M6 smoke ACP dispatch with invalid request type raises TypeError + Scenario: M6 smoke A2A dispatch with invalid request type raises TypeError Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke dispatch with a non-AcpRequest object + And a m6 smoke A2A local facade + When I m6 smoke dispatch with a non-A2aRequest object Then the m6 smoke facade should raise TypeError - # --- ACP facade service registration --- + # --- A2A facade service registration --- - Scenario: M6 smoke ACP register service stores service + Scenario: M6 smoke A2A register service stores service Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke register service "plan_service" on the facade Then the m6 smoke facade should have service "plan_service" - Scenario: M6 smoke ACP list operations returns all supported + Scenario: M6 smoke A2A list operations returns all supported Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke list facade operations Then the m6 smoke operations should include "session.create" And the m6 smoke operations should include "plan.execute" And the m6 smoke operations should include "event.subscribe" And the m6 smoke operations count should be 11 - # --- ACP event queue --- + # --- A2A event queue --- - Scenario: M6 smoke ACP event queue publish and retrieve + Scenario: M6 smoke A2A event queue publish and retrieve Given a m6 smoke test runner - And a m6 smoke ACP local facade - Given a m6 smoke ACP event queue + And a m6 smoke A2A local facade + Given a m6 smoke A2A event queue When I m6 smoke publish an event with type "plan.progress" Then the m6 smoke event queue should have 1 event And the m6 smoke last event type should be "plan.progress" - Scenario: M6 smoke ACP event queue subscribe local callback + Scenario: M6 smoke A2A event queue subscribe local callback Given a m6 smoke test runner - And a m6 smoke ACP local facade - Given a m6 smoke ACP event queue + And a m6 smoke A2A local facade + Given a m6 smoke A2A event queue When I m6 smoke subscribe a local callback And I m6 smoke publish an event with type "plan.complete" Then the m6 smoke callback should have been called once - Scenario: M6 smoke ACP event queue unsubscribe + Scenario: M6 smoke A2A event queue unsubscribe Given a m6 smoke test runner - And a m6 smoke ACP local facade - Given a m6 smoke ACP event queue + And a m6 smoke A2A local facade + Given a m6 smoke A2A event queue When I m6 smoke subscribe a local callback And I m6 smoke unsubscribe the callback And I m6 smoke publish an event with type "plan.complete" Then the m6 smoke callback should not have been called - Scenario: M6 smoke ACP event queue close prevents publish + Scenario: M6 smoke A2A event queue close prevents publish Given a m6 smoke test runner - And a m6 smoke ACP local facade - Given a m6 smoke ACP event queue + And a m6 smoke A2A local facade + Given a m6 smoke A2A event queue When I m6 smoke close the event queue Then the m6 smoke publishing should raise RuntimeError - Scenario: M6 smoke ACP event queue remote subscribe raises error + Scenario: M6 smoke A2A event queue remote subscribe raises error Given a m6 smoke test runner - And a m6 smoke ACP local facade - Given a m6 smoke ACP event queue + And a m6 smoke A2A local facade + Given a m6 smoke A2A event queue When I m6 smoke attempt remote subscribe to "https://example.com/events" - Then the m6 smoke facade should raise AcpNotAvailableError + Then the m6 smoke facade should raise A2aNotAvailableError - # --- ACP HTTP transport stub --- + # --- A2A HTTP transport stub --- - Scenario: M6 smoke ACP transport send raises not available + Scenario: M6 smoke A2A transport send raises not available Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke attempt transport send - Then the m6 smoke facade should raise AcpNotAvailableError + Then the m6 smoke facade should raise A2aNotAvailableError - Scenario: M6 smoke ACP transport connect raises not available + Scenario: M6 smoke A2A transport connect raises not available Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke attempt transport connect to "https://example.com/acp" - Then the m6 smoke facade should raise AcpNotAvailableError + And a m6 smoke A2A local facade + When I m6 smoke attempt transport connect to "https://example.com/a2a" + Then the m6 smoke facade should raise A2aNotAvailableError - Scenario: M6 smoke ACP transport disconnect raises not available + Scenario: M6 smoke A2A transport disconnect raises not available Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke attempt transport disconnect - Then the m6 smoke facade should raise AcpNotAvailableError + Then the m6 smoke facade should raise A2aNotAvailableError - Scenario: M6 smoke ACP transport is_connected returns false + Scenario: M6 smoke A2A transport is_connected returns false Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke check transport is_connected Then the m6 smoke transport should not be connected - # --- ACP version negotiation --- + # --- A2A version negotiation --- - Scenario: M6 smoke ACP version negotiation accepts 1.0 + Scenario: M6 smoke A2A version negotiation accepts 1.0 Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke negotiate ACP version "1.0" + And a m6 smoke A2A local facade + When I m6 smoke negotiate A2A version "1.0" Then the m6 smoke negotiated version should be "1.0" - Scenario: M6 smoke ACP version negotiation rejects unsupported + Scenario: M6 smoke A2A version negotiation rejects unsupported Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke negotiate ACP version "2.0" - Then the m6 smoke facade should raise AcpVersionMismatchError + And a m6 smoke A2A local facade + When I m6 smoke negotiate A2A version "2.0" + Then the m6 smoke facade should raise A2aVersionMismatchError - Scenario: M6 smoke ACP version is_supported returns correct result + Scenario: M6 smoke A2A version is_supported returns correct result Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke check if version "1.0" is supported Then the m6 smoke version support should be true When I m6 smoke check if version "99.0" is supported @@ -745,7 +745,7 @@ Feature: Consolidated Misc Scenario: M6 smoke all 8 built-in profiles exist Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke list all built-in profiles Then the m6 smoke profile count should be 8 And the m6 smoke profiles should include "manual" @@ -754,7 +754,7 @@ Feature: Consolidated Misc Scenario: M6 smoke manual profile has all thresholds at 1.0 Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke load built-in profile "manual" Then the m6 smoke profile auto_strategize should be 1.0 And the m6 smoke profile auto_execute should be 1.0 @@ -764,7 +764,7 @@ Feature: Consolidated Misc Scenario: M6 smoke full-auto profile has no gates Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke load built-in profile "full-auto" Then the m6 smoke profile auto_strategize should be 0.0 And the m6 smoke profile auto_execute should be 0.0 @@ -777,7 +777,7 @@ Feature: Consolidated Misc Scenario: M6 smoke create custom namespaced profile Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke create a profile named "acme/strict" with auto_apply 1.0 Then the m6 smoke created profile name should be "acme/strict" And the m6 smoke created profile auto_apply should be 1.0 @@ -785,21 +785,21 @@ Feature: Consolidated Misc Scenario: M6 smoke profile name validation rejects invalid Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke create a profile with invalid name "has spaces" Then the m6 smoke creation should raise ValueError Scenario: M6 smoke profile threshold validation rejects out of range Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade When I m6 smoke create a profile with auto_strategize 1.5 Then the m6 smoke creation should raise ValueError Scenario: M6 smoke profile from_yaml loads correctly Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke temporary profile YAML file When I m6 smoke load profile from the temp YAML Then the m6 smoke loaded profile name should be "test-yaml-profile" @@ -809,7 +809,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard denylist blocks denied tool Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with denylist guard for "rm_rf" When I m6 smoke check guard for tool "rm_rf" Then the m6 smoke guard result should not be allowed @@ -818,7 +818,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard denylist allows non-denied tool Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with denylist guard for "rm_rf" When I m6 smoke check guard for tool "read_file" Then the m6 smoke guard result should be allowed @@ -826,7 +826,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard allowlist blocks unlisted tool Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with allowlist guard for "read_file" and "search" When I m6 smoke check guard for tool "write_file" Then the m6 smoke guard result should not be allowed @@ -835,7 +835,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard max tool calls blocks at limit Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with max 5 tool calls per step When I m6 smoke check guard for tool "llm_call" with 5 calls so far Then the m6 smoke guard result should not be allowed @@ -844,7 +844,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard cost budget blocks at cap Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with max cost 10.0 When I m6 smoke check guard for tool "llm_call" with cost 10.0 Then the m6 smoke guard result should not be allowed @@ -853,7 +853,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard write approval blocks write operations Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with write approval required When I m6 smoke check guard for tool "write_file" as a write operation Then the m6 smoke guard result should not be allowed @@ -862,7 +862,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard apply approval blocks apply phase Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with apply approval required When I m6 smoke check guard for tool "__apply__" Then the m6 smoke guard result should not be allowed @@ -871,7 +871,7 @@ Feature: Consolidated Misc Scenario: M6 smoke guard with no guards allows everything Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke profile with no guards When I m6 smoke check guard for tool "anything" Then the m6 smoke guard result should be allowed @@ -881,7 +881,7 @@ Feature: Consolidated Misc Scenario: M6 smoke profile resolution plan takes precedence Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke automation profile service When I m6 smoke resolve profile with plan "ci" action "auto" project "manual" Then the m6 smoke resolved profile name should be "ci" @@ -889,7 +889,7 @@ Feature: Consolidated Misc Scenario: M6 smoke profile resolution action takes precedence over project Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke automation profile service When I m6 smoke resolve profile with plan null action "auto" project "manual" Then the m6 smoke resolved profile name should be "auto" @@ -897,7 +897,7 @@ Feature: Consolidated Misc Scenario: M6 smoke profile resolution falls back to global default Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke automation profile service When I m6 smoke resolve profile with plan null action null project null Then the m6 smoke resolved profile name should be "manual" @@ -907,39 +907,39 @@ Feature: Consolidated Misc Scenario: M6 smoke service evaluate guard delegates to profile Given a m6 smoke test runner - And a m6 smoke ACP local facade + And a m6 smoke A2A local facade Given a m6 smoke automation profile service When I m6 smoke evaluate guard for profile "manual" and tool "read_file" Then the m6 smoke guard result should be allowed - # --- ACP model validation --- + # --- A2A model validation --- - Scenario: M6 smoke AcpRequest validates non-empty operation + Scenario: M6 smoke A2aRequest validates non-empty operation Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke create AcpRequest with empty operation + And a m6 smoke A2A local facade + When I m6 smoke create A2aRequest with empty operation Then the m6 smoke creation should raise ValueError - Scenario: M6 smoke AcpResponse validates status values + Scenario: M6 smoke A2aResponse validates status values Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke create AcpResponse with invalid status "maybe" + And a m6 smoke A2A local facade + When I m6 smoke create A2aResponse with invalid status "maybe" Then the m6 smoke creation should raise ValueError - Scenario: M6 smoke AcpEvent validates non-empty event_type + Scenario: M6 smoke A2aEvent validates non-empty event_type Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke create AcpEvent with empty event_type + And a m6 smoke A2A local facade + When I m6 smoke create A2aEvent with empty event_type Then the m6 smoke creation should raise ValueError - Scenario: M6 smoke AcpErrorDetail validates non-empty fields + Scenario: M6 smoke A2aErrorDetail validates non-empty fields Given a m6 smoke test runner - And a m6 smoke ACP local facade - When I m6 smoke create AcpErrorDetail with empty code + And a m6 smoke A2A local facade + When I m6 smoke create A2aErrorDetail with empty code Then the m6 smoke creation should raise ValueError diff --git a/features/fixtures/m6/acp_facade_flows.json b/features/fixtures/m6/a2a_facade_flows.json similarity index 95% rename from features/fixtures/m6/acp_facade_flows.json rename to features/fixtures/m6/a2a_facade_flows.json index d88b3cb17..0c19d18f8 100644 --- a/features/fixtures/m6/acp_facade_flows.json +++ b/features/fixtures/m6/a2a_facade_flows.json @@ -1,5 +1,5 @@ { - "description": "ACP local-mode facade operation flows for M6 autonomy acceptance tests", + "description": "A2A local-mode facade operation flows for M6 autonomy acceptance tests", "fixtures": [ { "name": "session_lifecycle", @@ -35,7 +35,7 @@ { "name": "unknown_operation", "operations": [ - {"operation": "nonexistent.op", "params": {}, "expected_error": "AcpOperationNotFoundError"} + {"operation": "nonexistent.op", "params": {}, "expected_error": "A2aOperationNotFoundError"} ] } ] diff --git a/features/lsp_server_stub.feature b/features/lsp_server_stub.feature index 44c76cdc6..44c8f929e 100644 --- a/features/lsp_server_stub.feature +++ b/features/lsp_server_stub.feature @@ -145,20 +145,20 @@ Feature: LSP server stub JSON-RPC protocol And the server exit code should be 1 # --------------------------------------------------------------------------- - # ACP facade wiring + # A2A facade wiring # --------------------------------------------------------------------------- - @lsp_acp - Scenario: LSP server stores provided ACP facade - Given an LSP server with ACP facade + @lsp_a2a + Scenario: LSP server stores provided A2A facade + Given an LSP server with A2A facade And an initialize request with id 1 When the LSP server processes all messages Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" - And the server should hold the provided ACP facade + And the server should hold the provided A2A facade - @lsp_acp - Scenario: LSP server lazily creates ACP facade on property access + @lsp_a2a + Scenario: LSP server lazily creates A2A facade on property access Given an initialize request with id 1 And an exit notification When the LSP server processes all messages diff --git a/features/security_async.feature b/features/security_async.feature index 0be2c4eeb..80651708f 100644 --- a/features/security_async.feature +++ b/features/security_async.feature @@ -50,7 +50,7 @@ Feature: Async Resource Cleanup and Leak Prevention And the state manager should be marked as closed Scenario: Subscriptions are disposed on cleanup - Given I have an ACP event queue with 3 active subscriptions + Given I have an A2A event queue with 3 active subscriptions When I close the event queue Then all subscriptions should be removed And the subscription count should be zero @@ -106,14 +106,14 @@ Feature: Async Resource Cleanup and Leak Prevention And I try to reset state after close Then a RuntimeError should be raised mentioning "closed" - Scenario: Publish after close raises RuntimeError on AcpEventQueue - Given I have an ACP event queue with 1 active subscriptions + Scenario: Publish after close raises RuntimeError on A2aEventQueue + Given I have an A2A event queue with 1 active subscriptions When I close the event queue And I try to publish an event after close Then a RuntimeError should be raised mentioning "closed" - Scenario: AcpEventQueue exposes is_closed property - Given I have an ACP event queue with 0 active subscriptions + Scenario: A2aEventQueue exposes is_closed property + Given I have an A2A event queue with 0 active subscriptions Then the event queue is_closed should be False When I close the event queue Then the event queue is_closed should be True diff --git a/features/server_cli_coverage_boost.feature b/features/server_cli_coverage_boost.feature index da13777be..03a778508 100644 --- a/features/server_cli_coverage_boost.feature +++ b/features/server_cli_coverage_boost.feature @@ -1,4 +1,4 @@ -@phase2 @acp @server-cli-coverage +@phase2 @a2a @server-cli-coverage Feature: Server CLI command coverage boost As a developer I want full test coverage for server.py CLI commands diff --git a/features/server_client_stubs.feature b/features/server_client_stubs.feature index 134e282b8..70716c103 100644 --- a/features/server_client_stubs.feature +++ b/features/server_client_stubs.feature @@ -1,4 +1,4 @@ -@phase2 @acp @server-stubs +@phase2 @a2a @server-stubs Feature: Server Client Stubs and Connection Config As a developer I want protocol stubs for server communication diff --git a/features/steps/acp_clients_coverage_boost_steps.py b/features/steps/a2a_clients_coverage_boost_steps.py similarity index 98% rename from features/steps/acp_clients_coverage_boost_steps.py rename to features/steps/a2a_clients_coverage_boost_steps.py index 99d9c86e3..2c87ef943 100644 --- a/features/steps/acp_clients_coverage_boost_steps.py +++ b/features/steps/a2a_clients_coverage_boost_steps.py @@ -1,4 +1,4 @@ -"""Step definitions for ACP clients coverage boost scenarios. +"""Step definitions for A2A clients coverage boost scenarios. Covers: - Protocol default method bodies (the ``...`` expression on lines 28, 36, 52, 63, 74, 90, 101) @@ -10,7 +10,7 @@ from __future__ import annotations from behave import given, then, when from behave.runner import Context -from cleveragents.acp.clients import ( +from cleveragents.a2a.clients import ( AuthClient, RemoteExecutionClient, ServerClient, diff --git a/features/steps/acp_facade_coverage_boost_steps.py b/features/steps/a2a_facade_coverage_boost_steps.py similarity index 90% rename from features/steps/acp_facade_coverage_boost_steps.py rename to features/steps/a2a_facade_coverage_boost_steps.py index f79639afc..1be35dc4b 100644 --- a/features/steps/acp_facade_coverage_boost_steps.py +++ b/features/steps/a2a_facade_coverage_boost_steps.py @@ -1,6 +1,6 @@ -"""Step definitions for ACP facade coverage-boost scenarios. +"""Step definitions for A2A facade coverage-boost scenarios. -Targets uncovered lines in ``src/cleveragents/acp/facade.py``: +Targets uncovered lines in ``src/cleveragents/a2a/facade.py``: | Line | Code path | |------|--------------------------------------------------------| @@ -20,8 +20,8 @@ from unittest.mock import MagicMock from behave import given, then, use_step_matcher, when from behave.runner import Context -from cleveragents.acp.facade import AcpLocalFacade -from cleveragents.acp.models import AcpRequest +from cleveragents.a2a.facade import A2aLocalFacade +from cleveragents.a2a.models import A2aRequest use_step_matcher("re") @@ -61,7 +61,7 @@ def _build_mock_plan_lifecycle_service() -> MagicMock: @given(r"a coverage-boost facade with a mock PlanLifecycleService") def step_cb_facade_plan(context: Context) -> None: - context.cb_facade = AcpLocalFacade( + context.cb_facade = A2aLocalFacade( services={"plan_lifecycle_service": _build_mock_plan_lifecycle_service()} ) @@ -71,11 +71,11 @@ def step_cb_facade_plan(context: Context) -> None: # --------------------------------------------------------------------------- -@when(r"I try to create an AcpLocalFacade with a non-dict services argument") +@when(r"I try to create an A2aLocalFacade with a non-dict services argument") def step_cb_create_facade_non_dict(context: Context) -> None: context.cb_caught_error = None try: - AcpLocalFacade(services=["not", "a", "dict"]) # type: ignore[arg-type] + A2aLocalFacade(services=["not", "a", "dict"]) # type: ignore[arg-type] except TypeError as exc: context.cb_caught_error = exc @@ -86,7 +86,7 @@ def step_cb_create_facade_non_dict(context: Context) -> None: ) def step_cb_dispatch(context: Context, operation: str, params_json: str) -> None: params: dict[str, Any] = json.loads(params_json) - request = AcpRequest(operation=operation, params=params) + request = A2aRequest(operation=operation, params=params) context.cb_response = context.cb_facade.dispatch(request) diff --git a/features/steps/acp_facade_steps.py b/features/steps/a2a_facade_steps.py similarity index 74% rename from features/steps/acp_facade_steps.py rename to features/steps/a2a_facade_steps.py index b15b4317b..61683fd95 100644 --- a/features/steps/acp_facade_steps.py +++ b/features/steps/a2a_facade_steps.py @@ -1,4 +1,4 @@ -"""Step definitions for ACP facade and stubs Behave scenarios.""" +"""Step definitions for A2A facade and stubs Behave scenarios.""" from __future__ import annotations @@ -9,39 +9,39 @@ from behave import given, then, use_step_matcher, when from behave.runner import Context from pydantic import ValidationError -from cleveragents.acp.errors import ( - AcpError, - AcpNotAvailableError, - AcpOperationNotFoundError, - AcpVersionMismatchError, +from cleveragents.a2a.errors import ( + A2aError, + A2aNotAvailableError, + A2aOperationNotFoundError, + A2aVersionMismatchError, ) -from cleveragents.acp.events import AcpEventQueue -from cleveragents.acp.facade import AcpLocalFacade -from cleveragents.acp.models import ( - AcpErrorDetail, - AcpEvent, - AcpRequest, - AcpResponse, +from cleveragents.a2a.events import A2aEventQueue +from cleveragents.a2a.facade import A2aLocalFacade +from cleveragents.a2a.models import ( + A2aErrorDetail, + A2aEvent, + A2aRequest, + A2aResponse, ) -from cleveragents.acp.transport import AcpHttpTransport -from cleveragents.acp.versioning import AcpVersionNegotiator +from cleveragents.a2a.transport import A2aHttpTransport +from cleveragents.a2a.versioning import A2aVersionNegotiator from cleveragents.core.exceptions import CleverAgentsError use_step_matcher("re") -# AcpLocalFacade — creation and service registration +# A2aLocalFacade — creation and service registration -@given(r"a new AcpLocalFacade with no services") +@given(r"a new A2aLocalFacade with no services") def step_facade_no_services(context: Context) -> None: - context.facade = AcpLocalFacade() + context.facade = A2aLocalFacade() -@given(r"a new AcpLocalFacade with services (?P.+)") +@given(r"a new A2aLocalFacade with services (?P.+)") def step_facade_with_services(context: Context, services_json: str) -> None: services = json.loads(services_json) - context.facade = AcpLocalFacade(services=services) + context.facade = A2aLocalFacade(services=services) @then(r"the facade should be created successfully") @@ -70,20 +70,20 @@ def step_register_empty_name(context: Context) -> None: context.error = exc -@then(r"an ACP ValueError should be raised") +@then(r"an A2A ValueError should be raised") def step_value_error_raised(context: Context) -> None: assert isinstance(context.caught_error, ValueError), ( f"Expected ValueError, got {type(context.caught_error)}" ) -# AcpLocalFacade — dispatch operations +# A2aLocalFacade — dispatch operations @when(r'I dispatch operation "(?P[^"]+)" with params (?P.+)') def step_dispatch_operation(context: Context, operation: str, params_json: str) -> None: params: dict[str, Any] = json.loads(params_json) - request = AcpRequest(operation=operation, params=params) + request = A2aRequest(operation=operation, params=params) context.response = context.facade.dispatch(request) @@ -108,7 +108,7 @@ def step_response_data_key_value(context: Context, key: str, value: str) -> None assert str(actual) == value, f"Expected '{value}', got '{actual}'" -# AcpLocalFacade — unknown operation +# A2aLocalFacade — unknown operation @when(r'I dispatch an unknown operation "(?P[^"]+)"') @@ -116,16 +116,16 @@ def step_dispatch_unknown(context: Context, operation: str) -> None: context.caught_error = None context.error = None try: - request = AcpRequest(operation=operation) + request = A2aRequest(operation=operation) context.facade.dispatch(request) - except AcpOperationNotFoundError as exc: + except A2aOperationNotFoundError as exc: context.caught_error = exc context.error = exc -@then(r"an AcpOperationNotFoundError should be raised") +@then(r"an A2aOperationNotFoundError should be raised") def step_op_not_found_raised(context: Context) -> None: - assert isinstance(context.caught_error, AcpOperationNotFoundError) + assert isinstance(context.caught_error, A2aOperationNotFoundError) @then(r'the error operation attribute should be "(?P[^"]+)"') @@ -133,7 +133,7 @@ def step_error_operation_attr(context: Context, operation: str) -> None: assert context.caught_error.operation == operation -# AcpLocalFacade — list_operations +# A2aLocalFacade — list_operations @when(r"I call list_operations on the facade") @@ -154,12 +154,12 @@ def step_operations_count(context: Context, count: str) -> None: ) -# AcpHttpTransport +# A2aHttpTransport -@given(r"a new AcpHttpTransport") +@given(r"a new A2aHttpTransport") def step_transport(context: Context) -> None: - context.transport = AcpHttpTransport() + context.transport = A2aHttpTransport() @when(r"I try to send a request via the transport") @@ -167,9 +167,9 @@ def step_transport_send(context: Context) -> None: context.caught_error = None context.error = None try: - request = AcpRequest(operation="test.op") + request = A2aRequest(operation="test.op") context.transport.send(request) - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.caught_error = exc context.error = exc @@ -180,7 +180,7 @@ def step_transport_connect(context: Context, url: str) -> None: context.error = None try: context.transport.connect(url) - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.caught_error = exc context.error = exc @@ -191,15 +191,15 @@ def step_transport_disconnect(context: Context) -> None: context.error = None try: context.transport.disconnect() - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.caught_error = exc context.error = exc -@then(r"an AcpNotAvailableError should be raised") +@then(r"an A2aNotAvailableError should be raised") def step_not_available_raised(context: Context) -> None: - assert isinstance(context.caught_error, AcpNotAvailableError), ( - f"Expected AcpNotAvailableError, got {type(context.caught_error)}" + assert isinstance(context.caught_error, A2aNotAvailableError), ( + f"Expected A2aNotAvailableError, got {type(context.caught_error)}" ) @@ -208,18 +208,18 @@ def step_transport_not_connected(context: Context) -> None: assert context.transport.is_connected() is False -# AcpEventQueue — local mode +# A2aEventQueue — local mode -@given(r"a new AcpEventQueue") +@given(r"a new A2aEventQueue") def step_event_queue(context: Context) -> None: - context.queue = AcpEventQueue() - context.callback_events: list[AcpEvent] = [] + context.queue = A2aEventQueue() + context.callback_events: list[A2aEvent] = [] @given(r"I subscribe locally with a callback") def step_subscribe_local(context: Context) -> None: - def _cb(event: AcpEvent) -> None: + def _cb(event: A2aEvent) -> None: context.callback_events.append(event) context.subscription_id = context.queue.subscribe_local(_cb) @@ -227,14 +227,14 @@ def step_subscribe_local(context: Context) -> None: @when(r'I publish an event with type "(?P[^"]+)"') def step_publish_event(context: Context, event_type: str) -> None: - event = AcpEvent(event_type=event_type) + event = A2aEvent(event_type=event_type) context.queue.publish(event) @when(r"I publish (?P\d+) events") def step_publish_n_events(context: Context, count: str) -> None: for i in range(int(count)): - event = AcpEvent(event_type=f"test.event.{i}") + event = A2aEvent(event_type=f"test.event.{i}") context.queue.publish(event) @@ -284,7 +284,7 @@ def step_received_count(context: Context, count: str) -> None: ) -# AcpEventQueue — remote stub +# A2aEventQueue — remote stub @when(r'I try to subscribe remotely to "(?P[^"]+)"') @@ -293,17 +293,17 @@ def step_remote_subscribe(context: Context, endpoint: str) -> None: context.error = None try: context.queue.subscribe_remote(endpoint) - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.caught_error = exc context.error = exc -# AcpVersionNegotiator +# A2aVersionNegotiator -@given(r"a new AcpVersionNegotiator") +@given(r"a new A2aVersionNegotiator") def step_version_negotiator(context: Context) -> None: - context.negotiator = AcpVersionNegotiator() + context.negotiator = A2aVersionNegotiator() @when(r'I negotiate version "(?P[^"]+)"') @@ -322,14 +322,14 @@ def step_negotiate_fail(context: Context, version: str) -> None: context.error = None try: context.negotiator.negotiate(version) - except AcpVersionMismatchError as exc: + except A2aVersionMismatchError as exc: context.caught_error = exc context.error = exc -@then(r"an AcpVersionMismatchError should be raised") +@then(r"an A2aVersionMismatchError should be raised") def step_version_mismatch_raised(context: Context) -> None: - assert isinstance(context.caught_error, AcpVersionMismatchError) + assert isinstance(context.caught_error, A2aVersionMismatchError) @then(r'the error requested_version should be "(?P[^"]+)"') @@ -352,12 +352,12 @@ def step_current_version(context: Context, version: str) -> None: assert context.negotiator.get_current() == version -# AcpRequest model validation +# A2aRequest model validation -@when(r'I create an AcpRequest with operation "(?P[^"]+)"') +@when(r'I create an A2aRequest with operation "(?P[^"]+)"') def step_create_request(context: Context, operation: str) -> None: - context.request = AcpRequest(operation=operation) + context.request = A2aRequest(operation=operation) @then(r"the request should have a non-empty request_id") @@ -365,17 +365,17 @@ def step_request_has_id(context: Context) -> None: assert context.request.request_id, "request_id should not be empty" -@then(r'the request acp_version should be "(?P[^"]+)"') +@then(r'the request a2a_version should be "(?P[^"]+)"') def step_request_version(context: Context, version: str) -> None: - assert context.request.acp_version == version + assert context.request.a2a_version == version -@when(r"I try to create an AcpRequest with empty operation") +@when(r"I try to create an A2aRequest with empty operation") def step_create_request_empty(context: Context) -> None: context.caught_error = None context.error = None try: - AcpRequest(operation="") + A2aRequest(operation="") except ValidationError as exc: context.caught_error = exc context.error = exc @@ -386,14 +386,14 @@ def step_create_request_empty(context: Context) -> None: # We reuse those shared step definitions. -# AcpResponse model validation +# A2aResponse model validation @when( - r'I create an AcpResponse with status "(?P[^"]+)" and request_id "(?P[^"]+)"' + r'I create an A2aResponse with status "(?P[^"]+)" and request_id "(?P[^"]+)"' ) def step_create_response(context: Context, status: str, rid: str) -> None: - context.response = AcpResponse(request_id=rid, status=status) + context.response = A2aResponse(request_id=rid, status=status) @then(r"the response should be valid") @@ -401,25 +401,25 @@ def step_response_valid(context: Context) -> None: assert context.response is not None -@when(r'I try to create an AcpResponse with status "(?P[^"]+)"') +@when(r'I try to create an A2aResponse with status "(?P[^"]+)"') def step_create_response_invalid(context: Context, status: str) -> None: context.caught_error = None context.error = None try: - AcpResponse(request_id="REQ", status=status) + A2aResponse(request_id="REQ", status=status) except ValidationError as exc: context.caught_error = exc context.error = exc -# AcpErrorDetail model validation +# A2aErrorDetail model validation @when( - r'I create an AcpErrorDetail with code "(?P[^"]+)" and message "(?P[^"]+)"' + r'I create an A2aErrorDetail with code "(?P[^"]+)" and message "(?P[^"]+)"' ) def step_create_error_detail(context: Context, code: str, msg: str) -> None: - context.error_detail = AcpErrorDetail(code=code, message=msg) + context.error_detail = A2aErrorDetail(code=code, message=msg) @then(r"the error detail should be valid") @@ -427,23 +427,23 @@ def step_error_detail_valid(context: Context) -> None: assert context.error_detail is not None -@when(r"I try to create an AcpErrorDetail with empty code") +@when(r"I try to create an A2aErrorDetail with empty code") def step_create_error_detail_empty(context: Context) -> None: context.caught_error = None context.error = None try: - AcpErrorDetail(code="", message="test") + A2aErrorDetail(code="", message="test") except ValidationError as exc: context.caught_error = exc context.error = exc -# AcpEvent model construction +# A2aEvent model construction -@when(r'I create an AcpEvent with type "(?P[^"]+)"') +@when(r'I create an A2aEvent with type "(?P[^"]+)"') def step_create_event(context: Context, event_type: str) -> None: - context.event = AcpEvent(event_type=event_type) + context.event = A2aEvent(event_type=event_type) @then(r"the event should have a non-empty event_id") @@ -459,29 +459,29 @@ def step_event_has_timestamp(context: Context) -> None: # Error hierarchy -@then(r"AcpError should be a subclass of CleverAgentsError") -def step_acp_error_hierarchy(context: Context) -> None: - assert issubclass(AcpError, CleverAgentsError) +@then(r"A2aError should be a subclass of CleverAgentsError") +def step_a2a_error_hierarchy(context: Context) -> None: + assert issubclass(A2aError, CleverAgentsError) -@then(r"AcpNotAvailableError should be a subclass of AcpError") +@then(r"A2aNotAvailableError should be a subclass of A2aError") def step_not_available_hierarchy(context: Context) -> None: - assert issubclass(AcpNotAvailableError, AcpError) + assert issubclass(A2aNotAvailableError, A2aError) -@then(r"AcpVersionMismatchError should be a subclass of AcpError") +@then(r"A2aVersionMismatchError should be a subclass of A2aError") def step_version_mismatch_hierarchy(context: Context) -> None: - assert issubclass(AcpVersionMismatchError, AcpError) + assert issubclass(A2aVersionMismatchError, A2aError) -@then(r"AcpOperationNotFoundError should be a subclass of AcpError") +@then(r"A2aOperationNotFoundError should be a subclass of A2aError") def step_op_not_found_hierarchy(context: Context) -> None: - assert issubclass(AcpOperationNotFoundError, AcpError) + assert issubclass(A2aOperationNotFoundError, A2aError) -@when(r"I create an AcpNotAvailableError with default message") +@when(r"I create an A2aNotAvailableError with default message") def step_create_not_available_default(context: Context) -> None: - context.caught_error = AcpNotAvailableError() + context.caught_error = A2aNotAvailableError() context.error = context.caught_error diff --git a/features/steps/acp_facade_wiring_steps.py b/features/steps/a2a_facade_wiring_steps.py similarity index 81% rename from features/steps/acp_facade_wiring_steps.py rename to features/steps/a2a_facade_wiring_steps.py index 1c13e862e..2020219d2 100644 --- a/features/steps/acp_facade_wiring_steps.py +++ b/features/steps/a2a_facade_wiring_steps.py @@ -1,7 +1,7 @@ -"""Step definitions for ACP facade wiring Behave scenarios. +"""Step definitions for A2A facade wiring Behave scenarios. All mocks in this file are lightweight test doubles that simulate the -service contracts used by :class:`AcpLocalFacade`. They live here +service contracts used by :class:`A2aLocalFacade`. They live here (inside the test tree) per the project's mock-placement policy. """ @@ -14,9 +14,9 @@ from unittest.mock import MagicMock from behave import given, then, use_step_matcher, when from behave.runner import Context -from cleveragents.acp.events import AcpEventQueue -from cleveragents.acp.facade import AcpLocalFacade -from cleveragents.acp.models import AcpRequest +from cleveragents.a2a.events import A2aEventQueue +from cleveragents.a2a.facade import A2aLocalFacade +from cleveragents.a2a.models import A2aRequest from cleveragents.core.exceptions import ( BusinessRuleViolation, PlanError, @@ -112,74 +112,74 @@ def _build_mock_resource_registry_service() -> MagicMock: # --------------------------------------------------------------------------- -@given(r"a wired AcpLocalFacade with a mock SessionService") +@given(r"a wired A2aLocalFacade with a mock SessionService") def step_wired_facade_session(context: Context) -> None: - context.wired_facade = AcpLocalFacade( + context.wired_facade = A2aLocalFacade( services={"session_service": _build_mock_session_service()} ) -@given(r"a wired AcpLocalFacade with a mock PlanLifecycleService") +@given(r"a wired A2aLocalFacade with a mock PlanLifecycleService") def step_wired_facade_plan(context: Context) -> None: - context.wired_facade = AcpLocalFacade( + context.wired_facade = A2aLocalFacade( services={"plan_lifecycle_service": _build_mock_plan_lifecycle_service()} ) -@given(r"a wired AcpLocalFacade with a mock ToolRegistry") +@given(r"a wired A2aLocalFacade with a mock ToolRegistry") def step_wired_facade_tool_registry(context: Context) -> None: - context.wired_facade = AcpLocalFacade( + context.wired_facade = A2aLocalFacade( services={"tool_registry": _build_mock_tool_registry()} ) -@given(r"a wired AcpLocalFacade with a mock ResourceRegistryService") +@given(r"a wired A2aLocalFacade with a mock ResourceRegistryService") def step_wired_facade_resource_registry(context: Context) -> None: - context.wired_facade = AcpLocalFacade( + context.wired_facade = A2aLocalFacade( services={ "resource_registry_service": (_build_mock_resource_registry_service()) } ) -@given(r"a wired AcpLocalFacade with a mock AcpEventQueue") +@given(r"a wired A2aLocalFacade with a mock A2aEventQueue") def step_wired_facade_event_queue(context: Context) -> None: - context.wired_facade = AcpLocalFacade(services={"event_queue": AcpEventQueue()}) + context.wired_facade = A2aLocalFacade(services={"event_queue": A2aEventQueue()}) -@given(r"a wired AcpLocalFacade with no services") +@given(r"a wired A2aLocalFacade with no services") def step_wired_facade_no_services(context: Context) -> None: - context.wired_facade = AcpLocalFacade() + context.wired_facade = A2aLocalFacade() -@given(r"a wired AcpLocalFacade with a raising SessionService for not-found") +@given(r"a wired A2aLocalFacade with a raising SessionService for not-found") def step_wired_facade_not_found(context: Context) -> None: svc = MagicMock() svc.delete.side_effect = ResourceNotFoundError( resource_type="session", resource_id="nonexistent" ) - context.wired_facade = AcpLocalFacade(services={"session_service": svc}) + context.wired_facade = A2aLocalFacade(services={"session_service": svc}) -@given(r"a wired AcpLocalFacade with a raising service for validation-error") +@given(r"a wired A2aLocalFacade with a raising service for validation-error") def step_wired_facade_validation_error(context: Context) -> None: svc = MagicMock() svc.use_action.side_effect = ValidationError("Invalid action args") - context.wired_facade = AcpLocalFacade(services={"plan_lifecycle_service": svc}) + context.wired_facade = A2aLocalFacade(services={"plan_lifecycle_service": svc}) -@given(r"a wired AcpLocalFacade with a raising service for plan-error") +@given(r"a wired A2aLocalFacade with a raising service for plan-error") def step_wired_facade_plan_error(context: Context) -> None: svc = MagicMock() svc.execute_plan.side_effect = PlanError("Plan execution failed") - context.wired_facade = AcpLocalFacade(services={"plan_lifecycle_service": svc}) + context.wired_facade = A2aLocalFacade(services={"plan_lifecycle_service": svc}) -@given(r"a wired AcpLocalFacade with a raising service for invalid-state") +@given(r"a wired A2aLocalFacade with a raising service for invalid-state") def step_wired_facade_invalid_state(context: Context) -> None: svc = MagicMock() svc.apply_plan.side_effect = BusinessRuleViolation("Cannot apply in current state") - context.wired_facade = AcpLocalFacade(services={"plan_lifecycle_service": svc}) + context.wired_facade = A2aLocalFacade(services={"plan_lifecycle_service": svc}) # --------------------------------------------------------------------------- @@ -193,7 +193,7 @@ def step_wired_facade_invalid_state(context: Context) -> None: ) def step_dispatch_wired(context: Context, operation: str, params_json: str) -> None: params: dict[str, Any] = json.loads(params_json) - request = AcpRequest(operation=operation, params=params) + request = A2aRequest(operation=operation, params=params) context.wired_response = context.wired_facade.dispatch(request) diff --git a/features/steps/devcontainer_cleanup_steps.py b/features/steps/devcontainer_cleanup_steps.py index 747ea3d69..5641e82d8 100644 --- a/features/steps/devcontainer_cleanup_steps.py +++ b/features/steps/devcontainer_cleanup_steps.py @@ -19,8 +19,8 @@ from behave import given, then, when from behave.runner import Context from typer.testing import CliRunner -from cleveragents.acp.facade import AcpLocalFacade -from cleveragents.acp.models import AcpRequest +from cleveragents.a2a.facade import A2aLocalFacade +from cleveragents.a2a.models import A2aRequest from cleveragents.application.services.cleanup_service import CleanupService from cleveragents.cli.commands.resource import app as resource_app from cleveragents.core.exceptions import NotFoundError @@ -252,7 +252,7 @@ def step_create_active_container_no_docker_with_session( @given("a facade with no session service") def step_create_facade_no_session(context: Context) -> None: - context.facade = AcpLocalFacade(services={}) + context.facade = A2aLocalFacade(services={}) # ── When steps ─────────────────────────────────────────────── @@ -348,7 +348,7 @@ def step_list_for_empty_session(context: Context) -> None: @when('I close session "{session_id}" via the facade') def step_close_session_via_facade(context: Context, session_id: str) -> None: - request = AcpRequest( + request = A2aRequest( operation="session.close", params={"session_id": session_id}, ) diff --git a/features/steps/lsp_server_stub_steps.py b/features/steps/lsp_server_stub_steps.py index f5ea7cb09..d9b2e3179 100644 --- a/features/steps/lsp_server_stub_steps.py +++ b/features/steps/lsp_server_stub_steps.py @@ -1,7 +1,7 @@ """Step definitions for LSP server stub BDD tests. Covers the JSON-RPC transport, protocol handshake, error handling, -ACP facade wiring, and CLI serve command for the LSP server stub. +A2A facade wiring, and CLI serve command for the LSP server stub. """ from __future__ import annotations @@ -15,7 +15,7 @@ from behave import given, then, when from behave.runner import Context from typer.testing import CliRunner -from cleveragents.acp.facade import AcpLocalFacade +from cleveragents.a2a.facade import A2aLocalFacade from cleveragents.cli.commands.lsp import app as lsp_app from cleveragents.lsp.server import ( MAX_CONTENT_LENGTH, @@ -38,7 +38,7 @@ def step_mock_transport(context: Context) -> None: context.lsp_server: LspServer | None = None context.lsp_responses: list[dict[str, Any]] = [] context.lsp_exit_code: int | None = None - context.lsp_facade: AcpLocalFacade | None = None + context.lsp_facade: A2aLocalFacade | None = None context.lsp_error: Exception | None = None @@ -108,9 +108,9 @@ def step_missing_method(context: Context, req_id: int) -> None: context.lsp_transport.send_raw(framed) -@given("an LSP server with ACP facade") +@given("an LSP server with A2A facade") def step_server_with_facade(context: Context) -> None: - context.lsp_facade = AcpLocalFacade() + context.lsp_facade = A2aLocalFacade() # --------------------------------------------------------------------------- @@ -349,12 +349,12 @@ def step_error_no_data(context: Context, req_id: int) -> None: ) -@then("the server should hold the provided ACP facade") +@then("the server should hold the provided A2A facade") def step_server_holds_facade(context: Context) -> None: assert context.lsp_server is not None, "No server was created" assert context.lsp_facade is not None, "No facade was provided" assert context.lsp_server.facade is context.lsp_facade, ( - "Server does not hold the provided ACP facade instance" + "Server does not hold the provided A2A facade instance" ) @@ -364,10 +364,10 @@ def step_server_facade_lazy(context: Context) -> None: # Access the public property — this triggers lazy creation facade = context.lsp_server.facade assert facade is not None, "Facade was not lazily created" - from cleveragents.acp.facade import AcpLocalFacade + from cleveragents.a2a.facade import A2aLocalFacade - assert isinstance(facade, AcpLocalFacade), ( - f"Expected AcpLocalFacade, got {type(facade).__name__}" + assert isinstance(facade, A2aLocalFacade), ( + f"Expected A2aLocalFacade, got {type(facade).__name__}" ) diff --git a/features/steps/m6_facade_steps.py b/features/steps/m6_facade_steps.py index 5f3bc8efa..d1e324b0a 100644 --- a/features/steps/m6_facade_steps.py +++ b/features/steps/m6_facade_steps.py @@ -1,4 +1,4 @@ -"""Step definitions for M6 ACP facade, event queue, transport, and version tests. +"""Step definitions for M6 A2A facade, event queue, transport, and version tests. Split from ``m6_autonomy_acceptance_steps.py`` to stay under the project's 500-line guideline. All step names keep the ``m6 smoke`` prefix to avoid @@ -14,21 +14,21 @@ from unittest.mock import MagicMock from behave import given, then, when from behave.runner import Context -from cleveragents.acp.errors import ( - AcpNotAvailableError, - AcpOperationNotFoundError, - AcpVersionMismatchError, +from cleveragents.a2a.errors import ( + A2aNotAvailableError, + A2aOperationNotFoundError, + A2aVersionMismatchError, ) -from cleveragents.acp.events import AcpEventQueue -from cleveragents.acp.facade import AcpLocalFacade -from cleveragents.acp.models import ( - AcpErrorDetail, - AcpEvent, - AcpRequest, - AcpResponse, +from cleveragents.a2a.events import A2aEventQueue +from cleveragents.a2a.facade import A2aLocalFacade +from cleveragents.a2a.models import ( + A2aErrorDetail, + A2aEvent, + A2aRequest, + A2aResponse, ) -from cleveragents.acp.transport import AcpHttpTransport -from cleveragents.acp.versioning import AcpVersionNegotiator +from cleveragents.a2a.transport import A2aHttpTransport +from cleveragents.a2a.versioning import A2aVersionNegotiator _FIXTURES_DIR = Path(__file__).resolve().parents[1] / "fixtures" / "m6" @@ -56,20 +56,20 @@ def step_m6_smoke_runner(context: Context) -> None: context.m6_profiles_list = [] -@given("a m6 smoke ACP local facade") +@given("a m6 smoke A2A local facade") def step_m6_smoke_facade(context: Context) -> None: - """Create an AcpLocalFacade for testing.""" - context.m6_facade = AcpLocalFacade() + """Create an A2aLocalFacade for testing.""" + context.m6_facade = A2aLocalFacade() # ----------------------------------------------------------------------- -# Fixture loading — ACP facade flows +# Fixture loading — A2A facade flows # ----------------------------------------------------------------------- -@when("I m6 smoke load the ACP facade flows fixture") +@when("I m6 smoke load the A2A facade flows fixture") def step_m6_smoke_load_facade_fixture(context: Context) -> None: - with open(_FIXTURES_DIR / "acp_facade_flows.json") as f: + with open(_FIXTURES_DIR / "a2a_facade_flows.json") as f: context.m6_fixture_data = json.load(f) @@ -86,7 +86,7 @@ def step_m6_smoke_facade_plan(context: Context) -> None: # ----------------------------------------------------------------------- -# ACP facade dispatch operations +# A2A facade dispatch operations # ----------------------------------------------------------------------- @@ -97,7 +97,7 @@ def step_m6_smoke_dispatch( params_json: str, ) -> None: params = json.loads(params_json) - request = AcpRequest(operation=operation, params=params) + request = A2aRequest(operation=operation, params=params) context.m6_response = context.m6_facade.dispatch(request) @@ -124,26 +124,26 @@ def step_m6_smoke_response_value( # ----------------------------------------------------------------------- -# ACP facade error handling +# A2A facade error handling # ----------------------------------------------------------------------- @when('I m6 smoke dispatch unknown operation "{operation}"') def step_m6_smoke_dispatch_unknown(context: Context, operation: str) -> None: - request = AcpRequest(operation=operation, params={}) + request = A2aRequest(operation=operation, params={}) try: context.m6_facade.dispatch(request) context.m6_error = None - except AcpOperationNotFoundError as exc: + except A2aOperationNotFoundError as exc: context.m6_error = exc -@then("the m6 smoke facade should raise AcpOperationNotFoundError") +@then("the m6 smoke facade should raise A2aOperationNotFoundError") def step_m6_smoke_error_op_not_found(context: Context) -> None: - assert isinstance(context.m6_error, AcpOperationNotFoundError) + assert isinstance(context.m6_error, A2aOperationNotFoundError) -@when("I m6 smoke dispatch with a non-AcpRequest object") +@when("I m6 smoke dispatch with a non-A2aRequest object") def step_m6_smoke_dispatch_invalid(context: Context) -> None: try: context.m6_facade.dispatch("not a request") # type: ignore[arg-type] @@ -158,7 +158,7 @@ def step_m6_smoke_error_type(context: Context) -> None: # ----------------------------------------------------------------------- -# ACP facade service registration and list operations +# A2A facade service registration and list operations # ----------------------------------------------------------------------- @@ -189,19 +189,19 @@ def step_m6_smoke_ops_count(context: Context, count: int) -> None: # ----------------------------------------------------------------------- -# ACP event queue +# A2A event queue # ----------------------------------------------------------------------- -@given("a m6 smoke ACP event queue") +@given("a m6 smoke A2A event queue") def step_m6_smoke_event_queue(context: Context) -> None: - context.m6_event_queue = AcpEventQueue() + context.m6_event_queue = A2aEventQueue() context.m6_callback_calls = [] @when('I m6 smoke publish an event with type "{event_type}"') def step_m6_smoke_publish_event(context: Context, event_type: str) -> None: - event = AcpEvent(event_type=event_type, data={"test": True}) + event = A2aEvent(event_type=event_type, data={"test": True}) context.m6_event_queue.publish(event) @@ -219,7 +219,7 @@ def step_m6_smoke_last_event_type(context: Context, event_type: str) -> None: @when("I m6 smoke subscribe a local callback") def step_m6_smoke_subscribe_local(context: Context) -> None: - def _callback(event: AcpEvent) -> None: + def _callback(event: A2aEvent) -> None: context.m6_callback_calls.append(event) context.m6_subscription_id = context.m6_event_queue.subscribe_local(_callback) @@ -247,7 +247,7 @@ def step_m6_smoke_close_queue(context: Context) -> None: @then("the m6 smoke publishing should raise RuntimeError") def step_m6_smoke_publish_after_close(context: Context) -> None: - event = AcpEvent(event_type="after.close", data={}) + event = A2aEvent(event_type="after.close", data={}) try: context.m6_event_queue.publish(event) raise AssertionError("Expected RuntimeError") @@ -260,54 +260,54 @@ def step_m6_smoke_remote_subscribe(context: Context, endpoint: str) -> None: try: context.m6_event_queue.subscribe_remote(endpoint) context.m6_error = None - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.m6_error = exc -@then("the m6 smoke facade should raise AcpNotAvailableError") +@then("the m6 smoke facade should raise A2aNotAvailableError") def step_m6_smoke_error_not_available(context: Context) -> None: - assert isinstance(context.m6_error, AcpNotAvailableError) + assert isinstance(context.m6_error, A2aNotAvailableError) # ----------------------------------------------------------------------- -# ACP HTTP transport stub +# A2A HTTP transport stub # ----------------------------------------------------------------------- @when("I m6 smoke attempt transport send") def step_m6_smoke_transport_send(context: Context) -> None: - transport = AcpHttpTransport() - request = AcpRequest(operation="plan.create", params={}) + transport = A2aHttpTransport() + request = A2aRequest(operation="plan.create", params={}) try: transport.send(request) context.m6_error = None - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.m6_error = exc @when('I m6 smoke attempt transport connect to "{url}"') def step_m6_smoke_transport_connect(context: Context, url: str) -> None: - transport = AcpHttpTransport() + transport = A2aHttpTransport() try: transport.connect(url) context.m6_error = None - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.m6_error = exc @when("I m6 smoke attempt transport disconnect") def step_m6_smoke_transport_disconnect(context: Context) -> None: - transport = AcpHttpTransport() + transport = A2aHttpTransport() try: transport.disconnect() context.m6_error = None - except AcpNotAvailableError as exc: + except A2aNotAvailableError as exc: context.m6_error = exc @when("I m6 smoke check transport is_connected") def step_m6_smoke_transport_connected(context: Context) -> None: - transport = AcpHttpTransport() + transport = A2aHttpTransport() context.m6_transport_connected = transport.is_connected() @@ -317,17 +317,17 @@ def step_m6_smoke_transport_not_connected(context: Context) -> None: # ----------------------------------------------------------------------- -# ACP version negotiation +# A2A version negotiation # ----------------------------------------------------------------------- -@when('I m6 smoke negotiate ACP version "{version}"') +@when('I m6 smoke negotiate A2A version "{version}"') def step_m6_smoke_negotiate(context: Context, version: str) -> None: - negotiator = AcpVersionNegotiator() + negotiator = A2aVersionNegotiator() try: context.m6_version_result = negotiator.negotiate(version) context.m6_error = None - except AcpVersionMismatchError as exc: + except A2aVersionMismatchError as exc: context.m6_error = exc @@ -336,14 +336,14 @@ def step_m6_smoke_negotiated(context: Context, version: str) -> None: assert context.m6_version_result == version -@then("the m6 smoke facade should raise AcpVersionMismatchError") +@then("the m6 smoke facade should raise A2aVersionMismatchError") def step_m6_smoke_error_version(context: Context) -> None: - assert isinstance(context.m6_error, AcpVersionMismatchError) + assert isinstance(context.m6_error, A2aVersionMismatchError) @when('I m6 smoke check if version "{version}" is supported') def step_m6_smoke_version_supported(context: Context, version: str) -> None: - negotiator = AcpVersionNegotiator() + negotiator = A2aVersionNegotiator() context.m6_version_supported = negotiator.is_supported(version) @@ -358,41 +358,41 @@ def step_m6_smoke_version_false(context: Context) -> None: # ----------------------------------------------------------------------- -# ACP model validation +# A2A model validation # ----------------------------------------------------------------------- -@when("I m6 smoke create AcpRequest with empty operation") +@when("I m6 smoke create A2aRequest with empty operation") def step_m6_smoke_invalid_request(context: Context) -> None: try: - AcpRequest(operation="") + A2aRequest(operation="") context.m6_error = None except ValueError as exc: context.m6_error = exc -@when('I m6 smoke create AcpResponse with invalid status "{status}"') +@when('I m6 smoke create A2aResponse with invalid status "{status}"') def step_m6_smoke_invalid_response(context: Context, status: str) -> None: try: - AcpResponse(request_id="test", status=status) + A2aResponse(request_id="test", status=status) context.m6_error = None except ValueError as exc: context.m6_error = exc -@when("I m6 smoke create AcpEvent with empty event_type") +@when("I m6 smoke create A2aEvent with empty event_type") def step_m6_smoke_invalid_event(context: Context) -> None: try: - AcpEvent(event_type="") + A2aEvent(event_type="") context.m6_error = None except ValueError as exc: context.m6_error = exc -@when("I m6 smoke create AcpErrorDetail with empty code") +@when("I m6 smoke create A2aErrorDetail with empty code") def step_m6_smoke_invalid_error_detail(context: Context) -> None: try: - AcpErrorDetail(code="", message="test") + A2aErrorDetail(code="", message="test") context.m6_error = None except ValueError as exc: context.m6_error = exc diff --git a/features/steps/security_async_steps.py b/features/steps/security_async_steps.py index 12be6542a..2a04a1c31 100644 --- a/features/steps/security_async_steps.py +++ b/features/steps/security_async_steps.py @@ -7,7 +7,7 @@ from pathlib import Path from behave import given, then, when -from cleveragents.acp.events import AcpEventQueue +from cleveragents.a2a.events import A2aEventQueue from cleveragents.core.async_cleanup import AsyncResourceTracker from cleveragents.langgraph.state import GraphState, StateManager @@ -132,9 +132,9 @@ def step_create_state_manager(context): ) -@given("I have an ACP event queue with {count:d} active subscriptions") +@given("I have an A2A event queue with {count:d} active subscriptions") def step_create_event_queue(context, count): - context.event_queue = AcpEventQueue() + context.event_queue = A2aEventQueue() context.subscription_ids = [] for _ in range(count): sub_id = context.event_queue.subscribe_local(lambda _evt: None) @@ -483,10 +483,10 @@ def step_try_reset_after_close(context): @when("I try to publish an event after close") def step_try_publish_after_close(context): - from cleveragents.acp.models import AcpEvent + from cleveragents.a2a.models import A2aEvent try: - context.event_queue.publish(AcpEvent(event_type="test", data={})) + context.event_queue.publish(A2aEvent(event_type="test", data={})) context.runtime_error = None except RuntimeError as exc: context.runtime_error = exc diff --git a/features/steps/server_client_stubs_steps.py b/features/steps/server_client_stubs_steps.py index 2f1883bf3..d9dda4d0d 100644 --- a/features/steps/server_client_stubs_steps.py +++ b/features/steps/server_client_stubs_steps.py @@ -9,7 +9,7 @@ from behave import given, then, use_step_matcher, when from behave.runner import Context from pydantic import ValidationError -from cleveragents.acp.clients import ( +from cleveragents.a2a.clients import ( AuthClient, RemoteExecutionClient, ServerClient, @@ -17,7 +17,7 @@ from cleveragents.acp.clients import ( StubRemoteExecutionClient, StubServerClient, ) -from cleveragents.acp.server_config import ServerConnectionConfig +from cleveragents.a2a.server_config import ServerConnectionConfig from cleveragents.application.services.config_service import _REGISTRY use_step_matcher("re") diff --git a/robot/acp_facade.robot b/robot/a2a_facade.robot similarity index 70% rename from robot/acp_facade.robot rename to robot/a2a_facade.robot index ae91de959..3560e072e 100644 --- a/robot/acp_facade.robot +++ b/robot/a2a_facade.robot @@ -1,49 +1,49 @@ *** Settings *** -Documentation Smoke tests for ACP local facade and server stubs +Documentation Smoke tests for A2A local facade and server stubs Resource ${CURDIR}/common.resource Suite Setup Setup Test Environment Suite Teardown Cleanup Test Environment *** Variables *** -${HELPER} ${CURDIR}/helper_acp_facade.py +${HELPER} ${CURDIR}/helper_a2a_facade.py *** Test Cases *** -ACP Local Facade Dispatch Session Create +A2A Local Facade Dispatch Session Create [Documentation] Verify local facade dispatches session.create successfully ${result}= Run Process ${PYTHON} ${HELPER} facade-dispatch cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-facade-dispatch-ok + Should Contain ${result.stdout} a2a-facade-dispatch-ok -ACP HTTP Transport Stub Error - [Documentation] Verify HTTP transport raises AcpNotAvailableError +A2A HTTP Transport Stub Error + [Documentation] Verify HTTP transport raises A2aNotAvailableError ${result}= Run Process ${PYTHON} ${HELPER} transport-stub cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-transport-stub-ok + Should Contain ${result.stdout} a2a-transport-stub-ok -ACP Event Queue Local Mode +A2A Event Queue Local Mode [Documentation] Verify event queue publish and subscribe work locally ${result}= Run Process ${PYTHON} ${HELPER} event-queue cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-event-queue-ok + Should Contain ${result.stdout} a2a-event-queue-ok -ACP Version Negotiation +A2A Version Negotiation [Documentation] Verify version negotiation succeeds for 1.0 and fails for 2.0 ${result}= Run Process ${PYTHON} ${HELPER} version-negotiate cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-version-negotiate-ok + Should Contain ${result.stdout} a2a-version-negotiate-ok -ACP Operation Listing +A2A Operation Listing [Documentation] Verify list_operations returns all expected operations ${result}= Run Process ${PYTHON} ${HELPER} list-operations cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-list-operations-ok + Should Contain ${result.stdout} a2a-list-operations-ok diff --git a/robot/acp_facade_wiring.robot b/robot/a2a_facade_wiring.robot similarity index 79% rename from robot/acp_facade_wiring.robot rename to robot/a2a_facade_wiring.robot index 16f8b7262..24e04f19d 100644 --- a/robot/acp_facade_wiring.robot +++ b/robot/a2a_facade_wiring.robot @@ -1,11 +1,11 @@ *** Settings *** -Documentation Integration tests for ACP local facade wiring to live services +Documentation Integration tests for A2A local facade wiring to live services Resource ${CURDIR}/common.resource Suite Setup Setup Test Environment Suite Teardown Cleanup Test Environment *** Variables *** -${HELPER} ${CURDIR}/helper_acp_facade_wiring.py +${HELPER} ${CURDIR}/helper_a2a_facade_wiring.py *** Test Cases *** Wired Facade Session Create @@ -14,7 +14,7 @@ Wired Facade Session Create Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-session-create-ok + Should Contain ${result.stdout} a2a-wired-session-create-ok Wired Facade Session Close [Documentation] Verify session.close routes to SessionService @@ -22,7 +22,7 @@ Wired Facade Session Close Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-session-close-ok + Should Contain ${result.stdout} a2a-wired-session-close-ok Wired Facade Plan Create [Documentation] Verify plan.create routes to PlanLifecycleService @@ -30,7 +30,7 @@ Wired Facade Plan Create Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-plan-create-ok + Should Contain ${result.stdout} a2a-wired-plan-create-ok Wired Facade Plan Status [Documentation] Verify plan.status routes to PlanLifecycleService @@ -38,7 +38,7 @@ Wired Facade Plan Status Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-plan-status-ok + Should Contain ${result.stdout} a2a-wired-plan-status-ok Wired Facade Registry Tools [Documentation] Verify registry.list_tools routes to ToolRegistry @@ -46,7 +46,7 @@ Wired Facade Registry Tools Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-registry-tools-ok + Should Contain ${result.stdout} a2a-wired-registry-tools-ok Wired Facade Registry Resources [Documentation] Verify registry.list_resources routes to ResourceRegistryService @@ -54,7 +54,7 @@ Wired Facade Registry Resources Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-registry-resources-ok + Should Contain ${result.stdout} a2a-wired-registry-resources-ok Wired Facade Context Stub [Documentation] Verify context.get returns stub while ACMS pipeline is pending @@ -62,20 +62,20 @@ Wired Facade Context Stub Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-context-stub-ok + Should Contain ${result.stdout} a2a-wired-context-stub-ok Wired Facade Event Subscribe - [Documentation] Verify event.subscribe routes to AcpEventQueue + [Documentation] Verify event.subscribe routes to A2aEventQueue ${result}= Run Process ${PYTHON} ${HELPER} wired-event-subscribe cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-event-subscribe-ok + Should Contain ${result.stdout} a2a-wired-event-subscribe-ok Wired Facade Error Mapping - [Documentation] Verify domain exceptions map to correct ACP error codes + [Documentation] Verify domain exceptions map to correct A2A error codes ${result}= Run Process ${PYTHON} ${HELPER} wired-error-mapping cwd=${WORKSPACE} Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 - Should Contain ${result.stdout} acp-wired-error-mapping-ok + Should Contain ${result.stdout} a2a-wired-error-mapping-ok diff --git a/robot/helper_acp_facade.py b/robot/helper_a2a_facade.py similarity index 71% rename from robot/helper_acp_facade.py rename to robot/helper_a2a_facade.py index 9657a3775..254fde814 100644 --- a/robot/helper_acp_facade.py +++ b/robot/helper_a2a_facade.py @@ -1,4 +1,4 @@ -"""Helper script for acp_facade.robot smoke tests. +"""Helper script for a2a_facade.robot smoke tests. Each subcommand is a self-contained check that prints a sentinel on success. """ @@ -13,15 +13,15 @@ _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) -from cleveragents.acp.errors import ( # noqa: E402 - AcpNotAvailableError, - AcpVersionMismatchError, +from cleveragents.a2a.errors import ( # noqa: E402 + A2aNotAvailableError, + A2aVersionMismatchError, ) -from cleveragents.acp.events import AcpEventQueue # noqa: E402 -from cleveragents.acp.facade import AcpLocalFacade # noqa: E402 -from cleveragents.acp.models import AcpEvent, AcpRequest # noqa: E402 -from cleveragents.acp.transport import AcpHttpTransport # noqa: E402 -from cleveragents.acp.versioning import AcpVersionNegotiator # noqa: E402 +from cleveragents.a2a.events import A2aEventQueue # noqa: E402 +from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402 +from cleveragents.a2a.models import A2aEvent, A2aRequest # noqa: E402 +from cleveragents.a2a.transport import A2aHttpTransport # noqa: E402 +from cleveragents.a2a.versioning import A2aVersionNegotiator # noqa: E402 # --------------------------------------------------------------------------- # Subcommands @@ -30,28 +30,28 @@ from cleveragents.acp.versioning import AcpVersionNegotiator # noqa: E402 def facade_dispatch() -> None: """Dispatch session.create via local facade.""" - facade = AcpLocalFacade() - request = AcpRequest(operation="session.create") + facade = A2aLocalFacade() + request = A2aRequest(operation="session.create") response = facade.dispatch(request) if response.status == "ok" and "session_id" in response.data: - print("acp-facade-dispatch-ok") + print("a2a-facade-dispatch-ok") else: print(f"FAIL: unexpected response {response}", file=sys.stderr) sys.exit(1) def transport_stub() -> None: - """Verify transport stub raises AcpNotAvailableError.""" - transport = AcpHttpTransport() + """Verify transport stub raises A2aNotAvailableError.""" + transport = A2aHttpTransport() try: transport.connect("http://localhost:8080") print("FAIL: should have raised", file=sys.stderr) sys.exit(1) - except AcpNotAvailableError: + except A2aNotAvailableError: pass if transport.is_connected() is False: - print("acp-transport-stub-ok") + print("a2a-transport-stub-ok") else: print("FAIL: is_connected should be False", file=sys.stderr) sys.exit(1) @@ -59,12 +59,12 @@ def transport_stub() -> None: def event_queue() -> None: """Verify local event queue publish/subscribe.""" - queue = AcpEventQueue() - received: list[AcpEvent] = [] + queue = A2aEventQueue() + received: list[A2aEvent] = [] queue.subscribe_local(lambda e: received.append(e)) - queue.publish(AcpEvent(event_type="test.event")) + queue.publish(A2aEvent(event_type="test.event")) if len(received) == 1 and received[0].event_type == "test.event": - print("acp-event-queue-ok") + print("a2a-event-queue-ok") else: print(f"FAIL: received={received}", file=sys.stderr) sys.exit(1) @@ -72,7 +72,7 @@ def event_queue() -> None: def version_negotiate() -> None: """Verify version negotiation.""" - negotiator = AcpVersionNegotiator() + negotiator = A2aVersionNegotiator() result = negotiator.negotiate("1.0") if result != "1.0": print("FAIL: expected 1.0", file=sys.stderr) @@ -82,19 +82,19 @@ def version_negotiate() -> None: negotiator.negotiate("2.0") print("FAIL: should have raised", file=sys.stderr) sys.exit(1) - except AcpVersionMismatchError: + except A2aVersionMismatchError: pass - print("acp-version-negotiate-ok") + print("a2a-version-negotiate-ok") def list_operations() -> None: """Verify list_operations returns expected operations.""" - facade = AcpLocalFacade() + facade = A2aLocalFacade() ops = facade.list_operations() expected = {"session.create", "plan.create", "plan.execute", "context.get"} if expected.issubset(set(ops)) and len(ops) == 11: - print("acp-list-operations-ok") + print("a2a-list-operations-ok") else: print(f"FAIL: ops={ops}", file=sys.stderr) sys.exit(1) diff --git a/robot/helper_acp_facade_wiring.py b/robot/helper_a2a_facade_wiring.py similarity index 80% rename from robot/helper_acp_facade_wiring.py rename to robot/helper_a2a_facade_wiring.py index 0342f42ed..d08b92668 100644 --- a/robot/helper_acp_facade_wiring.py +++ b/robot/helper_a2a_facade_wiring.py @@ -1,6 +1,6 @@ -"""Helper script for acp_facade_wiring.robot integration tests. +"""Helper script for a2a_facade_wiring.robot integration tests. -Each subcommand exercises the wired ACP facade with lightweight mock +Each subcommand exercises the wired A2A facade with lightweight mock services and prints a sentinel on success. """ @@ -15,12 +15,12 @@ _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) -from cleveragents.acp.errors import ( # noqa: E402 +from cleveragents.a2a.errors import ( # noqa: E402 map_domain_error, ) -from cleveragents.acp.events import AcpEventQueue # noqa: E402 -from cleveragents.acp.facade import AcpLocalFacade # noqa: E402 -from cleveragents.acp.models import AcpRequest # noqa: E402 +from cleveragents.a2a.events import A2aEventQueue # noqa: E402 +from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402 +from cleveragents.a2a.models import A2aRequest # noqa: E402 from cleveragents.core.exceptions import ( # noqa: E402 BusinessRuleViolation, PlanError, @@ -102,10 +102,10 @@ def _mock_resource_registry_service() -> MagicMock: def wired_session_create() -> None: """Dispatch session.create through a wired facade.""" - facade = AcpLocalFacade(services={"session_service": _mock_session_service()}) - resp = facade.dispatch(AcpRequest(operation="session.create")) + facade = A2aLocalFacade(services={"session_service": _mock_session_service()}) + resp = facade.dispatch(A2aRequest(operation="session.create")) if resp.status == "ok" and resp.data["session_id"] == "INTEG-SESSION-001": - print("acp-wired-session-create-ok") + print("a2a-wired-session-create-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -113,15 +113,15 @@ def wired_session_create() -> None: def wired_session_close() -> None: """Dispatch session.close through a wired facade.""" - facade = AcpLocalFacade(services={"session_service": _mock_session_service()}) + facade = A2aLocalFacade(services={"session_service": _mock_session_service()}) resp = facade.dispatch( - AcpRequest( + A2aRequest( operation="session.close", params={"session_id": "INTEG-SESSION-001"}, ) ) if resp.status == "ok" and resp.data["status"] == "closed": - print("acp-wired-session-close-ok") + print("a2a-wired-session-close-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -129,17 +129,17 @@ def wired_session_close() -> None: def wired_plan_create() -> None: """Dispatch plan.create through a wired facade.""" - facade = AcpLocalFacade( + facade = A2aLocalFacade( services={"plan_lifecycle_service": _mock_plan_lifecycle_service()} ) resp = facade.dispatch( - AcpRequest( + A2aRequest( operation="plan.create", params={"action_name": "local/test"}, ) ) if resp.status == "ok" and resp.data["plan_id"] == "INTEG-PLAN-001": - print("acp-wired-plan-create-ok") + print("a2a-wired-plan-create-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -147,17 +147,17 @@ def wired_plan_create() -> None: def wired_plan_status() -> None: """Dispatch plan.status through a wired facade.""" - facade = AcpLocalFacade( + facade = A2aLocalFacade( services={"plan_lifecycle_service": _mock_plan_lifecycle_service()} ) resp = facade.dispatch( - AcpRequest( + A2aRequest( operation="plan.status", params={"plan_id": "INTEG-PLAN-001"}, ) ) if resp.status == "ok" and resp.data["phase"] == "strategize": - print("acp-wired-plan-status-ok") + print("a2a-wired-plan-status-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -165,11 +165,11 @@ def wired_plan_status() -> None: def wired_registry_tools() -> None: """Dispatch registry.list_tools through a wired facade.""" - facade = AcpLocalFacade(services={"tool_registry": _mock_tool_registry()}) - resp = facade.dispatch(AcpRequest(operation="registry.list_tools")) + facade = A2aLocalFacade(services={"tool_registry": _mock_tool_registry()}) + resp = facade.dispatch(A2aRequest(operation="registry.list_tools")) tools = resp.data.get("tools", []) if resp.status == "ok" and len(tools) == 1: - print("acp-wired-registry-tools-ok") + print("a2a-wired-registry-tools-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -177,13 +177,13 @@ def wired_registry_tools() -> None: def wired_registry_resources() -> None: """Dispatch registry.list_resources through a wired facade.""" - facade = AcpLocalFacade( + facade = A2aLocalFacade( services={"resource_registry_service": _mock_resource_registry_service()} ) - resp = facade.dispatch(AcpRequest(operation="registry.list_resources")) + resp = facade.dispatch(A2aRequest(operation="registry.list_resources")) resources = resp.data.get("resources", []) if resp.status == "ok" and len(resources) == 1: - print("acp-wired-registry-resources-ok") + print("a2a-wired-registry-resources-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -191,10 +191,10 @@ def wired_registry_resources() -> None: def wired_context_stub() -> None: """Dispatch context.get and verify stub response.""" - facade = AcpLocalFacade() - resp = facade.dispatch(AcpRequest(operation="context.get")) + facade = A2aLocalFacade() + resp = facade.dispatch(A2aRequest(operation="context.get")) if resp.status == "ok" and resp.data.get("stub") is True: - print("acp-wired-context-stub-ok") + print("a2a-wired-context-stub-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) @@ -202,19 +202,19 @@ def wired_context_stub() -> None: def wired_event_subscribe() -> None: """Dispatch event.subscribe through a wired facade.""" - queue = AcpEventQueue() - facade = AcpLocalFacade(services={"event_queue": queue}) - resp = facade.dispatch(AcpRequest(operation="event.subscribe")) + queue = A2aEventQueue() + facade = A2aLocalFacade(services={"event_queue": queue}) + resp = facade.dispatch(A2aRequest(operation="event.subscribe")) sub_id = resp.data.get("subscription_id", "") if resp.status == "ok" and sub_id: - print("acp-wired-event-subscribe-ok") + print("a2a-wired-event-subscribe-ok") else: print(f"FAIL: {resp}", file=sys.stderr) sys.exit(1) def wired_error_mapping() -> None: - """Verify domain-to-ACP error code mapping.""" + """Verify domain-to-A2A error code mapping.""" cases: list[tuple[Exception, str]] = [ (ResourceNotFoundError(resource_type="x", resource_id="1"), "NOT_FOUND"), (ValidationError("bad"), "VALIDATION_ERROR"), @@ -229,7 +229,7 @@ def wired_error_mapping() -> None: file=sys.stderr, ) sys.exit(1) - print("acp-wired-error-mapping-ok") + print("a2a-wired-error-mapping-ok") # --------------------------------------------------------------------------- diff --git a/robot/helper_m6_autonomy_acceptance.py b/robot/helper_m6_autonomy_acceptance.py index dabe1345b..2c56c74fc 100644 --- a/robot/helper_m6_autonomy_acceptance.py +++ b/robot/helper_m6_autonomy_acceptance.py @@ -14,16 +14,16 @@ _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) -from cleveragents.acp.errors import ( # noqa: E402 - AcpNotAvailableError, - AcpOperationNotFoundError, - AcpVersionMismatchError, +from cleveragents.a2a.errors import ( # noqa: E402 + A2aNotAvailableError, + A2aOperationNotFoundError, + A2aVersionMismatchError, ) -from cleveragents.acp.events import AcpEventQueue # noqa: E402 -from cleveragents.acp.facade import AcpLocalFacade # noqa: E402 -from cleveragents.acp.models import AcpEvent, AcpRequest # noqa: E402 -from cleveragents.acp.transport import AcpHttpTransport # noqa: E402 -from cleveragents.acp.versioning import AcpVersionNegotiator # noqa: E402 +from cleveragents.a2a.events import A2aEventQueue # noqa: E402 +from cleveragents.a2a.facade import A2aLocalFacade # noqa: E402 +from cleveragents.a2a.models import A2aEvent, A2aRequest # noqa: E402 +from cleveragents.a2a.transport import A2aHttpTransport # noqa: E402 +from cleveragents.a2a.versioning import A2aVersionNegotiator # noqa: E402 from cleveragents.application.services.automation_profile_service import ( # noqa: E402 AutomationProfileService, ) @@ -44,13 +44,13 @@ _FIXTURES_DIR = Path(__file__).resolve().parents[1] / "features" / "fixtures" / def facade_session() -> None: """Dispatch session.create and session.close.""" - facade = AcpLocalFacade() + facade = A2aLocalFacade() - resp_create = facade.dispatch(AcpRequest(operation="session.create", params={})) + resp_create = facade.dispatch(A2aRequest(operation="session.create", params={})) assert resp_create.status == "ok", f"Expected ok, got {resp_create.status}" assert "session_id" in resp_create.data - resp_close = facade.dispatch(AcpRequest(operation="session.close", params={})) + resp_close = facade.dispatch(A2aRequest(operation="session.close", params={})) assert resp_close.status == "ok" assert resp_close.data["status"] == "closed" @@ -59,33 +59,33 @@ def facade_session() -> None: def facade_plan() -> None: """Dispatch plan create/execute/status/diff/apply.""" - facade = AcpLocalFacade() + facade = A2aLocalFacade() - resp = facade.dispatch(AcpRequest(operation="plan.create", params={})) + resp = facade.dispatch(A2aRequest(operation="plan.create", params={})) assert resp.status == "ok" plan_id = resp.data["plan_id"] assert plan_id resp = facade.dispatch( - AcpRequest(operation="plan.execute", params={"plan_id": plan_id}) + A2aRequest(operation="plan.execute", params={"plan_id": plan_id}) ) assert resp.status == "ok" assert resp.data["status"] == "queued" resp = facade.dispatch( - AcpRequest(operation="plan.status", params={"plan_id": plan_id}) + A2aRequest(operation="plan.status", params={"plan_id": plan_id}) ) assert resp.status == "ok" assert "phase" in resp.data resp = facade.dispatch( - AcpRequest(operation="plan.diff", params={"plan_id": plan_id}) + A2aRequest(operation="plan.diff", params={"plan_id": plan_id}) ) assert resp.status == "ok" assert "changes" in resp.data resp = facade.dispatch( - AcpRequest(operation="plan.apply", params={"plan_id": plan_id}) + A2aRequest(operation="plan.apply", params={"plan_id": plan_id}) ) assert resp.status == "ok" assert resp.data["status"] == "applied" @@ -94,13 +94,13 @@ def facade_plan() -> None: def facade_unknown_op() -> None: - """Verify unknown operation raises AcpOperationNotFoundError.""" - facade = AcpLocalFacade() + """Verify unknown operation raises A2aOperationNotFoundError.""" + facade = A2aLocalFacade() try: - facade.dispatch(AcpRequest(operation="nonexistent.op", params={})) - print("FAIL: expected AcpOperationNotFoundError") + facade.dispatch(A2aRequest(operation="nonexistent.op", params={})) + print("FAIL: expected A2aOperationNotFoundError") sys.exit(1) - except AcpOperationNotFoundError: + except A2aOperationNotFoundError: pass print("m6-facade-unknown-op-ok") @@ -108,17 +108,17 @@ def facade_unknown_op() -> None: def event_queue() -> None: """Publish events and verify local subscriber receives them.""" - queue = AcpEventQueue() + queue = A2aEventQueue() received = [] sub_id = queue.subscribe_local(lambda e: received.append(e)) - queue.publish(AcpEvent(event_type="plan.progress", data={"step": 1})) + queue.publish(A2aEvent(event_type="plan.progress", data={"step": 1})) assert len(received) == 1 assert received[0].event_type == "plan.progress" # Unsubscribe and verify no more callbacks queue.unsubscribe(sub_id) - queue.publish(AcpEvent(event_type="plan.complete", data={})) + queue.publish(A2aEvent(event_type="plan.complete", data={})) assert len(received) == 1 # still 1 # Verify get_events returns both @@ -128,7 +128,7 @@ def event_queue() -> None: # Close and verify publish raises queue.close() try: - queue.publish(AcpEvent(event_type="after.close", data={})) + queue.publish(A2aEvent(event_type="after.close", data={})) print("FAIL: expected RuntimeError") sys.exit(1) except RuntimeError: @@ -138,31 +138,31 @@ def event_queue() -> None: def transport_stub() -> None: - """Verify HTTP transport stub raises AcpNotAvailableError.""" - transport = AcpHttpTransport() + """Verify HTTP transport stub raises A2aNotAvailableError.""" + transport = A2aHttpTransport() # send try: - transport.send(AcpRequest(operation="plan.create", params={})) - print("FAIL: expected AcpNotAvailableError on send") + transport.send(A2aRequest(operation="plan.create", params={})) + print("FAIL: expected A2aNotAvailableError on send") sys.exit(1) - except AcpNotAvailableError: + except A2aNotAvailableError: pass # connect try: - transport.connect("https://example.com/acp") - print("FAIL: expected AcpNotAvailableError on connect") + transport.connect("https://example.com/a2a") + print("FAIL: expected A2aNotAvailableError on connect") sys.exit(1) - except AcpNotAvailableError: + except A2aNotAvailableError: pass # disconnect try: transport.disconnect() - print("FAIL: expected AcpNotAvailableError on disconnect") + print("FAIL: expected A2aNotAvailableError on disconnect") sys.exit(1) - except AcpNotAvailableError: + except A2aNotAvailableError: pass # is_connected @@ -172,8 +172,8 @@ def transport_stub() -> None: def version_negotiation() -> None: - """Negotiate supported and unsupported ACP versions.""" - negotiator = AcpVersionNegotiator() + """Negotiate supported and unsupported A2A versions.""" + negotiator = A2aVersionNegotiator() # Supported result = negotiator.negotiate("1.0") @@ -184,9 +184,9 @@ def version_negotiation() -> None: # Unsupported try: negotiator.negotiate("2.0") - print("FAIL: expected AcpVersionMismatchError") + print("FAIL: expected A2aVersionMismatchError") sys.exit(1) - except AcpVersionMismatchError: + except A2aVersionMismatchError: pass assert negotiator.is_supported("99.0") is False @@ -271,7 +271,7 @@ def profile_resolution() -> None: def fixture_loading() -> None: """Load all M6 fixture files and verify structure.""" for fname in ( - "acp_facade_flows.json", + "a2a_facade_flows.json", "autonomy_guardrails.json", "automation_profiles.json", ): @@ -286,16 +286,16 @@ def fixture_loading() -> None: def full_flow() -> None: """End-to-end: facade dispatch + guard check + profile resolution.""" # Step 1: Facade dispatch - facade = AcpLocalFacade() - resp = facade.dispatch(AcpRequest(operation="session.create", params={})) + facade = A2aLocalFacade() + resp = facade.dispatch(A2aRequest(operation="session.create", params={})) assert resp.status == "ok" - resp = facade.dispatch(AcpRequest(operation="plan.create", params={})) + resp = facade.dispatch(A2aRequest(operation="plan.create", params={})) assert resp.status == "ok" plan_id = resp.data["plan_id"] resp = facade.dispatch( - AcpRequest(operation="plan.execute", params={"plan_id": plan_id}) + A2aRequest(operation="plan.execute", params={"plan_id": plan_id}) ) assert resp.status == "ok" @@ -318,12 +318,12 @@ def full_flow() -> None: assert p.name == "ci" # Step 4: Version negotiation - negotiator = AcpVersionNegotiator() + negotiator = A2aVersionNegotiator() assert negotiator.negotiate("1.0") == "1.0" # Step 5: Event queue - queue = AcpEventQueue() - queue.publish(AcpEvent(event_type="flow.complete", data={})) + queue = A2aEventQueue() + queue.publish(A2aEvent(event_type="flow.complete", data={})) assert len(queue.get_events()) == 1 queue.close() diff --git a/robot/helper_server_stubs.py b/robot/helper_server_stubs.py index 592414ecc..ae07c254f 100644 --- a/robot/helper_server_stubs.py +++ b/robot/helper_server_stubs.py @@ -14,7 +14,7 @@ _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) -from cleveragents.acp.clients import ( # noqa: E402 +from cleveragents.a2a.clients import ( # noqa: E402 AuthClient, RemoteExecutionClient, ServerClient, @@ -22,7 +22,7 @@ from cleveragents.acp.clients import ( # noqa: E402 StubRemoteExecutionClient, StubServerClient, ) -from cleveragents.acp.server_config import ServerConnectionConfig # noqa: E402 +from cleveragents.a2a.server_config import ServerConnectionConfig # noqa: E402 from cleveragents.application.services.config_service import _REGISTRY # noqa: E402 # --------------------------------------------------------------------------- diff --git a/robot/m6_autonomy_acceptance.robot b/robot/m6_autonomy_acceptance.robot index b9c439791..2ffc1286d 100644 --- a/robot/m6_autonomy_acceptance.robot +++ b/robot/m6_autonomy_acceptance.robot @@ -1,5 +1,5 @@ *** Settings *** -Documentation M6 autonomy acceptance E2E smoke tests — ACP facade, guardrails, audit +Documentation M6 autonomy acceptance E2E smoke tests — A2A facade, guardrails, audit Resource ${CURDIR}/common.resource Suite Setup Setup Test Environment Suite Teardown Cleanup Test Environment @@ -8,7 +8,7 @@ Suite Teardown Cleanup Test Environment ${HELPER} ${CURDIR}/helper_m6_autonomy_acceptance.py *** Test Cases *** -M6 ACP Facade Session Lifecycle +M6 A2A Facade Session Lifecycle [Documentation] Dispatch session.create and session.close via local facade ${result}= Run Process ${PYTHON} ${HELPER} facade-session cwd=${WORKSPACE} timeout=30s Log ${result.stdout} @@ -16,7 +16,7 @@ M6 ACP Facade Session Lifecycle Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout} m6-facade-session-ok -M6 ACP Facade Plan Lifecycle +M6 A2A Facade Plan Lifecycle [Documentation] Dispatch plan create/execute/status/diff/apply operations ${result}= Run Process ${PYTHON} ${HELPER} facade-plan cwd=${WORKSPACE} timeout=30s Log ${result.stdout} @@ -24,15 +24,15 @@ M6 ACP Facade Plan Lifecycle Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout} m6-facade-plan-ok -M6 ACP Facade Unknown Operation Error - [Documentation] Verify unknown operations raise AcpOperationNotFoundError +M6 A2A Facade Unknown Operation Error + [Documentation] Verify unknown operations raise A2aOperationNotFoundError ${result}= Run Process ${PYTHON} ${HELPER} facade-unknown-op cwd=${WORKSPACE} timeout=30s Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout} m6-facade-unknown-op-ok -M6 ACP Event Queue Publish Subscribe +M6 A2A Event Queue Publish Subscribe [Documentation] Publish events and verify local subscriber receives them ${result}= Run Process ${PYTHON} ${HELPER} event-queue cwd=${WORKSPACE} timeout=30s Log ${result.stdout} @@ -40,16 +40,16 @@ M6 ACP Event Queue Publish Subscribe Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout} m6-event-queue-ok -M6 ACP Transport Stub Rejects All - [Documentation] Verify HTTP transport stub raises AcpNotAvailableError +M6 A2A Transport Stub Rejects All + [Documentation] Verify HTTP transport stub raises A2aNotAvailableError ${result}= Run Process ${PYTHON} ${HELPER} transport-stub cwd=${WORKSPACE} timeout=30s Log ${result.stdout} Log ${result.stderr} Should Be Equal As Integers ${result.rc} 0 Should Contain ${result.stdout} m6-transport-stub-ok -M6 ACP Version Negotiation - [Documentation] Negotiate supported and unsupported ACP versions +M6 A2A Version Negotiation + [Documentation] Negotiate supported and unsupported A2A versions ${result}= Run Process ${PYTHON} ${HELPER} version-negotiation cwd=${WORKSPACE} timeout=30s Log ${result.stdout} Log ${result.stderr} diff --git a/robot/security_async.robot b/robot/security_async.robot index e17b00df6..7b0b0ec5f 100644 --- a/robot/security_async.robot +++ b/robot/security_async.robot @@ -1,7 +1,7 @@ *** Settings *** Documentation Integration smoke tests for async resource cleanup (#321). ... Validates that AsyncResourceTracker, enhanced bridge cleanup, -... AcpEventQueue.close(), and StateManager.close() work end-to-end +... A2aEventQueue.close(), and StateManager.close() work end-to-end ... by executing small Python driver scripts in a subprocess. Library OperatingSystem @@ -78,14 +78,14 @@ Test AsyncResourceTracker Timeout Warning ${output} = Get File ${TEST_OUTPUT} Should Contain ${output} PASS: timeout warning -Test AcpEventQueue Close - [Documentation] Verify AcpEventQueue.close() removes subscriptions +Test A2aEventQueue Close + [Documentation] Verify A2aEventQueue.close() removes subscriptions ${script} = Catenate SEPARATOR=\n ... import sys ... sys.path.insert(0, '${WORKSPACE_ROOT}/src') - ... from cleveragents.acp.events import AcpEventQueue + ... from cleveragents.a2a.events import A2aEventQueue ... - ... q = AcpEventQueue() + ... q = A2aEventQueue() ... q.subscribe_local(lambda e: None) ... q.subscribe_local(lambda e: None) ... assert len(q._subscriptions) == 2 diff --git a/src/cleveragents/a2a/__init__.py b/src/cleveragents/a2a/__init__.py new file mode 100644 index 000000000..a2db998f2 --- /dev/null +++ b/src/cleveragents/a2a/__init__.py @@ -0,0 +1,69 @@ +"""A2A (Agent-to-Agent Protocol) integration package. + +Provides the local-mode facade, server-mode transport stubs, request/response +models, event streaming stubs, and version negotiation for the A2A boundary. + +In **local mode** the :class:`A2aLocalFacade` maps A2A operation names to +direct Python method calls on existing application services. No serialization, +no network, no authentication. + +In **server mode** the :class:`A2aHttpTransport` is a stub that raises +:class:`A2aNotAvailableError` for every operation. When server mode is +implemented the concrete transport will replace these stubs. + +Server client protocols (:class:`ServerClient`, :class:`RemoteExecutionClient`, +:class:`AuthClient`) and their stub implementations are provided for forward +compatibility. :class:`ServerConnectionConfig` validates connection parameters. +""" + +from __future__ import annotations + +from cleveragents.a2a.clients import ( + AuthClient, + RemoteExecutionClient, + ServerClient, + StubAuthClient, + StubRemoteExecutionClient, + StubServerClient, +) +from cleveragents.a2a.errors import ( + A2aError, + A2aNotAvailableError, + A2aOperationNotFoundError, + A2aVersionMismatchError, +) +from cleveragents.a2a.events import A2aEventQueue +from cleveragents.a2a.facade import A2aLocalFacade +from cleveragents.a2a.models import ( + A2aErrorDetail, + A2aEvent, + A2aRequest, + A2aResponse, + A2aVersion, +) +from cleveragents.a2a.server_config import ServerConnectionConfig +from cleveragents.a2a.transport import A2aHttpTransport +from cleveragents.a2a.versioning import A2aVersionNegotiator + +__all__ = [ + "A2aError", + "A2aErrorDetail", + "A2aEvent", + "A2aEventQueue", + "A2aHttpTransport", + "A2aLocalFacade", + "A2aNotAvailableError", + "A2aOperationNotFoundError", + "A2aRequest", + "A2aResponse", + "A2aVersion", + "A2aVersionMismatchError", + "A2aVersionNegotiator", + "AuthClient", + "RemoteExecutionClient", + "ServerClient", + "ServerConnectionConfig", + "StubAuthClient", + "StubRemoteExecutionClient", + "StubServerClient", +] diff --git a/src/cleveragents/acp/clients.py b/src/cleveragents/a2a/clients.py similarity index 100% rename from src/cleveragents/acp/clients.py rename to src/cleveragents/a2a/clients.py diff --git a/src/cleveragents/acp/errors.py b/src/cleveragents/a2a/errors.py similarity index 82% rename from src/cleveragents/acp/errors.py rename to src/cleveragents/a2a/errors.py index 54a90283a..3d9aee3df 100644 --- a/src/cleveragents/acp/errors.py +++ b/src/cleveragents/a2a/errors.py @@ -1,11 +1,11 @@ -"""ACP error hierarchy and domain-to-ACP error mapping. +"""A2A error hierarchy and domain-to-A2A error mapping. -All ACP-specific exceptions inherit from :class:`AcpError` which itself +All A2A-specific exceptions inherit from :class:`A2aError` which itself extends the project-wide :class:`CleverAgentsError`. This mirrors the pattern established by the LSP package. The :func:`map_domain_error` function translates domain exceptions into -structured :class:`AcpErrorDetail` instances using a well-defined error +structured :class:`A2aErrorDetail` instances using a well-defined error code taxonomy so that clients receive consistent, machine-readable error responses regardless of which internal service produced the fault. """ @@ -26,7 +26,7 @@ from cleveragents.core.exceptions import ( ) # --------------------------------------------------------------------------- -# ACP error code constants +# A2A error code constants # --------------------------------------------------------------------------- NOT_FOUND: str = "NOT_FOUND" @@ -44,8 +44,8 @@ INTERNAL_ERROR: str = "INTERNAL_ERROR" # --------------------------------------------------------------------------- -class AcpError(CleverAgentsError): - """Base exception for all ACP errors.""" +class A2aError(CleverAgentsError): + """Base exception for all A2A errors.""" def __init__( self, @@ -55,19 +55,19 @@ class AcpError(CleverAgentsError): super().__init__(message, details) -class AcpNotAvailableError(AcpError): +class A2aNotAvailableError(A2aError): """Raised when a server-mode operation is attempted in local mode.""" def __init__( self, - message: str = "ACP server transport is not available in local mode", + message: str = "A2A server transport is not available in local mode", details: dict[str, Any] | None = None, ) -> None: super().__init__(message, details) -class AcpVersionMismatchError(AcpError): - """Raised when client and server ACP versions are incompatible.""" +class A2aVersionMismatchError(A2aError): + """Raised when client and server A2A versions are incompatible.""" def __init__( self, @@ -81,8 +81,8 @@ class AcpVersionMismatchError(AcpError): self.supported_versions = supported_versions -class AcpOperationNotFoundError(AcpError): - """Raised when an unknown ACP operation is requested.""" +class A2aOperationNotFoundError(A2aError): + """Raised when an unknown A2A operation is requested.""" def __init__( self, @@ -95,15 +95,15 @@ class AcpOperationNotFoundError(AcpError): # --------------------------------------------------------------------------- -# Domain-to-ACP error mapping +# Domain-to-A2A error mapping # --------------------------------------------------------------------------- def map_domain_error(exc: Exception) -> tuple[str, str]: - """Map a domain exception to an ACP error code and message. + """Map a domain exception to an A2A error code and message. Returns a ``(code, message)`` tuple suitable for constructing an - :class:`~cleveragents.acp.models.AcpErrorDetail`. + :class:`~cleveragents.a2a.models.A2aErrorDetail`. The mapping precedence is intentional: more specific exception types are checked before their base classes so that, for example, a @@ -148,9 +148,9 @@ __all__ = [ "NOT_FOUND", "PLAN_ERROR", "VALIDATION_ERROR", - "AcpError", - "AcpNotAvailableError", - "AcpOperationNotFoundError", - "AcpVersionMismatchError", + "A2aError", + "A2aNotAvailableError", + "A2aOperationNotFoundError", + "A2aVersionMismatchError", "map_domain_error", ] diff --git a/src/cleveragents/acp/events.py b/src/cleveragents/a2a/events.py similarity index 76% rename from src/cleveragents/acp/events.py rename to src/cleveragents/a2a/events.py index 74ca3830f..c15c77614 100644 --- a/src/cleveragents/acp/events.py +++ b/src/cleveragents/a2a/events.py @@ -1,8 +1,8 @@ -"""ACP event streaming — local queue and remote stub. +"""A2A event streaming — local queue and remote stub. -The :class:`AcpEventQueue` provides a working in-memory event queue for +The :class:`A2aEventQueue` provides a working in-memory event queue for local mode and a stub for remote subscriptions that raises -:class:`AcpNotAvailableError`. +:class:`A2aNotAvailableError`. """ from __future__ import annotations @@ -13,8 +13,8 @@ from typing import Any import structlog from ulid import ULID -from cleveragents.acp.errors import AcpNotAvailableError -from cleveragents.acp.models import AcpEvent +from cleveragents.a2a.errors import A2aNotAvailableError +from cleveragents.a2a.models import A2aEvent logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__) @@ -24,12 +24,12 @@ _REMOTE_MSG = ( ) -class AcpEventQueue: +class A2aEventQueue: """In-memory event queue with local pub/sub and remote stub.""" def __init__(self) -> None: - self._events: list[AcpEvent] = [] - self._subscriptions: dict[str, Callable[[AcpEvent], Any]] = {} + self._events: list[A2aEvent] = [] + self._subscriptions: dict[str, Callable[[A2aEvent], Any]] = {} self._is_closed: bool = False # ------------------------------------------------------------------ @@ -41,15 +41,15 @@ class AcpEventQueue: """Whether this queue has been closed.""" return self._is_closed - def publish(self, event: AcpEvent) -> None: + def publish(self, event: A2aEvent) -> None: """Append *event* to the local queue and notify subscribers.""" if self._is_closed: raise RuntimeError("Cannot publish to a closed event queue") - if not isinstance(event, AcpEvent): - raise TypeError("event must be an AcpEvent instance") + if not isinstance(event, A2aEvent): + raise TypeError("event must be an A2aEvent instance") self._events.append(event) logger.debug( - "acp.event.published", + "a2a.event.published", event_id=event.event_id, event_type=event.event_type, ) @@ -58,17 +58,17 @@ class AcpEventQueue: callback(event) except Exception: logger.exception( - "acp.event.callback_error", + "a2a.event.callback_error", subscription_id=sub_id, ) - def subscribe_local(self, callback: Callable[[AcpEvent], Any]) -> str: + def subscribe_local(self, callback: Callable[[A2aEvent], Any]) -> str: """Register a local callback and return a subscription ID.""" if not callable(callback): raise TypeError("callback must be callable") sub_id = str(ULID()) self._subscriptions[sub_id] = callback - logger.debug("acp.event.subscribed", subscription_id=sub_id) + logger.debug("a2a.event.subscribed", subscription_id=sub_id) return sub_id def unsubscribe(self, subscription_id: str) -> bool: @@ -77,10 +77,10 @@ class AcpEventQueue: raise ValueError("subscription_id must be a non-empty string") removed = self._subscriptions.pop(subscription_id, None) is not None if removed: - logger.debug("acp.event.unsubscribed", subscription_id=subscription_id) + logger.debug("a2a.event.unsubscribed", subscription_id=subscription_id) return removed - def get_events(self, limit: int = 100) -> list[AcpEvent]: + def get_events(self, limit: int = 100) -> list[A2aEvent]: """Return the most recent *limit* events from the queue.""" if not isinstance(limit, int) or limit < 1: raise ValueError("limit must be a positive integer") @@ -97,7 +97,7 @@ class AcpEventQueue: self._subscriptions.clear() self._events.clear() if count: - logger.info("acp.event_queue.closed", subscription_count=count) + logger.info("a2a.event_queue.closed", subscription_count=count) # ------------------------------------------------------------------ # Remote stub (raises) @@ -106,17 +106,17 @@ class AcpEventQueue: def subscribe_remote(self, endpoint: str) -> None: """Subscribe to a remote event stream. - Always raises :class:`AcpNotAvailableError` in local mode. + Always raises :class:`A2aNotAvailableError` in local mode. """ if not endpoint or not isinstance(endpoint, str): raise ValueError("endpoint must be a non-empty string") logger.warning("%s (endpoint=%s)", _REMOTE_MSG, endpoint) - raise AcpNotAvailableError( + raise A2aNotAvailableError( _REMOTE_MSG, details={"endpoint": endpoint}, ) __all__ = [ - "AcpEventQueue", + "A2aEventQueue", ] diff --git a/src/cleveragents/acp/facade.py b/src/cleveragents/a2a/facade.py similarity index 89% rename from src/cleveragents/acp/facade.py rename to src/cleveragents/a2a/facade.py index 3628d2e7f..27cb2988c 100644 --- a/src/cleveragents/acp/facade.py +++ b/src/cleveragents/a2a/facade.py @@ -1,6 +1,6 @@ -"""ACP local-mode facade routing operations to application services. +"""A2A local-mode facade routing operations to application services. -In local mode every ACP operation maps 1:1 to a method call on the +In local mode every A2A operation maps 1:1 to a method call on the appropriate application service. No serialization, network, or auth. Service dependencies are supplied via the ``services`` dict passed to the @@ -12,7 +12,7 @@ constructor. Expected keys: | ``plan_lifecycle_service`` | ``PlanLifecycleService`` | plan.* operations | | ``tool_registry`` | ``ToolRegistry`` | registry.list_tools | | ``resource_registry_service``| ``ResourceRegistryService`` | registry.list_resources| -| ``event_queue`` | ``AcpEventQueue`` | event.subscribe | +| ``event_queue`` | ``A2aEventQueue`` | event.subscribe | When a service is absent the handler falls back to a safe stub response so the facade never crashes due to missing wiring. @@ -26,19 +26,19 @@ from typing import TYPE_CHECKING, Any import structlog from ulid import ULID -from cleveragents.acp.errors import ( - AcpOperationNotFoundError, +from cleveragents.a2a.errors import ( + A2aOperationNotFoundError, map_domain_error, ) -from cleveragents.acp.models import ( - AcpErrorDetail, - AcpRequest, - AcpResponse, - AcpVersion, +from cleveragents.a2a.models import ( + A2aErrorDetail, + A2aRequest, + A2aResponse, + A2aVersion, ) if TYPE_CHECKING: - from cleveragents.acp.events import AcpEventQueue + from cleveragents.a2a.events import A2aEventQueue from cleveragents.application.services.plan_lifecycle_service import ( PlanLifecycleService, ) @@ -69,8 +69,8 @@ _SUPPORTED_OPERATIONS: list[str] = [ ] -class AcpLocalFacade: - """Local-mode facade that dispatches ACP operations to services. +class A2aLocalFacade: + """Local-mode facade that dispatches A2A operations to services. Accepts service dependencies via the ``services`` dict. Each handler checks for the presence of the required service and delegates to it @@ -82,7 +82,7 @@ class AcpLocalFacade: raise TypeError("services must be a dict or None") self._services: dict[str, Any] = dict(services) if services else {} # PERF-1 fix: cache the handler dispatch dict instead of - # rebuilding it on every ACP request. + # rebuilding it on every A2A request. self._handler_map: dict[str, Any] | None = None # ------------------------------------------------------------------ @@ -108,57 +108,57 @@ class AcpLocalFacade: return svc # type: ignore[return-value] @property - def _event_queue(self) -> AcpEventQueue | None: + def _event_queue(self) -> A2aEventQueue | None: return self._services.get("event_queue") # type: ignore[return-value] # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ - def dispatch(self, request: AcpRequest) -> AcpResponse: - """Route an :class:`AcpRequest` to the appropriate handler. + def dispatch(self, request: A2aRequest) -> A2aResponse: + """Route an :class:`A2aRequest` to the appropriate handler. - Returns an :class:`AcpResponse` with ``status='ok'`` on success or + Returns an :class:`A2aResponse` with ``status='ok'`` on success or ``status='error'`` when the operation fails. Domain exceptions - are mapped to ACP error codes via :func:`map_domain_error`. + are mapped to A2A error codes via :func:`map_domain_error`. """ - if not isinstance(request, AcpRequest): - raise TypeError("request must be an AcpRequest instance") + if not isinstance(request, A2aRequest): + raise TypeError("request must be an A2aRequest instance") start = time.monotonic() try: data = self._route_operation(request.operation, request.params) elapsed = (time.monotonic() - start) * 1000.0 logger.info( - "acp.local.dispatch", + "a2a.local.dispatch", operation=request.operation, request_id=request.request_id, timing_ms=round(elapsed, 2), ) - return AcpResponse( - acp_version=AcpVersion.CURRENT, + return A2aResponse( + a2a_version=A2aVersion.CURRENT, request_id=request.request_id, status="ok", data=data, timing_ms=round(elapsed, 2), ) - except AcpOperationNotFoundError: + except A2aOperationNotFoundError: raise except Exception as exc: elapsed = (time.monotonic() - start) * 1000.0 code, message = map_domain_error(exc) logger.error( - "acp.local.dispatch.error", + "a2a.local.dispatch.error", operation=request.operation, request_id=request.request_id, error_code=code, error=message, ) - return AcpResponse( - acp_version=AcpVersion.CURRENT, + return A2aResponse( + a2a_version=A2aVersion.CURRENT, request_id=request.request_id, status="error", - error=AcpErrorDetail( + error=A2aErrorDetail( code=code, message=message, ), @@ -173,10 +173,10 @@ class AcpLocalFacade: # PERF-1 fix: invalidate cached handler map so new service # wiring is picked up on the next dispatch. self._handler_map = None - logger.debug("acp.local.service_registered", service_name=name) + logger.debug("a2a.local.service_registered", service_name=name) def list_operations(self) -> list[str]: - """Return the list of supported ACP operation names.""" + """Return the list of supported A2A operation names.""" return list(_SUPPORTED_OPERATIONS) # ------------------------------------------------------------------ @@ -191,8 +191,8 @@ class AcpLocalFacade: """Map *operation* to a handler and return the result dict.""" handler = self._handlers().get(operation) if handler is None: - raise AcpOperationNotFoundError( - message=f"Unknown ACP operation: {operation}", + raise A2aOperationNotFoundError( + message=f"Unknown A2A operation: {operation}", operation=operation, ) return handler(params) @@ -267,13 +267,13 @@ class AcpLocalFacade: ) if stopped: logger.info( - "acp.session.close.devcontainer_cleanup", + "a2a.session.close.devcontainer_cleanup", session_id=session_id, stopped_count=len(stopped), ) except Exception: logger.warning( - "acp.session.close.devcontainer_cleanup_failed", + "a2a.session.close.devcontainer_cleanup_failed", session_id=session_id, exc_info=True, ) @@ -406,5 +406,5 @@ class AcpLocalFacade: __all__ = [ - "AcpLocalFacade", + "A2aLocalFacade", ] diff --git a/src/cleveragents/acp/models.py b/src/cleveragents/a2a/models.py similarity index 80% rename from src/cleveragents/acp/models.py rename to src/cleveragents/a2a/models.py index 584af1725..729f765ac 100644 --- a/src/cleveragents/acp/models.py +++ b/src/cleveragents/a2a/models.py @@ -1,6 +1,6 @@ -"""ACP request/response envelope models. +"""A2A request/response envelope models. -Pydantic v2 models for the ACP wire format. In local mode these are +Pydantic v2 models for the A2A wire format. In local mode these are used purely as validated data containers — no serialization to JSON actually occurs over a network. """ @@ -18,8 +18,8 @@ from ulid import ULID # --------------------------------------------------------------------------- -class AcpVersion: - """ACP protocol version constants.""" +class A2aVersion: + """A2A protocol version constants.""" CURRENT: str = "1.0" SUPPORTED: tuple[str, ...] = ("1.0",) @@ -45,8 +45,8 @@ def _iso_now_factory() -> str: # --------------------------------------------------------------------------- -class AcpErrorDetail(BaseModel): - """Structured error payload inside an :class:`AcpResponse`.""" +class A2aErrorDetail(BaseModel): + """Structured error payload inside an :class:`A2aResponse`.""" model_config = ConfigDict(strict=False) @@ -62,12 +62,12 @@ class AcpErrorDetail(BaseModel): return value -class AcpRequest(BaseModel): - """Inbound ACP operation envelope.""" +class A2aRequest(BaseModel): + """Inbound A2A operation envelope.""" model_config = ConfigDict(strict=False) - acp_version: str = AcpVersion.CURRENT + a2a_version: str = A2aVersion.CURRENT request_id: str = "" operation: str params: dict[str, Any] = {} @@ -81,22 +81,22 @@ class AcpRequest(BaseModel): return value @model_validator(mode="after") - def _default_request_id(self) -> AcpRequest: + def _default_request_id(self) -> A2aRequest: if not self.request_id: self.request_id = _ulid_factory() return self -class AcpResponse(BaseModel): - """Outbound ACP result envelope.""" +class A2aResponse(BaseModel): + """Outbound A2A result envelope.""" model_config = ConfigDict(strict=False) - acp_version: str = AcpVersion.CURRENT + a2a_version: str = A2aVersion.CURRENT request_id: str status: str data: dict[str, Any] = {} - error: AcpErrorDetail | None = None + error: A2aErrorDetail | None = None timing_ms: float | None = None @field_validator("status") @@ -107,7 +107,7 @@ class AcpResponse(BaseModel): return value -class AcpEvent(BaseModel): +class A2aEvent(BaseModel): """Server-sent event envelope for plan progress and streaming.""" model_config = ConfigDict(strict=False) @@ -126,7 +126,7 @@ class AcpEvent(BaseModel): return value @model_validator(mode="after") - def _set_defaults(self) -> AcpEvent: + def _set_defaults(self) -> A2aEvent: if not self.event_id: self.event_id = _ulid_factory() if not self.timestamp: @@ -135,9 +135,9 @@ class AcpEvent(BaseModel): __all__ = [ - "AcpErrorDetail", - "AcpEvent", - "AcpRequest", - "AcpResponse", - "AcpVersion", + "A2aErrorDetail", + "A2aEvent", + "A2aRequest", + "A2aResponse", + "A2aVersion", ] diff --git a/src/cleveragents/acp/server_config.py b/src/cleveragents/a2a/server_config.py similarity index 100% rename from src/cleveragents/acp/server_config.py rename to src/cleveragents/a2a/server_config.py diff --git a/src/cleveragents/acp/transport.py b/src/cleveragents/a2a/transport.py similarity index 55% rename from src/cleveragents/acp/transport.py rename to src/cleveragents/a2a/transport.py index 70fed14d9..cc6ef38ab 100644 --- a/src/cleveragents/acp/transport.py +++ b/src/cleveragents/a2a/transport.py @@ -1,6 +1,6 @@ -"""ACP server-mode HTTP transport stub. +"""A2A server-mode HTTP transport stub. -Every method raises :class:`AcpNotAvailableError`. When server mode is +Every method raises :class:`A2aNotAvailableError`. When server mode is implemented (separate project) the concrete transport will replace this stub. """ @@ -8,57 +8,57 @@ from __future__ import annotations import structlog -from cleveragents.acp.errors import AcpNotAvailableError -from cleveragents.acp.models import AcpRequest, AcpResponse +from cleveragents.a2a.errors import A2aNotAvailableError +from cleveragents.a2a.models import A2aRequest, A2aResponse logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__) _SERVER_MODE_MSG = ( - "ACP HTTP transport is not available in local mode" + "A2A HTTP transport is not available in local mode" " - server mode will be implemented as a separate project" ) -class AcpHttpTransport: +class A2aHttpTransport: """Stub HTTP transport — all mutating methods raise on invocation.""" - def send(self, request: AcpRequest) -> AcpResponse: - """Send an ACP request over HTTP. + def send(self, request: A2aRequest) -> A2aResponse: + """Send an A2A request over HTTP. - Always raises :class:`AcpNotAvailableError` in local mode. + Always raises :class:`A2aNotAvailableError` in local mode. """ - if not isinstance(request, AcpRequest): - raise TypeError("request must be an AcpRequest instance") + if not isinstance(request, A2aRequest): + raise TypeError("request must be an A2aRequest instance") logger.warning( "%s (operation=%s)", _SERVER_MODE_MSG, request.operation, ) - raise AcpNotAvailableError( + raise A2aNotAvailableError( _SERVER_MODE_MSG, details={"operation": request.operation}, ) def connect(self, base_url: str) -> None: - """Open a connection to the ACP server. + """Open a connection to the A2A server. - Always raises :class:`AcpNotAvailableError` in local mode. + Always raises :class:`A2aNotAvailableError` in local mode. """ if not base_url or not isinstance(base_url, str): raise ValueError("base_url must be a non-empty string") logger.warning("%s (url=%s)", _SERVER_MODE_MSG, base_url) - raise AcpNotAvailableError( + raise A2aNotAvailableError( _SERVER_MODE_MSG, details={"base_url": base_url}, ) def disconnect(self) -> None: - """Close the connection to the ACP server. + """Close the connection to the A2A server. - Always raises :class:`AcpNotAvailableError` in local mode. + Always raises :class:`A2aNotAvailableError` in local mode. """ logger.warning(_SERVER_MODE_MSG) - raise AcpNotAvailableError(_SERVER_MODE_MSG) + raise A2aNotAvailableError(_SERVER_MODE_MSG) def is_connected(self) -> bool: """Return connection status. Always ``False`` in local mode.""" @@ -66,5 +66,5 @@ class AcpHttpTransport: __all__ = [ - "AcpHttpTransport", + "A2aHttpTransport", ] diff --git a/src/cleveragents/acp/versioning.py b/src/cleveragents/a2a/versioning.py similarity index 67% rename from src/cleveragents/acp/versioning.py rename to src/cleveragents/a2a/versioning.py index 638af083a..6547af6fe 100644 --- a/src/cleveragents/acp/versioning.py +++ b/src/cleveragents/a2a/versioning.py @@ -1,7 +1,7 @@ -"""ACP version negotiation. +"""A2A version negotiation. -Ensures that a requested ACP version is within the set of supported -versions. Raises :class:`AcpVersionMismatchError` when the requested +Ensures that a requested A2A version is within the set of supported +versions. Raises :class:`A2aVersionMismatchError` when the requested version is not supported. """ @@ -9,13 +9,13 @@ from __future__ import annotations import structlog -from cleveragents.acp.errors import AcpVersionMismatchError +from cleveragents.a2a.errors import A2aVersionMismatchError logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__) -class AcpVersionNegotiator: - """Negotiates ACP protocol versions.""" +class A2aVersionNegotiator: + """Negotiates A2A protocol versions.""" CURRENT_VERSION: str = "1.0" SUPPORTED_VERSIONS: tuple[str, ...] = ("1.0",) @@ -25,16 +25,16 @@ class AcpVersionNegotiator: Raises: ValueError: If *requested* is empty. - AcpVersionMismatchError: If version is not in + A2aVersionMismatchError: If version is not in :attr:`SUPPORTED_VERSIONS`. """ if not requested or not isinstance(requested, str): raise ValueError("requested version must be a non-empty string") if requested in self.SUPPORTED_VERSIONS: - logger.debug("acp.version.negotiated", version=requested) + logger.debug("a2a.version.negotiated", version=requested) return requested - raise AcpVersionMismatchError( - message=f"ACP version '{requested}' is not supported", + raise A2aVersionMismatchError( + message=f"A2A version '{requested}' is not supported", requested_version=requested, supported_versions=list(self.SUPPORTED_VERSIONS), ) @@ -46,10 +46,10 @@ class AcpVersionNegotiator: return version in self.SUPPORTED_VERSIONS def get_current(self) -> str: - """Return the current ACP protocol version.""" + """Return the current A2A protocol version.""" return self.CURRENT_VERSION __all__ = [ - "AcpVersionNegotiator", + "A2aVersionNegotiator", ] diff --git a/src/cleveragents/acp/__init__.py b/src/cleveragents/acp/__init__.py deleted file mode 100644 index fbe58a95d..000000000 --- a/src/cleveragents/acp/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -"""ACP (Agent Communication Protocol) integration package. - -Provides the local-mode facade, server-mode transport stubs, request/response -models, event streaming stubs, and version negotiation for the ACP boundary. - -In **local mode** the :class:`AcpLocalFacade` maps ACP operation names to -direct Python method calls on existing application services. No serialization, -no network, no authentication. - -In **server mode** the :class:`AcpHttpTransport` is a stub that raises -:class:`AcpNotAvailableError` for every operation. When server mode is -implemented the concrete transport will replace these stubs. - -Server client protocols (:class:`ServerClient`, :class:`RemoteExecutionClient`, -:class:`AuthClient`) and their stub implementations are provided for forward -compatibility. :class:`ServerConnectionConfig` validates connection parameters. -""" - -from __future__ import annotations - -from cleveragents.acp.clients import ( - AuthClient, - RemoteExecutionClient, - ServerClient, - StubAuthClient, - StubRemoteExecutionClient, - StubServerClient, -) -from cleveragents.acp.errors import ( - AcpError, - AcpNotAvailableError, - AcpOperationNotFoundError, - AcpVersionMismatchError, -) -from cleveragents.acp.events import AcpEventQueue -from cleveragents.acp.facade import AcpLocalFacade -from cleveragents.acp.models import ( - AcpErrorDetail, - AcpEvent, - AcpRequest, - AcpResponse, - AcpVersion, -) -from cleveragents.acp.server_config import ServerConnectionConfig -from cleveragents.acp.transport import AcpHttpTransport -from cleveragents.acp.versioning import AcpVersionNegotiator - -__all__ = [ - "AcpError", - "AcpErrorDetail", - "AcpEvent", - "AcpEventQueue", - "AcpHttpTransport", - "AcpLocalFacade", - "AcpNotAvailableError", - "AcpOperationNotFoundError", - "AcpRequest", - "AcpResponse", - "AcpVersion", - "AcpVersionMismatchError", - "AcpVersionNegotiator", - "AuthClient", - "RemoteExecutionClient", - "ServerClient", - "ServerConnectionConfig", - "StubAuthClient", - "StubRemoteExecutionClient", - "StubServerClient", -] diff --git a/src/cleveragents/cli/commands/lsp.py b/src/cleveragents/cli/commands/lsp.py index d77f3fb75..f0fe71c2b 100644 --- a/src/cleveragents/cli/commands/lsp.py +++ b/src/cleveragents/cli/commands/lsp.py @@ -394,8 +394,8 @@ def serve( " All other methods → MethodNotFound (-32601)" ) - # Lazy import: LspServer transitively imports AcpLocalFacade - # which pulls the full ACP dependency chain (~200ms cold). + # Lazy import: LspServer transitively imports A2aLocalFacade + # which pulls the full A2A dependency chain (~200ms cold). # Non-serve subcommands (add, remove, list, show) do not # need LspServer, so deferring the import avoids that cost # for the common case. diff --git a/src/cleveragents/cli/commands/server.py b/src/cleveragents/cli/commands/server.py index 07d85bfe4..317fb7fa3 100644 --- a/src/cleveragents/cli/commands/server.py +++ b/src/cleveragents/cli/commands/server.py @@ -15,7 +15,7 @@ from pydantic import ValidationError from rich.console import Console from rich.panel import Panel -from cleveragents.acp.server_config import ServerConnectionConfig +from cleveragents.a2a.server_config import ServerConnectionConfig from cleveragents.application.services.config_service import ConfigService from cleveragents.cli.formatting import OutputFormat, format_output diff --git a/src/cleveragents/lsp/server.py b/src/cleveragents/lsp/server.py index 2c93564d8..ccb20e6b1 100644 --- a/src/cleveragents/lsp/server.py +++ b/src/cleveragents/lsp/server.py @@ -5,8 +5,8 @@ Content-Length header framing. Supports the ``initialize``, ``shutdown``, and ``exit`` lifecycle methods. All other LSP methods return a JSON-RPC ``MethodNotFound`` (-32601) error. -An :class:`AcpLocalFacade` instance is stored so that the -dispatch path can be wired through ACP when server mode lands. +An :class:`A2aLocalFacade` instance is stored so that the +dispatch path can be wired through A2A when server mode lands. In the current stub the facade is a placeholder — all methods are handled by hardcoded stub handlers. @@ -26,7 +26,7 @@ from typing import Any, BinaryIO import structlog -from cleveragents.acp.facade import AcpLocalFacade +from cleveragents.a2a.facade import A2aLocalFacade logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__) @@ -99,11 +99,11 @@ class LspServer: The server reads Content-Length framed JSON-RPC messages from *input_stream* and writes responses to *output_stream*. LSP - requests are routed through an :class:`AcpLocalFacade` instance + requests are routed through an :class:`A2aLocalFacade` instance in local mode. Attributes: - _facade: ACP local-mode facade for request routing. + _facade: A2A local-mode facade for request routing. _input: Binary stream to read from (default: ``sys.stdin.buffer``). _output: Binary stream to write to (default: ``sys.stdout.buffer``). _running: Whether the event loop is active. @@ -115,7 +115,7 @@ class LspServer: self, input_stream: BinaryIO | None = None, output_stream: BinaryIO | None = None, - facade: AcpLocalFacade | None = None, + facade: A2aLocalFacade | None = None, ) -> None: if input_stream is not None and not hasattr(input_stream, "read"): raise TypeError("input_stream must support read()") @@ -123,22 +123,22 @@ class LspServer: raise TypeError("output_stream must support write()") self._input: BinaryIO = input_stream or sys.stdin.buffer self._output: BinaryIO = output_stream or sys.stdout.buffer - self._facade: AcpLocalFacade | None = facade + self._facade: A2aLocalFacade | None = facade self._running: bool = False self._initialized: bool = False self._shutdown_requested: bool = False @property - def facade(self) -> AcpLocalFacade: - """Return the ACP facade, creating it lazily on first access. + def facade(self) -> A2aLocalFacade: + """Return the A2A facade, creating it lazily on first access. The facade is stored for future server-mode wiring but is not called in the current stub. Lazy creation avoids the - overhead of ``AcpLocalFacade()`` construction when the + overhead of ``A2aLocalFacade()`` construction when the facade is never needed (e.g., benchmarks). """ if self._facade is None: - self._facade = AcpLocalFacade() + self._facade = A2aLocalFacade() return self._facade @property