refactor: extract a2a into standalone crewai-a2a package

Move crewai.a2a into lib/crewai-a2a as its own workspace package, importable as crewai_a2a. The crewai[a2a] extra now pulls in crewai-a2a, which owns a2a-sdk, httpx-auth, httpx-sse, and aiocache.

crewai.a2a stays importable. Its __init__ is a compat shim that installs a meta-path finder routing crewai.a2a.* to crewai_a2a.*, so existing user code keeps working untouched.

a2a tests and cassettes moved alongside the package under lib/crewai-a2a/tests/. Added that path to the mypy and ruff per-file-ignores lists to match the other test dirs.
This commit is contained in:
Greyson LaLonde
2026-04-14 22:21:31 +08:00
parent 0dba95e166
commit 080c22678a
87 changed files with 415 additions and 321 deletions

View File

@@ -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.11.3
hooks:

0
lib/crewai-a2a/README.md Normal file
View File

View File

@@ -0,0 +1,22 @@
[project]
name = "crewai-a2a"
dynamic = ["version"]
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "Greyson LaLonde", email = "greyson@crewai.com" }
]
requires-python = ">=3.10, <3.14"
dependencies = [
"a2a-sdk~=0.3.10",
"httpx-auth~=0.23.1",
"httpx-sse~=0.4.0",
"aiocache[redis,memcached]~=0.12.3",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.version]
path = "src/crewai_a2a/__init__.py"

View File

@@ -0,0 +1,13 @@
"""Agent-to-Agent (A2A) protocol communication module for CrewAI."""
__version__ = "1.14.2a3"
from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig
__all__ = [
"A2AClientConfig",
"A2AConfig",
"A2AServerConfig",
"__version__",
]

View File

@@ -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,

View File

@@ -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",
]

View File

@@ -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,

View File

@@ -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,
)

View File

@@ -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,

View File

@@ -1,6 +1,6 @@
"""A2UI (Agent to UI) declarative UI protocol support for CrewAI."""
from crewai.a2a.extensions.a2ui.catalog import (
from crewai_a2a.extensions.a2ui.catalog import (
AudioPlayer,
Button,
Card,
@@ -20,8 +20,8 @@ from crewai.a2a.extensions.a2ui.catalog import (
TextField,
Video,
)
from crewai.a2a.extensions.a2ui.client_extension import A2UIClientExtension
from crewai.a2a.extensions.a2ui.models import (
from crewai_a2a.extensions.a2ui.client_extension import A2UIClientExtension
from crewai_a2a.extensions.a2ui.models import (
A2UIEvent,
A2UIMessage,
A2UIResponse,
@@ -31,13 +31,13 @@ from crewai.a2a.extensions.a2ui.models import (
SurfaceUpdate,
UserAction,
)
from crewai.a2a.extensions.a2ui.server_extension import (
from crewai_a2a.extensions.a2ui.server_extension import (
A2UI_STANDARD_CATALOG_ID,
A2UI_V09_BASIC_CATALOG_ID,
A2UI_V09_EXTENSION_URI,
A2UIServerExtension,
)
from crewai.a2a.extensions.a2ui.v0_9 import (
from crewai_a2a.extensions.a2ui.v0_9 import (
A2UIEventV09,
A2UIMessageV09,
ActionEvent,
@@ -68,7 +68,7 @@ from crewai.a2a.extensions.a2ui.v0_9 import (
UpdateDataModel,
VideoV09,
)
from crewai.a2a.extensions.a2ui.validator import (
from crewai_a2a.extensions.a2ui.validator import (
validate_a2ui_event,
validate_a2ui_event_v09,
validate_a2ui_message,

View File

@@ -10,18 +10,18 @@ from pydantic import Field
from pydantic.dataclasses import dataclass
from typing_extensions import TypeIs, TypedDict
from crewai.a2a.extensions.a2ui.models import extract_a2ui_json_objects
from crewai.a2a.extensions.a2ui.prompt import (
from crewai_a2a.extensions.a2ui.models import extract_a2ui_json_objects
from crewai_a2a.extensions.a2ui.prompt import (
build_a2ui_system_prompt,
build_a2ui_v09_system_prompt,
)
from crewai.a2a.extensions.a2ui.server_extension import (
from crewai_a2a.extensions.a2ui.server_extension import (
A2UI_MIME_TYPE,
A2UI_STANDARD_CATALOG_ID,
A2UI_V09_BASIC_CATALOG_ID,
)
from crewai.a2a.extensions.a2ui.v0_9 import extract_a2ui_v09_json_objects
from crewai.a2a.extensions.a2ui.validator import (
from crewai_a2a.extensions.a2ui.v0_9 import extract_a2ui_v09_json_objects
from crewai_a2a.extensions.a2ui.validator import (
A2UIValidationError,
validate_a2ui_message,
validate_a2ui_message_v09,
@@ -30,7 +30,6 @@ from crewai.a2a.extensions.a2ui.validator import (
if TYPE_CHECKING:
from a2a.types import Message
from crewai.agent.core import Agent

View File

@@ -4,13 +4,13 @@ from __future__ import annotations
import json
from crewai.a2a.extensions.a2ui.catalog import STANDARD_CATALOG_COMPONENTS
from crewai.a2a.extensions.a2ui.schema import load_schema
from crewai.a2a.extensions.a2ui.server_extension import (
from crewai_a2a.extensions.a2ui.catalog import STANDARD_CATALOG_COMPONENTS
from crewai_a2a.extensions.a2ui.schema import load_schema
from crewai_a2a.extensions.a2ui.server_extension import (
A2UI_EXTENSION_URI,
A2UI_V09_BASIC_CATALOG_ID,
)
from crewai.a2a.extensions.a2ui.v0_9 import (
from crewai_a2a.extensions.a2ui.v0_9 import (
BASIC_CATALOG_COMPONENTS as V09_CATALOG_COMPONENTS,
BASIC_CATALOG_FUNCTIONS,
)

View File

@@ -5,16 +5,16 @@ from __future__ import annotations
import logging
from typing import Any
from crewai.a2a.extensions.a2ui.models import A2UIResponse, extract_a2ui_json_objects
from crewai.a2a.extensions.a2ui.v0_9 import (
from crewai_a2a.extensions.a2ui.models import A2UIResponse, extract_a2ui_json_objects
from crewai_a2a.extensions.a2ui.v0_9 import (
extract_a2ui_v09_json_objects,
)
from crewai.a2a.extensions.a2ui.validator import (
from crewai_a2a.extensions.a2ui.validator import (
A2UIValidationError,
validate_a2ui_message,
validate_a2ui_message_v09,
)
from crewai.a2a.extensions.server import ExtensionContext, ServerExtension
from crewai_a2a.extensions.server import ExtensionContext, ServerExtension
logger = logging.getLogger(__name__)

View File

@@ -6,7 +6,7 @@ from typing import Any
from pydantic import BaseModel, ValidationError
from crewai.a2a.extensions.a2ui.catalog import (
from crewai_a2a.extensions.a2ui.catalog import (
AudioPlayer,
Button,
Card,
@@ -26,8 +26,8 @@ from crewai.a2a.extensions.a2ui.catalog import (
TextField,
Video,
)
from crewai.a2a.extensions.a2ui.models import A2UIEvent, A2UIMessage
from crewai.a2a.extensions.a2ui.v0_9 import (
from crewai_a2a.extensions.a2ui.models import A2UIEvent, A2UIMessage
from crewai_a2a.extensions.a2ui.v0_9 import (
A2UIEventV09,
A2UIMessageV09,
AudioPlayerV09,

View File

@@ -19,7 +19,6 @@ from pydantic import BeforeValidator
if TYPE_CHECKING:
from a2a.types import Message
from crewai.agent.core import Agent

View File

@@ -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(

View File

@@ -18,13 +18,12 @@ from a2a.types import (
TaskStatusUpdateEvent,
TextPart,
)
from typing_extensions import NotRequired, TypedDict
from crewai.events.event_bus import crewai_event_bus
from crewai.events.types.a2a_events import (
A2AConnectionErrorEvent,
A2AResponseReceivedEvent,
)
from typing_extensions import NotRequired, TypedDict
if TYPE_CHECKING:

View File

@@ -15,7 +15,7 @@ from typing_extensions import NotRequired, TypedDict
try:
from crewai.a2a.updates import (
from crewai_a2a.updates import (
PollingConfig,
PollingHandler,
PushNotificationConfig,

View File

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

View File

@@ -29,8 +29,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):

View File

@@ -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:

View File

@@ -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(

View File

@@ -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:

View File

@@ -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__)

View File

@@ -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()

View File

@@ -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]})
"""

View File

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

View File

@@ -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,

View File

@@ -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:

View File

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

View File

@@ -37,11 +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 typing_extensions import TypedDict
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,
@@ -51,12 +46,18 @@ 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 typing_extensions import TypedDict
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__)

View File

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

View File

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

View File

@@ -9,12 +9,11 @@ from __future__ import annotations
from typing import Any
import jsonschema
import pytest
from crewai.a2a.extensions.a2ui import catalog
from crewai.a2a.extensions.a2ui.models import A2UIEvent, A2UIMessage
from crewai.a2a.extensions.a2ui.schema import load_schema
import jsonschema
import pytest
SERVER_SCHEMA = load_schema("server_to_client")
@@ -206,7 +205,10 @@ VALID_COMPONENTS: dict[str, dict[str, Any]] = {
"Divider": {"axis": "horizontal"},
"Modal": {"entryPointChild": "trigger", "contentChild": "body"},
"Button": {"child": "label", "action": {"name": "go"}},
"CheckBox": {"label": {"literalString": "Accept"}, "value": {"literalBoolean": False}},
"CheckBox": {
"label": {"literalString": "Accept"},
"value": {"literalBoolean": False},
},
"TextField": {"label": {"literalString": "Name"}},
"DateTimeInput": {"value": {"path": "/date"}},
"MultipleChoice": {

View File

@@ -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 a2a.types import AgentCard, Message, Part, Role, Task, 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()

View File

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

View File

@@ -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
await execute_task

View File

@@ -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
assert a2a_config.trust_remote_completion_status is False

View File

@@ -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 = [
{

View File

@@ -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__")

View File

@@ -99,10 +99,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",
@@ -140,6 +137,7 @@ torchvision = [
{ index = "pytorch", marker = "python_version < '3.13'" },
]
crewai-files = { workspace = true }
crewai-a2a = { workspace = true }
[build-system]

View File

@@ -1,10 +1,57 @@
"""Agent-to-Agent (A2A) protocol communication module for CrewAI."""
"""Compatibility shim: ``crewai.a2a`` re-exports :mod:`crewai_a2a`.
from crewai.a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig
The package lives in the ``crewai-a2a`` distribution (install via the
``crewai[a2a]`` extra). This module aliases the old import path so existing
code using ``crewai.a2a.*`` keeps working.
"""
from __future__ import annotations
from collections.abc import Sequence
import importlib
from importlib.abc import Loader, MetaPathFinder
from importlib.machinery import ModuleSpec
import sys
from types import ModuleType
__all__ = [
"A2AClientConfig",
"A2AConfig",
"A2AServerConfig",
]
try:
import crewai_a2a as _crewai_a2a
except ImportError as exc:
raise ImportError(
"crewai.a2a requires the 'crewai-a2a' package. "
"Install it with: pip install 'crewai[a2a]'"
) from exc
class _A2AAliasFinder(MetaPathFinder, Loader):
_SRC = "crewai.a2a"
_DST = "crewai_a2a"
def find_spec(
self,
fullname: str,
path: Sequence[str] | None = None,
target: ModuleType | None = None,
) -> ModuleSpec | None:
if fullname == self._SRC or fullname.startswith(self._SRC + "."):
return ModuleSpec(fullname, self)
return None
def create_module(self, spec: ModuleSpec) -> ModuleType:
target = self._DST + spec.name[len(self._SRC) :]
module = importlib.import_module(target)
sys.modules[spec.name] = module
return module
def exec_module(self, module: ModuleType) -> None:
return None
if not any(isinstance(f, _A2AAliasFinder) for f in sys.meta_path):
sys.meta_path.insert(0, _A2AAliasFinder())
for _attr in getattr(_crewai_a2a, "__all__", []):
globals()[_attr] = getattr(_crewai_a2a, _attr)
__all__ = list(getattr(_crewai_a2a, "__all__", []))

View File

@@ -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",
]

View File

@@ -108,6 +108,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", "B017", "F841", "F401", "RUF012"] # Allow assert statements and blind exception assertions in tests
"lib/devtools/tests/**/*.py" = ["S101"]
@@ -121,7 +122,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"]
@@ -190,6 +191,7 @@ members = [
"lib/crewai-tools",
"lib/devtools",
"lib/crewai-files",
"lib/crewai-a2a",
]
@@ -198,3 +200,4 @@ crewai = { workspace = true }
crewai-tools = { workspace = true }
crewai-devtools = { workspace = true }
crewai-files = { workspace = true }
crewai-a2a = { workspace = true }

31
uv.lock generated
View File

@@ -13,12 +13,13 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-04-10T18:30:59.748668Z"
exclude-newer = "2026-04-11T13:34:53.333824Z"
exclude-newer-span = "P3D"
[manifest]
members = [
"crewai",
"crewai-a2a",
"crewai-devtools",
"crewai-files",
"crewai-tools",
@@ -1267,10 +1268,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" },
@@ -1327,9 +1325,7 @@ watson = [
[package.metadata]
requires-dist = [
{ name = "a2a-sdk", marker = "extra == 'a2a'", specifier = "~=0.3.10" },
{ name = "aiobotocore", marker = "extra == 'aws'", specifier = "~=3.4.0" },
{ name = "aiocache", extras = ["memcached", "redis"], marker = "extra == 'a2a'", specifier = "~=0.12.3" },
{ name = "aiofiles", specifier = "~=24.1.0" },
{ name = "aiosqlite", specifier = "~=0.21.0" },
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = "~=0.73.0" },
@@ -1339,13 +1335,12 @@ requires-dist = [
{ name = "boto3", marker = "extra == 'bedrock'", specifier = "~=1.42.79" },
{ 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.84.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" },
@@ -1382,6 +1377,24 @@ 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 = "httpx-auth" },
{ name = "httpx-sse" },
]
[package.metadata]
requires-dist = [
{ name = "a2a-sdk", specifier = "~=0.3.10" },
{ name = "aiocache", extras = ["memcached", "redis"], specifier = "~=0.12.3" },
{ name = "httpx-auth", specifier = "~=0.23.1" },
{ name = "httpx-sse", specifier = "~=0.4.0" },
]
[[package]]
name = "crewai-devtools"
source = { editable = "lib/devtools" }