Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion python/packages/kagent-adk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions python/packages/kagent-adk/src/kagent/adk/_a2a.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
)

Expand Down
16 changes: 13 additions & 3 deletions python/packages/kagent-adk/src/kagent/adk/_agent_executor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

# ruff: noqa: E402
import asyncio
import inspect
import logging
Expand All @@ -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,
Expand All @@ -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
Expand Down
39 changes: 25 additions & 14 deletions python/packages/kagent-adk/src/kagent/adk/_remote_a2a_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,39 @@
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,
Task,
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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion python/packages/kagent-adk/src/kagent/adk/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions python/packages/kagent-adk/src/kagent/adk/types.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
_parse_orchestration_chunk,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion python/packages/kagent-adk/tests/unittests/test_hitl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
"""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,
TaskState,
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,
Expand Down Expand Up @@ -148,30 +149,24 @@ 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"}},
)
assert headers.get("authorization") == "Bearer test-jwt"

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"},
)
Expand Down
2 changes: 1 addition & 1 deletion python/packages/kagent-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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",
Expand Down
Loading
Loading