diff --git a/lib/crewai-a2a/README.md b/lib/crewai-a2a/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/lib/crewai-a2a/pyproject.toml b/lib/crewai-a2a/pyproject.toml new file mode 100644 index 000000000..806b77129 --- /dev/null +++ b/lib/crewai-a2a/pyproject.toml @@ -0,0 +1,29 @@ +[project] +name = "crewai-a2a" +dynamic = ["version"] +description = "Agent-to-Agent (A2A) protocol communication for CrewAI" +readme = "README.md" +authors = [ + { name = "Joao Moura", email = "joao@crewai.com" } +] +requires-python = ">=3.10, <3.14" +dependencies = [ + "crewai", + "a2a-sdk~=0.3.10", + "httpx-auth~=0.23.1", + "httpx-sse~=0.4.0", + "aiocache[redis,memcached]~=0.12.3", + "pydantic~=2.11.9", + "pyjwt>=2.9.0,<3", + "httpx~=0.28.1", +] + +[tool.uv.sources] +crewai = { workspace = true } + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "src/crewai_a2a/__init__.py" \ No newline at end of file diff --git a/lib/crewai-a2a/src/crewai_a2a/__init__.py b/lib/crewai-a2a/src/crewai_a2a/__init__.py new file mode 100644 index 000000000..93cc4e6f9 --- /dev/null +++ b/lib/crewai-a2a/src/crewai_a2a/__init__.py @@ -0,0 +1,12 @@ +"""Agent-to-Agent (A2A) protocol communication for CrewAI.""" + +__version__ = "0.1.0" + +from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig + + +__all__ = [ + "A2AClientConfig", + "A2AConfig", + "A2AServerConfig", +] diff --git a/lib/crewai/src/crewai/a2a/auth/__init__.py b/lib/crewai-a2a/src/crewai_a2a/auth/__init__.py similarity index 88% rename from lib/crewai/src/crewai/a2a/auth/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/auth/__init__.py index cd7e67d72..95a7e84f5 100644 --- a/lib/crewai/src/crewai/a2a/auth/__init__.py +++ b/lib/crewai-a2a/src/crewai_a2a/auth/__init__.py @@ -1,6 +1,6 @@ """A2A authentication schemas.""" -from crewai.a2a.auth.client_schemes import ( +from crewai_a2a.auth.client_schemes import ( APIKeyAuth, AuthScheme, BearerTokenAuth, @@ -11,7 +11,7 @@ from crewai.a2a.auth.client_schemes import ( OAuth2ClientCredentials, TLSConfig, ) -from crewai.a2a.auth.server_schemes import ( +from crewai_a2a.auth.server_schemes import ( AuthenticatedUser, EnterpriseTokenAuth, OIDCAuth, diff --git a/lib/crewai/src/crewai/a2a/auth/client_schemes.py b/lib/crewai-a2a/src/crewai_a2a/auth/client_schemes.py similarity index 100% rename from lib/crewai/src/crewai/a2a/auth/client_schemes.py rename to lib/crewai-a2a/src/crewai_a2a/auth/client_schemes.py diff --git a/lib/crewai-a2a/src/crewai_a2a/auth/schemas.py b/lib/crewai-a2a/src/crewai_a2a/auth/schemas.py new file mode 100644 index 000000000..4372ed5fe --- /dev/null +++ b/lib/crewai-a2a/src/crewai_a2a/auth/schemas.py @@ -0,0 +1,71 @@ +"""Deprecated: Authentication schemes for A2A protocol agents. + +This module is deprecated. Import from crewai_a2a.auth instead: +- crewai_a2a.auth.ClientAuthScheme (replaces AuthScheme) +- crewai_a2a.auth.BearerTokenAuth +- crewai_a2a.auth.HTTPBasicAuth +- crewai_a2a.auth.HTTPDigestAuth +- crewai_a2a.auth.APIKeyAuth +- crewai_a2a.auth.OAuth2ClientCredentials +- crewai_a2a.auth.OAuth2AuthorizationCode +""" + +from __future__ import annotations + +from typing_extensions import deprecated + +from crewai_a2a.auth.client_schemes import ( + APIKeyAuth as _APIKeyAuth, + BearerTokenAuth as _BearerTokenAuth, + ClientAuthScheme as _ClientAuthScheme, + HTTPBasicAuth as _HTTPBasicAuth, + HTTPDigestAuth as _HTTPDigestAuth, + OAuth2AuthorizationCode as _OAuth2AuthorizationCode, + OAuth2ClientCredentials as _OAuth2ClientCredentials, +) + + +@deprecated("Use ClientAuthScheme from crewai_a2a.auth instead", category=FutureWarning) +class AuthScheme(_ClientAuthScheme): + """Deprecated: Use ClientAuthScheme from crewai_a2a.auth instead.""" + + +@deprecated("Import from crewai_a2a.auth instead", category=FutureWarning) +class BearerTokenAuth(_BearerTokenAuth): + """Deprecated: Import from crewai_a2a.auth instead.""" + + +@deprecated("Import from crewai_a2a.auth instead", category=FutureWarning) +class HTTPBasicAuth(_HTTPBasicAuth): + """Deprecated: Import from crewai_a2a.auth instead.""" + + +@deprecated("Import from crewai_a2a.auth instead", category=FutureWarning) +class HTTPDigestAuth(_HTTPDigestAuth): + """Deprecated: Import from crewai_a2a.auth instead.""" + + +@deprecated("Import from crewai_a2a.auth instead", category=FutureWarning) +class APIKeyAuth(_APIKeyAuth): + """Deprecated: Import from crewai_a2a.auth instead.""" + + +@deprecated("Import from crewai_a2a.auth instead", category=FutureWarning) +class OAuth2ClientCredentials(_OAuth2ClientCredentials): + """Deprecated: Import from crewai_a2a.auth instead.""" + + +@deprecated("Import from crewai_a2a.auth instead", category=FutureWarning) +class OAuth2AuthorizationCode(_OAuth2AuthorizationCode): + """Deprecated: Import from crewai_a2a.auth instead.""" + + +__all__ = [ + "APIKeyAuth", + "AuthScheme", + "BearerTokenAuth", + "HTTPBasicAuth", + "HTTPDigestAuth", + "OAuth2AuthorizationCode", + "OAuth2ClientCredentials", +] diff --git a/lib/crewai/src/crewai/a2a/auth/server_schemes.py b/lib/crewai-a2a/src/crewai_a2a/auth/server_schemes.py similarity index 99% rename from lib/crewai/src/crewai/a2a/auth/server_schemes.py rename to lib/crewai-a2a/src/crewai_a2a/auth/server_schemes.py index 9e8e9f6d5..50ed28904 100644 --- a/lib/crewai/src/crewai/a2a/auth/server_schemes.py +++ b/lib/crewai-a2a/src/crewai_a2a/auth/server_schemes.py @@ -42,7 +42,10 @@ logger = logging.getLogger(__name__) try: - from fastapi import HTTPException, status as http_status + from fastapi import ( # type: ignore[import-not-found] + HTTPException, + status as http_status, + ) HTTP_401_UNAUTHORIZED = http_status.HTTP_401_UNAUTHORIZED HTTP_500_INTERNAL_SERVER_ERROR = http_status.HTTP_500_INTERNAL_SERVER_ERROR diff --git a/lib/crewai/src/crewai/a2a/auth/utils.py b/lib/crewai-a2a/src/crewai_a2a/auth/utils.py similarity index 99% rename from lib/crewai/src/crewai/a2a/auth/utils.py rename to lib/crewai-a2a/src/crewai_a2a/auth/utils.py index 3e8de3e0d..d1699143e 100644 --- a/lib/crewai/src/crewai/a2a/auth/utils.py +++ b/lib/crewai-a2a/src/crewai_a2a/auth/utils.py @@ -20,7 +20,7 @@ from a2a.types import ( ) from httpx import AsyncClient, Response -from crewai.a2a.auth.client_schemes import ( +from crewai_a2a.auth.client_schemes import ( APIKeyAuth, BearerTokenAuth, ClientAuthScheme, diff --git a/lib/crewai/src/crewai/a2a/config.py b/lib/crewai-a2a/src/crewai_a2a/config.py similarity index 97% rename from lib/crewai/src/crewai/a2a/config.py rename to lib/crewai-a2a/src/crewai_a2a/config.py index 499248046..05c97164b 100644 --- a/lib/crewai/src/crewai/a2a/config.py +++ b/lib/crewai-a2a/src/crewai_a2a/config.py @@ -20,10 +20,10 @@ from pydantic import ( ) from typing_extensions import Self, deprecated -from crewai.a2a.auth.client_schemes import ClientAuthScheme -from crewai.a2a.auth.server_schemes import ServerAuthScheme -from crewai.a2a.extensions.base import ValidatedA2AExtension -from crewai.a2a.types import ProtocolVersion, TransportType, Url +from crewai_a2a.auth.client_schemes import ClientAuthScheme +from crewai_a2a.auth.server_schemes import ServerAuthScheme +from crewai_a2a.extensions.base import ValidatedA2AExtension +from crewai_a2a.types import ProtocolVersion, TransportType, Url try: @@ -36,8 +36,8 @@ try: SecurityScheme, ) - from crewai.a2a.extensions.server import ServerExtension - from crewai.a2a.updates import UpdateConfig + from crewai_a2a.extensions.server import ServerExtension + from crewai_a2a.updates import UpdateConfig except ImportError: UpdateConfig: Any = Any # type: ignore[no-redef] AgentCapabilities: Any = Any # type: ignore[no-redef] @@ -50,7 +50,7 @@ except ImportError: def _get_default_update_config() -> UpdateConfig: - from crewai.a2a.updates import StreamingConfig + from crewai_a2a.updates import StreamingConfig return StreamingConfig() @@ -360,8 +360,8 @@ class ClientTransportConfig(BaseModel): @deprecated( """ - `crewai.a2a.config.A2AConfig` is deprecated and will be removed in v2.0.0, - use `crewai.a2a.config.A2AClientConfig` or `crewai.a2a.config.A2AServerConfig` instead. + `crewai_a2a.config.A2AConfig` is deprecated and will be removed in v2.0.0, + use `crewai_a2a.config.A2AClientConfig` or `crewai_a2a.config.A2AServerConfig` instead. """, category=FutureWarning, ) diff --git a/lib/crewai/src/crewai/a2a/errors.py b/lib/crewai-a2a/src/crewai_a2a/errors.py similarity index 100% rename from lib/crewai/src/crewai/a2a/errors.py rename to lib/crewai-a2a/src/crewai_a2a/errors.py diff --git a/lib/crewai/src/crewai/a2a/extensions/__init__.py b/lib/crewai-a2a/src/crewai_a2a/extensions/__init__.py similarity index 91% rename from lib/crewai/src/crewai/a2a/extensions/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/extensions/__init__.py index b21ae10ad..ef066ca96 100644 --- a/lib/crewai/src/crewai/a2a/extensions/__init__.py +++ b/lib/crewai-a2a/src/crewai_a2a/extensions/__init__.py @@ -13,13 +13,13 @@ via the X-A2A-Extensions header. See: https://a2a-protocol.org/latest/topics/extensions/ """ -from crewai.a2a.extensions.base import ( +from crewai_a2a.extensions.base import ( A2AExtension, ConversationState, ExtensionRegistry, ValidatedA2AExtension, ) -from crewai.a2a.extensions.server import ( +from crewai_a2a.extensions.server import ( ExtensionContext, ServerExtension, ServerExtensionRegistry, diff --git a/lib/crewai/src/crewai/a2a/extensions/base.py b/lib/crewai-a2a/src/crewai_a2a/extensions/base.py similarity index 99% rename from lib/crewai/src/crewai/a2a/extensions/base.py rename to lib/crewai-a2a/src/crewai_a2a/extensions/base.py index 2d7a81a22..f3b3201b1 100644 --- a/lib/crewai/src/crewai/a2a/extensions/base.py +++ b/lib/crewai-a2a/src/crewai_a2a/extensions/base.py @@ -19,7 +19,6 @@ from pydantic import BeforeValidator if TYPE_CHECKING: from a2a.types import Message - from crewai.agent.core import Agent diff --git a/lib/crewai/src/crewai/a2a/extensions/registry.py b/lib/crewai-a2a/src/crewai_a2a/extensions/registry.py similarity index 97% rename from lib/crewai/src/crewai/a2a/extensions/registry.py rename to lib/crewai-a2a/src/crewai_a2a/extensions/registry.py index 4d195961b..9403de2a0 100644 --- a/lib/crewai/src/crewai/a2a/extensions/registry.py +++ b/lib/crewai-a2a/src/crewai_a2a/extensions/registry.py @@ -18,8 +18,8 @@ from a2a.extensions.common import ( ) from a2a.types import AgentCard, AgentExtension -from crewai.a2a.config import A2AClientConfig, A2AConfig -from crewai.a2a.extensions.base import ExtensionRegistry +from crewai_a2a.config import A2AClientConfig, A2AConfig +from crewai_a2a.extensions.base import ExtensionRegistry def get_extensions_from_config( diff --git a/lib/crewai/src/crewai/a2a/extensions/server.py b/lib/crewai-a2a/src/crewai_a2a/extensions/server.py similarity index 100% rename from lib/crewai/src/crewai/a2a/extensions/server.py rename to lib/crewai-a2a/src/crewai_a2a/extensions/server.py diff --git a/lib/crewai/src/crewai/a2a/task_helpers.py b/lib/crewai-a2a/src/crewai_a2a/task_helpers.py similarity index 99% rename from lib/crewai/src/crewai/a2a/task_helpers.py rename to lib/crewai-a2a/src/crewai_a2a/task_helpers.py index b4a758656..43ea61be2 100644 --- a/lib/crewai/src/crewai/a2a/task_helpers.py +++ b/lib/crewai-a2a/src/crewai_a2a/task_helpers.py @@ -18,13 +18,12 @@ from a2a.types import ( TaskStatusUpdateEvent, TextPart, ) -from typing_extensions import NotRequired - from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AConnectionErrorEvent, A2AResponseReceivedEvent, ) +from typing_extensions import NotRequired if TYPE_CHECKING: diff --git a/lib/crewai/src/crewai/a2a/templates.py b/lib/crewai-a2a/src/crewai_a2a/templates.py similarity index 100% rename from lib/crewai/src/crewai/a2a/templates.py rename to lib/crewai-a2a/src/crewai_a2a/templates.py diff --git a/lib/crewai/src/crewai/a2a/types.py b/lib/crewai-a2a/src/crewai_a2a/types.py similarity index 98% rename from lib/crewai/src/crewai/a2a/types.py rename to lib/crewai-a2a/src/crewai_a2a/types.py index 5a4a7672a..8826f67d6 100644 --- a/lib/crewai/src/crewai/a2a/types.py +++ b/lib/crewai-a2a/src/crewai_a2a/types.py @@ -16,7 +16,7 @@ from typing_extensions import NotRequired try: - from crewai.a2a.updates import ( + from crewai_a2a.updates import ( PollingConfig, PollingHandler, PushNotificationConfig, diff --git a/lib/crewai/src/crewai/a2a/updates/__init__.py b/lib/crewai-a2a/src/crewai_a2a/updates/__init__.py similarity index 62% rename from lib/crewai/src/crewai/a2a/updates/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/updates/__init__.py index 953eb48c3..5c9f98c05 100644 --- a/lib/crewai/src/crewai/a2a/updates/__init__.py +++ b/lib/crewai-a2a/src/crewai_a2a/updates/__init__.py @@ -1,6 +1,6 @@ """A2A update mechanism configuration types.""" -from crewai.a2a.updates.base import ( +from crewai_a2a.updates.base import ( BaseHandlerKwargs, PollingHandlerKwargs, PushNotificationHandlerKwargs, @@ -8,12 +8,12 @@ from crewai.a2a.updates.base import ( StreamingHandlerKwargs, UpdateHandler, ) -from crewai.a2a.updates.polling.config import PollingConfig -from crewai.a2a.updates.polling.handler import PollingHandler -from crewai.a2a.updates.push_notifications.config import PushNotificationConfig -from crewai.a2a.updates.push_notifications.handler import PushNotificationHandler -from crewai.a2a.updates.streaming.config import StreamingConfig -from crewai.a2a.updates.streaming.handler import StreamingHandler +from crewai_a2a.updates.polling.config import PollingConfig +from crewai_a2a.updates.polling.handler import PollingHandler +from crewai_a2a.updates.push_notifications.config import PushNotificationConfig +from crewai_a2a.updates.push_notifications.handler import PushNotificationHandler +from crewai_a2a.updates.streaming.config import StreamingConfig +from crewai_a2a.updates.streaming.handler import StreamingHandler UpdateConfig = PollingConfig | StreamingConfig | PushNotificationConfig diff --git a/lib/crewai/src/crewai/a2a/updates/base.py b/lib/crewai-a2a/src/crewai_a2a/updates/base.py similarity index 97% rename from lib/crewai/src/crewai/a2a/updates/base.py rename to lib/crewai-a2a/src/crewai_a2a/updates/base.py index 8a6a53aa3..ea44ebc10 100644 --- a/lib/crewai/src/crewai/a2a/updates/base.py +++ b/lib/crewai-a2a/src/crewai_a2a/updates/base.py @@ -28,8 +28,8 @@ if TYPE_CHECKING: from a2a.client import Client from a2a.types import AgentCard, Message, Task - from crewai.a2a.task_helpers import TaskStateResult - from crewai.a2a.updates.push_notifications.config import PushNotificationConfig + from crewai_a2a.task_helpers import TaskStateResult + from crewai_a2a.updates.push_notifications.config import PushNotificationConfig class BaseHandlerKwargs(TypedDict, total=False): diff --git a/lib/crewai/src/crewai/a2a/updates/polling/__init__.py b/lib/crewai-a2a/src/crewai_a2a/updates/polling/__init__.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/polling/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/updates/polling/__init__.py diff --git a/lib/crewai/src/crewai/a2a/updates/polling/config.py b/lib/crewai-a2a/src/crewai_a2a/updates/polling/config.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/polling/config.py rename to lib/crewai-a2a/src/crewai_a2a/updates/polling/config.py diff --git a/lib/crewai/src/crewai/a2a/updates/polling/handler.py b/lib/crewai-a2a/src/crewai_a2a/updates/polling/handler.py similarity index 98% rename from lib/crewai/src/crewai/a2a/updates/polling/handler.py rename to lib/crewai-a2a/src/crewai_a2a/updates/polling/handler.py index dad5bca57..30fbf90e5 100644 --- a/lib/crewai/src/crewai/a2a/updates/polling/handler.py +++ b/lib/crewai-a2a/src/crewai_a2a/updates/polling/handler.py @@ -18,17 +18,6 @@ from a2a.types import ( TaskState, TextPart, ) -from typing_extensions import Unpack - -from crewai.a2a.errors import A2APollingTimeoutError -from crewai.a2a.task_helpers import ( - ACTIONABLE_STATES, - TERMINAL_STATES, - TaskStateResult, - process_task_state, - send_message_and_get_task_id, -) -from crewai.a2a.updates.base import PollingHandlerKwargs from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AConnectionErrorEvent, @@ -36,6 +25,17 @@ from crewai.events.types.a2a_events import ( A2APollingStatusEvent, A2AResponseReceivedEvent, ) +from typing_extensions import Unpack + +from crewai_a2a.errors import A2APollingTimeoutError +from crewai_a2a.task_helpers import ( + ACTIONABLE_STATES, + TERMINAL_STATES, + TaskStateResult, + process_task_state, + send_message_and_get_task_id, +) +from crewai_a2a.updates.base import PollingHandlerKwargs if TYPE_CHECKING: diff --git a/lib/crewai/src/crewai/a2a/updates/push_notifications/__init__.py b/lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/__init__.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/push_notifications/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/__init__.py diff --git a/lib/crewai/src/crewai/a2a/updates/push_notifications/config.py b/lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/config.py similarity index 94% rename from lib/crewai/src/crewai/a2a/updates/push_notifications/config.py rename to lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/config.py index de81dbe80..d0f486cf0 100644 --- a/lib/crewai/src/crewai/a2a/updates/push_notifications/config.py +++ b/lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/config.py @@ -7,8 +7,8 @@ from typing import Annotated from a2a.types import PushNotificationAuthenticationInfo from pydantic import AnyHttpUrl, BaseModel, BeforeValidator, Field -from crewai.a2a.updates.base import PushNotificationResultStore -from crewai.a2a.updates.push_notifications.signature import WebhookSignatureConfig +from crewai_a2a.updates.base import PushNotificationResultStore +from crewai_a2a.updates.push_notifications.signature import WebhookSignatureConfig def _coerce_signature( diff --git a/lib/crewai/src/crewai/a2a/updates/push_notifications/handler.py b/lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/handler.py similarity index 99% rename from lib/crewai/src/crewai/a2a/updates/push_notifications/handler.py rename to lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/handler.py index 783bf6483..19add538b 100644 --- a/lib/crewai/src/crewai/a2a/updates/push_notifications/handler.py +++ b/lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/handler.py @@ -16,19 +16,6 @@ from a2a.types import ( TaskState, TextPart, ) -from typing_extensions import Unpack - -from crewai.a2a.task_helpers import ( - TaskStateResult, - process_task_state, - send_message_and_get_task_id, -) -from crewai.a2a.updates.base import ( - CommonParams, - PushNotificationHandlerKwargs, - PushNotificationResultStore, - extract_common_params, -) from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AConnectionErrorEvent, @@ -36,6 +23,19 @@ from crewai.events.types.a2a_events import ( A2APushNotificationTimeoutEvent, A2AResponseReceivedEvent, ) +from typing_extensions import Unpack + +from crewai_a2a.task_helpers import ( + TaskStateResult, + process_task_state, + send_message_and_get_task_id, +) +from crewai_a2a.updates.base import ( + CommonParams, + PushNotificationHandlerKwargs, + PushNotificationResultStore, + extract_common_params, +) if TYPE_CHECKING: diff --git a/lib/crewai/src/crewai/a2a/updates/push_notifications/signature.py b/lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/signature.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/push_notifications/signature.py rename to lib/crewai-a2a/src/crewai_a2a/updates/push_notifications/signature.py diff --git a/lib/crewai/src/crewai/a2a/updates/streaming/__init__.py b/lib/crewai-a2a/src/crewai_a2a/updates/streaming/__init__.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/streaming/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/updates/streaming/__init__.py diff --git a/lib/crewai/src/crewai/a2a/updates/streaming/config.py b/lib/crewai-a2a/src/crewai_a2a/updates/streaming/config.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/streaming/config.py rename to lib/crewai-a2a/src/crewai_a2a/updates/streaming/config.py diff --git a/lib/crewai/src/crewai/a2a/updates/streaming/handler.py b/lib/crewai-a2a/src/crewai_a2a/updates/streaming/handler.py similarity index 99% rename from lib/crewai/src/crewai/a2a/updates/streaming/handler.py rename to lib/crewai-a2a/src/crewai_a2a/updates/streaming/handler.py index 9b0c21d12..fc5abcbcd 100644 --- a/lib/crewai/src/crewai/a2a/updates/streaming/handler.py +++ b/lib/crewai-a2a/src/crewai_a2a/updates/streaming/handler.py @@ -22,18 +22,6 @@ from a2a.types import ( TaskStatusUpdateEvent, TextPart, ) -from typing_extensions import Unpack - -from crewai.a2a.task_helpers import ( - ACTIONABLE_STATES, - TERMINAL_STATES, - TaskStateResult, - process_task_state, -) -from crewai.a2a.updates.base import StreamingHandlerKwargs, extract_common_params -from crewai.a2a.updates.streaming.params import ( - process_status_update, -) from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AArtifactReceivedEvent, @@ -42,6 +30,18 @@ from crewai.events.types.a2a_events import ( A2AStreamingChunkEvent, A2AStreamingStartedEvent, ) +from typing_extensions import Unpack + +from crewai_a2a.task_helpers import ( + ACTIONABLE_STATES, + TERMINAL_STATES, + TaskStateResult, + process_task_state, +) +from crewai_a2a.updates.base import StreamingHandlerKwargs, extract_common_params +from crewai_a2a.updates.streaming.params import ( + process_status_update, +) logger = logging.getLogger(__name__) diff --git a/lib/crewai/src/crewai/a2a/updates/streaming/params.py b/lib/crewai-a2a/src/crewai_a2a/updates/streaming/params.py similarity index 100% rename from lib/crewai/src/crewai/a2a/updates/streaming/params.py rename to lib/crewai-a2a/src/crewai_a2a/updates/streaming/params.py diff --git a/lib/crewai/src/crewai/a2a/utils/__init__.py b/lib/crewai-a2a/src/crewai_a2a/utils/__init__.py similarity index 100% rename from lib/crewai/src/crewai/a2a/utils/__init__.py rename to lib/crewai-a2a/src/crewai_a2a/utils/__init__.py diff --git a/lib/crewai/src/crewai/a2a/utils/agent_card.py b/lib/crewai-a2a/src/crewai_a2a/utils/agent_card.py similarity index 98% rename from lib/crewai/src/crewai/a2a/utils/agent_card.py rename to lib/crewai-a2a/src/crewai_a2a/utils/agent_card.py index df5886988..55a1878ea 100644 --- a/lib/crewai/src/crewai/a2a/utils/agent_card.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/agent_card.py @@ -16,15 +16,6 @@ from a2a.client.errors import A2AClientHTTPError from a2a.types import AgentCapabilities, AgentCard, AgentSkill from aiocache import cached # type: ignore[import-untyped] from aiocache.serializers import PickleSerializer # type: ignore[import-untyped] -import httpx - -from crewai.a2a.auth.client_schemes import APIKeyAuth, HTTPDigestAuth -from crewai.a2a.auth.utils import ( - _auth_store, - configure_auth_client, - retry_on_401, -) -from crewai.a2a.config import A2AServerConfig from crewai.crew import Crew from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( @@ -32,13 +23,23 @@ from crewai.events.types.a2a_events import ( A2AAuthenticationFailedEvent, A2AConnectionErrorEvent, ) +import httpx + +from crewai_a2a.auth.client_schemes import APIKeyAuth, HTTPDigestAuth +from crewai_a2a.auth.utils import ( + _auth_store, + configure_auth_client, + retry_on_401, +) +from crewai_a2a.config import A2AServerConfig if TYPE_CHECKING: - from crewai.a2a.auth.client_schemes import ClientAuthScheme from crewai.agent import Agent from crewai.task import Task + from crewai_a2a.auth.client_schemes import ClientAuthScheme + def _get_tls_verify(auth: ClientAuthScheme | None) -> ssl.SSLContext | bool | str: """Get TLS verify parameter from auth scheme. @@ -495,7 +496,7 @@ def _agent_to_agent_card(agent: Agent, url: str) -> AgentCard: Returns: AgentCard describing the agent's capabilities. """ - from crewai.a2a.utils.agent_card_signing import sign_agent_card + from crewai_a2a.utils.agent_card_signing import sign_agent_card server_config = _get_server_config(agent) or A2AServerConfig() @@ -529,7 +530,7 @@ def _agent_to_agent_card(agent: Agent, url: str) -> AgentCard: capabilities = server_config.capabilities if server_config.server_extensions: - from crewai.a2a.extensions.server import ServerExtensionRegistry + from crewai_a2a.extensions.server import ServerExtensionRegistry registry = ServerExtensionRegistry(server_config.server_extensions) ext_list = registry.get_agent_extensions() diff --git a/lib/crewai/src/crewai/a2a/utils/agent_card_signing.py b/lib/crewai-a2a/src/crewai_a2a/utils/agent_card_signing.py similarity index 99% rename from lib/crewai/src/crewai/a2a/utils/agent_card_signing.py rename to lib/crewai-a2a/src/crewai_a2a/utils/agent_card_signing.py index d869020af..9dfe0ab89 100644 --- a/lib/crewai/src/crewai/a2a/utils/agent_card_signing.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/agent_card_signing.py @@ -5,7 +5,7 @@ JSON Web Signatures (JWS) as per RFC 7515. Signed agent cards allow clients to verify the authenticity and integrity of agent card information. Example: - >>> from crewai.a2a.utils.agent_card_signing import sign_agent_card + >>> from crewai_a2a.utils.agent_card_signing import sign_agent_card >>> signature = sign_agent_card(agent_card, private_key_pem, key_id="key-1") >>> card_with_sig = card.model_copy(update={"signatures": [signature]}) """ diff --git a/lib/crewai/src/crewai/a2a/utils/content_type.py b/lib/crewai-a2a/src/crewai_a2a/utils/content_type.py similarity index 99% rename from lib/crewai/src/crewai/a2a/utils/content_type.py rename to lib/crewai-a2a/src/crewai_a2a/utils/content_type.py index f063fef19..b752fb88b 100644 --- a/lib/crewai/src/crewai/a2a/utils/content_type.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/content_type.py @@ -10,7 +10,6 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Annotated, Final, Literal, cast from a2a.types import Part - from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import A2AContentTypeNegotiatedEvent diff --git a/lib/crewai/src/crewai/a2a/utils/delegation.py b/lib/crewai-a2a/src/crewai_a2a/utils/delegation.py similarity index 98% rename from lib/crewai/src/crewai/a2a/utils/delegation.py rename to lib/crewai-a2a/src/crewai_a2a/utils/delegation.py index c634aab1d..32b1611a6 100644 --- a/lib/crewai/src/crewai/a2a/utils/delegation.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/delegation.py @@ -23,48 +23,6 @@ from a2a.types import ( Role, TextPart, ) -import httpx -from pydantic import BaseModel - -from crewai.a2a.auth.client_schemes import APIKeyAuth, HTTPDigestAuth -from crewai.a2a.auth.utils import ( - _auth_store, - configure_auth_client, - validate_auth_against_agent_card, -) -from crewai.a2a.config import ClientTransportConfig, GRPCClientConfig -from crewai.a2a.extensions.registry import ( - ExtensionsMiddleware, - validate_required_extensions, -) -from crewai.a2a.task_helpers import TaskStateResult -from crewai.a2a.types import ( - HANDLER_REGISTRY, - HandlerType, - PartsDict, - PartsMetadataDict, - TransportType, -) -from crewai.a2a.updates import ( - PollingConfig, - PushNotificationConfig, - StreamingHandler, - UpdateConfig, -) -from crewai.a2a.utils.agent_card import ( - _afetch_agent_card_cached, - _get_tls_verify, - _prepare_auth_headers, -) -from crewai.a2a.utils.content_type import ( - DEFAULT_CLIENT_OUTPUT_MODES, - negotiate_content_types, -) -from crewai.a2a.utils.transport import ( - NegotiatedTransport, - TransportNegotiationError, - negotiate_transport, -) from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AConversationStartedEvent, @@ -72,6 +30,48 @@ from crewai.events.types.a2a_events import ( A2ADelegationStartedEvent, A2AMessageSentEvent, ) +import httpx +from pydantic import BaseModel + +from crewai_a2a.auth.client_schemes import APIKeyAuth, HTTPDigestAuth +from crewai_a2a.auth.utils import ( + _auth_store, + configure_auth_client, + validate_auth_against_agent_card, +) +from crewai_a2a.config import ClientTransportConfig, GRPCClientConfig +from crewai_a2a.extensions.registry import ( + ExtensionsMiddleware, + validate_required_extensions, +) +from crewai_a2a.task_helpers import TaskStateResult +from crewai_a2a.types import ( + HANDLER_REGISTRY, + HandlerType, + PartsDict, + PartsMetadataDict, + TransportType, +) +from crewai_a2a.updates import ( + PollingConfig, + PushNotificationConfig, + StreamingHandler, + UpdateConfig, +) +from crewai_a2a.utils.agent_card import ( + _afetch_agent_card_cached, + _get_tls_verify, + _prepare_auth_headers, +) +from crewai_a2a.utils.content_type import ( + DEFAULT_CLIENT_OUTPUT_MODES, + negotiate_content_types, +) +from crewai_a2a.utils.transport import ( + NegotiatedTransport, + TransportNegotiationError, + negotiate_transport, +) logger = logging.getLogger(__name__) @@ -80,7 +80,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: from a2a.types import Message - from crewai.a2a.auth.client_schemes import ClientAuthScheme + from crewai_a2a.auth.client_schemes import ClientAuthScheme _DEFAULT_TRANSPORT: Final[TransportType] = "JSONRPC" @@ -771,7 +771,7 @@ def _create_grpc_channel_factory( auth_metadata: list[tuple[str, str]] = [] if auth is not None: - from crewai.a2a.auth.client_schemes import ( + from crewai_a2a.auth.client_schemes import ( APIKeyAuth, BearerTokenAuth, HTTPBasicAuth, diff --git a/lib/crewai/src/crewai/a2a/utils/logging.py b/lib/crewai-a2a/src/crewai_a2a/utils/logging.py similarity index 98% rename from lib/crewai/src/crewai/a2a/utils/logging.py rename to lib/crewai-a2a/src/crewai_a2a/utils/logging.py index 585d1d8f3..f7470cafd 100644 --- a/lib/crewai/src/crewai/a2a/utils/logging.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/logging.py @@ -103,7 +103,7 @@ class LogContext: _log_context.reset(self._token) -def configure_json_logging(logger_name: str = "crewai.a2a") -> None: +def configure_json_logging(logger_name: str = "crewai_a2a") -> None: """Configure JSON logging for the A2A module. Args: diff --git a/lib/crewai/src/crewai/a2a/utils/response_model.py b/lib/crewai-a2a/src/crewai_a2a/utils/response_model.py similarity index 98% rename from lib/crewai/src/crewai/a2a/utils/response_model.py rename to lib/crewai-a2a/src/crewai_a2a/utils/response_model.py index 4e65ef2b7..6f4b5f057 100644 --- a/lib/crewai/src/crewai/a2a/utils/response_model.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/response_model.py @@ -4,10 +4,10 @@ from __future__ import annotations from typing import TypeAlias +from crewai.types.utils import create_literals_from_strings from pydantic import BaseModel, Field, create_model -from crewai.a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig -from crewai.types.utils import create_literals_from_strings +from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig A2AConfigTypes: TypeAlias = A2AConfig | A2AServerConfig | A2AClientConfig diff --git a/lib/crewai/src/crewai/a2a/utils/task.py b/lib/crewai-a2a/src/crewai_a2a/utils/task.py similarity index 98% rename from lib/crewai/src/crewai/a2a/utils/task.py rename to lib/crewai-a2a/src/crewai_a2a/utils/task.py index d73556875..8aa2484d9 100644 --- a/lib/crewai/src/crewai/a2a/utils/task.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/task.py @@ -37,10 +37,6 @@ from a2a.utils import ( ) from a2a.utils.errors import ServerError from aiocache import SimpleMemoryCache, caches # type: ignore[import-untyped] -from pydantic import BaseModel - -from crewai.a2a.utils.agent_card import _get_server_config -from crewai.a2a.utils.content_type import validate_message_parts from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AServerTaskCanceledEvent, @@ -50,12 +46,17 @@ from crewai.events.types.a2a_events import ( ) from crewai.task import Task from crewai.utilities.pydantic_schema_utils import create_model_from_schema +from pydantic import BaseModel + +from crewai_a2a.utils.agent_card import _get_server_config +from crewai_a2a.utils.content_type import validate_message_parts if TYPE_CHECKING: - from crewai.a2a.extensions.server import ExtensionContext, ServerExtensionRegistry from crewai.agent import Agent + from crewai_a2a.extensions.server import ExtensionContext, ServerExtensionRegistry + logger = logging.getLogger(__name__) diff --git a/lib/crewai/src/crewai/a2a/utils/transport.py b/lib/crewai-a2a/src/crewai_a2a/utils/transport.py similarity index 99% rename from lib/crewai/src/crewai/a2a/utils/transport.py rename to lib/crewai-a2a/src/crewai_a2a/utils/transport.py index cc57ba20c..d4667e1de 100644 --- a/lib/crewai/src/crewai/a2a/utils/transport.py +++ b/lib/crewai-a2a/src/crewai_a2a/utils/transport.py @@ -11,7 +11,6 @@ from dataclasses import dataclass from typing import Final, Literal from a2a.types import AgentCard, AgentInterface - from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import A2ATransportNegotiatedEvent diff --git a/lib/crewai/src/crewai/a2a/wrapper.py b/lib/crewai-a2a/src/crewai_a2a/wrapper.py similarity index 99% rename from lib/crewai/src/crewai/a2a/wrapper.py rename to lib/crewai-a2a/src/crewai_a2a/wrapper.py index 6f85951a1..e4cbd559f 100644 --- a/lib/crewai/src/crewai/a2a/wrapper.py +++ b/lib/crewai-a2a/src/crewai_a2a/wrapper.py @@ -15,33 +15,6 @@ from types import MethodType from typing import TYPE_CHECKING, Any, NamedTuple from a2a.types import Role, TaskState -from pydantic import BaseModel, ValidationError - -from crewai.a2a.config import A2AClientConfig, A2AConfig -from crewai.a2a.extensions.base import ( - A2AExtension, - ConversationState, - ExtensionRegistry, -) -from crewai.a2a.task_helpers import TaskStateResult -from crewai.a2a.templates import ( - AVAILABLE_AGENTS_TEMPLATE, - CONVERSATION_TURN_INFO_TEMPLATE, - PREVIOUS_A2A_CONVERSATION_TEMPLATE, - REMOTE_AGENT_RESPONSE_NOTICE, - UNAVAILABLE_AGENTS_NOTICE_TEMPLATE, -) -from crewai.a2a.types import AgentResponseProtocol -from crewai.a2a.utils.agent_card import ( - afetch_agent_card, - fetch_agent_card, - inject_a2a_server_methods, -) -from crewai.a2a.utils.delegation import ( - aexecute_a2a_delegation, - execute_a2a_delegation, -) -from crewai.a2a.utils.response_model import get_a2a_agents_and_response_model from crewai.events.event_bus import crewai_event_bus from crewai.events.types.a2a_events import ( A2AConversationCompletedEvent, @@ -49,11 +22,37 @@ from crewai.events.types.a2a_events import ( ) from crewai.lite_agent_output import LiteAgentOutput from crewai.task import Task +from pydantic import BaseModel, ValidationError + +from crewai_a2a.config import A2AClientConfig, A2AConfig +from crewai_a2a.extensions.base import ( + A2AExtension, + ConversationState, + ExtensionRegistry, +) +from crewai_a2a.task_helpers import TaskStateResult +from crewai_a2a.templates import ( + AVAILABLE_AGENTS_TEMPLATE, + CONVERSATION_TURN_INFO_TEMPLATE, + PREVIOUS_A2A_CONVERSATION_TEMPLATE, + REMOTE_AGENT_RESPONSE_NOTICE, + UNAVAILABLE_AGENTS_NOTICE_TEMPLATE, +) +from crewai_a2a.types import AgentResponseProtocol +from crewai_a2a.utils.agent_card import ( + afetch_agent_card, + fetch_agent_card, + inject_a2a_server_methods, +) +from crewai_a2a.utils.delegation import ( + aexecute_a2a_delegation, + execute_a2a_delegation, +) +from crewai_a2a.utils.response_model import get_a2a_agents_and_response_model if TYPE_CHECKING: from a2a.types import AgentCard, Message - from crewai.agent.core import Agent from crewai.tools.base_tool import BaseTool diff --git a/lib/crewai/pyproject.toml b/lib/crewai/pyproject.toml index 705cdcb6f..a026f505d 100644 --- a/lib/crewai/pyproject.toml +++ b/lib/crewai/pyproject.toml @@ -98,10 +98,7 @@ anthropic = [ "anthropic~=0.73.0", ] a2a = [ - "a2a-sdk~=0.3.10", - "httpx-auth~=0.23.1", - "httpx-sse~=0.4.0", - "aiocache[redis,memcached]~=0.12.3", + "crewai-a2a", ] file-processing = [ "crewai-files", @@ -136,6 +133,7 @@ torchvision = [ { index = "pytorch", marker = "python_version < '3.13'" }, ] crewai-files = { workspace = true } +crewai-a2a = { workspace = true } [build-system] diff --git a/lib/crewai/src/crewai/__init__.py b/lib/crewai/src/crewai/__init__.py index 2ebfbf99b..ccc53c08b 100644 --- a/lib/crewai/src/crewai/__init__.py +++ b/lib/crewai/src/crewai/__init__.py @@ -4,7 +4,7 @@ from typing import Any import urllib.request import warnings -from pydantic import PydanticUserError +from pydantic import PydanticUndefinedAnnotation, PydanticUserError from crewai.agent.core import Agent from crewai.agent.planning_config import PlanningConfig @@ -119,11 +119,11 @@ try: "ToolResult": _ToolResult, }, ) -except (ImportError, PydanticUserError): +except (ImportError, PydanticUserError, PydanticUndefinedAnnotation): import logging as _logging - _logging.getLogger(__name__).warning( - "AgentExecutor.model_rebuild() failed; forward refs may be unresolved.", + _logging.getLogger(__name__).debug( + "AgentExecutor.model_rebuild() deferred; forward refs may be unresolved.", exc_info=True, ) diff --git a/lib/crewai/src/crewai/a2a/__init__.py b/lib/crewai/src/crewai/a2a/__init__.py deleted file mode 100644 index 634f77708..000000000 --- a/lib/crewai/src/crewai/a2a/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Agent-to-Agent (A2A) protocol communication module for CrewAI.""" - -from crewai.a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig - - -__all__ = [ - "A2AClientConfig", - "A2AConfig", - "A2AServerConfig", -] diff --git a/lib/crewai/src/crewai/a2a/auth/schemas.py b/lib/crewai/src/crewai/a2a/auth/schemas.py deleted file mode 100644 index 4c8f3c0d7..000000000 --- a/lib/crewai/src/crewai/a2a/auth/schemas.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Deprecated: Authentication schemes for A2A protocol agents. - -This module is deprecated. Import from crewai.a2a.auth instead: -- crewai.a2a.auth.ClientAuthScheme (replaces AuthScheme) -- crewai.a2a.auth.BearerTokenAuth -- crewai.a2a.auth.HTTPBasicAuth -- crewai.a2a.auth.HTTPDigestAuth -- crewai.a2a.auth.APIKeyAuth -- crewai.a2a.auth.OAuth2ClientCredentials -- crewai.a2a.auth.OAuth2AuthorizationCode -""" - -from __future__ import annotations - -from typing_extensions import deprecated - -from crewai.a2a.auth.client_schemes import ( - APIKeyAuth as _APIKeyAuth, - BearerTokenAuth as _BearerTokenAuth, - ClientAuthScheme as _ClientAuthScheme, - HTTPBasicAuth as _HTTPBasicAuth, - HTTPDigestAuth as _HTTPDigestAuth, - OAuth2AuthorizationCode as _OAuth2AuthorizationCode, - OAuth2ClientCredentials as _OAuth2ClientCredentials, -) - - -@deprecated("Use ClientAuthScheme from crewai.a2a.auth instead", category=FutureWarning) -class AuthScheme(_ClientAuthScheme): - """Deprecated: Use ClientAuthScheme from crewai.a2a.auth instead.""" - - -@deprecated("Import from crewai.a2a.auth instead", category=FutureWarning) -class BearerTokenAuth(_BearerTokenAuth): - """Deprecated: Import from crewai.a2a.auth instead.""" - - -@deprecated("Import from crewai.a2a.auth instead", category=FutureWarning) -class HTTPBasicAuth(_HTTPBasicAuth): - """Deprecated: Import from crewai.a2a.auth instead.""" - - -@deprecated("Import from crewai.a2a.auth instead", category=FutureWarning) -class HTTPDigestAuth(_HTTPDigestAuth): - """Deprecated: Import from crewai.a2a.auth instead.""" - - -@deprecated("Import from crewai.a2a.auth instead", category=FutureWarning) -class APIKeyAuth(_APIKeyAuth): - """Deprecated: Import from crewai.a2a.auth instead.""" - - -@deprecated("Import from crewai.a2a.auth instead", category=FutureWarning) -class OAuth2ClientCredentials(_OAuth2ClientCredentials): - """Deprecated: Import from crewai.a2a.auth instead.""" - - -@deprecated("Import from crewai.a2a.auth instead", category=FutureWarning) -class OAuth2AuthorizationCode(_OAuth2AuthorizationCode): - """Deprecated: Import from crewai.a2a.auth instead.""" - - -__all__ = [ - "APIKeyAuth", - "AuthScheme", - "BearerTokenAuth", - "HTTPBasicAuth", - "HTTPDigestAuth", - "OAuth2AuthorizationCode", - "OAuth2ClientCredentials", -] diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index e125dd7d4..1360c93cc 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -102,16 +102,16 @@ from crewai.utilities.training_handler import CrewTrainingHandler try: - from crewai.a2a.types import AgentResponseProtocol + from crewai_a2a.types import AgentResponseProtocol except ImportError: AgentResponseProtocol = None # type: ignore[assignment, misc] if TYPE_CHECKING: + from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig from crewai_files import FileInput from crewai_tools import CodeInterpreterTool - from crewai.a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig from crewai.agents.agent_builder.base_agent import PlatformAppOrAction from crewai.task import Task from crewai.tools.base_tool import BaseTool @@ -1790,7 +1790,7 @@ class Agent(BaseAgent): try: - from crewai.a2a.config import ( + from crewai_a2a.config import ( A2AClientConfig as _A2AClientConfig, A2AConfig as _A2AConfig, A2AServerConfig as _A2AServerConfig, diff --git a/lib/crewai/src/crewai/agent/internal/meta.py b/lib/crewai/src/crewai/agent/internal/meta.py index 7ecea9b35..1a7892f9f 100644 --- a/lib/crewai/src/crewai/agent/internal/meta.py +++ b/lib/crewai/src/crewai/agent/internal/meta.py @@ -58,10 +58,10 @@ class AgentMeta(ModelMetaclass): a2a_value = getattr(self, "a2a", None) if a2a_value is not None: - from crewai.a2a.extensions.registry import ( + from crewai_a2a.extensions.registry import ( create_extension_registry_from_config, ) - from crewai.a2a.wrapper import wrap_agent_with_a2a_instance + from crewai_a2a.wrapper import wrap_agent_with_a2a_instance extension_registry = create_extension_registry_from_config( a2a_value diff --git a/lib/crewai/src/crewai/lite_agent.py b/lib/crewai/src/crewai/lite_agent.py index bbb464010..93701d532 100644 --- a/lib/crewai/src/crewai/lite_agent.py +++ b/lib/crewai/src/crewai/lite_agent.py @@ -30,10 +30,9 @@ from typing_extensions import Self if TYPE_CHECKING: + from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig from crewai_files import FileInput - from crewai.a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig - from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess from crewai.agents.cache.cache_handler import CacheHandler @@ -119,8 +118,9 @@ def _kickoff_with_a2a_support( Returns: LiteAgentOutput from either local execution or A2A delegation. """ - from crewai.a2a.utils.response_model import get_a2a_agents_and_response_model - from crewai.a2a.wrapper import _execute_task_with_a2a + from crewai_a2a.utils.response_model import get_a2a_agents_and_response_model + from crewai_a2a.wrapper import _execute_task_with_a2a + from crewai.task import Task a2a_agents, agent_response_model = get_a2a_agents_and_response_model(agent.a2a) @@ -318,11 +318,11 @@ class LiteAgent(FlowTrackable, BaseModel): def setup_a2a_support(self) -> Self: """Setup A2A extensions and server methods if a2a config exists.""" if self.a2a: - from crewai.a2a.config import A2AClientConfig, A2AConfig - from crewai.a2a.extensions.registry import ( + from crewai_a2a.config import A2AClientConfig, A2AConfig + from crewai_a2a.extensions.registry import ( create_extension_registry_from_config, ) - from crewai.a2a.utils.agent_card import inject_a2a_server_methods + from crewai_a2a.utils.agent_card import inject_a2a_server_methods configs = self.a2a if isinstance(self.a2a, list) else [self.a2a] client_configs = [ @@ -994,7 +994,7 @@ class LiteAgent(FlowTrackable, BaseModel): try: - from crewai.a2a.config import ( + from crewai_a2a.config import ( A2AClientConfig as _A2AClientConfig, A2AConfig as _A2AConfig, A2AServerConfig as _A2AServerConfig, diff --git a/lib/crewai/tests/a2a/test_a2a_integration.py b/lib/crewai/tests/a2a/test_a2a_integration.py index 9950ee0a2..ca48a0bbb 100644 --- a/lib/crewai/tests/a2a/test_a2a_integration.py +++ b/lib/crewai/tests/a2a/test_a2a_integration.py @@ -9,8 +9,8 @@ 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 +from crewai_a2a.updates.polling.handler import PollingHandler +from crewai_a2a.updates.streaming.handler import StreamingHandler A2A_TEST_ENDPOINT = os.getenv("A2A_TEST_ENDPOINT", "http://localhost:9999") @@ -184,8 +184,8 @@ class TestA2APushNotificationHandler: 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.config import PushNotificationConfig + from crewai_a2a.updates.push_notifications.handler import PushNotificationHandler completed_task = Task( id="task-123", @@ -248,8 +248,8 @@ class TestA2APushNotificationHandler: 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.config import PushNotificationConfig + from crewai_a2a.updates.push_notifications.handler import PushNotificationHandler mock_store = MagicMock() mock_store.wait_for_result = AsyncMock(return_value=None) @@ -303,7 +303,7 @@ 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/a2a/utils/test_agent_card.py b/lib/crewai/tests/a2a/utils/test_agent_card.py index fb96710a7..84dcbca73 100644 --- a/lib/crewai/tests/a2a/utils/test_agent_card.py +++ b/lib/crewai/tests/a2a/utils/test_agent_card.py @@ -5,8 +5,8 @@ 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 +from crewai_a2a.config import A2AClientConfig, A2AServerConfig +from crewai_a2a.utils.agent_card import inject_a2a_server_methods class TestInjectA2AServerMethods: diff --git a/lib/crewai/tests/a2a/utils/test_task.py b/lib/crewai/tests/a2a/utils/test_task.py index 781827ac8..04394f0d5 100644 --- a/lib/crewai/tests/a2a/utils/test_task.py +++ b/lib/crewai/tests/a2a/utils/test_task.py @@ -12,7 +12,7 @@ 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 +from crewai_a2a.utils.task import cancel, cancellable, execute @pytest.fixture @@ -156,8 +156,8 @@ class TestExecute: ) -> None: """Execute completes successfully and enqueues completed task.""" with ( - patch("crewai.a2a.utils.task.Task", return_value=mock_task), - patch("crewai.a2a.utils.task.crewai_event_bus") as mock_bus, + patch("crewai_a2a.utils.task.Task", return_value=mock_task), + patch("crewai_a2a.utils.task.crewai_event_bus") as mock_bus, ): await execute(mock_agent, mock_context, mock_event_queue) @@ -175,8 +175,8 @@ class TestExecute: ) -> None: """Execute emits A2AServerTaskStartedEvent.""" with ( - patch("crewai.a2a.utils.task.Task", return_value=mock_task), - patch("crewai.a2a.utils.task.crewai_event_bus") as mock_bus, + patch("crewai_a2a.utils.task.Task", return_value=mock_task), + patch("crewai_a2a.utils.task.crewai_event_bus") as mock_bus, ): await execute(mock_agent, mock_context, mock_event_queue) @@ -197,8 +197,8 @@ class TestExecute: ) -> None: """Execute emits A2AServerTaskCompletedEvent on success.""" with ( - patch("crewai.a2a.utils.task.Task", return_value=mock_task), - patch("crewai.a2a.utils.task.crewai_event_bus") as mock_bus, + patch("crewai_a2a.utils.task.Task", return_value=mock_task), + patch("crewai_a2a.utils.task.crewai_event_bus") as mock_bus, ): await execute(mock_agent, mock_context, mock_event_queue) @@ -221,8 +221,8 @@ class TestExecute: mock_agent.aexecute_task = AsyncMock(side_effect=ValueError("Test error")) with ( - patch("crewai.a2a.utils.task.Task", return_value=mock_task), - patch("crewai.a2a.utils.task.crewai_event_bus") as mock_bus, + patch("crewai_a2a.utils.task.Task", return_value=mock_task), + patch("crewai_a2a.utils.task.crewai_event_bus") as mock_bus, ): with pytest.raises(Exception): await execute(mock_agent, mock_context, mock_event_queue) @@ -245,8 +245,8 @@ class TestExecute: mock_agent.aexecute_task = AsyncMock(side_effect=asyncio.CancelledError()) with ( - patch("crewai.a2a.utils.task.Task", return_value=mock_task), - patch("crewai.a2a.utils.task.crewai_event_bus") as mock_bus, + patch("crewai_a2a.utils.task.Task", return_value=mock_task), + patch("crewai_a2a.utils.task.crewai_event_bus") as mock_bus, ): with pytest.raises(asyncio.CancelledError): await execute(mock_agent, mock_context, mock_event_queue) @@ -361,8 +361,8 @@ class TestExecuteAndCancelIntegration: mock_agent.aexecute_task = slow_task with ( - patch("crewai.a2a.utils.task.Task", return_value=mock_task), - patch("crewai.a2a.utils.task.crewai_event_bus"), + patch("crewai_a2a.utils.task.Task", return_value=mock_task), + patch("crewai_a2a.utils.task.crewai_event_bus"), ): execute_task = asyncio.create_task( execute(mock_agent, mock_context, mock_event_queue) diff --git a/lib/crewai/tests/agents/test_a2a_trust_completion_status.py b/lib/crewai/tests/agents/test_a2a_trust_completion_status.py index 6347f8e1c..f3d6ec0fb 100644 --- a/lib/crewai/tests/agents/test_a2a_trust_completion_status.py +++ b/lib/crewai/tests/agents/test_a2a_trust_completion_status.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch import pytest -from crewai.a2a.config import A2AConfig +from crewai_a2a.config import A2AConfig try: from a2a.types import Message, Role @@ -27,8 +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_a2a.wrapper import _delegate_to_a2a + from crewai_a2a.types import AgentResponseProtocol from crewai import Agent, Task a2a_config = A2AConfig( @@ -51,8 +51,8 @@ def test_trust_remote_completion_status_true_returns_directly(): a2a_ids = ["http://test-endpoint.com/"] with ( - patch("crewai.a2a.wrapper.execute_a2a_delegation") as mock_execute, - patch("crewai.a2a.wrapper._fetch_agent_cards_concurrently") as mock_fetch, + patch("crewai_a2a.wrapper.execute_a2a_delegation") as mock_execute, + patch("crewai_a2a.wrapper._fetch_agent_cards_concurrently") as mock_fetch, ): mock_card = _create_mock_agent_card() mock_fetch.return_value = ({"http://test-endpoint.com/": mock_card}, {}) @@ -83,7 +83,7 @@ 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_a2a.wrapper import _delegate_to_a2a from crewai import Agent, Task a2a_config = A2AConfig( @@ -116,8 +116,8 @@ def test_trust_remote_completion_status_false_continues_conversation(): return "unexpected" with ( - patch("crewai.a2a.wrapper.execute_a2a_delegation") as mock_execute, - patch("crewai.a2a.wrapper._fetch_agent_cards_concurrently") as mock_fetch, + patch("crewai_a2a.wrapper.execute_a2a_delegation") as mock_execute, + patch("crewai_a2a.wrapper._fetch_agent_cards_concurrently") as mock_fetch, ): mock_card = _create_mock_agent_card() mock_fetch.return_value = ({"http://test-endpoint.com/": mock_card}, {}) diff --git a/lib/crewai/tests/agents/test_agent_a2a_kickoff.py b/lib/crewai/tests/agents/test_agent_a2a_kickoff.py index 00123c4cf..c3b34d21d 100644 --- a/lib/crewai/tests/agents/test_agent_a2a_kickoff.py +++ b/lib/crewai/tests/agents/test_agent_a2a_kickoff.py @@ -7,7 +7,7 @@ import os import pytest from crewai import Agent -from crewai.a2a.config import A2AClientConfig +from crewai_a2a.config import A2AClientConfig A2A_TEST_ENDPOINT = os.getenv( diff --git a/lib/crewai/tests/agents/test_agent_a2a_wrapping.py b/lib/crewai/tests/agents/test_agent_a2a_wrapping.py index 6b7d4be9f..b6e08f0bc 100644 --- a/lib/crewai/tests/agents/test_agent_a2a_wrapping.py +++ b/lib/crewai/tests/agents/test_agent_a2a_wrapping.py @@ -5,7 +5,7 @@ from unittest.mock import patch import pytest from crewai import Agent -from crewai.a2a.config import A2AConfig +from crewai_a2a.config import A2AConfig try: import a2a # noqa: F401 diff --git a/pyproject.toml b/pyproject.toml index 1667ca25b..020288446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,6 +179,7 @@ members = [ "lib/crewai-tools", "lib/devtools", "lib/crewai-files", + "lib/crewai-a2a", ] @@ -187,3 +188,4 @@ crewai = { workspace = true } crewai-tools = { workspace = true } crewai-devtools = { workspace = true } crewai-files = { workspace = true } +crewai-a2a = { workspace = true } diff --git a/uv.lock b/uv.lock index 018efb0bf..946b2e65f 100644 --- a/uv.lock +++ b/uv.lock @@ -15,6 +15,7 @@ resolution-markers = [ [manifest] members = [ "crewai", + "crewai-a2a", "crewai-devtools", "crewai-files", "crewai-tools", @@ -1160,10 +1161,7 @@ dependencies = [ [package.optional-dependencies] a2a = [ - { name = "a2a-sdk" }, - { name = "aiocache", extra = ["memcached", "redis"] }, - { name = "httpx-auth" }, - { name = "httpx-sse" }, + { name = "crewai-a2a" }, ] anthropic = [ { name = "anthropic" }, @@ -1220,9 +1218,7 @@ watson = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", marker = "extra == 'a2a'", specifier = "~=0.3.10" }, { name = "aiobotocore", marker = "extra == 'aws'", specifier = "~=2.25.2" }, - { name = "aiocache", extras = ["memcached", "redis"], marker = "extra == 'a2a'", specifier = "~=0.12.3" }, { name = "aiosqlite", specifier = "~=0.21.0" }, { name = "anthropic", marker = "extra == 'anthropic'", specifier = "~=0.73.0" }, { name = "appdirs", specifier = "~=1.4.4" }, @@ -1231,13 +1227,12 @@ requires-dist = [ { name = "boto3", marker = "extra == 'bedrock'", specifier = "~=1.40.45" }, { name = "chromadb", specifier = "~=1.1.0" }, { name = "click", specifier = "~=8.1.7" }, + { name = "crewai-a2a", marker = "extra == 'a2a'", editable = "lib/crewai-a2a" }, { name = "crewai-files", marker = "extra == 'file-processing'", editable = "lib/crewai-files" }, { name = "crewai-tools", marker = "extra == 'tools'", editable = "lib/crewai-tools" }, { name = "docling", marker = "extra == 'docling'", specifier = "~=2.75.0" }, { name = "google-genai", marker = "extra == 'google-genai'", specifier = "~=1.65.0" }, { name = "httpx", specifier = "~=0.28.1" }, - { name = "httpx-auth", marker = "extra == 'a2a'", specifier = "~=0.23.1" }, - { name = "httpx-sse", marker = "extra == 'a2a'", specifier = "~=0.4.0" }, { name = "ibm-watsonx-ai", marker = "extra == 'watson'", specifier = "~=1.3.39" }, { name = "instructor", specifier = ">=1.3.3" }, { name = "json-repair", specifier = "~=0.25.2" }, @@ -1274,6 +1269,32 @@ requires-dist = [ ] provides-extras = ["a2a", "anthropic", "aws", "azure-ai-inference", "bedrock", "docling", "embeddings", "file-processing", "google-genai", "litellm", "mem0", "openpyxl", "pandas", "qdrant", "qdrant-edge", "tools", "voyageai", "watson"] +[[package]] +name = "crewai-a2a" +source = { editable = "lib/crewai-a2a" } +dependencies = [ + { name = "a2a-sdk" }, + { name = "aiocache", extra = ["memcached", "redis"] }, + { name = "crewai" }, + { name = "httpx" }, + { name = "httpx-auth" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pyjwt" }, +] + +[package.metadata] +requires-dist = [ + { name = "a2a-sdk", specifier = "~=0.3.10" }, + { name = "aiocache", extras = ["memcached", "redis"], specifier = "~=0.12.3" }, + { name = "crewai", editable = "lib/crewai" }, + { name = "httpx", specifier = "~=0.28.1" }, + { name = "httpx-auth", specifier = "~=0.23.1" }, + { name = "httpx-sse", specifier = "~=0.4.0" }, + { name = "pydantic", specifier = "~=2.11.9" }, + { name = "pyjwt", specifier = ">=2.9.0,<3" }, +] + [[package]] name = "crewai-devtools" source = { editable = "lib/devtools" }