diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index defe87b5c..ba68fec38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: language: system pass_filenames: true types: [python] - exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/) + exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/|lib/crewai-a2a/tests/) - repo: https://github.com/astral-sh/uv-pre-commit rev: 0.9.3 hooks: diff --git a/conftest.py b/conftest.py index 09852767e..bc4e7cd6c 100644 --- a/conftest.py +++ b/conftest.py @@ -59,7 +59,11 @@ if _original_from_serialized_response is not None: request: Any, serialized_response: Any, history: Any = None ) -> Any: """Patched version that ensures response._content is properly set.""" - response = _original_from_serialized_response(request, serialized_response, history) + if not _original_from_serialized_response: + return None + response = _original_from_serialized_response( + request, serialized_response, history + ) # Explicitly set _content to avoid ResponseNotRead errors # The content was passed to the constructor but the mocked read() prevents # proper initialization of the internal state @@ -255,7 +259,7 @@ def vcr_cassette_dir(request: Any) -> str: for parent in test_file.parents: if ( - parent.name in ("crewai", "crewai-tools", "crewai-files") + parent.name in ("crewai", "crewai-tools", "crewai-files", "crewai-a2a") and parent.parent.name == "lib" ): package_root = parent diff --git a/lib/crewai-a2a/pyproject.toml b/lib/crewai-a2a/pyproject.toml index 806b77129..ff224344f 100644 --- a/lib/crewai-a2a/pyproject.toml +++ b/lib/crewai-a2a/pyproject.toml @@ -8,7 +8,7 @@ authors = [ ] requires-python = ">=3.10, <3.14" dependencies = [ - "crewai", + "crewai==1.13.0a6", "a2a-sdk~=0.3.10", "httpx-auth~=0.23.1", "httpx-sse~=0.4.0", diff --git a/lib/crewai-a2a/src/crewai_a2a/__init__.py b/lib/crewai-a2a/src/crewai_a2a/__init__.py index 93cc4e6f9..d768121e4 100644 --- a/lib/crewai-a2a/src/crewai_a2a/__init__.py +++ b/lib/crewai-a2a/src/crewai_a2a/__init__.py @@ -1,6 +1,6 @@ """Agent-to-Agent (A2A) protocol communication for CrewAI.""" -__version__ = "0.1.0" +__version__ = "1.13.0a6" from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig diff --git a/lib/crewai/tests/a2a/test_a2a_integration.py b/lib/crewai-a2a/tests/a2a/test_a2a_integration.py similarity index 98% rename from lib/crewai/tests/a2a/test_a2a_integration.py rename to lib/crewai-a2a/tests/a2a/test_a2a_integration.py index ca48a0bbb..cbb31ef01 100644 --- a/lib/crewai/tests/a2a/test_a2a_integration.py +++ b/lib/crewai-a2a/tests/a2a/test_a2a_integration.py @@ -3,14 +3,12 @@ from __future__ import annotations import os import uuid -import pytest -import pytest_asyncio - from a2a.client import ClientFactory from a2a.types import AgentCard, Message, Part, Role, TaskState, TextPart - from crewai_a2a.updates.polling.handler import PollingHandler from crewai_a2a.updates.streaming.handler import StreamingHandler +import pytest +import pytest_asyncio A2A_TEST_ENDPOINT = os.getenv("A2A_TEST_ENDPOINT", "http://localhost:9999") @@ -162,7 +160,7 @@ class TestA2APushNotificationHandler: ) @pytest.fixture - def mock_task(self) -> "Task": + def mock_task(self) -> Task: """Create a minimal valid task for testing.""" from a2a.types import Task, TaskStatus @@ -182,10 +180,11 @@ class TestA2APushNotificationHandler: from unittest.mock import AsyncMock, MagicMock from a2a.types import Task, TaskStatus - from pydantic import AnyHttpUrl - from crewai_a2a.updates.push_notifications.config import PushNotificationConfig - from crewai_a2a.updates.push_notifications.handler import PushNotificationHandler + from crewai_a2a.updates.push_notifications.handler import ( + PushNotificationHandler, + ) + from pydantic import AnyHttpUrl completed_task = Task( id="task-123", @@ -246,10 +245,11 @@ class TestA2APushNotificationHandler: from unittest.mock import AsyncMock, MagicMock from a2a.types import Task, TaskStatus - from pydantic import AnyHttpUrl - from crewai_a2a.updates.push_notifications.config import PushNotificationConfig - from crewai_a2a.updates.push_notifications.handler import PushNotificationHandler + from crewai_a2a.updates.push_notifications.handler import ( + PushNotificationHandler, + ) + from pydantic import AnyHttpUrl mock_store = MagicMock() mock_store.wait_for_result = AsyncMock(return_value=None) @@ -303,7 +303,9 @@ class TestA2APushNotificationHandler: """Test that push handler fails gracefully without config.""" from unittest.mock import MagicMock - from crewai_a2a.updates.push_notifications.handler import PushNotificationHandler + from crewai_a2a.updates.push_notifications.handler import ( + PushNotificationHandler, + ) mock_client = MagicMock() diff --git a/lib/crewai/tests/agents/test_a2a_trust_completion_status.py b/lib/crewai-a2a/tests/agents/test_a2a_trust_completion_status.py similarity index 97% rename from lib/crewai/tests/agents/test_a2a_trust_completion_status.py rename to lib/crewai-a2a/tests/agents/test_a2a_trust_completion_status.py index f3d6ec0fb..6b873947c 100644 --- a/lib/crewai/tests/agents/test_a2a_trust_completion_status.py +++ b/lib/crewai-a2a/tests/agents/test_a2a_trust_completion_status.py @@ -2,9 +2,9 @@ from unittest.mock import MagicMock, patch +from crewai_a2a.config import A2AConfig import pytest -from crewai_a2a.config import A2AConfig try: from a2a.types import Message, Role @@ -27,9 +27,8 @@ def _create_mock_agent_card(name: str = "Test", url: str = "http://test-endpoint @pytest.mark.skipif(not A2A_SDK_INSTALLED, reason="Requires a2a-sdk to be installed") def test_trust_remote_completion_status_true_returns_directly(): """When trust_remote_completion_status=True and A2A returns completed, return result directly.""" - from crewai_a2a.wrapper import _delegate_to_a2a - from crewai_a2a.types import AgentResponseProtocol from crewai import Agent, Task + from crewai_a2a.wrapper import _delegate_to_a2a a2a_config = A2AConfig( endpoint="http://test-endpoint.com", @@ -83,8 +82,8 @@ def test_trust_remote_completion_status_true_returns_directly(): @pytest.mark.skipif(not A2A_SDK_INSTALLED, reason="Requires a2a-sdk to be installed") def test_trust_remote_completion_status_false_continues_conversation(): """When trust_remote_completion_status=False and A2A returns completed, ask server agent.""" - from crewai_a2a.wrapper import _delegate_to_a2a from crewai import Agent, Task + from crewai_a2a.wrapper import _delegate_to_a2a a2a_config = A2AConfig( endpoint="http://test-endpoint.com", @@ -152,4 +151,4 @@ def test_default_trust_remote_completion_status_is_false(): endpoint="http://test-endpoint.com", ) - assert a2a_config.trust_remote_completion_status is False \ No newline at end of file + assert a2a_config.trust_remote_completion_status is False diff --git a/lib/crewai/tests/agents/test_agent_a2a_kickoff.py b/lib/crewai-a2a/tests/agents/test_agent_a2a_kickoff.py similarity index 97% rename from lib/crewai/tests/agents/test_agent_a2a_kickoff.py rename to lib/crewai-a2a/tests/agents/test_agent_a2a_kickoff.py index c3b34d21d..796d7e594 100644 --- a/lib/crewai/tests/agents/test_agent_a2a_kickoff.py +++ b/lib/crewai-a2a/tests/agents/test_agent_a2a_kickoff.py @@ -4,10 +4,9 @@ from __future__ import annotations import os -import pytest - from crewai import Agent from crewai_a2a.config import A2AClientConfig +import pytest A2A_TEST_ENDPOINT = os.getenv( @@ -50,9 +49,7 @@ class TestAgentA2AKickoff: @pytest.mark.skip(reason="VCR cassette matching issue with agent card caching") @pytest.mark.vcr() - def test_agent_kickoff_with_calculator_skill( - self, researcher_agent: Agent - ) -> None: + def test_agent_kickoff_with_calculator_skill(self, researcher_agent: Agent) -> None: """Test that agent can delegate calculation to A2A server.""" result = researcher_agent.kickoff( "Ask the remote A2A agent to calculate 25 times 17." @@ -149,9 +146,7 @@ class TestAgentA2AKickoff: @pytest.mark.skip(reason="VCR cassette matching issue with agent card caching") @pytest.mark.vcr() - def test_agent_kickoff_with_list_messages( - self, researcher_agent: Agent - ) -> None: + def test_agent_kickoff_with_list_messages(self, researcher_agent: Agent) -> None: """Test that agent.kickoff() works with list of messages.""" messages = [ { diff --git a/lib/crewai/tests/agents/test_agent_a2a_wrapping.py b/lib/crewai-a2a/tests/agents/test_agent_a2a_wrapping.py similarity index 94% rename from lib/crewai/tests/agents/test_agent_a2a_wrapping.py rename to lib/crewai-a2a/tests/agents/test_agent_a2a_wrapping.py index b6e08f0bc..6fd59580d 100644 --- a/lib/crewai/tests/agents/test_agent_a2a_wrapping.py +++ b/lib/crewai-a2a/tests/agents/test_agent_a2a_wrapping.py @@ -1,14 +1,12 @@ """Test A2A wrapper is only applied when a2a is passed to Agent.""" -from unittest.mock import patch - -import pytest - from crewai import Agent from crewai_a2a.config import A2AConfig +import pytest + try: - import a2a # noqa: F401 + import a2a A2A_SDK_INSTALLED = True except ImportError: @@ -106,6 +104,9 @@ def test_wrapper_is_applied_differently_per_instance(): a2a=a2a_config, ) - assert agent_without_a2a.execute_task.__func__ is not agent_with_a2a.execute_task.__func__ + assert ( + agent_without_a2a.execute_task.__func__ + is not agent_with_a2a.execute_task.__func__ + ) assert not hasattr(agent_without_a2a.execute_task, "__wrapped__") assert hasattr(agent_with_a2a.execute_task, "__wrapped__") diff --git a/lib/crewai-a2a/tests/cassettes/a2a/TestA2AAgentCardFetching.test_fetch_agent_card.yaml b/lib/crewai-a2a/tests/cassettes/a2a/TestA2AAgentCardFetching.test_fetch_agent_card.yaml new file mode 100644 index 000000000..d60788a55 --- /dev/null +++ b/lib/crewai-a2a/tests/cassettes/a2a/TestA2AAgentCardFetching.test_fetch_agent_card.yaml @@ -0,0 +1,44 @@ +interactions: +- request: + body: '' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + host: + - localhost:9999 + method: GET + uri: http://localhost:9999/.well-known/agent-card.json + response: + body: + string: '{"capabilities":{"streaming":true},"defaultInputModes":["text"],"defaultOutputModes":["text"],"description":"An + AI assistant powered by OpenAI GPT with calculator and time tools. Ask questions, + perform calculations, or get the current time in any timezone.","name":"GPT + Assistant","preferredTransport":"JSONRPC","protocolVersion":"0.3.0","skills":[{"description":"Have + a general conversation with the AI assistant. Ask questions, get explanations, + or just chat.","examples":["Hello, how are you?","Explain quantum computing + in simple terms","What can you help me with?"],"id":"conversation","name":"General + Conversation","tags":["chat","conversation","general"]},{"description":"Perform + mathematical calculations including arithmetic, exponents, and more.","examples":["What + is 25 * 17?","Calculate 2^10","What''s (100 + 50) / 3?"],"id":"calculator","name":"Calculator","tags":["math","calculator","arithmetic"]},{"description":"Get + the current date and time in any timezone.","examples":["What time is it?","What''s + the current time in Tokyo?","What''s today''s date in New York?"],"id":"time","name":"Current + Time","tags":["time","date","timezone"]}],"url":"http://localhost:9999/","version":"1.0.0"}' + headers: + content-length: + - '1198' + content-type: + - application/json + date: + - Tue, 06 Jan 2026 14:17:00 GMT + server: + - uvicorn + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai-a2a/tests/cassettes/a2a/TestA2APollingIntegration.test_polling_completes_task.yaml b/lib/crewai-a2a/tests/cassettes/a2a/TestA2APollingIntegration.test_polling_completes_task.yaml new file mode 100644 index 000000000..3832dc7da --- /dev/null +++ b/lib/crewai-a2a/tests/cassettes/a2a/TestA2APollingIntegration.test_polling_completes_task.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: '' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + host: + - localhost:9999 + method: GET + uri: http://localhost:9999/.well-known/agent-card.json + response: + body: + string: '{"capabilities":{"streaming":true},"defaultInputModes":["text"],"defaultOutputModes":["text"],"description":"An + AI assistant powered by OpenAI GPT with calculator and time tools. Ask questions, + perform calculations, or get the current time in any timezone.","name":"GPT + Assistant","preferredTransport":"JSONRPC","protocolVersion":"0.3.0","skills":[{"description":"Have + a general conversation with the AI assistant. Ask questions, get explanations, + or just chat.","examples":["Hello, how are you?","Explain quantum computing + in simple terms","What can you help me with?"],"id":"conversation","name":"General + Conversation","tags":["chat","conversation","general"]},{"description":"Perform + mathematical calculations including arithmetic, exponents, and more.","examples":["What + is 25 * 17?","Calculate 2^10","What''s (100 + 50) / 3?"],"id":"calculator","name":"Calculator","tags":["math","calculator","arithmetic"]},{"description":"Get + the current date and time in any timezone.","examples":["What time is it?","What''s + the current time in Tokyo?","What''s today''s date in New York?"],"id":"time","name":"Current + Time","tags":["time","date","timezone"]}],"url":"http://localhost:9999/","version":"1.0.0"}' + headers: + content-length: + - '1198' + content-type: + - application/json + date: + - Tue, 06 Jan 2026 14:16:58 GMT + server: + - uvicorn + status: + code: 200 + message: OK +- request: + body: '{"id":"e5ac2160-ae9b-4bf9-aad7-14bf0d53d6d9","jsonrpc":"2.0","method":"message/stream","params":{"configuration":{"acceptedOutputModes":[],"blocking":true},"message":{"kind":"message","messageId":"e1e63c75-3ea0-49fb-b512-5128a2476416","parts":[{"kind":"text","text":"What + is 2 + 2?"}],"role":"user"}}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*, text/event-stream' + accept-encoding: + - ACCEPT-ENCODING-XXX + cache-control: + - no-store + connection: + - keep-alive + content-length: + - '301' + content-type: + - application/json + host: + - localhost:9999 + method: POST + uri: http://localhost:9999/ + response: + body: + string: "data: {\"id\":\"e5ac2160-ae9b-4bf9-aad7-14bf0d53d6d9\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"b9e14c1b-734d-4d1e-864a-e6dda5231d71\",\"final\":false,\"kind\":\"status-update\",\"status\":{\"state\":\"submitted\"},\"taskId\":\"0dd4d3af-f35d-409d-9462-01218e5641f9\"}}\r\n\r\ndata: + {\"id\":\"e5ac2160-ae9b-4bf9-aad7-14bf0d53d6d9\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"b9e14c1b-734d-4d1e-864a-e6dda5231d71\",\"final\":false,\"kind\":\"status-update\",\"status\":{\"state\":\"working\"},\"taskId\":\"0dd4d3af-f35d-409d-9462-01218e5641f9\"}}\r\n\r\ndata: + {\"id\":\"e5ac2160-ae9b-4bf9-aad7-14bf0d53d6d9\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"b9e14c1b-734d-4d1e-864a-e6dda5231d71\",\"final\":true,\"kind\":\"status-update\",\"status\":{\"message\":{\"kind\":\"message\",\"messageId\":\"54bb7ff3-f2c0-4eb3-b427-bf1c8cf90832\",\"parts\":[{\"kind\":\"text\",\"text\":\"\\n[Tool: + calculator] 2 + 2 = 4\\n2 + 2 equals 4.\"}],\"role\":\"agent\"},\"state\":\"completed\"},\"taskId\":\"0dd4d3af-f35d-409d-9462-01218e5641f9\"}}\r\n\r\n" + headers: + Transfer-Encoding: + - chunked + cache-control: + - no-store + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 06 Jan 2026 14:16:58 GMT + server: + - uvicorn + x-accel-buffering: + - 'no' + status: + code: 200 + message: OK +- request: + body: '{"id":"cb1e4af3-d2d0-4848-96b8-7082ee6171d1","jsonrpc":"2.0","method":"tasks/get","params":{"historyLength":100,"id":"0dd4d3af-f35d-409d-9462-01218e5641f9"}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + content-length: + - '157' + content-type: + - application/json + host: + - localhost:9999 + method: POST + uri: http://localhost:9999/ + response: + body: + string: '{"id":"cb1e4af3-d2d0-4848-96b8-7082ee6171d1","jsonrpc":"2.0","result":{"contextId":"b9e14c1b-734d-4d1e-864a-e6dda5231d71","history":[{"contextId":"b9e14c1b-734d-4d1e-864a-e6dda5231d71","kind":"message","messageId":"e1e63c75-3ea0-49fb-b512-5128a2476416","parts":[{"kind":"text","text":"What + is 2 + 2?"}],"role":"user","taskId":"0dd4d3af-f35d-409d-9462-01218e5641f9"}],"id":"0dd4d3af-f35d-409d-9462-01218e5641f9","kind":"task","status":{"message":{"kind":"message","messageId":"54bb7ff3-f2c0-4eb3-b427-bf1c8cf90832","parts":[{"kind":"text","text":"\n[Tool: + calculator] 2 + 2 = 4\n2 + 2 equals 4."}],"role":"agent"},"state":"completed"}}}' + headers: + content-length: + - '635' + content-type: + - application/json + date: + - Tue, 06 Jan 2026 14:17:00 GMT + server: + - uvicorn + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai-a2a/tests/cassettes/a2a/TestA2AStreamingIntegration.test_streaming_completes_task.yaml b/lib/crewai-a2a/tests/cassettes/a2a/TestA2AStreamingIntegration.test_streaming_completes_task.yaml new file mode 100644 index 000000000..e98e61c2b --- /dev/null +++ b/lib/crewai-a2a/tests/cassettes/a2a/TestA2AStreamingIntegration.test_streaming_completes_task.yaml @@ -0,0 +1,90 @@ +interactions: +- request: + body: '' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + host: + - localhost:9999 + method: GET + uri: http://localhost:9999/.well-known/agent-card.json + response: + body: + string: '{"capabilities":{"streaming":true},"defaultInputModes":["text"],"defaultOutputModes":["text"],"description":"An + AI assistant powered by OpenAI GPT with calculator and time tools. Ask questions, + perform calculations, or get the current time in any timezone.","name":"GPT + Assistant","preferredTransport":"JSONRPC","protocolVersion":"0.3.0","skills":[{"description":"Have + a general conversation with the AI assistant. Ask questions, get explanations, + or just chat.","examples":["Hello, how are you?","Explain quantum computing + in simple terms","What can you help me with?"],"id":"conversation","name":"General + Conversation","tags":["chat","conversation","general"]},{"description":"Perform + mathematical calculations including arithmetic, exponents, and more.","examples":["What + is 25 * 17?","Calculate 2^10","What''s (100 + 50) / 3?"],"id":"calculator","name":"Calculator","tags":["math","calculator","arithmetic"]},{"description":"Get + the current date and time in any timezone.","examples":["What time is it?","What''s + the current time in Tokyo?","What''s today''s date in New York?"],"id":"time","name":"Current + Time","tags":["time","date","timezone"]}],"url":"http://localhost:9999/","version":"1.0.0"}' + headers: + content-length: + - '1198' + content-type: + - application/json + date: + - Tue, 06 Jan 2026 14:17:02 GMT + server: + - uvicorn + status: + code: 200 + message: OK +- request: + body: '{"id":"8cf25b61-8884-4246-adce-fccb32e176ab","jsonrpc":"2.0","method":"message/stream","params":{"configuration":{"acceptedOutputModes":[],"blocking":true},"message":{"kind":"message","messageId":"c145297f-7331-4835-adcc-66b51de92a2b","parts":[{"kind":"text","text":"What + is 2 + 2?"}],"role":"user"}}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*, text/event-stream' + accept-encoding: + - ACCEPT-ENCODING-XXX + cache-control: + - no-store + connection: + - keep-alive + content-length: + - '301' + content-type: + - application/json + host: + - localhost:9999 + method: POST + uri: http://localhost:9999/ + response: + body: + string: "data: {\"id\":\"8cf25b61-8884-4246-adce-fccb32e176ab\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"30601267-ab3b-48ef-afc8-916c37a18651\",\"final\":false,\"kind\":\"status-update\",\"status\":{\"state\":\"submitted\"},\"taskId\":\"3083d3da-4739-4f4f-a4e8-7c048ea819c1\"}}\r\n\r\ndata: + {\"id\":\"8cf25b61-8884-4246-adce-fccb32e176ab\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"30601267-ab3b-48ef-afc8-916c37a18651\",\"final\":false,\"kind\":\"status-update\",\"status\":{\"state\":\"working\"},\"taskId\":\"3083d3da-4739-4f4f-a4e8-7c048ea819c1\"}}\r\n\r\ndata: + {\"id\":\"8cf25b61-8884-4246-adce-fccb32e176ab\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"30601267-ab3b-48ef-afc8-916c37a18651\",\"final\":true,\"kind\":\"status-update\",\"status\":{\"message\":{\"kind\":\"message\",\"messageId\":\"25f81e3c-b7e8-48b5-a98a-4066f3637a13\",\"parts\":[{\"kind\":\"text\",\"text\":\"\\n[Tool: + calculator] 2 + 2 = 4\\n2 + 2 equals 4.\"}],\"role\":\"agent\"},\"state\":\"completed\"},\"taskId\":\"3083d3da-4739-4f4f-a4e8-7c048ea819c1\"}}\r\n\r\n" + headers: + Transfer-Encoding: + - chunked + cache-control: + - no-store + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 06 Jan 2026 14:17:02 GMT + server: + - uvicorn + x-accel-buffering: + - 'no' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai-a2a/tests/cassettes/a2a/TestA2ATaskOperations.test_send_message_and_get_response.yaml b/lib/crewai-a2a/tests/cassettes/a2a/TestA2ATaskOperations.test_send_message_and_get_response.yaml new file mode 100644 index 000000000..e3623e8da --- /dev/null +++ b/lib/crewai-a2a/tests/cassettes/a2a/TestA2ATaskOperations.test_send_message_and_get_response.yaml @@ -0,0 +1,90 @@ +interactions: +- request: + body: '' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*' + accept-encoding: + - ACCEPT-ENCODING-XXX + connection: + - keep-alive + host: + - localhost:9999 + method: GET + uri: http://localhost:9999/.well-known/agent-card.json + response: + body: + string: '{"capabilities":{"streaming":true},"defaultInputModes":["text"],"defaultOutputModes":["text"],"description":"An + AI assistant powered by OpenAI GPT with calculator and time tools. Ask questions, + perform calculations, or get the current time in any timezone.","name":"GPT + Assistant","preferredTransport":"JSONRPC","protocolVersion":"0.3.0","skills":[{"description":"Have + a general conversation with the AI assistant. Ask questions, get explanations, + or just chat.","examples":["Hello, how are you?","Explain quantum computing + in simple terms","What can you help me with?"],"id":"conversation","name":"General + Conversation","tags":["chat","conversation","general"]},{"description":"Perform + mathematical calculations including arithmetic, exponents, and more.","examples":["What + is 25 * 17?","Calculate 2^10","What''s (100 + 50) / 3?"],"id":"calculator","name":"Calculator","tags":["math","calculator","arithmetic"]},{"description":"Get + the current date and time in any timezone.","examples":["What time is it?","What''s + the current time in Tokyo?","What''s today''s date in New York?"],"id":"time","name":"Current + Time","tags":["time","date","timezone"]}],"url":"http://localhost:9999/","version":"1.0.0"}' + headers: + content-length: + - '1198' + content-type: + - application/json + date: + - Tue, 06 Jan 2026 14:17:00 GMT + server: + - uvicorn + status: + code: 200 + message: OK +- request: + body: '{"id":"3a17c6bf-8db6-45a6-8535-34c45c0c4936","jsonrpc":"2.0","method":"message/stream","params":{"configuration":{"acceptedOutputModes":[],"blocking":true},"message":{"kind":"message","messageId":"712558a3-6d92-4591-be8a-9dd8566dde82","parts":[{"kind":"text","text":"What + is 2 + 2?"}],"role":"user"}}}' + headers: + User-Agent: + - X-USER-AGENT-XXX + accept: + - '*/*, text/event-stream' + accept-encoding: + - ACCEPT-ENCODING-XXX + cache-control: + - no-store + connection: + - keep-alive + content-length: + - '301' + content-type: + - application/json + host: + - localhost:9999 + method: POST + uri: http://localhost:9999/ + response: + body: + string: "data: {\"id\":\"3a17c6bf-8db6-45a6-8535-34c45c0c4936\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"ca2fbbc9-761e-45d9-a929-0c68b1f8acbf\",\"final\":false,\"kind\":\"status-update\",\"status\":{\"state\":\"submitted\"},\"taskId\":\"c6e88db0-36e9-4269-8b9a-ecb6dfdcf6a1\"}}\r\n\r\ndata: + {\"id\":\"3a17c6bf-8db6-45a6-8535-34c45c0c4936\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"ca2fbbc9-761e-45d9-a929-0c68b1f8acbf\",\"final\":false,\"kind\":\"status-update\",\"status\":{\"state\":\"working\"},\"taskId\":\"c6e88db0-36e9-4269-8b9a-ecb6dfdcf6a1\"}}\r\n\r\ndata: + {\"id\":\"3a17c6bf-8db6-45a6-8535-34c45c0c4936\",\"jsonrpc\":\"2.0\",\"result\":{\"contextId\":\"ca2fbbc9-761e-45d9-a929-0c68b1f8acbf\",\"final\":true,\"kind\":\"status-update\",\"status\":{\"message\":{\"kind\":\"message\",\"messageId\":\"916324aa-fd25-4849-bceb-c4644e2fcbb0\",\"parts\":[{\"kind\":\"text\",\"text\":\"\\n[Tool: + calculator] 2 + 2 = 4\\n2 + 2 equals 4.\"}],\"role\":\"agent\"},\"state\":\"completed\"},\"taskId\":\"c6e88db0-36e9-4269-8b9a-ecb6dfdcf6a1\"}}\r\n\r\n" + headers: + Transfer-Encoding: + - chunked + cache-control: + - no-store + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 06 Jan 2026 14:17:00 GMT + server: + - uvicorn + x-accel-buffering: + - 'no' + status: + code: 200 + message: OK +version: 1 diff --git a/lib/crewai/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_delegates_to_a2a.yaml b/lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_delegates_to_a2a.yaml similarity index 100% rename from lib/crewai/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_delegates_to_a2a.yaml rename to lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_delegates_to_a2a.yaml diff --git a/lib/crewai/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_with_failed_a2a_endpoint.yaml b/lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_with_failed_a2a_endpoint.yaml similarity index 100% rename from lib/crewai/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_with_failed_a2a_endpoint.yaml rename to lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_kickoff_with_failed_a2a_endpoint.yaml diff --git a/lib/crewai/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_without_a2a_works_normally.yaml b/lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_without_a2a_works_normally.yaml similarity index 100% rename from lib/crewai/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_without_a2a_works_normally.yaml rename to lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoff.test_agent_without_a2a_works_normally.yaml diff --git a/lib/crewai/tests/cassettes/agents/TestAgentA2AKickoffAsync.test_agent_kickoff_async_delegates_to_a2a.yaml b/lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoffAsync.test_agent_kickoff_async_delegates_to_a2a.yaml similarity index 100% rename from lib/crewai/tests/cassettes/agents/TestAgentA2AKickoffAsync.test_agent_kickoff_async_delegates_to_a2a.yaml rename to lib/crewai-a2a/tests/cassettes/agents/TestAgentA2AKickoffAsync.test_agent_kickoff_async_delegates_to_a2a.yaml diff --git a/lib/crewai/tests/a2a/utils/test_agent_card.py b/lib/crewai-a2a/tests/utils/test_agent_card.py similarity index 99% rename from lib/crewai/tests/a2a/utils/test_agent_card.py rename to lib/crewai-a2a/tests/utils/test_agent_card.py index 84dcbca73..9da1a7b6c 100644 --- a/lib/crewai/tests/a2a/utils/test_agent_card.py +++ b/lib/crewai-a2a/tests/utils/test_agent_card.py @@ -3,7 +3,6 @@ from __future__ import annotations from a2a.types import AgentCard, AgentSkill - from crewai import Agent from crewai_a2a.config import A2AClientConfig, A2AServerConfig from crewai_a2a.utils.agent_card import inject_a2a_server_methods diff --git a/lib/crewai/tests/a2a/utils/test_task.py b/lib/crewai-a2a/tests/utils/test_task.py similarity index 98% rename from lib/crewai/tests/a2a/utils/test_task.py rename to lib/crewai-a2a/tests/utils/test_task.py index 04394f0d5..5882fe723 100644 --- a/lib/crewai/tests/a2a/utils/test_task.py +++ b/lib/crewai-a2a/tests/utils/test_task.py @@ -6,13 +6,12 @@ import asyncio from typing import Any from unittest.mock import AsyncMock, MagicMock, patch -import pytest -import pytest_asyncio from a2a.server.agent_execution import RequestContext from a2a.server.events import EventQueue from a2a.types import Message, Task as A2ATask, TaskState, TaskStatus - from crewai_a2a.utils.task import cancel, cancellable, execute +import pytest +import pytest_asyncio @pytest.fixture @@ -85,8 +84,11 @@ class TestCancellableDecorator: assert call_count == 1 @pytest.mark.asyncio - async def test_executes_function_with_context(self, mock_context: MagicMock) -> None: + async def test_executes_function_with_context( + self, mock_context: MagicMock + ) -> None: """Function executes normally with RequestContext when not cancelled.""" + @cancellable async def my_func(context: RequestContext) -> str: await asyncio.sleep(0.01) @@ -134,6 +136,7 @@ class TestCancellableDecorator: @pytest.mark.asyncio async def test_extracts_context_from_kwargs(self, mock_context: MagicMock) -> None: """Context can be passed as keyword argument.""" + @cancellable async def my_func(value: int, context: RequestContext | None = None) -> int: return value + 1 @@ -354,6 +357,7 @@ class TestExecuteAndCancelIntegration: mock_task: MagicMock, ) -> None: """Calling cancel stops a running execute.""" + async def slow_task(**kwargs: Any) -> str: await asyncio.sleep(2.0) return "should not complete" @@ -372,4 +376,4 @@ class TestExecuteAndCancelIntegration: await cancel(mock_context, mock_event_queue) with pytest.raises(asyncio.CancelledError): - await execute_task \ No newline at end of file + await execute_task diff --git a/lib/crewai/pyproject.toml b/lib/crewai/pyproject.toml index a026f505d..3d5cfa303 100644 --- a/lib/crewai/pyproject.toml +++ b/lib/crewai/pyproject.toml @@ -98,7 +98,7 @@ anthropic = [ "anthropic~=0.73.0", ] a2a = [ - "crewai-a2a", + "crewai-a2a==1.13.0a6", ] file-processing = [ "crewai-files", diff --git a/lib/devtools/src/crewai_devtools/cli.py b/lib/devtools/src/crewai_devtools/cli.py index 9f7b469be..b0cc93685 100644 --- a/lib/devtools/src/crewai_devtools/cli.py +++ b/lib/devtools/src/crewai_devtools/cli.py @@ -187,6 +187,7 @@ _DEFAULT_WORKSPACE_PACKAGES: Final[list[str]] = [ "crewai", "crewai-tools", "crewai-devtools", + "crewai-a2a", ] diff --git a/pyproject.toml b/pyproject.toml index 020288446..83c1e7eab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,6 +107,7 @@ ignore-decorators = ["typing.overload"] "lib/crewai/tests/**/*.py" = ["S101", "RET504", "S105", "S106"] # Allow assert statements, unnecessary assignments, and hardcoded passwords in tests "lib/crewai-tools/tests/**/*.py" = ["S101", "RET504", "S105", "S106", "RUF012", "N818", "E402", "RUF043", "S110", "B017"] # Allow various test-specific patterns "lib/crewai-files/tests/**/*.py" = ["S101", "RET504", "S105", "S106", "B017", "F841"] # Allow assert statements and blind exception assertions in tests +"lib/crewai-a2a/tests/**/*.py" = ["S101", "RET504", "S105", "S106", "RUF012", "F821", "F401", "B017"] # Allow test-specific patterns [tool.mypy] @@ -119,7 +120,7 @@ warn_return_any = true show_error_codes = true warn_unused_ignores = true python_version = "3.12" -exclude = "(?x)(^lib/crewai/src/crewai/cli/templates/|^lib/crewai/tests/|^lib/crewai-tools/tests/|^lib/crewai-files/tests/)" +exclude = "(?x)(^lib/crewai/src/crewai/cli/templates/|^lib/crewai/tests/|^lib/crewai-tools/tests/|^lib/crewai-files/tests/|^lib/crewai-a2a/tests/)" plugins = ["pydantic.mypy"] @@ -135,6 +136,7 @@ testpaths = [ "lib/crewai/tests", "lib/crewai-tools/tests", "lib/crewai-files/tests", + "lib/crewai-a2a/tests", ] asyncio_mode = "strict" asyncio_default_fixture_loop_scope = "function"