mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-15 15:32:40 +00:00
Compare commits
6 Commits
refactor/e
...
feat/azure
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6082003fa0 | ||
|
|
5a40d97819 | ||
|
|
95c6e2ec2e | ||
|
|
cbcc3a900d | ||
|
|
ad5e66d1d0 | ||
|
|
94e7d86df1 |
@@ -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/|lib/crewai-a2a/tests/)
|
||||
exclude: ^(lib/crewai/src/crewai/cli/templates/|lib/crewai/tests/|lib/crewai-tools/tests/|lib/crewai-files/tests/)
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.11.3
|
||||
hooks:
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "تحديثات المنتج والتحسينات وإصلاحات
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="15 أبريل 2026">
|
||||
## v1.14.2a4
|
||||
|
||||
[عرض الإصدار على GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## ما الذي تغير
|
||||
|
||||
### الميزات
|
||||
- إضافة تلميحات استئناف إلى إصدار أدوات المطورين عند الفشل
|
||||
|
||||
### إصلاحات الأخطاء
|
||||
- إصلاح توجيه وضع الصرامة إلى واجهة برمجة تطبيقات Bedrock Converse
|
||||
- إصلاح إصدار pytest إلى 9.0.3 لثغرة الأمان GHSA-6w46-j5rx-g56g
|
||||
- رفع الحد الأدنى لـ OpenAI إلى >=2.0.0
|
||||
|
||||
### الوثائق
|
||||
- تحديث سجل التغييرات والإصدار لـ v1.14.2a3
|
||||
|
||||
## المساهمون
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="13 أبريل 2026">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="Apr 15, 2026">
|
||||
## v1.14.2a4
|
||||
|
||||
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Features
|
||||
- Add resume hints to devtools release on failure
|
||||
|
||||
### Bug Fixes
|
||||
- Fix strict mode forwarding to Bedrock Converse API
|
||||
- Fix pytest version to 9.0.3 for security vulnerability GHSA-6w46-j5rx-g56g
|
||||
- Bump OpenAI lower bound to >=2.0.0
|
||||
|
||||
### Documentation
|
||||
- Update changelog and version for v1.14.2a3
|
||||
|
||||
## Contributors
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="Apr 13, 2026">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="2026년 4월 15일">
|
||||
## v1.14.2a4
|
||||
|
||||
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 기능
|
||||
- 실패 시 devtools 릴리스에 이력서 힌트 추가
|
||||
|
||||
### 버그 수정
|
||||
- Bedrock Converse API로의 엄격 모드 포워딩 수정
|
||||
- 보안 취약점 GHSA-6w46-j5rx-g56g에 대해 pytest 버전을 9.0.3으로 수정
|
||||
- OpenAI 하한을 >=2.0.0으로 상향 조정
|
||||
|
||||
### 문서
|
||||
- v1.14.2a3에 대한 변경 로그 및 버전 업데이트
|
||||
|
||||
## 기여자
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026년 4월 13일">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="15 abr 2026">
|
||||
## v1.14.2a4
|
||||
|
||||
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## O que Mudou
|
||||
|
||||
### Recursos
|
||||
- Adicionar dicas de retomar ao release do devtools em caso de falha
|
||||
|
||||
### Correções de Bugs
|
||||
- Corrigir o encaminhamento do modo estrito para a API Bedrock Converse
|
||||
- Corrigir a versão do pytest para 9.0.3 devido à vulnerabilidade de segurança GHSA-6w46-j5rx-g56g
|
||||
- Aumentar o limite inferior do OpenAI para >=2.0.0
|
||||
|
||||
### Documentação
|
||||
- Atualizar o changelog e a versão para v1.14.2a3
|
||||
|
||||
## Contribuidores
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="13 abr 2026">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[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"
|
||||
@@ -1,13 +0,0 @@
|
||||
"""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__",
|
||||
]
|
||||
@@ -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",
|
||||
]
|
||||
@@ -152,4 +152,4 @@ __all__ = [
|
||||
"wrap_file_source",
|
||||
]
|
||||
|
||||
__version__ = "1.14.2a3"
|
||||
__version__ = "1.14.2a4"
|
||||
|
||||
@@ -10,7 +10,7 @@ requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"pytube~=15.0.0",
|
||||
"requests>=2.33.0,<3",
|
||||
"crewai==1.14.2a3",
|
||||
"crewai==1.14.2a4",
|
||||
"tiktoken~=0.8.0",
|
||||
"beautifulsoup4~=4.13.4",
|
||||
"python-docx~=1.2.0",
|
||||
|
||||
@@ -305,4 +305,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.14.2a3"
|
||||
__version__ = "1.14.2a4"
|
||||
|
||||
@@ -55,7 +55,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai-tools==1.14.2a3",
|
||||
"crewai-tools==1.14.2a4",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
@@ -94,12 +94,16 @@ google-genai = [
|
||||
]
|
||||
azure-ai-inference = [
|
||||
"azure-ai-inference~=1.0.0b9",
|
||||
"azure-identity>=1.17.0,<2",
|
||||
]
|
||||
anthropic = [
|
||||
"anthropic~=0.73.0",
|
||||
]
|
||||
a2a = [
|
||||
"crewai-a2a",
|
||||
"a2a-sdk~=0.3.10",
|
||||
"httpx-auth~=0.23.1",
|
||||
"httpx-sse~=0.4.0",
|
||||
"aiocache[redis,memcached]~=0.12.3",
|
||||
]
|
||||
file-processing = [
|
||||
"crewai-files",
|
||||
@@ -137,7 +141,6 @@ torchvision = [
|
||||
{ index = "pytorch", marker = "python_version < '3.13'" },
|
||||
]
|
||||
crewai-files = { workspace = true }
|
||||
crewai-a2a = { workspace = true }
|
||||
|
||||
|
||||
[build-system]
|
||||
|
||||
@@ -46,7 +46,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.14.2a3"
|
||||
__version__ = "1.14.2a4"
|
||||
_telemetry_submitted = False
|
||||
|
||||
|
||||
|
||||
@@ -1,57 +1,10 @@
|
||||
"""Compatibility shim: ``crewai.a2a`` re-exports :mod:`crewai_a2a`.
|
||||
"""Agent-to-Agent (A2A) protocol communication module for CrewAI."""
|
||||
|
||||
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
|
||||
from crewai.a2a.config import 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__", []))
|
||||
__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/src/crewai/a2a/auth/schemas.py
Normal file
71
lib/crewai/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",
|
||||
]
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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,6 +30,7 @@ from crewai_a2a.extensions.a2ui.validator import (
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from a2a.types import Message
|
||||
|
||||
from crewai.agent.core import Agent
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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__)
|
||||
@@ -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,
|
||||
@@ -19,6 +19,7 @@ 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,12 +18,13 @@ 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:
|
||||
@@ -15,7 +15,7 @@ from typing_extensions import NotRequired, TypedDict
|
||||
|
||||
|
||||
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
|
||||
@@ -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):
|
||||
@@ -18,6 +18,17 @@ 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,
|
||||
@@ -25,17 +36,6 @@ 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,6 +16,19 @@ 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,
|
||||
@@ -23,19 +36,6 @@ 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,6 +22,18 @@ 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,
|
||||
@@ -30,18 +42,6 @@ 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,6 +16,15 @@ 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 (
|
||||
@@ -23,23 +32,13 @@ 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.
|
||||
@@ -496,7 +495,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()
|
||||
|
||||
@@ -530,7 +529,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,6 +10,7 @@ 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,55 +23,55 @@ from a2a.types import (
|
||||
Role,
|
||||
TextPart,
|
||||
)
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.a2a_events import (
|
||||
A2AConversationStartedEvent,
|
||||
A2ADelegationCompletedEvent,
|
||||
A2ADelegationStartedEvent,
|
||||
A2AMessageSentEvent,
|
||||
)
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai_a2a.auth.client_schemes import APIKeyAuth, HTTPDigestAuth
|
||||
from crewai_a2a.auth.utils import (
|
||||
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 (
|
||||
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 (
|
||||
from crewai.a2a.task_helpers import TaskStateResult
|
||||
from crewai.a2a.types import (
|
||||
HANDLER_REGISTRY,
|
||||
HandlerType,
|
||||
PartsDict,
|
||||
PartsMetadataDict,
|
||||
TransportType,
|
||||
)
|
||||
from crewai_a2a.updates import (
|
||||
from crewai.a2a.updates import (
|
||||
PollingConfig,
|
||||
PushNotificationConfig,
|
||||
StreamingHandler,
|
||||
UpdateConfig,
|
||||
)
|
||||
from crewai_a2a.utils.agent_card import (
|
||||
from crewai.a2a.utils.agent_card import (
|
||||
_afetch_agent_card_cached,
|
||||
_get_tls_verify,
|
||||
_prepare_auth_headers,
|
||||
)
|
||||
from crewai_a2a.utils.content_type import (
|
||||
from crewai.a2a.utils.content_type import (
|
||||
DEFAULT_CLIENT_OUTPUT_MODES,
|
||||
negotiate_content_types,
|
||||
)
|
||||
from crewai_a2a.utils.transport import (
|
||||
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,
|
||||
A2ADelegationCompletedEvent,
|
||||
A2ADelegationStartedEvent,
|
||||
A2AMessageSentEvent,
|
||||
)
|
||||
|
||||
|
||||
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.a2a.config import A2AClientConfig, A2AConfig, A2AServerConfig
|
||||
from crewai.types.utils import create_literals_from_strings
|
||||
|
||||
|
||||
A2AConfigTypes: TypeAlias = A2AConfig | A2AServerConfig | A2AClientConfig
|
||||
@@ -37,6 +37,11 @@ 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,
|
||||
@@ -46,18 +51,12 @@ 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__)
|
||||
|
||||
@@ -11,6 +11,7 @@ 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,6 +15,33 @@ 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,
|
||||
@@ -22,37 +49,11 @@ 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
|
||||
|
||||
@@ -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.14.2a3"
|
||||
"crewai[tools]==1.14.2a4"
|
||||
]
|
||||
|
||||
[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.14.2a3"
|
||||
"crewai[tools]==1.14.2a4"
|
||||
]
|
||||
|
||||
[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.14.2a3"
|
||||
"crewai[tools]==1.14.2a4"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -35,6 +35,7 @@ try:
|
||||
)
|
||||
from azure.core.credentials import (
|
||||
AzureKeyCredential,
|
||||
TokenCredential,
|
||||
)
|
||||
from azure.core.exceptions import (
|
||||
HttpResponseError,
|
||||
@@ -88,6 +89,8 @@ class AzureCompletion(BaseLLM):
|
||||
response_format: type[BaseModel] | None = None
|
||||
is_openai_model: bool = False
|
||||
is_azure_openai_endpoint: bool = False
|
||||
azure_tenant_id: str | None = None
|
||||
azure_client_id: str | None = None
|
||||
|
||||
_client: Any = PrivateAttr(default=None)
|
||||
_async_client: Any = PrivateAttr(default=None)
|
||||
@@ -115,6 +118,12 @@ class AzureCompletion(BaseLLM):
|
||||
data["api_version"] = (
|
||||
data.get("api_version") or os.getenv("AZURE_API_VERSION") or "2024-06-01"
|
||||
)
|
||||
data["azure_tenant_id"] = data.get("azure_tenant_id") or os.getenv(
|
||||
"AZURE_TENANT_ID"
|
||||
)
|
||||
data["azure_client_id"] = data.get("azure_client_id") or os.getenv(
|
||||
"AZURE_CLIENT_ID"
|
||||
)
|
||||
|
||||
# Credentials and endpoint are validated lazily in `_init_clients`
|
||||
# so the LLM can be constructed before deployment env vars are set.
|
||||
@@ -149,7 +158,7 @@ class AzureCompletion(BaseLLM):
|
||||
try:
|
||||
self._client = self._build_sync_client()
|
||||
self._async_client = self._build_async_client()
|
||||
except ValueError:
|
||||
except (ValueError, ImportError):
|
||||
pass
|
||||
return self
|
||||
|
||||
@@ -183,24 +192,89 @@ class AzureCompletion(BaseLLM):
|
||||
AzureCompletion._is_azure_openai_endpoint(self.endpoint)
|
||||
)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
"Azure API key is required. Set AZURE_API_KEY environment "
|
||||
"variable or pass api_key parameter."
|
||||
)
|
||||
# Re-read identity env vars for deferred builds
|
||||
if not self.azure_tenant_id:
|
||||
self.azure_tenant_id = os.getenv("AZURE_TENANT_ID")
|
||||
if not self.azure_client_id:
|
||||
self.azure_client_id = os.getenv("AZURE_CLIENT_ID")
|
||||
|
||||
if not self.endpoint:
|
||||
raise ValueError(
|
||||
"Azure endpoint is required. Set AZURE_ENDPOINT environment "
|
||||
"variable or pass endpoint parameter."
|
||||
)
|
||||
|
||||
credential = self._resolve_credential()
|
||||
|
||||
client_kwargs: dict[str, Any] = {
|
||||
"endpoint": self.endpoint,
|
||||
"credential": AzureKeyCredential(self.api_key),
|
||||
"credential": credential,
|
||||
}
|
||||
if self.api_version:
|
||||
client_kwargs["api_version"] = self.api_version
|
||||
return client_kwargs
|
||||
|
||||
def _resolve_credential(self) -> AzureKeyCredential | TokenCredential:
|
||||
"""Resolve the Azure credential using a priority chain.
|
||||
|
||||
1. OIDC federation (WorkloadIdentityCredential) — auto-discovered
|
||||
from AZURE_FEDERATED_TOKEN_FILE + AZURE_TENANT_ID + AZURE_CLIENT_ID
|
||||
2. Client secret (ClientSecretCredential) — explicit SP credentials
|
||||
3. Default chain (DefaultAzureCredential) — Managed Identity et al.
|
||||
4. API key fallback (AzureKeyCredential) — existing path
|
||||
"""
|
||||
federated_token_file = os.getenv("AZURE_FEDERATED_TOKEN_FILE")
|
||||
client_secret = os.getenv("AZURE_CLIENT_SECRET")
|
||||
|
||||
# Path 1: OIDC Workload Identity Federation
|
||||
if federated_token_file and self.azure_tenant_id and self.azure_client_id:
|
||||
try:
|
||||
from azure.identity import WorkloadIdentityCredential
|
||||
|
||||
return WorkloadIdentityCredential(
|
||||
tenant_id=self.azure_tenant_id,
|
||||
client_id=self.azure_client_id,
|
||||
token_file_path=federated_token_file,
|
||||
)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"azure-identity is required for workload identity federation. "
|
||||
'Install with: uv add "crewai[azure-ai-inference]"'
|
||||
) from None
|
||||
|
||||
# Path 2: Client Secret (Service Principal)
|
||||
if client_secret and self.azure_tenant_id and self.azure_client_id:
|
||||
try:
|
||||
from azure.identity import ClientSecretCredential
|
||||
|
||||
return ClientSecretCredential(
|
||||
tenant_id=self.azure_tenant_id,
|
||||
client_id=self.azure_client_id,
|
||||
client_secret=client_secret,
|
||||
)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"azure-identity is required for service principal authentication. "
|
||||
'Install with: uv add "crewai[azure-ai-inference]"'
|
||||
) from None
|
||||
|
||||
# Path 3: DefaultAzureCredential (Managed Identity, Azure CLI, etc.)
|
||||
# Only attempt if azure-identity is installed and no API key is available
|
||||
if not self.api_key:
|
||||
try:
|
||||
from azure.identity import DefaultAzureCredential
|
||||
|
||||
return DefaultAzureCredential()
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Azure API key is required when azure-identity is not installed. "
|
||||
"Set AZURE_API_KEY environment variable, pass api_key parameter, "
|
||||
'or install azure-identity: uv add "crewai[azure-ai-inference]"'
|
||||
) from None
|
||||
|
||||
# Path 4: API Key (existing path)
|
||||
return AzureKeyCredential(self.api_key)
|
||||
|
||||
def _get_sync_client(self) -> Any:
|
||||
if self._client is None:
|
||||
self._client = self._build_sync_client()
|
||||
|
||||
@@ -17,10 +17,7 @@ from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
)
|
||||
from crewai.utilities.pydantic_schema_utils import (
|
||||
generate_model_description,
|
||||
sanitize_tool_params_for_bedrock_strict,
|
||||
)
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
@@ -173,7 +170,6 @@ class ToolSpec(TypedDict, total=False):
|
||||
name: Required[str]
|
||||
description: Required[str]
|
||||
inputSchema: ToolInputSchema
|
||||
strict: bool
|
||||
|
||||
|
||||
class ConverseToolTypeDef(TypedDict):
|
||||
@@ -1988,21 +1984,10 @@ class BedrockCompletion(BaseLLM):
|
||||
"description": description,
|
||||
}
|
||||
|
||||
func_info = tool.get("function", {})
|
||||
strict_enabled = bool(func_info.get("strict"))
|
||||
|
||||
if parameters and isinstance(parameters, dict):
|
||||
schema_params = (
|
||||
sanitize_tool_params_for_bedrock_strict(parameters)
|
||||
if strict_enabled
|
||||
else parameters
|
||||
)
|
||||
input_schema: ToolInputSchema = {"json": schema_params}
|
||||
input_schema: ToolInputSchema = {"json": parameters}
|
||||
tool_spec["inputSchema"] = input_schema
|
||||
|
||||
if strict_enabled:
|
||||
tool_spec["strict"] = True
|
||||
|
||||
converse_tool: ConverseToolTypeDef = {"toolSpec": tool_spec}
|
||||
|
||||
converse_tools.append(converse_tool)
|
||||
|
||||
@@ -9,11 +9,12 @@ 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")
|
||||
@@ -205,10 +206,7 @@ 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": {
|
||||
@@ -3,13 +3,15 @@ from __future__ import annotations
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from a2a.client import ClientFactory
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
A2A_TEST_ENDPOINT = os.getenv("A2A_TEST_ENDPOINT", "http://localhost:9999")
|
||||
|
||||
@@ -160,7 +162,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
|
||||
|
||||
@@ -180,12 +182,11 @@ 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",
|
||||
@@ -245,12 +246,11 @@ 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,9 +303,7 @@ class TestA2APushNotificationHandler:
|
||||
"""Test that push handler fails gracefully without config."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from crewai.a2a.updates.push_notifications.handler import (
|
||||
PushNotificationHandler,
|
||||
)
|
||||
from crewai.a2a.updates.push_notifications.handler import PushNotificationHandler
|
||||
|
||||
mock_client = MagicMock()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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
|
||||
@@ -6,12 +6,13 @@ 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
|
||||
@@ -84,11 +85,8 @@ 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)
|
||||
@@ -136,7 +134,6 @@ 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
|
||||
@@ -357,7 +354,6 @@ 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"
|
||||
@@ -376,4 +372,4 @@ class TestExecuteAndCancelIntegration:
|
||||
await cancel(mock_context, mock_event_queue)
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await execute_task
|
||||
await execute_task
|
||||
@@ -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,8 +27,9 @@ 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 import Agent, Task
|
||||
from crewai.a2a.wrapper import _delegate_to_a2a
|
||||
from crewai.a2a.types import AgentResponseProtocol
|
||||
from crewai import Agent, Task
|
||||
|
||||
a2a_config = A2AConfig(
|
||||
endpoint="http://test-endpoint.com",
|
||||
@@ -82,8 +83,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 import Agent, Task
|
||||
from crewai.a2a.wrapper import _delegate_to_a2a
|
||||
from crewai import Agent, Task
|
||||
|
||||
a2a_config = A2AConfig(
|
||||
endpoint="http://test-endpoint.com",
|
||||
@@ -151,4 +152,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,9 +4,10 @@ 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(
|
||||
@@ -49,7 +50,9 @@ 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."
|
||||
@@ -146,7 +149,9 @@ 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,12 +1,14 @@
|
||||
"""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
|
||||
import a2a # noqa: F401
|
||||
|
||||
A2A_SDK_INSTALLED = True
|
||||
except ImportError:
|
||||
@@ -104,9 +106,6 @@ 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__")
|
||||
@@ -390,16 +390,26 @@ def test_azure_raises_error_when_endpoint_missing():
|
||||
|
||||
|
||||
def test_azure_raises_error_when_api_key_missing():
|
||||
"""Credentials are validated lazily: construction succeeds, first
|
||||
client build raises the descriptive error."""
|
||||
"""When no API key AND azure-identity is not installed, credentials
|
||||
are validated lazily: construction succeeds, first client build raises.
|
||||
With azure-identity installed, DefaultAzureCredential is used instead."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
llm = AzureCompletion(
|
||||
model="gpt-4", endpoint="https://test.openai.azure.com"
|
||||
)
|
||||
with pytest.raises(ValueError, match="Azure API key is required"):
|
||||
llm._get_sync_client()
|
||||
# With azure-identity installed, DefaultAzureCredential is used as
|
||||
# fallback instead of raising. Only raises when azure-identity is
|
||||
# not available.
|
||||
try:
|
||||
import azure.identity # noqa: F401
|
||||
# azure-identity is installed — DefaultAzureCredential will be used
|
||||
client = llm._get_sync_client()
|
||||
assert client is not None
|
||||
except ImportError:
|
||||
with pytest.raises(ValueError, match="Azure API key is required"):
|
||||
llm._get_sync_client()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
258
lib/crewai/tests/llms/azure/test_azure_credentials.py
Normal file
258
lib/crewai/tests/llms/azure/test_azure_credentials.py
Normal file
@@ -0,0 +1,258 @@
|
||||
"""Tests for Azure credential resolution chain in AzureCompletion.
|
||||
|
||||
Covers the four credential paths:
|
||||
1. WorkloadIdentityCredential (OIDC federation)
|
||||
2. ClientSecretCredential (Service Principal)
|
||||
3. DefaultAzureCredential (Managed Identity / CLI fallback)
|
||||
4. AzureKeyCredential (API key - existing path)
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# Use a non-Azure-OpenAI endpoint to avoid _validate_and_fix_endpoint suffixing
|
||||
ENDPOINT = "https://test-ai.services.example.com"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _clear_azure_env(monkeypatch):
|
||||
"""Remove all Azure env vars to start clean."""
|
||||
for key in [
|
||||
"AZURE_API_KEY", "AZURE_ENDPOINT", "AZURE_OPENAI_ENDPOINT",
|
||||
"AZURE_API_BASE", "AZURE_API_VERSION", "AZURE_TENANT_ID",
|
||||
"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_FEDERATED_TOKEN_FILE",
|
||||
]:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_clear_azure_env")
|
||||
class TestCredentialResolution:
|
||||
"""Tests for AzureCompletion._resolve_credential."""
|
||||
|
||||
def test_api_key_credential_when_api_key_set(self):
|
||||
"""Path 4: API key produces AzureKeyCredential."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from azure.core.credentials import AzureKeyCredential
|
||||
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
api_key="test-key",
|
||||
endpoint=ENDPOINT,
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert isinstance(cred, AzureKeyCredential)
|
||||
|
||||
def test_api_key_from_env(self, monkeypatch):
|
||||
"""Path 4: api_key picked up from AZURE_API_KEY env var."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from azure.core.credentials import AzureKeyCredential
|
||||
|
||||
monkeypatch.setenv("AZURE_API_KEY", "env-key")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
cred = completion._resolve_credential()
|
||||
assert isinstance(cred, AzureKeyCredential)
|
||||
|
||||
def test_workload_identity_credential(self, monkeypatch, tmp_path):
|
||||
"""Path 1: OIDC federation via WorkloadIdentityCredential."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.test")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
) as mock_cls:
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_wi_cred
|
||||
# Called at least once with the right args (init may also call it)
|
||||
mock_cls.assert_any_call(
|
||||
tenant_id="tenant-123",
|
||||
client_id="client-456",
|
||||
token_file_path=str(token_file),
|
||||
)
|
||||
|
||||
def test_workload_identity_from_env_vars(self, monkeypatch, tmp_path):
|
||||
"""Path 1: All WI fields discovered from environment."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.test")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_TENANT_ID", "env-tenant")
|
||||
monkeypatch.setenv("AZURE_CLIENT_ID", "env-client")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
) as mock_cls:
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_wi_cred
|
||||
mock_cls.assert_any_call(
|
||||
tenant_id="env-tenant",
|
||||
client_id="env-client",
|
||||
token_file_path=str(token_file),
|
||||
)
|
||||
|
||||
def test_client_secret_credential(self, monkeypatch):
|
||||
"""Path 2: Service Principal with client secret."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_CLIENT_SECRET", "sp-secret")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_cs_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.ClientSecretCredential",
|
||||
return_value=mock_cs_cred,
|
||||
) as mock_cls:
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_cs_cred
|
||||
mock_cls.assert_any_call(
|
||||
tenant_id="tenant-123",
|
||||
client_id="client-456",
|
||||
client_secret="sp-secret",
|
||||
)
|
||||
|
||||
def test_default_azure_credential_when_no_api_key(self, monkeypatch):
|
||||
"""Path 3: DefaultAzureCredential when no api_key and no SP/WI vars."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_default_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.DefaultAzureCredential",
|
||||
return_value=mock_default_cred,
|
||||
):
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_default_cred
|
||||
|
||||
def test_workload_identity_takes_priority_over_api_key(self, monkeypatch, tmp_path):
|
||||
"""WI credential should take priority even when api_key is also set."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.test")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_API_KEY", "should-not-use-this")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
):
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_wi_cred
|
||||
|
||||
def test_client_secret_takes_priority_over_api_key(self, monkeypatch):
|
||||
"""SP credential should take priority over API key."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_CLIENT_SECRET", "sp-secret")
|
||||
monkeypatch.setenv("AZURE_API_KEY", "should-not-use-this")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_cs_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.ClientSecretCredential",
|
||||
return_value=mock_cs_cred,
|
||||
):
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_cs_cred
|
||||
|
||||
def test_raises_when_no_api_key_and_no_azure_identity(self, monkeypatch):
|
||||
"""ValueError when no api_key and azure-identity not installed."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
with patch.dict("sys.modules", {"azure.identity": None}):
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
with pytest.raises(ValueError, match="Azure API key is required"):
|
||||
completion._resolve_credential()
|
||||
|
||||
def test_endpoint_still_required(self, monkeypatch, tmp_path):
|
||||
"""Endpoint is always required regardless of credential type."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("test-jwt")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_TENANT_ID", "tenant-123")
|
||||
monkeypatch.setenv("AZURE_CLIENT_ID", "client-456")
|
||||
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
with pytest.raises(ValueError, match="Azure endpoint is required"):
|
||||
completion._make_client_kwargs()
|
||||
|
||||
def test_deferred_build_picks_up_wi_env_vars(self, monkeypatch, tmp_path):
|
||||
"""Env vars set after construction are picked up on deferred build."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
# Construct with endpoint only — no credentials yet
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
|
||||
# Now set WI env vars (simulating WI manager setting them before crew run)
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.deferred")
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_TENANT_ID", "deferred-tenant")
|
||||
monkeypatch.setenv("AZURE_CLIENT_ID", "deferred-client")
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
):
|
||||
kwargs = completion._make_client_kwargs()
|
||||
assert kwargs["credential"] is mock_wi_cred
|
||||
|
||||
def test_make_client_kwargs_includes_api_version(self, monkeypatch):
|
||||
"""api_version is included in client kwargs."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_API_KEY", "test-key")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
completion = AzureCompletion(model="gpt-4", api_version="2025-01-01")
|
||||
kwargs = completion._make_client_kwargs()
|
||||
assert kwargs["api_version"] == "2025-01-01"
|
||||
assert kwargs["endpoint"] == ENDPOINT
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user