From 0fcafadfb5e2bc7af71885af78d7f275863cedc0 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Fri, 17 Apr 2026 15:50:36 +0000 Subject: [PATCH 01/17] =?UTF-8?q?docs:=20add=20v0.3=20=E2=86=92=20v1.0=20m?= =?UTF-8?q?igration=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/migrations/v1_0/README.md | 423 +++++++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 docs/migrations/v1_0/README.md diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md new file mode 100644 index 000000000..ad3a0ca51 --- /dev/null +++ b/docs/migrations/v1_0/README.md @@ -0,0 +1,423 @@ +# Migration Guide: v0.3 → v1.0 + +This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains how to update your code. + +> **Related guides**: If you use the database persistence layer, also see the [Database Migration Guide](database/). + +--- + +## Table of Contents + +1. [Package Dependency](#1-package-dependency) +2. [Types](#2-types) +3. [Server: DefaultRequestHandler](#3-server-defaultrequesthandler) +4. [Server: Application Setup](#4-server-application-setup) +5. [Client: Creating a Client](#5-client-creating-a-client) +6. [Client: Sending Messages & Handling Responses](#6-client-sending-messages--handling-responses) +7. [Client: Push Notifications Config](#7-client-push-notifications-config) +8. [Helper Utilities](#8-helper-utilities) +9. [Import Path Changes (Quick Reference)](#9-import-path-changes-quick-reference) + +--- + +## 1. Package Dependency + +Update your dependency to the new version: + +```toml +# pyproject.toml — before +dependencies = ["a2a-sdk>=0.3.0"] + +# pyproject.toml — after +dependencies = ["a2a-sdk>=1.0.0"] +``` + +--- + +## 2. Types + +Types are now **Protobuf-based** instead of Pydantic models. + + +### Enum values: kebab-case → SCREAMING_SNAKE_CASE + +All enum values have been renamed from kebab-case strings to `SCREAMING_SNAKE_CASE`. + +This affects every enum in the SDK: `TaskState`, `Role`. + +| Enum | v0.3 | v1.0 | +|---|---|---| +| `TaskState` | *(no equivalent — protobuf default)* | `TaskState.TASK_STATE_UNSPECIFIED` | +| `TaskState` | `TaskState.submitted` | `TaskState.TASK_STATE_SUBMITTED` | +| `TaskState` | `TaskState.working` | `TaskState.TASK_STATE_WORKING` | +| `TaskState` | `TaskState.completed` | `TaskState.TASK_STATE_COMPLETED` | +| `TaskState` | `TaskState.failed` | `TaskState.TASK_STATE_FAILED` | +| `TaskState` | `TaskState.canceled` | `TaskState.TASK_STATE_CANCELED` | +| `TaskState` | `TaskState.input_required` | `TaskState.TASK_STATE_INPUT_REQUIRED` | +| `TaskState` | `TaskState.auth_required` | `TaskState.TASK_STATE_AUTH_REQUIRED` | +| `TaskState` | `TaskState.rejected` | `TaskState.TASK_STATE_REJECTED` | +| `Role` | *(no equivalent — protobuf default)* | `Role.ROLE_UNSPECIFIED` | +| `Role` | `Role.user` | `Role.ROLE_USER` | +| `Role` | `Role.agent` | `Role.ROLE_AGENT` | + +### Message and Part construction + +**Before (v0.3):** +```python +from a2a.types import Message, Part, Role, TextPart +from uuid import uuid4 + +message = Message( + role=Role.user, + parts=[Part(TextPart(text="Hello"))], + message_id=uuid4().hex, + task_id=uuid4().hex, +) +``` + +**After (v1.0):** +```python +from a2a.helpers import new_text_message +from a2a.types import Role + +# Use the helper for text messages +message = new_text_message(text="Hello", role=Role.ROLE_USER) + +# Or construct directly +from a2a.types import Message, Part +from uuid import uuid4 + +message = Message( + role=Role.ROLE_USER, + parts=[Part(text="Hello")], + message_id=uuid4().hex, + task_id=uuid4().hex, +) +``` + +Key differences: +- `Part(TextPart(text=...))` → `Part(text=...)` (flat union field) +- `Role.user` → `Role.ROLE_USER`, `Role.agent` → `Role.ROLE_AGENT` +- `TextPart` is no longer needed; use `Part(text=...)` directly + +### AgentCard Structure + +The `AgentCard` has been significantly restructured to support multiple transport interfaces. + +#### `url` → `supported_interfaces` + +The top-level `url` field is replaced by a list of `AgentInterface` objects, each describing a specific transport endpoint. + +**Before (v0.3):** +```python +from a2a.types import AgentCard, AgentCapabilities, AgentSkill + +agent_card = AgentCard( + name='My Agent', + description='...', + url='http://localhost:9999/', + version='1.0.0', + default_input_modes=['text'], + default_output_modes=['text'], + supports_authenticated_extended_card=True, + capabilities=AgentCapabilities( + input_modes=['text'], + output_modes=['text'], + streaming=True, + ), + skills=[skill], +) +``` + +**After (v1.0):** +```python +from a2a.types import AgentCard, AgentCapabilities, AgentInterface, AgentSkill + +agent_card = AgentCard( + name='My Agent', + description='...', + supported_interfaces=[ + AgentInterface( + protocol_binding='JSONRPC', + url='http://localhost:9999/', + ) + ], + version='1.0.0', + default_input_modes=['text'], + default_output_modes=['text'], + capabilities=AgentCapabilities( + streaming=True, + extended_agent_card=True, + ), + skills=[skill], +) +``` + +Key differences: +- `url` is gone; use `supported_interfaces` with one or more `AgentInterface` entries +- `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed +- `supports_authenticated_extended_card` is no longer a top-level `AgentCard` field; it has moved into `AgentCapabilities` and is renamed to `extended_agent_card` +- `AgentInterface.protocol_binding` accepted values: `'JSONRPC'`, `'HTTP_JSON'`, `'GRPC'` + +--- + +## 3. Server: DefaultRequestHandler + +### Constructor signature: `agent_card` is now required + +`DefaultRequestHandler` now requires `agent_card` as a constructor argument (it was previously passed to the application wrapper). + +**Before (v0.3):** +```python +request_handler = DefaultRequestHandler( + agent_executor=MyAgentExecutor(), + task_store=InMemoryTaskStore(), +) +``` + +**After (v1.0):** +```python +request_handler = DefaultRequestHandler( + agent_executor=MyAgentExecutor(), + task_store=InMemoryTaskStore(), + agent_card=agent_card, +) +``` + +--- + +## 4. Server: Application Setup + +The `A2AStarletteApplication` wrapper class has been removed. Server setup now uses **Starlette route factory functions** directly, giving you full control over the routing. + +**Before (v0.3):** +```python +from a2a.server.apps import A2AStarletteApplication +import uvicorn + +server = A2AStarletteApplication( + agent_card=agent_card, + http_handler=request_handler, +) +uvicorn.run(server.build(), host=host, port=port) +``` + +**After (v1.0):** +```python +from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes +from starlette.applications import Starlette +import uvicorn + +routes = [] +routes.extend(create_agent_card_routes(agent_card)) +routes.extend(create_jsonrpc_routes(request_handler, rpc_url='/')) + +app = Starlette(routes=routes) +uvicorn.run(app, host=host, port=port) +``` + +If you need REST transport in addition to JSON-RPC: +```python +from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes, create_rest_routes +from starlette.applications import Starlette +import uvicorn + +routes = [] +routes.extend(create_agent_card_routes(agent_card)) +routes.extend(create_jsonrpc_routes(request_handler, rpc_url='/')) +routes.extend(create_rest_routes(request_handler)) + +app = Starlette(routes=routes) +uvicorn.run(app, host=host, port=port) +``` + +--- + +## 5. Client: Creating a Client + +The `A2AClient` class has been removed. Use the new `create_client()` factory function or `ClientFactory`. + +### Simple usage: `create_client()` + +**Before (v0.3):** +```python +import httpx +from a2a.client import A2AClient, A2ACardResolver + +async with httpx.AsyncClient() as httpx_client: + resolver = A2ACardResolver(httpx_client, base_url) + agent_card = await resolver.get_agent_card() + client = A2AClient(httpx_client, agent_card=agent_card) + # use client... +``` + +**After (v1.0):** +```python +from a2a.client import create_client + +# From URL — resolves the agent card automatically +async with await create_client('http://localhost:9999/') as client: + # use client... + +# From an already-resolved AgentCard +async with await create_client(agent_card) as client: + # use client... +``` + +### Advanced usage: `ClientFactory` + +For reusing connections across multiple agents, registering custom transports, or configuring timeouts: + +```python +from a2a.client import ClientFactory, ClientConfig + +config = ClientConfig(streaming=True) +factory = ClientFactory(config) + +# Create from URL (async) +client = await factory.create_from_url('http://localhost:9999/') + +# Create from AgentCard (sync) +client = factory.create(agent_card) +``` + +### `ClientTaskManager` and `Consumers` removed + +The `ClientTaskManager` class and `Consumers` abstraction have been removed. Response handling is now done directly by iterating the stream returned from `send_message()`. + +--- + +## 6. Client: Sending Messages & Handling Responses + +### `SendStreamingMessageRequest` removed + +There is now a single `send_message()` method on the client that returns a stream of `StreamResponse` proto messages regardless of transport. + +**Before (v0.3):** +```python +from a2a.types import ( + Message, MessageSendParams, Part, Role, SendStreamingMessageRequest, + SendStreamingMessageSuccessResponse, TaskStatusUpdateEvent, TextPart, +) +from uuid import uuid4 + +message_params = MessageSendParams( + message=Message( + role=Role.user, + parts=[Part(TextPart(text=user_input))], + message_id=uuid4().hex, + task_id=uuid4().hex, + ) +) +request = SendStreamingMessageRequest(id=uuid4().hex, params=message_params) + +async for chunk in client.send_message_streaming(request): + if isinstance(chunk.root, SendStreamingMessageSuccessResponse) and \ + isinstance(chunk.root.result, TaskStatusUpdateEvent): + msg = chunk.root.result.status.message + if msg: + print(msg.parts[0].root.text) +``` + +**After (v1.0):** +```python +from a2a.helpers import get_artifact_text, new_text_message +from a2a.types import SendMessageRequest + +message = new_text_message(text=user_input) +request = SendMessageRequest(message=message) + +async for chunk in client.send_message(request): + if chunk.HasField('artifact_update'): + text = get_artifact_text(chunk.artifact_update.artifact) + if text: + print(text) + elif chunk.HasField('status_update'): + # handle status updates + ... +``` + +Key differences: +- `send_message_streaming()` → `send_message()` (unified method) +- `SendStreamingMessageRequest` → `SendMessageRequest` +- `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` +- Response chunks are `StreamResponse` proto messages; use `HasField()` to check the payload type +- Agent outputs should now be published as **Artifacts**, not status message text + +--- + +## 7. Client: Push Notifications Config + +`ClientConfig.push_notification_config` is now **singular** (a single `TaskPushNotificationConfig` or `None`), not a list. + +**Before (v0.3):** +```python +config = ClientConfig( + push_notification_configs=[my_push_config], +) +``` + +**After (v1.0):** +```python +config = ClientConfig( + push_notification_config=my_push_config, +) +``` + +--- + +## 8. Helper Utilities + +A new `a2a.helpers` module provides convenience functions previously scattered across `a2a.utils.*` and adds new helpers for v1.0 proto types. + +```python +from a2a.helpers import ( + # --- moved from a2a.utils.* --- + new_text_message, # was a2a.utils.message.new_agent_text_message; gained role param + new_message, # was a2a.utils.message.new_agent_parts_message; gained role param + get_message_text, # was a2a.utils.message.get_message_text + new_text_artifact, # was a2a.utils.artifact.new_text_artifact; gained artifact_id param + new_artifact, # was a2a.utils.artifact.new_artifact; gained artifact_id param + get_artifact_text, # was a2a.utils.artifact.get_artifact_text + get_text_parts, # was a2a.utils.parts.get_text_parts + new_task_from_user_message, # was a2a.utils.task.new_task; renamed, now validates role == ROLE_USER + + # --- new in v1.0 --- + new_task, # create a Task with explicit task_id, context_id, and state + new_text_artifact_update_event, # create a TaskArtifactUpdateEvent with a text artifact + new_text_status_update_event, # create a TaskStatusUpdateEvent with a text message + get_stream_response_text, # extract text from a StreamResponse proto message + display_agent_card, # print a human-readable summary of an AgentCard to stdout +) +``` + +**Before (v0.3) — reading status message text:** +```python +text = chunk.root.result.status.message.parts[0].root.text +``` + +**After (v1.0) — reading artifact text:** +```python +from a2a.helpers import get_artifact_text + +text = get_artifact_text(chunk.artifact_update.artifact) +``` + +> In v1.0, agents are expected to publish results as **Artifacts** rather than embedding text in status update messages. Use `TaskArtifactUpdateEvent` (via `event_queue.enqueue_event()`) in your `AgentExecutor` and read from `chunk.artifact_update` on the client side. + +--- + +## 9. Import Path Changes (Quick Reference) + +| What | v0.3 import | v1.0 import | +|---|---|---| +| HTTP client for agent | `from a2a.client import A2AClient` | `from a2a.client import create_client` (or `ClientFactory`) | +| Card resolver | `from a2a.client import A2ACardResolver` | `from a2a.client import A2ACardResolver` *(unchanged)* | +| Request handler | `from a2a.server.request_handlers.default_request_handler import DefaultRequestHandler` | `from a2a.server.request_handlers import DefaultRequestHandler` | +| Server setup | `from a2a.server.apps import A2AStarletteApplication` | `from a2a.server.routes import create_jsonrpc_routes, create_agent_card_routes` | +| REST routes | `from a2a.server.apps import A2AStarletteApplication` | `from a2a.server.routes import create_rest_routes` | +| Agent execution | `from a2a.server.agent_execution import AgentExecutor, RequestContext` | *(unchanged)* | +| Task store | `from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore` | *(unchanged)* | +| Types | `from a2a.types import AgentCard, Message, Part, Role, ...` | `from a2a.types import AgentCard, Message, Part, Role, AgentInterface, ...` | +| Helpers | `from a2a.utils.artifact import get_artifact_text` | `from a2a.helpers import get_artifact_text` | +| Message helpers | *(manual construction)* | `from a2a.helpers import new_text_message, new_text_artifact, ...` | From 79aac2368c5eadba04ed685294d3e118391bbcd9 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 08:57:07 +0000 Subject: [PATCH 02/17] few fixes and improvements --- docs/migrations/v1_0/README.md | 128 ++++++++++++++++----------------- 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index ad3a0ca51..eab20b3a1 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -39,9 +39,9 @@ dependencies = ["a2a-sdk>=1.0.0"] Types are now **Protobuf-based** instead of Pydantic models. -### Enum values: kebab-case → SCREAMING_SNAKE_CASE +### Enum values: snake_case → SCREAMING_SNAKE_CASE -All enum values have been renamed from kebab-case strings to `SCREAMING_SNAKE_CASE`. +All enum values have been renamed from snake_case strings to `SCREAMING_SNAKE_CASE`. This affects every enum in the SDK: `TaskState`, `Role`. @@ -60,6 +60,8 @@ This affects every enum in the SDK: `TaskState`, `Role`. | `Role` | `Role.user` | `Role.ROLE_USER` | | `Role` | `Role.agent` | `Role.ROLE_AGENT` | +> **Example**: [`a2a-mcp-without-framework/server/agent_executor.py` in PR #509](https://github.com/a2aproject/a2a-samples/pull/509/changes#diff-1f9b098f9f82ee40666ee61db56dc2246281423c445bcf017079c53a0a05954f) + ### Message and Part construction **Before (v0.3):** @@ -100,13 +102,18 @@ Key differences: - `Role.user` → `Role.ROLE_USER`, `Role.agent` → `Role.ROLE_AGENT` - `TextPart` is no longer needed; use `Part(text=...)` directly +> **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) + ### AgentCard Structure The `AgentCard` has been significantly restructured to support multiple transport interfaces. -#### `url` → `supported_interfaces` - -The top-level `url` field is replaced by a list of `AgentInterface` objects, each describing a specific transport endpoint. +Key differences: +- `url` is gone; use `supported_interfaces` with one or more `AgentInterface` entries +- `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed from `AgentCapabilities`; use `AgentCard.default_input_modes` / `AgentCard.default_output_modes` for card-level defaults, or `AgentSkill.input_modes` / `AgentSkill.output_modes` for per-skill overrides +- `supports_authenticated_extended_card` is no longer a top-level `AgentCard` field; it has moved into `AgentCapabilities` and is renamed to `extended_agent_card` +- `AgentInterface.protocol_binding` accepted values: `'JSONRPC'`, `'HTTP+JSON'`, `'GRPC'` +- `examples` field has moved to `AgentSkill.examples` (set it per skill instead) **Before (v0.3):** ```python @@ -117,15 +124,16 @@ agent_card = AgentCard( description='...', url='http://localhost:9999/', version='1.0.0', - default_input_modes=['text'], - default_output_modes=['text'], + default_input_modes=['text/plain'], + default_output_modes=['text/plain'], supports_authenticated_extended_card=True, capabilities=AgentCapabilities( - input_modes=['text'], - output_modes=['text'], + input_modes=['text/plain'], + output_modes=['text/plain'], streaming=True, ), skills=[skill], + examples=['example'], ) ``` @@ -143,8 +151,8 @@ agent_card = AgentCard( ) ], version='1.0.0', - default_input_modes=['text'], - default_output_modes=['text'], + default_input_modes=['text/plain'], + default_output_modes=['text/plain'], capabilities=AgentCapabilities( streaming=True, extended_agent_card=True, @@ -153,11 +161,7 @@ agent_card = AgentCard( ) ``` -Key differences: -- `url` is gone; use `supported_interfaces` with one or more `AgentInterface` entries -- `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed -- `supports_authenticated_extended_card` is no longer a top-level `AgentCard` field; it has moved into `AgentCapabilities` and is renamed to `extended_agent_card` -- `AgentInterface.protocol_binding` accepted values: `'JSONRPC'`, `'HTTP_JSON'`, `'GRPC'` +> **Example**: [`a2a-mcp-without-framework/server/__main__.py` in PR #509](https://github.com/a2aproject/a2a-samples/pull/509/files#diff-d15d39ae64c3d4e3a36cc6fb442302caf4e32a6dbd858792e7a4bed180a625ac) --- @@ -184,6 +188,8 @@ request_handler = DefaultRequestHandler( ) ``` +> **Example**: [`a2a-mcp-without-framework/server/__main__.py` in PR #509](https://github.com/a2aproject/a2a-samples/pull/509/files#diff-d15d39ae64c3d4e3a36cc6fb442302caf4e32a6dbd858792e7a4bed180a625ac) + --- ## 4. Server: Application Setup @@ -231,11 +237,13 @@ app = Starlette(routes=routes) uvicorn.run(app, host=host, port=port) ``` +> **Example**: [`a2a-mcp-without-framework/server/__main__.py` in PR #509](https://github.com/a2aproject/a2a-samples/pull/509/files#diff-d15d39ae64c3d4e3a36cc6fb442302caf4e32a6dbd858792e7a4bed180a625ac) + --- ## 5. Client: Creating a Client -The `A2AClient` class has been removed. Use the new `create_client()` factory function or `ClientFactory`. +The `A2AClient` class has been removed. Use the new `create_client()` factory function. ### Simple usage: `create_client()` @@ -256,34 +264,18 @@ async with httpx.AsyncClient() as httpx_client: from a2a.client import create_client # From URL — resolves the agent card automatically -async with await create_client('http://localhost:9999/') as client: +client = await create_client('http://localhost:9999/') +async with client: # use client... # From an already-resolved AgentCard -async with await create_client(agent_card) as client: +client = await create_client(agent_card) +async with client: # use client... ``` -### Advanced usage: `ClientFactory` - -For reusing connections across multiple agents, registering custom transports, or configuring timeouts: - -```python -from a2a.client import ClientFactory, ClientConfig - -config = ClientConfig(streaming=True) -factory = ClientFactory(config) - -# Create from URL (async) -client = await factory.create_from_url('http://localhost:9999/') - -# Create from AgentCard (sync) -client = factory.create(agent_card) -``` - -### `ClientTaskManager` and `Consumers` removed -The `ClientTaskManager` class and `Consumers` abstraction have been removed. Response handling is now done directly by iterating the stream returned from `send_message()`. +> **Example**: [`a2a-mcp-without-framework/client/agent.py` in PR #509](https://github.com/a2aproject/a2a-samples/pull/509/files#diff-56cfce97ff9686166e4b14790ffb7ed46f4c14519261ce5c18365a53cf05e9aa) (`create_client()` usage) --- @@ -297,7 +289,7 @@ There is now a single `send_message()` method on the client that returns a strea ```python from a2a.types import ( Message, MessageSendParams, Part, Role, SendStreamingMessageRequest, - SendStreamingMessageSuccessResponse, TaskStatusUpdateEvent, TextPart, + TextPart, ) from uuid import uuid4 @@ -311,39 +303,42 @@ message_params = MessageSendParams( ) request = SendStreamingMessageRequest(id=uuid4().hex, params=message_params) -async for chunk in client.send_message_streaming(request): - if isinstance(chunk.root, SendStreamingMessageSuccessResponse) and \ - isinstance(chunk.root.result, TaskStatusUpdateEvent): - msg = chunk.root.result.status.message - if msg: - print(msg.parts[0].root.text) +response = client.send_message_streaming(request) + ``` **After (v1.0):** ```python -from a2a.helpers import get_artifact_text, new_text_message -from a2a.types import SendMessageRequest +from a2a.types import ( + Message, Part, Role, SendMessageRequest, +) +from uuid import uuid4 -message = new_text_message(text=user_input) +parts = [Part(text=user_input)] +message = Message( + role=Role.ROLE_USER, + parts=parts, + message_id=uuid4().hex, +) request = SendMessageRequest(message=message) async for chunk in client.send_message(request): if chunk.HasField('artifact_update'): - text = get_artifact_text(chunk.artifact_update.artifact) - if text: - print(text) + print(get_artifact_text(chunk.artifact_update.artifact)) elif chunk.HasField('status_update'): - # handle status updates - ... + print(chunk.status_update.status.state) ``` Key differences: - `send_message_streaming()` → `send_message()` (unified method) - `SendStreamingMessageRequest` → `SendMessageRequest` - `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` -- Response chunks are `StreamResponse` proto messages; use `HasField()` to check the payload type +- `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` +- Each `StreamResponse` has a `payload` oneof — use `HasField()` to check which field is set (`'task'`, `'message'`, `'status_update'`, `'artifact_update'`) - Agent outputs should now be published as **Artifacts**, not status message text +> **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) + --- ## 7. Client: Push Notifications Config @@ -368,26 +363,23 @@ config = ClientConfig( ## 8. Helper Utilities -A new `a2a.helpers` module provides convenience functions previously scattered across `a2a.utils.*` and adds new helpers for v1.0 proto types. +A new `a2a.helpers` module consolidates helper functions into a single import. Most were previously available under `a2a.utils.*`; a few are new in v1.0. ```python from a2a.helpers import ( - # --- moved from a2a.utils.* --- - new_text_message, # was a2a.utils.message.new_agent_text_message; gained role param - new_message, # was a2a.utils.message.new_agent_parts_message; gained role param - get_message_text, # was a2a.utils.message.get_message_text - new_text_artifact, # was a2a.utils.artifact.new_text_artifact; gained artifact_id param - new_artifact, # was a2a.utils.artifact.new_artifact; gained artifact_id param - get_artifact_text, # was a2a.utils.artifact.get_artifact_text - get_text_parts, # was a2a.utils.parts.get_text_parts - new_task_from_user_message, # was a2a.utils.task.new_task; renamed, now validates role == ROLE_USER - - # --- new in v1.0 --- + display_agent_card, # print a human-readable summary of an AgentCard to stdout + get_artifact_text, # join all text parts of an Artifact into a single string (delimiter='\n') + get_message_text, # join all text parts of a Message into a single string (delimiter='\n') + get_stream_response_text, # extract text from a StreamResponse proto message + get_text_parts, # return a list of raw text strings from a sequence of Parts (skips non-text parts) + new_artifact, # create an Artifact from a list of Parts, name, optional description and artifact_id + new_message, # create a Message from a list of Parts with role (default ROLE_AGENT), optional task_id/context_id new_task, # create a Task with explicit task_id, context_id, and state + new_task_from_user_message, # create a TASK_STATE_SUBMITTED Task from a user Message; raises if role != ROLE_USER or parts are empty + new_text_artifact, # create an Artifact with a single text Part, name, optional description and artifact_id new_text_artifact_update_event, # create a TaskArtifactUpdateEvent with a text artifact + new_text_message, # create a Message with a single text Part; role defaults to ROLE_AGENT new_text_status_update_event, # create a TaskStatusUpdateEvent with a text message - get_stream_response_text, # extract text from a StreamResponse proto message - display_agent_card, # print a human-readable summary of an AgentCard to stdout ) ``` From a24eb07c27634c961430877dce9a622170ad2233 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 09:19:00 +0000 Subject: [PATCH 03/17] fix spelling --- .github/actions/spelling/allow.txt | 1 + docs/migrations/v1_0/README.md | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index b3b2d56e8..03774d1f0 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -85,6 +85,7 @@ lifecycles linting Llm lstrips +mcp middleware mikeas mockurl diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index eab20b3a1..6a8ca347e 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -38,7 +38,6 @@ dependencies = ["a2a-sdk>=1.0.0"] Types are now **Protobuf-based** instead of Pydantic models. - ### Enum values: snake_case → SCREAMING_SNAKE_CASE All enum values have been renamed from snake_case strings to `SCREAMING_SNAKE_CASE`. @@ -110,7 +109,7 @@ The `AgentCard` has been significantly restructured to support multiple transpor Key differences: - `url` is gone; use `supported_interfaces` with one or more `AgentInterface` entries -- `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed from `AgentCapabilities`; use `AgentCard.default_input_modes` / `AgentCard.default_output_modes` for card-level defaults, or `AgentSkill.input_modes` / `AgentSkill.output_modes` for per-skill overrides +- `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed; use `AgentCard.default_input_modes` / `AgentCard.default_output_modes` for card-level defaults, or `AgentSkill.input_modes` / `AgentSkill.output_modes` for per-skill overrides - `supports_authenticated_extended_card` is no longer a top-level `AgentCard` field; it has moved into `AgentCapabilities` and is renamed to `extended_agent_card` - `AgentInterface.protocol_binding` accepted values: `'JSONRPC'`, `'HTTP+JSON'`, `'GRPC'` - `examples` field has moved to `AgentSkill.examples` (set it per skill instead) @@ -334,7 +333,7 @@ Key differences: - `SendStreamingMessageRequest` → `SendMessageRequest` - `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` - `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` -- Each `StreamResponse` has a `payload` oneof — use `HasField()` to check which field is set (`'task'`, `'message'`, `'status_update'`, `'artifact_update'`) +- Each `StreamResponse` has a `payload` which is one of: `'task'`, `'message'`, `'status_update'`, `'artifact_update'`. Use `HasField()` to check which field is set. - Agent outputs should now be published as **Artifacts**, not status message text > **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) From 08b92d979d1f01314be50dbae579a5c5a352a55b Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 09:25:00 +0000 Subject: [PATCH 04/17] remove unchanged --- docs/migrations/v1_0/README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 6a8ca347e..c1f2117f7 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -16,7 +16,6 @@ This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains 6. [Client: Sending Messages & Handling Responses](#6-client-sending-messages--handling-responses) 7. [Client: Push Notifications Config](#7-client-push-notifications-config) 8. [Helper Utilities](#8-helper-utilities) -9. [Import Path Changes (Quick Reference)](#9-import-path-changes-quick-reference) --- @@ -396,19 +395,3 @@ text = get_artifact_text(chunk.artifact_update.artifact) > In v1.0, agents are expected to publish results as **Artifacts** rather than embedding text in status update messages. Use `TaskArtifactUpdateEvent` (via `event_queue.enqueue_event()`) in your `AgentExecutor` and read from `chunk.artifact_update` on the client side. ---- - -## 9. Import Path Changes (Quick Reference) - -| What | v0.3 import | v1.0 import | -|---|---|---| -| HTTP client for agent | `from a2a.client import A2AClient` | `from a2a.client import create_client` (or `ClientFactory`) | -| Card resolver | `from a2a.client import A2ACardResolver` | `from a2a.client import A2ACardResolver` *(unchanged)* | -| Request handler | `from a2a.server.request_handlers.default_request_handler import DefaultRequestHandler` | `from a2a.server.request_handlers import DefaultRequestHandler` | -| Server setup | `from a2a.server.apps import A2AStarletteApplication` | `from a2a.server.routes import create_jsonrpc_routes, create_agent_card_routes` | -| REST routes | `from a2a.server.apps import A2AStarletteApplication` | `from a2a.server.routes import create_rest_routes` | -| Agent execution | `from a2a.server.agent_execution import AgentExecutor, RequestContext` | *(unchanged)* | -| Task store | `from a2a.server.tasks.inmemory_task_store import InMemoryTaskStore` | *(unchanged)* | -| Types | `from a2a.types import AgentCard, Message, Part, Role, ...` | `from a2a.types import AgentCard, Message, Part, Role, AgentInterface, ...` | -| Helpers | `from a2a.utils.artifact import get_artifact_text` | `from a2a.helpers import get_artifact_text` | -| Message helpers | *(manual construction)* | `from a2a.helpers import new_text_message, new_text_artifact, ...` | From c7ad6008cb65bb955af0240302471b4774eade4d Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 09:28:44 +0000 Subject: [PATCH 05/17] small fix --- docs/migrations/v1_0/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index c1f2117f7..0bae83e2c 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -393,5 +393,5 @@ from a2a.helpers import get_artifact_text text = get_artifact_text(chunk.artifact_update.artifact) ``` -> In v1.0, agents are expected to publish results as **Artifacts** rather than embedding text in status update messages. Use `TaskArtifactUpdateEvent` (via `event_queue.enqueue_event()`) in your `AgentExecutor` and read from `chunk.artifact_update` on the client side. +> In v1.0, agents are expected to publish results as **Artifacts**. Use `TaskArtifactUpdateEvent` (via `event_queue.enqueue_event()`) in your `AgentExecutor` and read from `chunk.artifact_update` on the client side. From aef5bc05973631cfdc95892f081c7ebabdc6397b Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 09:29:32 +0000 Subject: [PATCH 06/17] remove unecessary --- docs/migrations/v1_0/README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 0bae83e2c..969d2db99 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -381,17 +381,3 @@ from a2a.helpers import ( ) ``` -**Before (v0.3) — reading status message text:** -```python -text = chunk.root.result.status.message.parts[0].root.text -``` - -**After (v1.0) — reading artifact text:** -```python -from a2a.helpers import get_artifact_text - -text = get_artifact_text(chunk.artifact_update.artifact) -``` - -> In v1.0, agents are expected to publish results as **Artifacts**. Use `TaskArtifactUpdateEvent` (via `event_queue.enqueue_event()`) in your `AgentExecutor` and read from `chunk.artifact_update` on the client side. - From 6900e348d82bb7cc7c3f63afac83eab8b6ff1776 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 09:46:12 +0000 Subject: [PATCH 07/17] move key differences to top --- docs/migrations/v1_0/README.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 969d2db99..0912c95b4 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -62,6 +62,11 @@ This affects every enum in the SDK: `TaskState`, `Role`. ### Message and Part construction +Key differences: +- `Part(TextPart(text=...))` → `Part(text=...)` (flat union field) +- `Role.user` → `Role.ROLE_USER`, `Role.agent` → `Role.ROLE_AGENT` +- `TextPart` is no longer needed; use `Part(text=...)` directly + **Before (v0.3):** ```python from a2a.types import Message, Part, Role, TextPart @@ -95,11 +100,6 @@ message = Message( ) ``` -Key differences: -- `Part(TextPart(text=...))` → `Part(text=...)` (flat union field) -- `Role.user` → `Role.ROLE_USER`, `Role.agent` → `Role.ROLE_AGENT` -- `TextPart` is no longer needed; use `Part(text=...)` directly - > **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) ### AgentCard Structure @@ -279,9 +279,13 @@ async with client: ## 6. Client: Sending Messages & Handling Responses -### `SendStreamingMessageRequest` removed - -There is now a single `send_message()` method on the client that returns a stream of `StreamResponse` proto messages regardless of transport. +Key differences: +- `send_message_streaming()` → `send_message()` (unified method) +- `SendStreamingMessageRequest` → `SendMessageRequest` +- `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` +- `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` +- Each `StreamResponse` has a `payload` which is one of: `'task'`, `'message'`, `'status_update'`, `'artifact_update'`. Use `HasField()` to check which field is set. +- Agent outputs should now be published as **Artifacts**, not status message text **Before (v0.3):** ```python @@ -307,6 +311,7 @@ response = client.send_message_streaming(request) **After (v1.0):** ```python +from a2a.helpers import get_artifact_text from a2a.types import ( Message, Part, Role, SendMessageRequest, ) @@ -317,6 +322,7 @@ message = Message( role=Role.ROLE_USER, parts=parts, message_id=uuid4().hex, + task_id=uuid4().hex, ) request = SendMessageRequest(message=message) @@ -327,14 +333,6 @@ async for chunk in client.send_message(request): print(chunk.status_update.status.state) ``` -Key differences: -- `send_message_streaming()` → `send_message()` (unified method) -- `SendStreamingMessageRequest` → `SendMessageRequest` -- `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` -- `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` -- Each `StreamResponse` has a `payload` which is one of: `'task'`, `'message'`, `'status_update'`, `'artifact_update'`. Use `HasField()` to check which field is set. -- Agent outputs should now be published as **Artifacts**, not status message text - > **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) --- From 3d35e44ca39d06d8f632d084d7dcdd09dcce9b37 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 10:10:39 +0000 Subject: [PATCH 08/17] add `A2AFastApiApplication` and `A2ARESTFastApiApplication` mentions --- docs/migrations/v1_0/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 0912c95b4..367b37b90 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -192,7 +192,7 @@ request_handler = DefaultRequestHandler( ## 4. Server: Application Setup -The `A2AStarletteApplication` wrapper class has been removed. Server setup now uses **Starlette route factory functions** directly, giving you full control over the routing. +The `A2AStarletteApplication`, `A2AFastApiApplication` and `A2ARESTFastApiApplication` wrapper classes have been removed. Server setup now uses **Starlette route factory functions** directly, giving you full control over the routing. **Before (v0.3):** ```python @@ -280,10 +280,10 @@ async with client: ## 6. Client: Sending Messages & Handling Responses Key differences: +- `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` - `send_message_streaming()` → `send_message()` (unified method) - `SendStreamingMessageRequest` → `SendMessageRequest` - `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` -- `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` - Each `StreamResponse` has a `payload` which is one of: `'task'`, `'message'`, `'status_update'`, `'artifact_update'`. Use `HasField()` to check which field is set. - Agent outputs should now be published as **Artifacts**, not status message text From 2887c1f365435ca1586cc078755201b8aa14262f Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 10:25:47 +0000 Subject: [PATCH 09/17] change Sending Messages & Handling Responses --- docs/migrations/v1_0/README.md | 59 +++++++++------------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 367b37b90..a5ff6c738 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -13,7 +13,7 @@ This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains 3. [Server: DefaultRequestHandler](#3-server-defaultrequesthandler) 4. [Server: Application Setup](#4-server-application-setup) 5. [Client: Creating a Client](#5-client-creating-a-client) -6. [Client: Sending Messages & Handling Responses](#6-client-sending-messages--handling-responses) +6. [Client: Send Message](#6-client-send-message) 7. [Client: Push Notifications Config](#7-client-push-notifications-config) 8. [Helper Utilities](#8-helper-utilities) @@ -277,63 +277,36 @@ async with client: --- -## 6. Client: Sending Messages & Handling Responses +## 6. Client: Send Message -Key differences: -- `send_message()` returns `AsyncIterator[StreamResponse]`; iterate with `async for` -- `send_message_streaming()` → `send_message()` (unified method) -- `SendStreamingMessageRequest` → `SendMessageRequest` -- `MessageSendParams` wrapper is gone; `message` is a field directly on `SendMessageRequest` -- Each `StreamResponse` has a `payload` which is one of: `'task'`, `'message'`, `'status_update'`, `'artifact_update'`. Use `HasField()` to check which field is set. -- Agent outputs should now be published as **Artifacts**, not status message text +The key change in `BaseClient` is the return type of `send_message()`: it **now returns `AsyncIterator[StreamResponse]`** (v0.3 returned `AsyncIterator[ClientEvent | Message]`). -**Before (v0.3):** ```python -from a2a.types import ( - Message, MessageSendParams, Part, Role, SendStreamingMessageRequest, - TextPart, -) +from a2a.types import Message, Part, Role, SendMessageRequest from uuid import uuid4 -message_params = MessageSendParams( +request = SendMessageRequest( message=Message( - role=Role.user, - parts=[Part(TextPart(text=user_input))], + role=Role.ROLE_USER, + parts=[Part(text=user_input)], message_id=uuid4().hex, - task_id=uuid4().hex, ) ) -request = SendStreamingMessageRequest(id=uuid4().hex, params=message_params) - -response = client.send_message_streaming(request) - -``` - -**After (v1.0):** -```python -from a2a.helpers import get_artifact_text -from a2a.types import ( - Message, Part, Role, SendMessageRequest, -) -from uuid import uuid4 - -parts = [Part(text=user_input)] -message = Message( - role=Role.ROLE_USER, - parts=parts, - message_id=uuid4().hex, - task_id=uuid4().hex, -) -request = SendMessageRequest(message=message) async for chunk in client.send_message(request): if chunk.HasField('artifact_update'): - print(get_artifact_text(chunk.artifact_update.artifact)) + ... elif chunk.HasField('status_update'): - print(chunk.status_update.status.state) + ... + elif chunk.HasField('task'): + ... + elif chunk.HasField('message'): + ... ``` -> **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) +Each `StreamResponse` yields exactly one of: `task`, `message`, `status_update`, or `artifact_update`. Use `HasField()` to check which field is set. + +> **Note**: The legacy `A2AClient` class has been removed. Use `create_client()` as shown in [section 5](#5-client-creating-a-client). **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) --- From 722bbf18a08c79247e60918887052a31e8a0ffa9 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 11:34:55 +0000 Subject: [PATCH 10/17] fixes --- docs/migrations/v1_0/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index a5ff6c738..1f7940693 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -1,6 +1,6 @@ # Migration Guide: v0.3 → v1.0 -This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains how to update your code. +This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains how to update your code. The changes reflect updates to the A2A protocol specification — see the [A2A protocol What's new in v1.0](https://a2a-protocol.org/latest/whats-new-v1/). > **Related guides**: If you use the database persistence layer, also see the [Database Migration Guide](database/). @@ -65,7 +65,6 @@ This affects every enum in the SDK: `TaskState`, `Role`. Key differences: - `Part(TextPart(text=...))` → `Part(text=...)` (flat union field) - `Role.user` → `Role.ROLE_USER`, `Role.agent` → `Role.ROLE_AGENT` -- `TextPart` is no longer needed; use `Part(text=...)` directly **Before (v0.3):** ```python @@ -111,7 +110,7 @@ Key differences: - `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed; use `AgentCard.default_input_modes` / `AgentCard.default_output_modes` for card-level defaults, or `AgentSkill.input_modes` / `AgentSkill.output_modes` for per-skill overrides - `supports_authenticated_extended_card` is no longer a top-level `AgentCard` field; it has moved into `AgentCapabilities` and is renamed to `extended_agent_card` - `AgentInterface.protocol_binding` accepted values: `'JSONRPC'`, `'HTTP+JSON'`, `'GRPC'` -- `examples` field has moved to `AgentSkill.examples` (set it per skill instead) +- `examples` field was removed; set it per `AgentSkill` instead **Before (v0.3):** ```python From 2efdf051016ceaaf3bfd4cd8d8c533ebf7acb5e8 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 12:13:18 +0000 Subject: [PATCH 11/17] add ## 5. Supporting v0.3 Clients --- docs/migrations/v1_0/README.md | 40 +++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 1f7940693..de28a45d7 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -12,10 +12,11 @@ This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains 2. [Types](#2-types) 3. [Server: DefaultRequestHandler](#3-server-defaultrequesthandler) 4. [Server: Application Setup](#4-server-application-setup) -5. [Client: Creating a Client](#5-client-creating-a-client) -6. [Client: Send Message](#6-client-send-message) -7. [Client: Push Notifications Config](#7-client-push-notifications-config) -8. [Helper Utilities](#8-helper-utilities) +5. [Supporting v0.3 Clients](#5-supporting-v03-clients) +6. [Client: Creating a Client](#6-client-creating-a-client) +7. [Client: Send Message](#7-client-send-message) +8. [Client: Push Notifications Config](#8-client-push-notifications-config) +9. [Helper Utilities](#9-helper-utilities) --- @@ -238,7 +239,30 @@ uvicorn.run(app, host=host, port=port) --- -## 5. Client: Creating a Client +## 5. Supporting v0.3 Clients + +If you cannot update all clients at once, you can run a v1.0 server that simultaneously accepts v0.3 connections. Two changes are needed. + +**1. Add the v0.3 AgentInterface to `supported_interfaces` in your `AgentCard`**: + +```python +supported_interfaces=[ + AgentInterface(protocol_binding='JSONRPC', protocol_version='0.3', url='http://localhost:9999/'), +] +``` + +**2. Enable the compat flag** on the relevant route factory: + +```python +create_jsonrpc_routes(request_handler, rpc_url='/', enable_v0_3_compat=True) +create_rest_routes(request_handler, enable_v0_3_compat=True) +``` + +> For a full working example see [`samples/hello_world_agent.py`](../../../samples/hello_world_agent.py). For known limitations see [issue #742](https://github.com/a2aproject/a2a-python/issues/742). + +--- + +## 6. Client: Creating a Client The `A2AClient` class has been removed. Use the new `create_client()` factory function. @@ -276,7 +300,7 @@ async with client: --- -## 6. Client: Send Message +## 7. Client: Send Message The key change in `BaseClient` is the return type of `send_message()`: it **now returns `AsyncIterator[StreamResponse]`** (v0.3 returned `AsyncIterator[ClientEvent | Message]`). @@ -309,7 +333,7 @@ Each `StreamResponse` yields exactly one of: `task`, `message`, `status_update`, --- -## 7. Client: Push Notifications Config +## 8. Client: Push Notifications Config `ClientConfig.push_notification_config` is now **singular** (a single `TaskPushNotificationConfig` or `None`), not a list. @@ -329,7 +353,7 @@ config = ClientConfig( --- -## 8. Helper Utilities +## 9. Helper Utilities A new `a2a.helpers` module consolidates helper functions into a single import. Most were previously available under `a2a.utils.*`; a few are new in v1.0. From b1143fa58b88c780b96166f59ab0ed06683e238b Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 14:24:12 +0000 Subject: [PATCH 12/17] add the before example to ## 7. Client: Send Message --- docs/migrations/v1_0/README.md | 56 ++++++++++++++++------------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index de28a45d7..a65b8586d 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -1,6 +1,6 @@ # Migration Guide: v0.3 → v1.0 -This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains how to update your code. The changes reflect updates to the A2A protocol specification — see the [A2A protocol What's new in v1.0](https://a2a-protocol.org/latest/whats-new-v1/). +This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains how to update your code. The changes reflect updates to the A2A protocol specification: [What's new in v1.0](https://a2a-protocol.org/latest/whats-new-v1/). > **Related guides**: If you use the database persistence layer, also see the [Database Migration Guide](database/). @@ -264,20 +264,21 @@ create_rest_routes(request_handler, enable_v0_3_compat=True) ## 6. Client: Creating a Client -The `A2AClient` class has been removed. Use the new `create_client()` factory function. +New `create_client()` `ClientFactory` function that creates a client for the agent. -### Simple usage: `create_client()` +> **Note**: The legacy `A2AClient` class has been removed. **Before (v0.3):** ```python -import httpx -from a2a.client import A2AClient, A2ACardResolver - -async with httpx.AsyncClient() as httpx_client: - resolver = A2ACardResolver(httpx_client, base_url) - agent_card = await resolver.get_agent_card() - client = A2AClient(httpx_client, agent_card=agent_card) - # use client... +from a2a.client import ClientFactory + +# From URL +factory = ClientFactory() +client = factory.create_client('http://localhost:9999/') + +# From an already-resolved AgentCard +factory = ClientFactory() +client = factory.create_client(agent_card) ``` **After (v1.0):** @@ -286,13 +287,9 @@ from a2a.client import create_client # From URL — resolves the agent card automatically client = await create_client('http://localhost:9999/') -async with client: - # use client... # From an already-resolved AgentCard client = await create_client(agent_card) -async with client: - # use client... ``` @@ -302,20 +299,24 @@ async with client: ## 7. Client: Send Message -The key change in `BaseClient` is the return type of `send_message()`: it **now returns `AsyncIterator[StreamResponse]`** (v0.3 returned `AsyncIterator[ClientEvent | Message]`). +The `BaseClient.send_message()` return type is standardised from `AsyncIterator[ClientEvent | Message]` to `AsyncIterator[StreamResponse]`. -```python -from a2a.types import Message, Part, Role, SendMessageRequest -from uuid import uuid4 +Each `StreamResponse` yields exactly one of: `task`, `message`, `status_update`, or `artifact_update`. Use `HasField()` to check which field is set. -request = SendMessageRequest( - message=Message( - role=Role.ROLE_USER, - parts=[Part(text=user_input)], - message_id=uuid4().hex, - ) -) +**Before (v0.3):** +```python +async for event, message in client.send_message(request): + if isinstance(event, Task): + ... + if isinstance(event, UpdateEvent): + ... + if message: + ... +``` + +**After (v1.0):** +```python async for chunk in client.send_message(request): if chunk.HasField('artifact_update'): ... @@ -327,9 +328,6 @@ async for chunk in client.send_message(request): ... ``` -Each `StreamResponse` yields exactly one of: `task`, `message`, `status_update`, or `artifact_update`. Use `HasField()` to check which field is set. - -> **Note**: The legacy `A2AClient` class has been removed. Use `create_client()` as shown in [section 5](#5-client-creating-a-client). **Example**: [`helloworld/test_client.py` in PR #474](https://github.com/a2aproject/a2a-samples/pull/474/files#diff-f62c07d3b00364a3100b7effb3e2a1cca0624277d3e40da1bdb07bb46b6a8cef) --- From cb83bdd02dac57aa8cc1a4140cf193a5c3c2184f Mon Sep 17 00:00:00 2001 From: Sampath Kumar Date: Mon, 20 Apr 2026 16:40:57 +0200 Subject: [PATCH 13/17] docs: refine Intro and expand v1.0 migration guide --- docs/migrations/v1_0/README.md | 55 ++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index de28a45d7..c078a55b7 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -1,14 +1,16 @@ -# Migration Guide: v0.3 → v1.0 +# A2A Python SDK Migration Guide: v0.3 → v1.0 -This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains how to update your code. The changes reflect updates to the A2A protocol specification — see the [A2A protocol What's new in v1.0](https://a2a-protocol.org/latest/whats-new-v1/). +The `a2a-sdk` has achieved a major milestone in stability and reliability with the update to full **A2A Protocol v1.0 compatibility**. This guide provides a detailed overview of the breaking changes in version `v1.0` and instructions for migrating your codebase. -> **Related guides**: If you use the database persistence layer, also see the [Database Migration Guide](database/). +Beyond protocol support, `v1.0` enhances the developer experience by introducing unified helper utilities for easier object creation and adopting Starlette route factory functions for more flexible server configuration. + +This documentation details the technical upgrades and architectural modifications introduced in A2A Python SDK v1.0; For developers using the database persistence layer, please refer to the [Database Migration Guide](database/) for specific update instructions. --- ## Table of Contents -1. [Package Dependency](#1-package-dependency) +1. [Update Dependency](#1-package-dependency) 2. [Types](#2-types) 3. [Server: DefaultRequestHandler](#3-server-defaultrequesthandler) 4. [Server: Application Setup](#4-server-application-setup) @@ -20,27 +22,40 @@ This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains --- -## 1. Package Dependency +## 1. Update Dependencies + +(UV users) To upgrade to the latest version of the `a2a-sdk`, update the dependencies section in your `pyproject.toml` file. + +| File | Before (`v0.3`) | After (`v1.0`) | +|------------------|-----------------------------------|-----------------------------------| +| `pyproject.toml` | dependencies = ["a2a-sdk>=0.3.0"] | dependencies = ["a2a-sdk>=1.0.0"] | + +**Installation** -Update your dependency to the new version: +After updating your configuration file, sync your environment: + +* Using UV: + +```bash +uv sync +``` -```toml -# pyproject.toml — before -dependencies = ["a2a-sdk>=0.3.0"] +* Using pip: -# pyproject.toml — after -dependencies = ["a2a-sdk>=1.0.0"] +```bash +pip install --upgrade a2a-sdk ``` --- ## 2. Types -Types are now **Protobuf-based** instead of Pydantic models. +Types have migrated from Pydantic models to Protobuf-based classes. + ### Enum values: snake_case → SCREAMING_SNAKE_CASE -All enum values have been renamed from snake_case strings to `SCREAMING_SNAKE_CASE`. +All the enum values are now standardized from snake_case to **SCREAMING_SNAKE_CASE** format. This affects every enum in the SDK: `TaskState`, `Role`. @@ -55,6 +70,7 @@ This affects every enum in the SDK: `TaskState`, `Role`. | `TaskState` | `TaskState.input_required` | `TaskState.TASK_STATE_INPUT_REQUIRED` | | `TaskState` | `TaskState.auth_required` | `TaskState.TASK_STATE_AUTH_REQUIRED` | | `TaskState` | `TaskState.rejected` | `TaskState.TASK_STATE_REJECTED` | +||| | `Role` | *(no equivalent — protobuf default)* | `Role.ROLE_UNSPECIFIED` | | `Role` | `Role.user` | `Role.ROLE_USER` | | `Role` | `Role.agent` | `Role.ROLE_AGENT` | @@ -81,15 +97,22 @@ message = Message( ``` **After (v1.0):** + +Using [A2A helper utilities](#helper-utilities) + ```python from a2a.helpers import new_text_message from a2a.types import Role -# Use the helper for text messages +# Use the helper function to create `Hello` message message = new_text_message(text="Hello", role=Role.ROLE_USER) -# Or construct directly -from a2a.types import Message, Part +``` + +Without helper utils, you can still construct directly + +```python +from a2a.types import Message, Part, Role from uuid import uuid4 message = Message( From 3393c12794c60169a08628eedd82f022e3a3dc56 Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 14:49:14 +0000 Subject: [PATCH 14/17] changes --- docs/migrations/v1_0/README.md | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index a65b8586d..375da51cb 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -17,6 +17,8 @@ This guide covers the breaking changes introduced in `a2a-sdk` v1.0 and explains 7. [Client: Send Message](#7-client-send-message) 8. [Client: Push Notifications Config](#8-client-push-notifications-config) 9. [Helper Utilities](#9-helper-utilities) +10. [Summary of Key Changes](#10-summary-of-key-changes-in-v10) +11. [Get Started](#11-get-started) --- @@ -373,3 +375,37 @@ from a2a.helpers import ( ) ``` +--- + +## 10. Summary of Key Changes in v1.0 + +- **Standardisation to `SCREAMING_SNAKE_CASE`** — All enum values have been renamed from `kebab-case` strings to `SCREAMING_SNAKE_CASE` for compliance with the ProtoJSON specification. +- **`AgentCard`** — Significantly restructured to support multiple transport interfaces. + - **`AgentInterface`** — The top-level `url` field is replaced by `supported_interfaces`, a list of `AgentInterface` objects. Each entry describes a single transport endpoint carrying `protocol_binding`, `protocol_version`, and `url`. + - **Input and output modes** — `AgentCapabilities.input_modes` and `AgentCapabilities.output_modes` are removed and now live directly on `AgentCard` as `default_input_modes` and `default_output_modes`. Individual skills can override these with their own `input_modes` and `output_modes`. +- **Application setup** — The wrapper classes (`A2AStarletteApplication`, `A2AFastApiApplication` and `A2ARESTFastApiApplication`) are now removed. Server setup now uses route factory functions `create_jsonrpc_routes()`, `create_rest_routes()`, `create_agent_card_routes()` composed directly into a Starlette or FastAPI app. +- **Helper utilities** — A new `a2a.helpers` module consolidates all helper functions under a single import, replacing the scattered `a2a.utils.*` modules and adding new helpers for constructing and reading v1.0 proto types. + +--- + +## 11. Get Started + +The fastest way to see v1.0 in action is to run the samples: + +| File | Role | Description | +|---|---|---| +| [`samples/hello_world_agent.py`](../../../samples/hello_world_agent.py) | **Server** | A2A agent exposing JSON-RPC, REST, and gRPC — with v0.3 compat enabled | +| [`samples/cli.py`](../../../samples/cli.py) | **Client** | Interactive terminal client; supports all three transports | + +```bash +# In one terminal — start the agent: +uv run python samples/hello_world_agent.py + +# In another — connect with the CLI: +uv run python samples/cli.py +``` + +Then type a message like `hello` and press Enter. See [`samples/README.md`](../../../samples/README.md) for full details. + +For more examples see the [a2a-samples repository](https://github.com/a2aproject/a2a-samples/tree/main/samples/python). + From 1d43ac3dc0a12fa8b98490aff8dc854346f8a56e Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 14:53:41 +0000 Subject: [PATCH 15/17] fix table of contents --- docs/migrations/v1_0/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 8926b947f..4743def73 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -10,7 +10,7 @@ This documentation details the technical upgrades and architectural modification ## Table of Contents -1. [Update Dependency](#1-package-dependency) +1. [Update Dependencies](#1-update-dependencies) 2. [Types](#2-types) 3. [Server: DefaultRequestHandler](#3-server-defaultrequesthandler) 4. [Server: Application Setup](#4-server-application-setup) From 7d38ca73db0d4249921a822ada11680dd6af0f0d Mon Sep 17 00:00:00 2001 From: Sampath Kumar Date: Mon, 20 Apr 2026 17:08:30 +0200 Subject: [PATCH 16/17] update docs --- docs/migrations/v1_0/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 8926b947f..aa34ca46b 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -129,7 +129,7 @@ message = Message( ### AgentCard Structure -The `AgentCard` has been significantly restructured to support multiple transport interfaces. +The new `AgentCard` can supports multiple transport bindings using `AgentInterface` class. Key differences: - `url` is gone; use `supported_interfaces` with one or more `AgentInterface` entries @@ -162,15 +162,21 @@ agent_card = AgentCard( **After (v1.0):** ```python -from a2a.types import AgentCard, AgentCapabilities, AgentInterface, AgentSkill +from a2a.types import AgentCard, AgentCapabilities, AgentInterface, AgentSkill, agent_card = AgentCard( name='My Agent', description='...', supported_interfaces=[ + # JSON-RPC AgentInterface( protocol_binding='JSONRPC', - url='http://localhost:9999/', + url='http://localhost:41241/a2a/jsonrpc/', + ), + # GRPC + AgentInterface( + protocol_binding='GRPC', + url='http://localhost:50051/a2a/grpc/', ) ], version='1.0.0', @@ -217,7 +223,7 @@ request_handler = DefaultRequestHandler( ## 4. Server: Application Setup -The `A2AStarletteApplication`, `A2AFastApiApplication` and `A2ARESTFastApiApplication` wrapper classes have been removed. Server setup now uses **Starlette route factory functions** directly, giving you full control over the routing. +The wrapper classes (`A2AStarletteApplication`, `A2AFastApiApplication` and `A2ARESTFastApiApplication`) are now removed. The Server setup now uses Starlette route factory functions directly, giving you full control over the routing. **Before (v0.3):** ```python From 59c1411d5d8bfb7d9d42af6a692414384ad3b98f Mon Sep 17 00:00:00 2001 From: sokoliva Date: Mon, 20 Apr 2026 15:16:46 +0000 Subject: [PATCH 17/17] docs: Update README.md --- docs/migrations/v1_0/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/migrations/v1_0/README.md b/docs/migrations/v1_0/README.md index 4743def73..9f76a47ba 100644 --- a/docs/migrations/v1_0/README.md +++ b/docs/migrations/v1_0/README.md @@ -4,7 +4,7 @@ The `a2a-sdk` has achieved a major milestone in stability and reliability with t Beyond protocol support, `v1.0` enhances the developer experience by introducing unified helper utilities for easier object creation and adopting Starlette route factory functions for more flexible server configuration. -This documentation details the technical upgrades and architectural modifications introduced in A2A Python SDK v1.0; For developers using the database persistence layer, please refer to the [Database Migration Guide](database/) for specific update instructions. +This documentation details the technical upgrades and architectural modifications introduced in A2A Python SDK v1.0. For developers using the database persistence layer, please refer to the [Database Migration Guide](database/) for specific update instructions. --- @@ -81,6 +81,8 @@ This affects every enum in the SDK: `TaskState`, `Role`. ### Message and Part construction +Constructing messages is simplified in v1.0. The old API required wrapping content in an intermediate type (`TextPart`, `FilePart`, `DataPart`) before placing it inside a `Part`. In v1.0, `Part` is a single unified message — set the content type directly on it and the wrapper types are gone entirely. + Key differences: - `Part(TextPart(text=...))` → `Part(text=...)` (flat union field) - `Role.user` → `Role.ROLE_USER`, `Role.agent` → `Role.ROLE_AGENT`