mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-12 14:02:47 +00:00
Compare commits
6 Commits
fix/docs-l
...
refactor/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aec7ad9731 | ||
|
|
bba48ec9df | ||
|
|
2ada20e9c6 | ||
|
|
258f31d44c | ||
|
|
68720fd4e5 | ||
|
|
3132910084 |
@@ -19,7 +19,7 @@ repos:
|
||||
language: system
|
||||
pass_filenames: true
|
||||
types: [python]
|
||||
exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/)
|
||||
exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/|lib/crewai-a2a/tests/)
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.9.3
|
||||
hooks:
|
||||
|
||||
@@ -59,7 +59,11 @@ if _original_from_serialized_response is not None:
|
||||
request: Any, serialized_response: Any, history: Any = None
|
||||
) -> Any:
|
||||
"""Patched version that ensures response._content is properly set."""
|
||||
response = _original_from_serialized_response(request, serialized_response, history)
|
||||
if not _original_from_serialized_response:
|
||||
return None
|
||||
response = _original_from_serialized_response(
|
||||
request, serialized_response, history
|
||||
)
|
||||
# Explicitly set _content to avoid ResponseNotRead errors
|
||||
# The content was passed to the constructor but the mocked read() prevents
|
||||
# proper initialization of the internal state
|
||||
@@ -255,7 +259,7 @@ def vcr_cassette_dir(request: Any) -> str:
|
||||
|
||||
for parent in test_file.parents:
|
||||
if (
|
||||
parent.name in ("crewai", "crewai-tools", "crewai-files")
|
||||
parent.name in ("crewai", "crewai-tools", "crewai-files", "crewai-a2a")
|
||||
and parent.parent.name == "lib"
|
||||
):
|
||||
package_root = parent
|
||||
|
||||
@@ -4,6 +4,26 @@ description: "تحديثات المنتج والتحسينات وإصلاحات
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="1 أبريل 2026">
|
||||
## v1.13.0a6
|
||||
|
||||
[عرض الإصدار على GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.13.0a6)
|
||||
|
||||
## ما الذي تغير
|
||||
|
||||
### الوثائق
|
||||
- إصلاح مستويات أذونات RBAC لتتوافق مع خيارات واجهة المستخدم الفعلية (#5210)
|
||||
- تحديث سجل التغييرات والإصدار لـ v1.13.0a5 (#5200)
|
||||
|
||||
### الأداء
|
||||
- تقليل عبء العمل على الإطار من خلال تنفيذ حافلة أحداث كسولة وتجاوز التتبع عند تعطيله (#5187)
|
||||
|
||||
## المساهمون
|
||||
|
||||
@alex-clawd, @joaomdmoura, @lucasgomide
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="31 مارس 2026">
|
||||
## v1.13.0a5
|
||||
|
||||
|
||||
@@ -3339,8 +3339,7 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": true
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "pt-BR",
|
||||
|
||||
@@ -4,6 +4,26 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="Apr 01, 2026">
|
||||
## v1.13.0a6
|
||||
|
||||
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.13.0a6)
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Documentation
|
||||
- Fix RBAC permission levels to match actual UI options (#5210)
|
||||
- Update changelog and version for v1.13.0a5 (#5200)
|
||||
|
||||
### Performance
|
||||
- Reduce framework overhead by implementing a lazy event bus and skipping tracing when disabled (#5187)
|
||||
|
||||
## Contributors
|
||||
|
||||
@alex-clawd, @joaomdmoura, @lucasgomide
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="Mar 31, 2026">
|
||||
## v1.13.0a5
|
||||
|
||||
|
||||
@@ -4,6 +4,26 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="2026년 4월 1일">
|
||||
## v1.13.0a6
|
||||
|
||||
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.13.0a6)
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 문서
|
||||
- 실제 UI 옵션에 맞게 RBAC 권한 수준 수정 (#5210)
|
||||
- v1.13.0a5에 대한 변경 로그 및 버전 업데이트 (#5200)
|
||||
|
||||
### 성능
|
||||
- 지연 이벤트 버스를 구현하고 비활성화 시 추적을 건너뛰어 프레임워크 오버헤드 감소 (#5187)
|
||||
|
||||
## 기여자
|
||||
|
||||
@alex-clawd, @joaomdmoura, @lucasgomide
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026년 3월 31일">
|
||||
## v1.13.0a5
|
||||
|
||||
|
||||
@@ -4,6 +4,26 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="01 abr 2026">
|
||||
## v1.13.0a6
|
||||
|
||||
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.13.0a6)
|
||||
|
||||
## O que Mudou
|
||||
|
||||
### Documentação
|
||||
- Corrigir níveis de permissão RBAC para corresponder às opções reais da interface do usuário (#5210)
|
||||
- Atualizar changelog e versão para v1.13.0a5 (#5200)
|
||||
|
||||
### Desempenho
|
||||
- Reduzir a sobrecarga do framework implementando um barramento de eventos preguiçoso e pulando o rastreamento quando desativado (#5187)
|
||||
|
||||
## Contributors
|
||||
|
||||
@alex-clawd, @joaomdmoura, @lucasgomide
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="31 mar 2026">
|
||||
## v1.13.0a5
|
||||
|
||||
|
||||
0
lib/crewai-a2a/README.md
Normal file
0
lib/crewai-a2a/README.md
Normal file
29
lib/crewai-a2a/pyproject.toml
Normal file
29
lib/crewai-a2a/pyproject.toml
Normal file
@@ -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==1.13.0a6",
|
||||
"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"
|
||||
12
lib/crewai-a2a/src/crewai_a2a/__init__.py
Normal file
12
lib/crewai-a2a/src/crewai_a2a/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Agent-to-Agent (A2A) protocol communication for CrewAI."""
|
||||
|
||||
__version__ = "1.13.0a6"
|
||||
|
||||
from crewai_a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig
|
||||
|
||||
|
||||
__all__ = [
|
||||
"A2AClientConfig",
|
||||
"A2AConfig",
|
||||
"A2AServerConfig",
|
||||
]
|
||||
@@ -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,
|
||||
71
lib/crewai-a2a/src/crewai_a2a/auth/schemas.py
Normal file
71
lib/crewai-a2a/src/crewai_a2a/auth/schemas.py
Normal 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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
@@ -19,7 +19,6 @@ from pydantic import BeforeValidator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from a2a.types import Message
|
||||
|
||||
from crewai.agent.core import Agent
|
||||
|
||||
|
||||
@@ -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(
|
||||
@@ -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:
|
||||
@@ -16,7 +16,7 @@ from typing_extensions import NotRequired
|
||||
|
||||
|
||||
try:
|
||||
from crewai.a2a.updates import (
|
||||
from crewai_a2a.updates import (
|
||||
PollingConfig,
|
||||
PollingHandler,
|
||||
PushNotificationConfig,
|
||||
@@ -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
|
||||
@@ -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):
|
||||
@@ -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:
|
||||
@@ -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(
|
||||
@@ -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:
|
||||
@@ -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__)
|
||||
@@ -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()
|
||||
@@ -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]})
|
||||
"""
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,14 +3,12 @@ from __future__ import annotations
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from a2a.client import ClientFactory
|
||||
from a2a.types import AgentCard, Message, Part, Role, TaskState, TextPart
|
||||
|
||||
from crewai.a2a.updates.polling.handler import PollingHandler
|
||||
from crewai.a2a.updates.streaming.handler import StreamingHandler
|
||||
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,11 +180,12 @@ class TestA2APushNotificationHandler:
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from a2a.types import Task, TaskStatus
|
||||
from crewai_a2a.updates.push_notifications.config import PushNotificationConfig
|
||||
from crewai_a2a.updates.push_notifications.handler import (
|
||||
PushNotificationHandler,
|
||||
)
|
||||
from pydantic import AnyHttpUrl
|
||||
|
||||
from crewai.a2a.updates.push_notifications.config import PushNotificationConfig
|
||||
from crewai.a2a.updates.push_notifications.handler import PushNotificationHandler
|
||||
|
||||
completed_task = Task(
|
||||
id="task-123",
|
||||
context_id="ctx-123",
|
||||
@@ -246,11 +245,12 @@ class TestA2APushNotificationHandler:
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from a2a.types import Task, TaskStatus
|
||||
from crewai_a2a.updates.push_notifications.config import PushNotificationConfig
|
||||
from crewai_a2a.updates.push_notifications.handler import (
|
||||
PushNotificationHandler,
|
||||
)
|
||||
from pydantic import AnyHttpUrl
|
||||
|
||||
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,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()
|
||||
|
||||
@@ -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",
|
||||
@@ -51,8 +50,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,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",
|
||||
@@ -116,8 +115,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}, {})
|
||||
@@ -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
|
||||
@@ -4,10 +4,9 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from crewai import Agent
|
||||
from crewai.a2a.config import A2AClientConfig
|
||||
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 = [
|
||||
{
|
||||
@@ -1,14 +1,12 @@
|
||||
"""Test A2A wrapper is only applied when a2a is passed to Agent."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from crewai import Agent
|
||||
from crewai_a2a.config import A2AConfig
|
||||
import pytest
|
||||
|
||||
from crewai import Agent
|
||||
from crewai.a2a.config import A2AConfig
|
||||
|
||||
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__")
|
||||
@@ -3,10 +3,9 @@
|
||||
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:
|
||||
@@ -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
|
||||
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
|
||||
@@ -156,8 +159,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 +178,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 +200,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 +224,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 +248,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)
|
||||
@@ -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"
|
||||
@@ -361,8 +365,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)
|
||||
@@ -372,4 +376,4 @@ class TestExecuteAndCancelIntegration:
|
||||
await cancel(mock_context, mock_event_queue)
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await execute_task
|
||||
await execute_task
|
||||
@@ -152,4 +152,4 @@ __all__ = [
|
||||
"wrap_file_source",
|
||||
]
|
||||
|
||||
__version__ = "1.13.0a5"
|
||||
__version__ = "1.13.0a6"
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies = [
|
||||
"pytube~=15.0.0",
|
||||
"requests~=2.32.5",
|
||||
"docker~=7.1.0",
|
||||
"crewai==1.13.0a5",
|
||||
"crewai==1.13.0a6",
|
||||
"tiktoken~=0.8.0",
|
||||
"beautifulsoup4~=4.13.4",
|
||||
"python-docx~=1.2.0",
|
||||
|
||||
@@ -309,4 +309,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.13.0a5"
|
||||
__version__ = "1.13.0a6"
|
||||
|
||||
@@ -54,7 +54,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai-tools==1.13.0a5",
|
||||
"crewai-tools==1.13.0a6",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
@@ -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==1.13.0a6",
|
||||
]
|
||||
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]
|
||||
|
||||
@@ -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
|
||||
@@ -44,7 +44,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.13.0a5"
|
||||
__version__ = "1.13.0a6"
|
||||
_telemetry_submitted = False
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.13.0a5"
|
||||
"crewai[tools]==1.13.0a6"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.13.0a5"
|
||||
"crewai[tools]==1.13.0a6"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.13.0a5"
|
||||
"crewai[tools]==1.13.0a6"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -85,6 +85,8 @@ class CrewAIEventsBus:
|
||||
_shutting_down: bool
|
||||
_pending_futures: set[Future[Any]]
|
||||
_futures_lock: threading.Lock
|
||||
_executor_initialized: bool
|
||||
_has_pending_events: bool
|
||||
|
||||
def __new__(cls) -> Self:
|
||||
"""Create or return the singleton instance.
|
||||
@@ -102,8 +104,9 @@ class CrewAIEventsBus:
|
||||
def _initialize(self) -> None:
|
||||
"""Initialize the event bus internal state.
|
||||
|
||||
Creates handler dictionaries and starts a dedicated background
|
||||
event loop for async handler execution.
|
||||
Creates handler dictionaries. The thread pool executor and event loop
|
||||
are lazily initialized on first emit() to avoid overhead when events
|
||||
are never emitted.
|
||||
"""
|
||||
self._shutting_down = False
|
||||
self._rwlock = RWLock()
|
||||
@@ -115,19 +118,37 @@ class CrewAIEventsBus:
|
||||
type[BaseEvent], dict[Handler, list[Depends[Any]]]
|
||||
] = {}
|
||||
self._execution_plan_cache: dict[type[BaseEvent], ExecutionPlan] = {}
|
||||
self._sync_executor = ThreadPoolExecutor(
|
||||
max_workers=10,
|
||||
thread_name_prefix="CrewAISyncHandler",
|
||||
)
|
||||
self._console = ConsoleFormatter()
|
||||
# Lazy initialization flags - executor and loop created on first emit
|
||||
self._executor_initialized = False
|
||||
self._has_pending_events = False
|
||||
|
||||
self._loop = asyncio.new_event_loop()
|
||||
self._loop_thread = threading.Thread(
|
||||
target=self._run_loop,
|
||||
name="CrewAIEventsLoop",
|
||||
daemon=True,
|
||||
)
|
||||
self._loop_thread.start()
|
||||
def _ensure_executor_initialized(self) -> None:
|
||||
"""Lazily initialize the thread pool executor and event loop.
|
||||
|
||||
Called on first emit() to avoid startup overhead when events are never used.
|
||||
Thread-safe via double-checked locking.
|
||||
"""
|
||||
if self._executor_initialized:
|
||||
return
|
||||
|
||||
with self._instance_lock:
|
||||
if self._executor_initialized:
|
||||
return
|
||||
|
||||
self._sync_executor = ThreadPoolExecutor(
|
||||
max_workers=10,
|
||||
thread_name_prefix="CrewAISyncHandler",
|
||||
)
|
||||
|
||||
self._loop = asyncio.new_event_loop()
|
||||
self._loop_thread = threading.Thread(
|
||||
target=self._run_loop,
|
||||
name="CrewAIEventsLoop",
|
||||
daemon=True,
|
||||
)
|
||||
self._loop_thread.start()
|
||||
self._executor_initialized = True
|
||||
|
||||
def _track_future(self, future: Future[Any]) -> Future[Any]:
|
||||
"""Track a future and set up automatic cleanup when it completes.
|
||||
@@ -431,6 +452,15 @@ class CrewAIEventsBus:
|
||||
sync_handlers = self._sync_handlers.get(event_type, frozenset())
|
||||
async_handlers = self._async_handlers.get(event_type, frozenset())
|
||||
|
||||
# Skip executor initialization if no handlers exist for this event
|
||||
if not sync_handlers and not async_handlers:
|
||||
return None
|
||||
|
||||
# Lazily initialize executor and event loop only when handlers exist
|
||||
self._ensure_executor_initialized()
|
||||
# Track that we have pending events for flush optimization
|
||||
self._has_pending_events = True
|
||||
|
||||
if has_dependencies:
|
||||
return self._track_future(
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
@@ -474,6 +504,10 @@ class CrewAIEventsBus:
|
||||
Returns:
|
||||
True if all handlers completed, False if timeout occurred.
|
||||
"""
|
||||
# Skip flush entirely if no events were ever emitted
|
||||
if not self._has_pending_events:
|
||||
return True
|
||||
|
||||
with self._futures_lock:
|
||||
futures_to_wait = list(self._pending_futures)
|
||||
|
||||
@@ -629,6 +663,9 @@ class CrewAIEventsBus:
|
||||
|
||||
with self._rwlock.w_locked():
|
||||
self._shutting_down = True
|
||||
# Check if executor was ever initialized (lazy init optimization)
|
||||
if not self._executor_initialized:
|
||||
return
|
||||
loop = getattr(self, "_loop", None)
|
||||
|
||||
if loop is None or loop.is_closed():
|
||||
|
||||
@@ -17,7 +17,10 @@ from crewai.events.listeners.tracing.first_time_trace_handler import (
|
||||
from crewai.events.listeners.tracing.trace_batch_manager import TraceBatchManager
|
||||
from crewai.events.listeners.tracing.types import TraceEvent
|
||||
from crewai.events.listeners.tracing.utils import (
|
||||
is_tracing_enabled_in_context,
|
||||
safe_serialize_to_dict,
|
||||
should_auto_collect_first_time_traces,
|
||||
should_enable_tracing,
|
||||
)
|
||||
from crewai.events.types.a2a_events import (
|
||||
A2AAgentCardFetchedEvent,
|
||||
@@ -198,6 +201,17 @@ class TraceCollectionListener(BaseEventListener):
|
||||
if self._listeners_setup:
|
||||
return
|
||||
|
||||
# Skip registration entirely if tracing is disabled and not first-time user
|
||||
# This avoids overhead of 50+ handler registrations when tracing won't be used
|
||||
# Also check is_tracing_enabled_in_context() so per-run overrides (Crew(tracing=True)) still work
|
||||
if (
|
||||
not should_enable_tracing()
|
||||
and not is_tracing_enabled_in_context()
|
||||
and not should_auto_collect_first_time_traces()
|
||||
):
|
||||
self._listeners_setup = True
|
||||
return
|
||||
|
||||
self._register_env_event_handlers(crewai_event_bus)
|
||||
self._register_flow_event_handlers(crewai_event_bus)
|
||||
self._register_context_event_handlers(crewai_event_bus)
|
||||
|
||||
@@ -481,6 +481,26 @@ def should_auto_collect_first_time_traces() -> bool:
|
||||
return is_first_execution()
|
||||
|
||||
|
||||
def _is_interactive_terminal() -> bool:
|
||||
"""Check if stdin is an interactive terminal.
|
||||
|
||||
Returns False in non-interactive contexts (CI, API servers, Docker, etc.)
|
||||
to avoid blocking on prompts that no one can respond to.
|
||||
"""
|
||||
import sys
|
||||
|
||||
try:
|
||||
stdin = getattr(sys, "stdin", None)
|
||||
if stdin is None:
|
||||
return False
|
||||
isatty = getattr(stdin, "isatty", None)
|
||||
if not callable(isatty):
|
||||
return False
|
||||
return bool(isatty())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool:
|
||||
"""
|
||||
Prompt user if they want to see their traces with timeout.
|
||||
@@ -492,6 +512,11 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool:
|
||||
if should_suppress_tracing_messages():
|
||||
return False
|
||||
|
||||
# Skip prompt in non-interactive contexts (CI, API servers, Docker, etc.)
|
||||
# This avoids blocking for 20 seconds when no one can respond
|
||||
if not _is_interactive_terminal():
|
||||
return False
|
||||
|
||||
try:
|
||||
import threading
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -793,6 +793,10 @@ class TestTraceListenerSetup:
|
||||
"crewai.events.listeners.tracing.utils._is_test_environment",
|
||||
return_value=False,
|
||||
),
|
||||
patch(
|
||||
"crewai.events.listeners.tracing.utils._is_interactive_terminal",
|
||||
return_value=True,
|
||||
),
|
||||
patch("threading.Thread") as mock_thread,
|
||||
):
|
||||
from crewai.events.listeners.tracing.utils import (
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""CrewAI development tools."""
|
||||
|
||||
__version__ = "1.13.0a5"
|
||||
__version__ = "1.13.0a6"
|
||||
|
||||
@@ -187,6 +187,7 @@ _DEFAULT_WORKSPACE_PACKAGES: Final[list[str]] = [
|
||||
"crewai",
|
||||
"crewai-tools",
|
||||
"crewai-devtools",
|
||||
"crewai-a2a",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ ignore-decorators = ["typing.overload"]
|
||||
"lib/crewai/tests/**/*.py" = ["S101", "RET504", "S105", "S106"] # Allow assert statements, unnecessary assignments, and hardcoded passwords in tests
|
||||
"lib/crewai-tools/tests/**/*.py" = ["S101", "RET504", "S105", "S106", "RUF012", "N818", "E402", "RUF043", "S110", "B017"] # Allow various test-specific patterns
|
||||
"lib/crewai-files/tests/**/*.py" = ["S101", "RET504", "S105", "S106", "B017", "F841"] # Allow assert statements and blind exception assertions in tests
|
||||
"lib/crewai-a2a/tests/**/*.py" = ["S101", "RET504", "S105", "S106", "RUF012", "F821", "F401", "B017"] # Allow test-specific patterns
|
||||
|
||||
|
||||
[tool.mypy]
|
||||
@@ -119,7 +120,7 @@ warn_return_any = true
|
||||
show_error_codes = true
|
||||
warn_unused_ignores = true
|
||||
python_version = "3.12"
|
||||
exclude = "(?x)(^lib/crewai/src/crewai/cli/templates/|^lib/crewai/tests/|^lib/crewai-tools/tests/|^lib/crewai-files/tests/)"
|
||||
exclude = "(?x)(^lib/crewai/src/crewai/cli/templates/|^lib/crewai/tests/|^lib/crewai-tools/tests/|^lib/crewai-files/tests/|^lib/crewai-a2a/tests/)"
|
||||
plugins = ["pydantic.mypy"]
|
||||
|
||||
|
||||
@@ -135,6 +136,7 @@ testpaths = [
|
||||
"lib/crewai/tests",
|
||||
"lib/crewai-tools/tests",
|
||||
"lib/crewai-files/tests",
|
||||
"lib/crewai-a2a/tests",
|
||||
]
|
||||
asyncio_mode = "strict"
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
@@ -179,6 +181,7 @@ members = [
|
||||
"lib/crewai-tools",
|
||||
"lib/devtools",
|
||||
"lib/crewai-files",
|
||||
"lib/crewai-a2a",
|
||||
]
|
||||
|
||||
|
||||
@@ -187,3 +190,4 @@ crewai = { workspace = true }
|
||||
crewai-tools = { workspace = true }
|
||||
crewai-devtools = { workspace = true }
|
||||
crewai-files = { workspace = true }
|
||||
crewai-a2a = { workspace = true }
|
||||
|
||||
37
uv.lock
generated
37
uv.lock
generated
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user