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
8 changes: 7 additions & 1 deletion src/a2a/client/client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
try:
from a2a.client.transports.grpc import GrpcTransport
except ImportError:
GrpcTransport = None # type: ignore # pyright: ignore

Check warning on line 41 in src/a2a/client/client_factory.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ty (unused-type-ignore-comment)

src/a2a/client/client_factory.py:41:27: unused-type-ignore-comment: Unused blanket `type: ignore` directive help: Remove the unused suppression comment


try:
from a2a.compat.v0_3.grpc_transport import CompatGrpcTransport
except ImportError:
CompatGrpcTransport = None # type: ignore # pyright: ignore

Check warning on line 47 in src/a2a/client/client_factory.py

View workflow job for this annotation

GitHub Actions / Lint Code Base

ty (unused-type-ignore-comment)

src/a2a/client/client_factory.py:47:33: unused-type-ignore-comment: Unused blanket `type: ignore` directive help: Remove the unused suppression comment

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -78,7 +78,13 @@
config: ClientConfig | None = None,
):
config = config or ClientConfig()
httpx_client = config.httpx_client or httpx.AsyncClient()
# Follow redirects by default: a server may 307 a trailing-slash
# variant of the JSON-RPC/REST endpoint (Starlette's default
# redirect_slashes). 307 preserves method and body, so this is safe
# for POST payloads and keeps the client robust to either spelling.
httpx_client = config.httpx_client or httpx.AsyncClient(
follow_redirects=True
)
httpx_client.headers.setdefault(
VERSION_HEADER, PROTOCOL_VERSION_CURRENT
)
Expand Down
2 changes: 1 addition & 1 deletion src/a2a/server/routes/jsonrpc_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ def create_jsonrpc_routes(
path=rpc_url,
endpoint=dispatcher.handle_requests,
methods=['POST'],
)
),
]
12 changes: 10 additions & 2 deletions tck/sut_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import a2a.types.a2a_pb2_grpc as a2a_grpc

from a2a.compat.v0_3.grpc_handler import CompatGrpcHandler
from a2a.helpers.proto_helpers import new_task_from_user_message
from a2a.server.agent_execution.agent_executor import AgentExecutor
from a2a.server.agent_execution.context import RequestContext
from a2a.server.events.event_queue import EventQueue
Expand Down Expand Up @@ -87,6 +88,13 @@ async def execute(

self.running_tasks.add(task_id)

# 1.0 semantics: the Task itself must be enqueued before any
# TaskStatusUpdateEvent (the SDK enforces this ordering).
task = context.current_task
if not task:
task = new_task_from_user_message(user_message)
await event_queue.enqueue_event(task)

logger.info(
'[SUTAgentExecutor] Processing message %s for task %s (context: %s)',
user_message.message_id,
Expand Down Expand Up @@ -157,11 +165,11 @@ def serve(task_store: TaskStore) -> None:
),
AgentInterface(
url=f'http://localhost:{http_port}{REST_URL}',
protocol_binding='REST',
protocol_binding='HTTP+JSON',
protocol_version='1.0.0',
),
AgentInterface(
url=f'http://localhost:{grpc_port}',
url=f'localhost:{grpc_port}',
protocol_binding='GRPC',
protocol_version='1.0.0',
),
Expand Down
18 changes: 18 additions & 0 deletions tests/client/test_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,24 @@ def test_client_factory_create_with_default_config(
assert client._transport.url == 'http://primary-url.com' # type: ignore[attr-defined]


def test_client_factory_default_client_follows_redirects(
base_agent_card: AgentCard,
):
"""Default factory client follows redirects (307 trailing-slash handling)."""
factory = ClientFactory()
assert factory._httpx_client.follow_redirects is True


def test_client_factory_respects_custom_client_redirect_setting(
base_agent_card: AgentCard,
):
"""A user-supplied httpx client keeps its own redirect policy."""
custom = httpx.AsyncClient(follow_redirects=False)
factory = ClientFactory(ClientConfig(httpx_client=custom))
assert factory._httpx_client is custom
assert factory._httpx_client.follow_redirects is False


@pytest.mark.asyncio
async def test_client_factory_create_from_url(base_agent_card: AgentCard):
"""Verify that create_from_url resolves the card and creates a client."""
Expand Down
Loading