Skip to content

Commit 2a375bb

Browse files
authored
Merge branch '1.0-dev' into bump-tk-version
2 parents f5388f1 + fe5de77 commit 2a375bb

59 files changed

Lines changed: 3194 additions & 1632 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/coverage-comment.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@ jobs:
1818
github.event.workflow_run.conclusion == 'success'
1919
steps:
2020
- name: Download Coverage Artifacts
21-
uses: actions/download-artifact@v4
21+
uses: actions/download-artifact@v8
2222
with:
2323
run-id: ${{ github.event.workflow_run.id }}
2424
github-token: ${{ secrets.A2A_BOT_PAT }}
2525
name: coverage-data
2626

2727
- name: Upload Coverage Report
2828
id: upload-report
29-
uses: actions/upload-artifact@v4
29+
uses: actions/upload-artifact@v7
3030
with:
3131
name: coverage-report
3232
path: coverage/
3333
retention-days: 14
3434

3535
- name: Post Comment
36-
uses: actions/github-script@v6
36+
uses: actions/github-script@v8
3737
env:
3838
ARTIFACT_URL: ${{ steps.upload-report.outputs.artifact-url }}
3939
with:

.github/workflows/spelling.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
steps:
2828
- name: check-spelling
2929
id: spelling
30-
uses: check-spelling/check-spelling@main
30+
uses: check-spelling/check-spelling@a35147f799f30f8739c33f92222c847214e82e67 # https://github.com/check-spelling/check-spelling/issues/103#issuecomment-4181666219
3131
with:
3232
suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
3333
checkout: true

.github/workflows/unit-tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
# Coverage comparison for PRs (only on Python 3.14 to avoid duplicate work)
6060
- name: Checkout Base Branch
6161
if: github.event_name == 'pull_request' && matrix.python-version == '3.14'
62-
uses: actions/checkout@v4
62+
uses: actions/checkout@v6
6363
with:
6464
ref: ${{ github.event.pull_request.base.ref || 'main' }}
6565
clean: true
@@ -75,7 +75,7 @@ jobs:
7575
7676
- name: Checkout PR Branch (Restore)
7777
if: github.event_name == 'pull_request' && matrix.python-version == '3.14'
78-
uses: actions/checkout@v4
78+
uses: actions/checkout@v6
7979
with:
8080
clean: true
8181

@@ -93,7 +93,7 @@ jobs:
9393
echo ${{ github.event.pull_request.base.ref || 'main' }} > ./BASE_BRANCH
9494
9595
- name: Upload Coverage Artifacts
96-
uses: actions/upload-artifact@v4
96+
uses: actions/upload-artifact@v7
9797
if: github.event_name == 'pull_request' && matrix.python-version == '3.14'
9898
with:
9999
name: coverage-data
@@ -111,7 +111,7 @@ jobs:
111111
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=88
112112

113113
- name: Upload Artifact (base)
114-
uses: actions/upload-artifact@v4
114+
uses: actions/upload-artifact@v7
115115
if: github.event_name != 'pull_request' && matrix.python-version == '3.14'
116116
with:
117117
name: coverage-report

GEMINI.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
- **Language**: Python 3.10+
1010
- **Package Manager**: `uv`
11-
- **Lead Transports**: FastAPI (REST/JSON-RPC), gRPC
11+
- **Lead Transports**: Starlette (REST/JSON-RPC), gRPC
1212
- **Data Layer**: SQLAlchemy (SQL), Pydantic (Logic/Legacy), Protobuf (Modern Messaging)
1313
- **Key Directories**:
1414
- `/src`: Core implementation logic.

itk/main.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,19 @@ async def handle_call_agent(call: instruction_pb2.CallAgent) -> list[str]:
138138
nested_msg = wrap_instruction_to_request(call.instruction)
139139
request = SendMessageRequest(message=nested_msg)
140140

141-
results = []
141+
results: list[str] = []
142142
async for event in client.send_message(request):
143-
# Event is streaming response and task
143+
# Event is StreamResponse
144144
logger.info('Event: %s', event)
145-
stream_resp, task = event
145+
stream_resp = event
146146

147147
message = None
148148
if stream_resp.HasField('message'):
149149
message = stream_resp.message
150-
elif task and task.status.HasField('message'):
151-
message = task.status.message
150+
elif stream_resp.HasField(
151+
'task'
152+
) and stream_resp.task.status.HasField('message'):
153+
message = stream_resp.task.status.message
152154
elif stream_resp.HasField(
153155
'status_update'
154156
) and stream_resp.status_update.status.HasField('message'):

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ classifiers = [
3434
]
3535

3636
[project.optional-dependencies]
37-
http-server = ["fastapi>=0.115.2", "sse-starlette", "starlette"]
37+
http-server = ["sse-starlette", "starlette"]
3838
encryption = ["cryptography>=43.0.0"]
3939
grpc = ["grpcio>=1.60", "grpcio-tools>=1.60", "grpcio-status>=1.60", "grpcio_reflection>=1.7.0"]
4040
telemetry = ["opentelemetry-api>=1.33.0", "opentelemetry-sdk>=1.33.0"]
@@ -107,6 +107,7 @@ style = "pep440"
107107

108108
[dependency-groups]
109109
dev = [
110+
"fastapi>=0.115.2",
110111
"mypy>=1.15.0",
111112
"PyJWT>=2.0.0",
112113
"pytest>=8.3.5",

src/a2a/client/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Client-side components for interacting with an A2A agent."""
22

3-
import logging
4-
53
from a2a.client.auth import (
64
AuthInterceptor,
75
CredentialService,
@@ -13,8 +11,6 @@
1311
Client,
1412
ClientCallContext,
1513
ClientConfig,
16-
ClientEvent,
17-
Consumer,
1814
)
1915
from a2a.client.client_factory import ClientFactory, minimal_agent_card
2016
from a2a.client.errors import (
@@ -26,9 +22,6 @@
2622
from a2a.client.interceptors import ClientCallInterceptor
2723

2824

29-
logger = logging.getLogger(__name__)
30-
31-
3225
__all__ = [
3326
'A2ACardResolver',
3427
'A2AClientError',
@@ -40,9 +33,7 @@
4033
'ClientCallContext',
4134
'ClientCallInterceptor',
4235
'ClientConfig',
43-
'ClientEvent',
4436
'ClientFactory',
45-
'Consumer',
4637
'CredentialService',
4738
'InMemoryContextCredentialStore',
4839
'create_text_message_object',

src/a2a/client/auth/interceptor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ async def before(self, args: BeforeArgs) -> None:
3939
scheme_name, args.context
4040
)
4141
if credential and scheme_name in agent_card.security_schemes:
42-
scheme = agent_card.security_schemes.get(scheme_name)
43-
if not scheme:
44-
continue
42+
scheme = agent_card.security_schemes[scheme_name]
4543

4644
if args.context is None:
4745
args.context = ClientCallContext()

src/a2a/client/base_client.py

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
Client,
66
ClientCallContext,
77
ClientConfig,
8-
ClientEvent,
9-
Consumer,
108
)
11-
from a2a.client.client_task_manager import ClientTaskManager
129
from a2a.client.interceptors import (
1310
AfterArgs,
1411
BeforeArgs,
@@ -42,10 +39,9 @@ def __init__(
4239
card: AgentCard,
4340
config: ClientConfig,
4441
transport: ClientTransport,
45-
consumers: list[Consumer],
4642
interceptors: list[ClientCallInterceptor],
4743
):
48-
super().__init__(consumers, interceptors)
44+
super().__init__(interceptors)
4945
self._card = card
5046
self._config = config
5147
self._transport = transport
@@ -56,7 +52,7 @@ async def send_message(
5652
request: SendMessageRequest,
5753
*,
5854
context: ClientCallContext | None = None,
59-
) -> AsyncIterator[ClientEvent]:
55+
) -> AsyncIterator[StreamResponse]:
6056
"""Sends a message to the agent.
6157
6258
This method handles both streaming and non-streaming (polling) interactions
@@ -68,7 +64,7 @@ async def send_message(
6864
context: Optional client call context.
6965
7066
Yields:
71-
An async iterator of `ClientEvent`
67+
An async iterator of `StreamResponse`
7268
"""
7369
self._apply_client_config(request)
7470
if not self._config.streaming or not self._card.capabilities.streaming:
@@ -84,19 +80,14 @@ async def send_message(
8480
# In non-streaming case we convert to a StreamResponse so that the
8581
# client always sees the same iterator.
8682
stream_response = StreamResponse()
87-
client_event: ClientEvent
8883
if response.HasField('task'):
8984
stream_response.task.CopyFrom(response.task)
90-
client_event = (stream_response, response.task)
9185
elif response.HasField('message'):
9286
stream_response.message.CopyFrom(response.message)
93-
client_event = (stream_response, None)
9487
else:
95-
# Response must have either task or message
9688
raise ValueError('Response has neither task nor message')
9789

98-
await self.consume(client_event, self._card)
99-
yield client_event
90+
yield stream_response
10091
return
10192

10293
async for event in self._execute_stream_with_interceptors(
@@ -130,8 +121,7 @@ async def _process_stream(
130121
self,
131122
stream: AsyncIterator[StreamResponse],
132123
before_args: BeforeArgs,
133-
) -> AsyncGenerator[ClientEvent]:
134-
tracker = ClientTaskManager()
124+
) -> AsyncGenerator[StreamResponse, None]:
135125
async for stream_response in stream:
136126
after_args = AfterArgs(
137127
result=stream_response,
@@ -140,12 +130,8 @@ async def _process_stream(
140130
context=before_args.context,
141131
)
142132
await self._intercept_after(after_args)
143-
intercepted_response = after_args.result
144-
client_event = await self._format_stream_event(
145-
intercepted_response, tracker
146-
)
147-
yield client_event
148-
if intercepted_response.HasField('message'):
133+
yield after_args.result
134+
if after_args.result.HasField('message'):
149135
return
150136

151137
async def get_task(
@@ -318,7 +304,7 @@ async def subscribe(
318304
request: SubscribeToTaskRequest,
319305
*,
320306
context: ClientCallContext | None = None,
321-
) -> AsyncIterator[ClientEvent]:
307+
) -> AsyncIterator[StreamResponse]:
322308
"""Resubscribes to a task's event stream.
323309
324310
This is only available if both the client and server support streaming.
@@ -328,7 +314,7 @@ async def subscribe(
328314
context: Optional client call context.
329315
330316
Yields:
331-
An async iterator of `ClientEvent` objects.
317+
An async iterator of `StreamResponse` objects.
332318
333319
Raises:
334320
NotImplementedError: If streaming is not supported by the client or server.
@@ -436,7 +422,7 @@ async def _execute_stream_with_interceptors(
436422
transport_call: Callable[
437423
[Any, ClientCallContext | None], AsyncIterator[StreamResponse]
438424
],
439-
) -> AsyncIterator[ClientEvent]:
425+
) -> AsyncIterator[StreamResponse]:
440426

441427
before_args = BeforeArgs(
442428
input=input_data,
@@ -446,7 +432,7 @@ async def _execute_stream_with_interceptors(
446432
)
447433
before_result = await self._intercept_before(before_args)
448434

449-
if before_result:
435+
if before_result is not None:
450436
after_args = AfterArgs(
451437
result=before_result['early_return'],
452438
method=method,
@@ -455,8 +441,7 @@ async def _execute_stream_with_interceptors(
455441
)
456442
await self._intercept_after(after_args, before_result['executed'])
457443

458-
tracker = ClientTaskManager()
459-
yield await self._format_stream_event(after_args.result, tracker)
444+
yield after_args.result
460445
return
461446

462447
stream = transport_call(before_args.input, before_args.context)
@@ -495,19 +480,3 @@ async def _intercept_after(
495480
await interceptor.after(args)
496481
if args.early_return:
497482
return
498-
499-
async def _format_stream_event(
500-
self, stream_response: StreamResponse, tracker: ClientTaskManager
501-
) -> ClientEvent:
502-
client_event: ClientEvent
503-
if stream_response.HasField('message'):
504-
client_event = (stream_response, None)
505-
await self.consume(client_event, self._card)
506-
return client_event
507-
508-
await tracker.process(stream_response)
509-
updated_task = tracker.get_task_or_raise()
510-
client_event = (stream_response, updated_task)
511-
512-
await self.consume(client_event, self._card)
513-
return client_event

0 commit comments

Comments
 (0)