From 2451b85e672fc6d068b8ba5a47ea14ce83e2f969 Mon Sep 17 00:00:00 2001 From: Jaison Paul Date: Wed, 13 May 2026 21:38:41 -0400 Subject: [PATCH] fix: update a2a-sdk compatibility Signed-off-by: Jaison Paul --- python/packages/kagent-adk/pyproject.toml | 2 +- .../kagent-adk/src/kagent/adk/_a2a.py | 6 +- .../src/kagent/adk/_agent_executor.py | 16 +++- .../src/kagent/adk/_remote_a2a_tool.py | 39 ++++++---- .../packages/kagent-adk/src/kagent/adk/cli.py | 2 +- .../kagent/adk/converters/event_converter.py | 4 +- .../kagent/adk/converters/part_converter.py | 2 +- .../kagent-adk/src/kagent/adk/types.py | 9 +++ .../converters/test_event_converter.py | 2 +- .../unittests/models/test_sap_ai_core.py | 1 - .../kagent-adk/tests/unittests/test_hitl.py | 2 +- .../tests/unittests/test_remote_a2a_tool.py | 23 +++--- python/packages/kagent-core/pyproject.toml | 2 +- .../src/kagent/core/a2a/__init__.py | 6 +- .../src/kagent/core/a2a/_compat.py | 76 +++++++++++++++++++ .../src/kagent/core/a2a/_hitl_utils.py | 2 +- .../src/kagent/core/a2a/_requests.py | 2 +- .../src/kagent/core/a2a/_server_apps.py | 49 ++++++++++++ .../core/a2a/_task_result_aggregator.py | 2 +- .../src/kagent/core/a2a/_task_store.py | 2 +- .../kagent-core/tests/test_hitl_utils.py | 2 +- python/packages/kagent-crewai/pyproject.toml | 2 +- .../kagent-crewai/src/kagent/crewai/_a2a.py | 6 +- .../src/kagent/crewai/_executor.py | 8 +- .../src/kagent/crewai/_listeners.py | 6 +- .../packages/kagent-langgraph/pyproject.toml | 2 +- .../src/kagent/langgraph/_a2a.py | 6 +- .../src/kagent/langgraph/_converters.py | 2 +- .../src/kagent/langgraph/_executor.py | 8 +- python/packages/kagent-openai/pyproject.toml | 2 +- .../kagent-openai/src/kagent/openai/_a2a.py | 7 +- .../src/kagent/openai/_agent_executor.py | 8 +- .../src/kagent/openai/_event_converter.py | 6 +- python/uv.lock | 57 +++++++++++--- 34 files changed, 286 insertions(+), 85 deletions(-) create mode 100644 python/packages/kagent-core/src/kagent/core/a2a/_compat.py create mode 100644 python/packages/kagent-core/src/kagent/core/a2a/_server_apps.py diff --git a/python/packages/kagent-adk/pyproject.toml b/python/packages/kagent-adk/pyproject.toml index 55ef9047e..ce8ddaa6d 100644 --- a/python/packages/kagent-adk/pyproject.toml +++ b/python/packages/kagent-adk/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "pydantic>=2.5.0", "typing-extensions>=4.8.0", "jsonref>=1.1.0", - "a2a-sdk>=0.3.23", + "a2a-sdk>=1.0.2,<2.0", # Security: pin minimum versions for CVE fixes in transitive dependencies "urllib3>=2.6.3", # CVE-2025-66418, CVE-2025-66471, CVE-2026-21441: unbounded decompression DoS "filelock>=3.20.3", # CVE-2025-68146, CVE-2026-22701: TOCTOU symlink race condition diff --git a/python/packages/kagent-adk/src/kagent/adk/_a2a.py b/python/packages/kagent-adk/src/kagent/adk/_a2a.py index 7b08e6f17..1432bcf10 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_a2a.py +++ b/python/packages/kagent-adk/src/kagent/adk/_a2a.py @@ -5,10 +5,10 @@ from typing import Any, Callable, List, Optional import httpx -from a2a.server.apps import A2AFastAPIApplication +from a2a.compat.v0_3.conversions import to_core_agent_card +from a2a.compat.v0_3.types import AgentCard from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore -from a2a.types import AgentCard from agentsts.adk import ADKSTSIntegration, ADKTokenPropagationPlugin from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse @@ -25,6 +25,7 @@ KAgentTaskStore, get_a2a_max_content_length, ) +from kagent.core.a2a._server_apps import A2AFastAPIApplication from ._agent_executor import A2aAgentExecutor, A2aAgentExecutorConfig from ._lifespan import LifespanManager @@ -147,6 +148,7 @@ def create_runner() -> Runner: request_handler = DefaultRequestHandler( agent_executor=agent_executor, task_store=task_store, + agent_card=to_core_agent_card(self.agent_card), request_context_builder=request_context_builder, ) diff --git a/python/packages/kagent-adk/src/kagent/adk/_agent_executor.py b/python/packages/kagent-adk/src/kagent/adk/_agent_executor.py index 0ed10a317..3954fce82 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_agent_executor.py +++ b/python/packages/kagent-adk/src/kagent/adk/_agent_executor.py @@ -1,5 +1,6 @@ from __future__ import annotations +# ruff: noqa: E402 import asyncio import inspect import logging @@ -8,9 +9,7 @@ from datetime import datetime, timezone from typing import Any, Awaitable, Callable, Optional -from a2a.server.agent_execution.context import RequestContext -from a2a.server.events.event_queue import EventQueue -from a2a.types import ( +from a2a.compat.v0_3.types import ( Artifact, Message, Part, @@ -21,12 +20,23 @@ TaskStatusUpdateEvent, TextPart, ) +from a2a.server.agent_execution.context import RequestContext +from a2a.server.events.event_queue import EventQueue +from kagent.core.a2a._compat import install_v03_type_aliases, restore_a2a_type_aliases + +# google-adk imports v0.3 A2A types from a2a.types during module import. +# Keep these imports below the temporary alias install, then restore the 1.x +# namespace so a2a-sdk internals continue to see their native protobuf types. +_a2a_type_originals = install_v03_type_aliases(overwrite=True) + from google.adk.a2a.executor.a2a_agent_executor import ( A2aAgentExecutor as UpstreamA2aAgentExecutor, ) from google.adk.a2a.executor.a2a_agent_executor import ( A2aAgentExecutorConfig as UpstreamA2aAgentExecutorConfig, ) + +restore_a2a_type_aliases(_a2a_type_originals) from google.adk.events import Event, EventActions from google.adk.flows.llm_flows.functions import REQUEST_CONFIRMATION_FUNCTION_CALL_NAME from google.adk.runners import Runner diff --git a/python/packages/kagent-adk/src/kagent/adk/_remote_a2a_tool.py b/python/packages/kagent-adk/src/kagent/adk/_remote_a2a_tool.py index 3cbaabcb2..f43ee558b 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_remote_a2a_tool.py +++ b/python/packages/kagent-adk/src/kagent/adk/_remote_a2a_tool.py @@ -11,19 +11,25 @@ This is a BaseToolset wrapper around KAgentRemoteA2ATool for runner cleanup purposes. """ +# ruff: noqa: E402 + import logging import uuid from typing import Any, Callable, Optional, Protocol, runtime_checkable from urllib.parse import urlparse import httpx +from kagent.core.a2a._compat import install_v03_type_aliases + +install_v03_type_aliases() + from a2a.client import Client as A2AClient from a2a.client.card_resolver import A2ACardResolver from a2a.client.client import ClientConfig as A2AClientConfig from a2a.client.client_factory import ClientFactory as A2AClientFactory from a2a.client.errors import A2AClientHTTPError from a2a.client.middleware import ClientCallContext, ClientCallInterceptor -from a2a.types import ( +from a2a.compat.v0_3.types import ( AgentCard, DataPart, Role, @@ -31,13 +37,13 @@ TaskState, TextPart, ) -from a2a.types import ( +from a2a.compat.v0_3.types import ( Message as A2AMessage, ) -from a2a.types import ( +from a2a.compat.v0_3.types import ( Part as A2APart, ) -from a2a.types import ( +from a2a.compat.v0_3.types import ( TransportProtocol as A2ATransport, ) from google.adk.agents.readonly_context import ReadonlyContext @@ -70,18 +76,23 @@ class _SubagentInterceptor(ClientCallInterceptor): headers stored in the call context state under ``_EXTRA_HEADERS_CONTEXT_KEY``. """ - async def intercept(self, method_name, request_payload, http_kwargs, agent_card, context): - headers = dict(http_kwargs.get("headers", {})) + async def before(self, args) -> None: + context = args.context + if context is None: + return + headers = dict(context.service_parameters or {}) headers[_SOURCE_HEADER] = _SOURCE_SUBAGENT - if context: - if _USER_ID_CONTEXT_KEY in context.state: - headers["x-user-id"] = context.state[_USER_ID_CONTEXT_KEY] - extra = context.state.get(_EXTRA_HEADERS_CONTEXT_KEY) - if extra: - headers.update(extra) - http_kwargs["headers"] = headers - return request_payload, http_kwargs + if _USER_ID_CONTEXT_KEY in context.state: + headers["x-user-id"] = context.state[_USER_ID_CONTEXT_KEY] + extra = context.state.get(_EXTRA_HEADERS_CONTEXT_KEY) + if extra: + headers.update(extra) + + context.service_parameters = headers + + async def after(self, args) -> None: + return None def _extract_text_from_task(task: Task) -> str: diff --git a/python/packages/kagent-adk/src/kagent/adk/cli.py b/python/packages/kagent-adk/src/kagent/adk/cli.py index e32d0aacb..f8fca3c5f 100644 --- a/python/packages/kagent-adk/src/kagent/adk/cli.py +++ b/python/packages/kagent-adk/src/kagent/adk/cli.py @@ -7,7 +7,7 @@ import typer import uvicorn -from a2a.types import AgentCard +from a2a.compat.v0_3.types import AgentCard from agentsts.adk import ADKSTSIntegration, ADKTokenPropagationPlugin from google.adk.agents import BaseAgent from google.adk.cli.utils.agent_loader import AgentLoader diff --git a/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py b/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py index 92c4b664d..24780552d 100644 --- a/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py +++ b/python/packages/kagent-adk/src/kagent/adk/converters/event_converter.py @@ -5,9 +5,9 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Optional +from a2a.compat.v0_3.types import DataPart, Message, Role, Task, TaskState, TaskStatus, TaskStatusUpdateEvent, TextPart +from a2a.compat.v0_3.types import Part as A2APart from a2a.server.events import Event as A2AEvent -from a2a.types import DataPart, Message, Role, Task, TaskState, TaskStatus, TaskStatusUpdateEvent, TextPart -from a2a.types import Part as A2APart from google.adk.agents.invocation_context import InvocationContext from google.adk.events.event import Event from google.adk.flows.llm_flows.functions import REQUEST_EUC_FUNCTION_CALL_NAME diff --git a/python/packages/kagent-adk/src/kagent/adk/converters/part_converter.py b/python/packages/kagent-adk/src/kagent/adk/converters/part_converter.py index c0e70e031..5267a6735 100644 --- a/python/packages/kagent-adk/src/kagent/adk/converters/part_converter.py +++ b/python/packages/kagent-adk/src/kagent/adk/converters/part_converter.py @@ -23,7 +23,7 @@ import logging from typing import Optional -from a2a import types as a2a_types +from a2a.compat.v0_3 import types as a2a_types from google.genai import types as genai_types from kagent.core.a2a import ( A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT, diff --git a/python/packages/kagent-adk/src/kagent/adk/types.py b/python/packages/kagent-adk/src/kagent/adk/types.py index 5e2f4a97a..332f8ad58 100644 --- a/python/packages/kagent-adk/src/kagent/adk/types.py +++ b/python/packages/kagent-adk/src/kagent/adk/types.py @@ -1,8 +1,17 @@ +# ruff: noqa: E402 + import logging from typing import Any, Callable, Literal, Optional, Union import httpx from agentsts.adk import ADKTokenPropagationPlugin +from kagent.core.a2a._compat import install_v03_type_aliases + +# google-adk's RemoteA2aAgent still imports the removed v0.3 model names from +# a2a.types. This import-order guard keeps that transitive import working with +# a2a-sdk 1.x; keep the google.adk imports below this call. +install_v03_type_aliases(overwrite=True) + from google.adk.agents import Agent from google.adk.agents.callback_context import CallbackContext from google.adk.agents.llm_agent import ToolUnion diff --git a/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py b/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py index 6040a7e04..5f39b25be 100644 --- a/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py +++ b/python/packages/kagent-adk/tests/unittests/converters/test_event_converter.py @@ -1,7 +1,7 @@ from unittest.mock import Mock import pytest -from a2a.types import TaskState, TaskStatusUpdateEvent +from a2a.compat.v0_3.types import TaskState, TaskStatusUpdateEvent from google.genai import types as genai_types from kagent.core.a2a import get_kagent_metadata_key diff --git a/python/packages/kagent-adk/tests/unittests/models/test_sap_ai_core.py b/python/packages/kagent-adk/tests/unittests/models/test_sap_ai_core.py index aace9575a..fab636b54 100644 --- a/python/packages/kagent-adk/tests/unittests/models/test_sap_ai_core.py +++ b/python/packages/kagent-adk/tests/unittests/models/test_sap_ai_core.py @@ -17,7 +17,6 @@ _parse_orchestration_chunk, ) - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- diff --git a/python/packages/kagent-adk/tests/unittests/test_hitl.py b/python/packages/kagent-adk/tests/unittests/test_hitl.py index 4ad2371de..3367913b4 100644 --- a/python/packages/kagent-adk/tests/unittests/test_hitl.py +++ b/python/packages/kagent-adk/tests/unittests/test_hitl.py @@ -3,7 +3,7 @@ import json from unittest.mock import MagicMock -from a2a.types import DataPart, Message, Part, Role +from a2a.compat.v0_3.types import DataPart, Message, Part, Role from google.adk.flows.llm_flows.functions import REQUEST_CONFIRMATION_FUNCTION_CALL_NAME from google.adk.sessions import Session from google.adk.tools.tool_confirmation import ToolConfirmation diff --git a/python/packages/kagent-adk/tests/unittests/test_remote_a2a_tool.py b/python/packages/kagent-adk/tests/unittests/test_remote_a2a_tool.py index 8682741bb..0fcc49898 100644 --- a/python/packages/kagent-adk/tests/unittests/test_remote_a2a_tool.py +++ b/python/packages/kagent-adk/tests/unittests/test_remote_a2a_tool.py @@ -1,10 +1,11 @@ """Tests for KAgentRemoteA2ATool.""" +from types import SimpleNamespace from typing import Any, AsyncIterator from unittest.mock import AsyncMock, MagicMock, patch import httpx -from a2a.types import ( +from a2a.compat.v0_3.types import ( DataPart, Role, Task, @@ -12,8 +13,8 @@ TaskStatus, TextPart, ) -from a2a.types import Message as A2AMessage -from a2a.types import Part as A2APart +from a2a.compat.v0_3.types import Message as A2AMessage +from a2a.compat.v0_3.types import Part as A2APart from google.adk.tools.tool_confirmation import ToolConfirmation from kagent.core.a2a import ( KAGENT_HITL_DECISION_TYPE_APPROVE, @@ -148,22 +149,16 @@ def _approval_ctx(confirmed: bool, payload: dict | None = None, **kwargs) -> Moc class TestSubagentInterceptorHeaderPropagation: """Tests for header propagation in _SubagentInterceptor via context state.""" - async def _call_intercept(self, interceptor, state: dict) -> dict: + async def _call_before(self, interceptor, state: dict) -> dict: from a2a.client.middleware import ClientCallContext ctx = ClientCallContext(state=state) - _, http_kwargs = await interceptor.intercept( - method_name="message/send", - request_payload={}, - http_kwargs={}, - agent_card=None, - context=ctx, - ) - return http_kwargs.get("headers", {}) + await interceptor.before(SimpleNamespace(context=ctx)) + return ctx.service_parameters or {} async def test_forwards_extra_headers_from_context_state(self): interceptor = _SubagentInterceptor() - headers = await self._call_intercept( + headers = await self._call_before( interceptor, state={"x-user-id": "user1", "_a2a_extra_headers": {"authorization": "Bearer test-jwt"}}, ) @@ -171,7 +166,7 @@ async def test_forwards_extra_headers_from_context_state(self): async def test_no_extra_headers_without_state_key(self): interceptor = _SubagentInterceptor() - headers = await self._call_intercept( + headers = await self._call_before( interceptor, state={"x-user-id": "user1", "authorization": "Bearer test-jwt"}, ) diff --git a/python/packages/kagent-core/pyproject.toml b/python/packages/kagent-core/pyproject.toml index ce2333fdc..8e81e2c5e 100644 --- a/python/packages/kagent-core/pyproject.toml +++ b/python/packages/kagent-core/pyproject.toml @@ -9,7 +9,7 @@ description = "kagent common library for kagent python packages" readme = "README.md" requires-python = ">=3.10" dependencies = [ - "a2a-sdk[http-server]>=0.3.23", + "a2a-sdk[http-server]>=1.0.2,<2.0", "opentelemetry-api>=1.38.0,<1.39.0", "opentelemetry-sdk>=1.38.0,<1.39.0", "opentelemetry-exporter-otlp-proto-grpc>=1.38.0,<1.39.0", diff --git a/python/packages/kagent-core/src/kagent/core/a2a/__init__.py b/python/packages/kagent-core/src/kagent/core/a2a/__init__.py index 3de48d635..a1dd8c778 100644 --- a/python/packages/kagent-core/src/kagent/core/a2a/__init__.py +++ b/python/packages/kagent-core/src/kagent/core/a2a/__init__.py @@ -1,5 +1,5 @@ +from ._compat import install_v03_type_aliases from ._config import get_a2a_max_content_length -from ._context import get_request_user_id, set_request_user_id from ._consts import ( A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY, A2A_DATA_PART_METADATA_TYPE_CODE_EXECUTION_RESULT, @@ -18,6 +18,7 @@ get_kagent_metadata_key, read_metadata_value, ) +from ._context import get_request_user_id, set_request_user_id from ._hitl_utils import ( DecisionType, HitlPartInfo, @@ -32,7 +33,10 @@ from ._task_result_aggregator import TaskResultAggregator from ._task_store import KAgentTaskStore +install_v03_type_aliases() + __all__ = [ + "install_v03_type_aliases", "get_a2a_max_content_length", "get_request_user_id", "set_request_user_id", diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_compat.py b/python/packages/kagent-core/src/kagent/core/a2a/_compat.py new file mode 100644 index 000000000..0733412cc --- /dev/null +++ b/python/packages/kagent-core/src/kagent/core/a2a/_compat.py @@ -0,0 +1,76 @@ +"""Compatibility helpers for a2a-sdk 1.x.""" + +_V03_TYPE_ALIAS_NAMES = ( + "AgentCard", + "Artifact", + "DataPart", + "FilePart", + "Message", + "MessageSendConfiguration", + "MessageSendParams", + "Part", + "PartBase", + "Role", + "Task", + "TaskArtifactUpdateEvent", + "TaskIdParams", + "TaskQueryParams", + "TaskState", + "TaskStatus", + "TaskStatusUpdateEvent", + "TextPart", + "TransportProtocol", +) + + +def install_v03_type_aliases(*, overwrite: bool = False) -> dict[str, object]: + """Expose removed v0.3 Pydantic type aliases for transitive imports. + + google-adk still imports several v0.3 model names from ``a2a.types``. + a2a-sdk 1.x keeps those models under ``a2a.compat.v0_3.types`` instead. + """ + + import sys + import types + + import a2a.client as a2a_client + import a2a.client.errors as a2a_client_errors + import a2a.types as a2a_types + from a2a.compat.v0_3 import types as a2a_v03_types + + originals = {} + for name in _V03_TYPE_ALIAS_NAMES: + if hasattr(a2a_types, name): + originals[name] = getattr(a2a_types, name) + if (overwrite or not hasattr(a2a_types, name)) and hasattr(a2a_v03_types, name): + setattr(a2a_types, name, getattr(a2a_v03_types, name)) + + if not hasattr(a2a_client_errors, "A2AClientHTTPError"): + a2a_client_errors.A2AClientHTTPError = a2a_client_errors.A2AClientError + + if not hasattr(a2a_client, "ClientEvent"): + a2a_client.ClientEvent = tuple + + if "a2a.client.middleware" not in sys.modules: + middleware = types.ModuleType("a2a.client.middleware") + middleware.ClientCallContext = a2a_client.ClientCallContext + middleware.ClientCallInterceptor = a2a_client.ClientCallInterceptor + + def __getattr__(name: str) -> object: + raise AttributeError( + "a2a.client.middleware was removed in a2a-sdk 1.x; " + f"KAgent only provides compatibility aliases for ClientCallContext " + f"and ClientCallInterceptor, not {name!r}." + ) + + middleware.__getattr__ = __getattr__ + sys.modules["a2a.client.middleware"] = middleware + + return originals + + +def restore_a2a_type_aliases(originals: dict[str, object]) -> None: + import a2a.types as a2a_types + + for name, value in originals.items(): + setattr(a2a_types, name, value) diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_hitl_utils.py b/python/packages/kagent-core/src/kagent/core/a2a/_hitl_utils.py index 29ef107be..6c3df9fd4 100644 --- a/python/packages/kagent-core/src/kagent/core/a2a/_hitl_utils.py +++ b/python/packages/kagent-core/src/kagent/core/a2a/_hitl_utils.py @@ -9,7 +9,7 @@ import logging from typing import Any, Literal -from a2a.types import ( +from a2a.compat.v0_3.types import ( DataPart, Message, Task, diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_requests.py b/python/packages/kagent-core/src/kagent/core/a2a/_requests.py index 13b36ffa9..956ac1de1 100644 --- a/python/packages/kagent-core/src/kagent/core/a2a/_requests.py +++ b/python/packages/kagent-core/src/kagent/core/a2a/_requests.py @@ -1,10 +1,10 @@ import logging from a2a.auth.user import User +from a2a.compat.v0_3.types import MessageSendParams, Task from a2a.server.agent_execution import RequestContext, SimpleRequestContextBuilder from a2a.server.context import ServerCallContext from a2a.server.tasks import TaskStore -from a2a.types import MessageSendParams, Task from ._context import set_request_user_id diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_server_apps.py b/python/packages/kagent-core/src/kagent/core/a2a/_server_apps.py new file mode 100644 index 000000000..261867c14 --- /dev/null +++ b/python/packages/kagent-core/src/kagent/core/a2a/_server_apps.py @@ -0,0 +1,49 @@ +"""Compatibility wrappers for a2a-sdk HTTP route registration. + +a2a-sdk 1.x removed the old ``a2a.server.apps`` helper classes. KAgent still +uses the v0.3 Pydantic model surface internally, so these wrappers keep the +local app-building code small while registering the 1.x Starlette routes with +v0.3 JSON-RPC compatibility enabled. +""" + +from urllib.parse import urlparse + +from a2a.compat.v0_3.conversions import to_core_agent_card +from a2a.compat.v0_3.types import AgentCard +from a2a.server.request_handlers import RequestHandler +from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes + + +def _route_path(url: str | None) -> str: + parsed = urlparse(url or "/") + return parsed.path or "/" + + +class A2AStarletteApplication: + def __init__( + self, + *, + agent_card: AgentCard, + http_handler: RequestHandler, + max_content_length: int | None = None, + ) -> None: + self.agent_card = agent_card + self.http_handler = http_handler + self.max_content_length = max_content_length + + def add_routes_to_app(self, app) -> None: + core_agent_card = to_core_agent_card(self.agent_card) + for route in create_agent_card_routes(core_agent_card): + app.router.routes.append(route) + for route in create_jsonrpc_routes( + self.http_handler, + rpc_url=_route_path(self.agent_card.url), + enable_v0_3_compat=True, + ): + app.router.routes.append(route) + + +class A2AFastAPIApplication(A2AStarletteApplication): + """Name-compatible alias for the old a2a.server.apps FastAPI helper.""" + + pass diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_task_result_aggregator.py b/python/packages/kagent-core/src/kagent/core/a2a/_task_result_aggregator.py index 403be62fd..24964dee2 100644 --- a/python/packages/kagent-core/src/kagent/core/a2a/_task_result_aggregator.py +++ b/python/packages/kagent-core/src/kagent/core/a2a/_task_result_aggregator.py @@ -1,5 +1,5 @@ +from a2a.compat.v0_3.types import Message, TaskState, TaskStatusUpdateEvent from a2a.server.events import Event -from a2a.types import Message, TaskState, TaskStatusUpdateEvent class TaskResultAggregator: diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py b/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py index 134fa25e9..bdd5c1e98 100644 --- a/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py +++ b/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py @@ -1,8 +1,8 @@ import asyncio import httpx +from a2a.compat.v0_3.types import Message, Task from a2a.server.tasks import TaskStore -from a2a.types import Message, Task from pydantic import BaseModel from typing_extensions import override diff --git a/python/packages/kagent-core/tests/test_hitl_utils.py b/python/packages/kagent-core/tests/test_hitl_utils.py index aa980d883..ac6bbf78a 100644 --- a/python/packages/kagent-core/tests/test_hitl_utils.py +++ b/python/packages/kagent-core/tests/test_hitl_utils.py @@ -1,6 +1,6 @@ """Tests for HITL utility functions in kagent.core.a2a._hitl_utils.""" -from a2a.types import DataPart, Message, Part, Role, Task, TaskState, TaskStatus +from a2a.compat.v0_3.types import DataPart, Message, Part, Role, Task, TaskState, TaskStatus from kagent.core.a2a import ( KAGENT_HITL_DECISION_TYPE_APPROVE, diff --git a/python/packages/kagent-crewai/pyproject.toml b/python/packages/kagent-crewai/pyproject.toml index 69cb3eb5a..31f1e30f9 100644 --- a/python/packages/kagent-crewai/pyproject.toml +++ b/python/packages/kagent-crewai/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "pydantic>=2.0.0", "typing-extensions>=4.0.0", "uvicorn>=0.20.0", - "a2a-sdk[http-server]>=0.3.23", + "a2a-sdk[http-server]>=1.0.2,<2.0", "kagent-core>=0.1.0", "opentelemetry-instrumentation-crewai>=0.47.3", "google-genai>=1.21.1" diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py b/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py index 957047c1d..3d73b1e3d 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py @@ -4,9 +4,9 @@ from typing import Union import httpx -from a2a.server.apps import A2AStarletteApplication +from a2a.compat.v0_3.conversions import to_core_agent_card +from a2a.compat.v0_3.types import AgentCard from a2a.server.request_handlers import DefaultRequestHandler -from a2a.types import AgentCard from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse from kagent.core import KAgentConfig, configure_tracing @@ -15,6 +15,7 @@ KAgentTaskStore, get_a2a_max_content_length, ) +from kagent.core.a2a._server_apps import A2AStarletteApplication from opentelemetry.instrumentation.crewai import CrewAIInstrumentor from crewai import Crew, Flow @@ -68,6 +69,7 @@ def build(self) -> FastAPI: request_handler = DefaultRequestHandler( agent_executor=agent_executor, task_store=task_store, + agent_card=to_core_agent_card(self.agent_card), request_context_builder=request_context_builder, ) diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_executor.py b/python/packages/kagent-crewai/src/kagent/crewai/_executor.py index 56f8d3e01..b5feea0c2 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_executor.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_executor.py @@ -9,10 +9,7 @@ from typing_extensions import override import httpx -from a2a.server.agent_execution import AgentExecutor -from a2a.server.agent_execution.context import RequestContext -from a2a.server.events.event_queue import EventQueue -from a2a.types import ( +from a2a.compat.v0_3.types import ( Artifact, DataPart, Message, @@ -24,6 +21,9 @@ TaskStatusUpdateEvent, TextPart, ) +from a2a.server.agent_execution import AgentExecutor +from a2a.server.agent_execution.context import RequestContext +from a2a.server.events.event_queue import EventQueue from kagent.core.tracing._span_processor import ( clear_kagent_span_attributes, set_kagent_span_attributes, diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_listeners.py b/python/packages/kagent-crewai/src/kagent/crewai/_listeners.py index 38378901c..aa4bc9a3a 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_listeners.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_listeners.py @@ -3,9 +3,7 @@ from datetime import datetime, timezone from typing import Any -from a2a.server.agent_execution.context import RequestContext -from a2a.server.events.event_queue import EventQueue -from a2a.types import ( +from a2a.compat.v0_3.types import ( DataPart, Message, Part, @@ -15,6 +13,8 @@ TaskStatusUpdateEvent, TextPart, ) +from a2a.server.agent_execution.context import RequestContext +from a2a.server.events.event_queue import EventQueue from kagent.core.a2a import ( A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL, A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE, diff --git a/python/packages/kagent-langgraph/pyproject.toml b/python/packages/kagent-langgraph/pyproject.toml index d84417738..52a232c3b 100644 --- a/python/packages/kagent-langgraph/pyproject.toml +++ b/python/packages/kagent-langgraph/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "pydantic>=2.0.0", "typing-extensions>=4.0.0", "uvicorn>=0.20.0", - "a2a-sdk>=0.3.23", + "a2a-sdk>=1.0.2,<2.0", "kagent-core>=0.1.0", "langsmith[otel]>=0.4.30", ] diff --git a/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py b/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py index 0f3be75d5..d22389ee0 100644 --- a/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py +++ b/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py @@ -8,9 +8,9 @@ import logging import httpx -from a2a.server.apps import A2AStarletteApplication +from a2a.compat.v0_3.conversions import to_core_agent_card +from a2a.compat.v0_3.types import AgentCard from a2a.server.request_handlers import DefaultRequestHandler -from a2a.types import AgentCard from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse from kagent.core import KAgentConfig, configure_tracing @@ -19,6 +19,7 @@ KAgentTaskStore, get_a2a_max_content_length, ) +from kagent.core.a2a._server_apps import A2AStarletteApplication from langgraph.graph.state import CompiledStateGraph @@ -102,6 +103,7 @@ def build(self) -> FastAPI: request_handler = DefaultRequestHandler( agent_executor=agent_executor, task_store=task_store, + agent_card=to_core_agent_card(self.agent_card), request_context_builder=request_context_builder, ) diff --git a/python/packages/kagent-langgraph/src/kagent/langgraph/_converters.py b/python/packages/kagent-langgraph/src/kagent/langgraph/_converters.py index 9fb66358f..004711746 100644 --- a/python/packages/kagent-langgraph/src/kagent/langgraph/_converters.py +++ b/python/packages/kagent-langgraph/src/kagent/langgraph/_converters.py @@ -16,7 +16,7 @@ UTC = timezone.utc -from a2a.types import ( +from a2a.compat.v0_3.types import ( DataPart, Message, Part, diff --git a/python/packages/kagent-langgraph/src/kagent/langgraph/_executor.py b/python/packages/kagent-langgraph/src/kagent/langgraph/_executor.py index 4663bd226..18005a78f 100644 --- a/python/packages/kagent-langgraph/src/kagent/langgraph/_executor.py +++ b/python/packages/kagent-langgraph/src/kagent/langgraph/_executor.py @@ -23,10 +23,7 @@ except ImportError: from typing_extensions import override -from a2a.server.agent_execution import AgentExecutor -from a2a.server.agent_execution.context import RequestContext -from a2a.server.events.event_queue import EventQueue -from a2a.types import ( +from a2a.compat.v0_3.types import ( Artifact, DataPart, Message, @@ -38,6 +35,9 @@ TaskStatusUpdateEvent, TextPart, ) +from a2a.server.agent_execution import AgentExecutor +from a2a.server.agent_execution.context import RequestContext +from a2a.server.events.event_queue import EventQueue from kagent.core.a2a import ( A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY, A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL, diff --git a/python/packages/kagent-openai/pyproject.toml b/python/packages/kagent-openai/pyproject.toml index 9c0751e85..2c946b75c 100644 --- a/python/packages/kagent-openai/pyproject.toml +++ b/python/packages/kagent-openai/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.10" dependencies = [ "openai>=1.72.0", "openai-agents>=0.4.0", - "a2a-sdk>=0.3.23", + "a2a-sdk>=1.0.2,<2.0", "kagent-core>=0.1.0", "kagent-skills>=0.1.0", "httpx>=0.25.0", diff --git a/python/packages/kagent-openai/src/kagent/openai/_a2a.py b/python/packages/kagent-openai/src/kagent/openai/_a2a.py index 131a4283a..a2612df48 100644 --- a/python/packages/kagent-openai/src/kagent/openai/_a2a.py +++ b/python/packages/kagent-openai/src/kagent/openai/_a2a.py @@ -12,10 +12,10 @@ from collections.abc import Callable import httpx -from a2a.server.apps import A2AFastAPIApplication +from a2a.compat.v0_3.conversions import to_core_agent_card +from a2a.compat.v0_3.types import AgentCard from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore -from a2a.types import AgentCard from agents import Agent, set_default_openai_api, set_default_openai_client, set_tracing_disabled from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse @@ -25,6 +25,7 @@ KAgentTaskStore, get_a2a_max_content_length, ) +from kagent.core.a2a._server_apps import A2AFastAPIApplication from opentelemetry.instrumentation.openai_agents import OpenAIAgentsInstrumentor from openai import AsyncOpenAI @@ -145,6 +146,7 @@ def build(self) -> FastAPI: request_handler = DefaultRequestHandler( agent_executor=agent_executor, task_store=kagent_task_store, + agent_card=to_core_agent_card(self.agent_card), request_context_builder=request_context_builder, ) @@ -218,6 +220,7 @@ def build_local(self) -> FastAPI: request_handler = DefaultRequestHandler( agent_executor=agent_executor, task_store=task_store, + agent_card=to_core_agent_card(self.agent_card), request_context_builder=request_context_builder, ) diff --git a/python/packages/kagent-openai/src/kagent/openai/_agent_executor.py b/python/packages/kagent-openai/src/kagent/openai/_agent_executor.py index d1711fb23..9dd493368 100644 --- a/python/packages/kagent-openai/src/kagent/openai/_agent_executor.py +++ b/python/packages/kagent-openai/src/kagent/openai/_agent_executor.py @@ -25,10 +25,7 @@ except ImportError: from typing_extensions import override -from a2a.server.agent_execution import AgentExecutor -from a2a.server.agent_execution.context import RequestContext -from a2a.server.events.event_queue import EventQueue -from a2a.types import ( +from a2a.compat.v0_3.types import ( Artifact, Message, Part, @@ -39,6 +36,9 @@ TaskStatusUpdateEvent, TextPart, ) +from a2a.server.agent_execution import AgentExecutor +from a2a.server.agent_execution.context import RequestContext +from a2a.server.events.event_queue import EventQueue from agents.agent import Agent from agents.run import Runner from kagent.core.a2a import TaskResultAggregator, get_kagent_metadata_key diff --git a/python/packages/kagent-openai/src/kagent/openai/_event_converter.py b/python/packages/kagent-openai/src/kagent/openai/_event_converter.py index b1c92c783..852440689 100644 --- a/python/packages/kagent-openai/src/kagent/openai/_event_converter.py +++ b/python/packages/kagent-openai/src/kagent/openai/_event_converter.py @@ -17,8 +17,7 @@ UTC = timezone.utc -from a2a.server.events import Event as A2AEvent -from a2a.types import ( +from a2a.compat.v0_3.types import ( DataPart, Message, Role, @@ -27,7 +26,8 @@ TaskStatusUpdateEvent, TextPart, ) -from a2a.types import Part as A2APart +from a2a.compat.v0_3.types import Part as A2APart +from a2a.server.events import Event as A2AEvent from agents.items import MessageOutputItem, ToolCallItem, ToolCallOutputItem from agents.stream_events import ( AgentUpdatedStreamEvent, diff --git a/python/uv.lock b/python/uv.lock index 360afbbbc..4bb43dd17 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -43,23 +43,26 @@ dev = [ [[package]] name = "a2a-sdk" -version = "0.3.23" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "culsans", marker = "python_full_version < '3.13'" }, { name = "google-api-core" }, + { name = "googleapis-common-protos" }, { name = "httpx" }, { name = "httpx-sse" }, + { name = "json-rpc" }, + { name = "packaging" }, { name = "protobuf" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/2fe24e0a85240a651006c12f79bdb37156adc760a96c44bc002ebda77916/a2a_sdk-0.3.23.tar.gz", hash = "sha256:7c46b8572c4633a2b41fced2833e11e62871e8539a5b3c782ba2ba1e33d213c2", size = 255265, upload-time = "2026-02-17T08:34:34.648Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/35/8b7ac94f405f57c591925fa0afc105a0f797151876fffa666b57722eefa9/a2a_sdk-1.0.3.tar.gz", hash = "sha256:c57ddd910aece4a426ae26b8f0d0e8e2f3271a6adde974078075e4f600aaf628", size = 367155, upload-time = "2026-05-13T06:52:33.929Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/20/77d119f19ab03449d3e6bc0b1f11296d593dae99775c1d891ab1e290e416/a2a_sdk-0.3.23-py3-none-any.whl", hash = "sha256:8c2f01dffbfdd3509eafc15c4684743e6ae75e69a5df5d6f87be214c948e7530", size = 145689, upload-time = "2026-02-17T08:34:33.263Z" }, + { url = "https://files.pythonhosted.org/packages/53/6f/ae79f8210f1ecd70e1c37c310a523b26f1d6da458d4c1365914bf1ea58e0/a2a_sdk-1.0.3-py3-none-any.whl", hash = "sha256:068e5b2ceb4e962ac61d9e1fd43ca0c1016b64f0c80d901f6e23420bc8a31a93", size = 235705, upload-time = "2026-05-13T06:52:31.88Z" }, ] [package.optional-dependencies] http-server = [ - { name = "fastapi" }, { name = "sse-starlette" }, { name = "starlette" }, ] @@ -242,6 +245,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, ] +[[package]] +name = "aiologic" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sniffio", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "wrapt", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/13/50b91a3ea6b030d280d2654be97c48b6ed81753a50286ee43c646ba36d3c/aiologic-0.16.0.tar.gz", hash = "sha256:c267ccbd3ff417ec93e78d28d4d577ccca115d5797cdbd16785a551d9658858f", size = 225952, upload-time = "2025-11-27T23:48:41.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/27/206615942005471499f6fbc36621582e24d0686f33c74b2d018fcfd4fe67/aiologic-0.16.0-py3-none-any.whl", hash = "sha256:e00ce5f68c5607c864d26aec99c0a33a83bdf8237aa7312ffbb96805af67d8b6", size = 135193, upload-time = "2025-11-27T23:48:40.099Z" }, +] + [[package]] name = "aiosignal" version = "1.4.0" @@ -915,6 +932,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, ] +[[package]] +name = "culsans" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiologic", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/5d/9fb19fb38f6d6120422064279ea5532e22b84aa2be8831d49607194feda3/culsans-0.11.0-py3-none-any.whl", hash = "sha256:278d118f63fc75b9db11b664b436a1b83cc30d9577127848ba41420e66eb5a47", size = 21811, upload-time = "2025-12-31T23:15:37.189Z" }, +] + [[package]] name = "currency" version = "0.1.0" @@ -2212,6 +2242,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/43/ac6691c7b5aa7191c964a04ae926d2bb06d9297dba1f2287df5b85cb3715/json_repair-0.25.2-py3-none-any.whl", hash = "sha256:51d67295c3184b6c41a3572689661c6128cef6cfc9fb04db63130709adfc5bf0", size = 12740, upload-time = "2024-06-27T16:26:13.823Z" }, ] +[[package]] +name = "json-rpc" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/9e/59f4a5b7855ced7346ebf40a2e9a8942863f644378d956f68bcef2c88b90/json-rpc-1.15.0.tar.gz", hash = "sha256:e6441d56c1dcd54241c937d0a2dcd193bdf0bdc539b5316524713f554b7f85b9", size = 28854, upload-time = "2023-06-11T09:45:49.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/9e/820c4b086ad01ba7d77369fb8b11470a01fac9b4977f02e18659cf378b6b/json_rpc-1.15.0-py2.py3-none-any.whl", hash = "sha256:4a4668bbbe7116feb4abbd0f54e64a4adcf4b8f648f19ffa0848ad0f6606a9bf", size = 39450, upload-time = "2023-06-11T09:45:47.136Z" }, +] + [[package]] name = "json5" version = "0.12.1" @@ -2325,7 +2364,7 @@ test = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", specifier = ">=0.3.23" }, + { name = "a2a-sdk", specifier = ">=1.0.2,<2.0" }, { name = "agentsts-adk", editable = "packages/agentsts-adk" }, { name = "agentsts-core", editable = "packages/agentsts-core" }, { name = "aiofiles", specifier = ">=24.1.0" }, @@ -2379,7 +2418,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", extras = ["http-server"], specifier = ">=0.3.23" }, + { name = "a2a-sdk", extras = ["http-server"], specifier = ">=1.0.2,<2.0" }, { name = "opentelemetry-api", specifier = ">=1.38.0,<1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.38.0,<1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.38.0,<1.39.0" }, @@ -2419,7 +2458,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", extras = ["http-server"], specifier = ">=0.3.23" }, + { name = "a2a-sdk", extras = ["http-server"], specifier = ">=1.0.2,<2.0" }, { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, { name = "crewai", extras = ["tools"], specifier = ">=1.2.0" }, { name = "fastapi", specifier = ">=0.100.0" }, @@ -2463,7 +2502,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", specifier = ">=0.3.23" }, + { name = "a2a-sdk", specifier = ">=1.0.2,<2.0" }, { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, { name = "fastapi", specifier = ">=0.100.0" }, { name = "httpx", specifier = ">=0.25.0" }, @@ -2508,7 +2547,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "a2a-sdk", specifier = ">=0.3.23" }, + { name = "a2a-sdk", specifier = ">=1.0.2,<2.0" }, { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, { name = "fastapi", specifier = ">=0.100.0" }, { name = "httpx", specifier = ">=0.25.0" },