Skip to content

feat: add a TextClient class for a simplified text-based communication#963

Open
sokoliva wants to merge 5 commits intoa2aproject:1.0-devfrom
sokoliva:reduce-boilerplate
Open

feat: add a TextClient class for a simplified text-based communication#963
sokoliva wants to merge 5 commits intoa2aproject:1.0-devfrom
sokoliva:reduce-boilerplate

Conversation

@sokoliva
Copy link
Copy Markdown
Member

@sokoliva sokoliva commented Apr 13, 2026

Description

This feature dramatically reduces the complexity for common text-only exchanges, improving developer experience.

Changes

  • Add TextClient, a facade around Client that simplifies text-based
    communication by hiding proto construction, stream iteration, and
    multi-part text aggregation behind a single send_text_message method.
  • Add create_text_client factory function mirroring create_client,
    and export both from a2a.client.
  • Add samples/text_client_cli.py as a minimal worked example, and
    samples/README.md documenting all three sample files.
  • Refactor samples/cli.py to iterate StreamResponse directly and
    use the shared get_message_text helper.

Tested

Output from running simpletext_client_cli.py

uv run samples/text_client_cli.py 
Connecting to http://127.0.0.1:41241 (preferred transport: Any)

✓ Agent Card Found:
  Name: Sample Agent
  Picked Transport: JsonRpcTransport

Connected! Send a message or type /quit to exit.
You: hi
Agent: Processing your question... Hello World! Nice to meet you!
You: bye
Agent: Processing your question... Goodbye! Have a wonderful day!
You: /quit

Output from running non-simplified cli.py

uv run samples/cli.py
Connecting to http://127.0.0.1:41241 (preferred transport: Any)

✓ Agent Card Found:
  Name: Sample Agent
  Picked Transport: JsonRpcTransport

Connected! Send a message or type /quit to exit.
You: hi
Task [state=TASK_STATE_SUBMITTED]
TaskStatusUpdate [state=TASK_STATE_WORKING]: Processing your question...
TaskArtifactUpdate [name=response]: Hello World! Nice to meet you!
TaskStatusUpdate [state=TASK_STATE_COMPLETED]: 
--- Task Completed ---
You: bye
Task [state=TASK_STATE_SUBMITTED]
TaskStatusUpdate [state=TASK_STATE_WORKING]: Processing your question...
TaskArtifactUpdate [name=response]: Goodbye! Have a wonderful day!
TaskStatusUpdate [state=TASK_STATE_COMPLETED]: 
--- Task Completed ---
You: /quit

Fixes #959 🦕

@sokoliva sokoliva requested a review from a team as a code owner April 13, 2026 07:59
@sokoliva sokoliva requested a review from guglielmo-san April 13, 2026 07:59
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 13, 2026

🧪 Code Coverage (vs 1.0-dev)

⬇️ Download Full Report

Base PR Delta
src/a2a/client/client_factory.py 88.67% 88.89% 🟢 +0.22%
src/a2a/client/text_client.py (new) 84.62%
Total 92.53% 92.48% 🔴 -0.05%

Generated by coverage-comment.yml

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces TextClient, a facade that simplifies text-based communication by aggregating response parts into a single string, and refactors the existing CLI sample to use message utilities. It includes a new sample CLI, factory methods, and unit tests. Feedback suggests improving lifecycle management via the asynchronous context manager protocol, refining response aggregation to handle streamed tokens correctly, and utilizing existing utilities to reduce code duplication in the new client.

Comment thread src/a2a/client/text_client.py
Comment thread src/a2a/client/text_client.py Outdated
Comment thread src/a2a/client/text_client.py


async def create_text_client( # noqa: PLR0913
agent: str | AgentCard,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent word is too broad. Can we do ?

Suggested change
agent: str | AgentCard,
agent_card: str | AgentCard,

Copy link
Copy Markdown
Member Author

@sokoliva sokoliva Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent can also be the base URL of the agent so renaming it to agent_card could be misleading. WDYT?


config = ClientConfig(supported_protocol_bindings=['JSONRPC'])

text_client = await create_text_client(card, client_config=config)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, when I do

text_client = await create_text_client(
    agent=SERVER_URL,
    client_config=ClientConfig(streaming=False, supported_protocol_bindings="JSONRPC")
)

I get

Traceback (most recent call last):
 ....
 ....
 ....
  File ".../a2a/client/client_factory.py", line 360, in create
    return BaseClient(
        card,
    ...<2 lines>...
        interceptors or [],
    )
TypeError: 
TypeError: BaseClient.__init__() missing 1 required positional argument: 'interceptors'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants