a2a-handler 0.1.16__tar.gz → 0.1.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/PKG-INFO +3 -3
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/pyproject.toml +3 -3
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/__init__.py +3 -2
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/card.py +5 -1
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/server/app.py +1 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/service.py +36 -1
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/app.py +19 -4
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/auth.py +10 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/messages.py +5 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/validation.py +19 -1
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/LICENSE +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/README.md +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/__init__.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/auth.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/_config.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/_helpers.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/auth.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/mcp.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/message.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/server.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/session.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/cli/task.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/common/__init__.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/common/config.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/common/logging.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/common/output.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/mcp/__init__.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/mcp/server.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/server/__init__.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/server/agent.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/server/card.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/server/ollama.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/session.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/__init__.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/app.tcss +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/__init__.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/artifacts.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/card.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/contact.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/input.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/logs.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/tui/components/tasks.py +0 -0
- {a2a_handler-0.1.16 → a2a_handler-0.1.17}/src/a2a_handler/webhook.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: a2a-handler
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.17
|
|
4
4
|
Summary: An A2A Protocol client TUI and CLI.
|
|
5
5
|
Keywords: a2a,a2a-protocol,agent,agent-to-agent,ai,cli,tui
|
|
6
6
|
Author: Al Duncanson
|
|
@@ -18,10 +18,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
|
-
Requires-Dist: a2a-sdk>=0.3.
|
|
21
|
+
Requires-Dist: a2a-sdk>=0.3.24
|
|
22
22
|
Requires-Dist: click>=8.0.0
|
|
23
23
|
Requires-Dist: rich-click>=1.8.0
|
|
24
|
-
Requires-Dist: google-adk>=
|
|
24
|
+
Requires-Dist: google-adk>=1.26.0
|
|
25
25
|
Requires-Dist: httpx>=0.27.0
|
|
26
26
|
Requires-Dist: litellm>=1.0.0
|
|
27
27
|
Requires-Dist: python-dotenv>=1.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "a2a-handler"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.17"
|
|
4
4
|
description = "An A2A Protocol client TUI and CLI."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -32,10 +32,10 @@ classifiers = [
|
|
|
32
32
|
"Typing :: Typed",
|
|
33
33
|
]
|
|
34
34
|
dependencies = [
|
|
35
|
-
"a2a-sdk>=0.3.
|
|
35
|
+
"a2a-sdk>=0.3.24",
|
|
36
36
|
"click>=8.0.0",
|
|
37
37
|
"rich-click>=1.8.0",
|
|
38
|
-
"google-adk>=
|
|
38
|
+
"google-adk>=1.26.0",
|
|
39
39
|
"httpx>=0.27.0",
|
|
40
40
|
"litellm>=1.0.0",
|
|
41
41
|
"python-dotenv>=1.0.0",
|
|
@@ -67,11 +67,12 @@ def version() -> None:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
@cli.command()
|
|
70
|
-
|
|
70
|
+
@click.option("--bearer", "-b", "bearer_token", help="Bearer token for agent auth")
|
|
71
|
+
def tui(bearer_token: str | None) -> None:
|
|
71
72
|
"""Launch the interactive terminal interface."""
|
|
72
73
|
log.info("Launching TUI")
|
|
73
74
|
logging.getLogger().handlers = []
|
|
74
|
-
app = HandlerTUI()
|
|
75
|
+
app = HandlerTUI(initial_bearer_token=bearer_token)
|
|
75
76
|
app.run()
|
|
76
77
|
|
|
77
78
|
|
|
@@ -9,6 +9,7 @@ from a2a.types import AgentCard
|
|
|
9
9
|
|
|
10
10
|
from a2a_handler.common import Output, get_logger
|
|
11
11
|
from a2a_handler.service import A2AService
|
|
12
|
+
from a2a_handler.session import get_credentials
|
|
12
13
|
from a2a_handler.validation import (
|
|
13
14
|
ValidationResult,
|
|
14
15
|
validate_agent_card_from_file,
|
|
@@ -34,12 +35,15 @@ def card() -> None:
|
|
|
34
35
|
def card_get(agent_url: str, authenticated: bool) -> None:
|
|
35
36
|
"""Retrieve an agent's card."""
|
|
36
37
|
log.info("Fetching agent card from %s", agent_url)
|
|
38
|
+
credentials = get_credentials(agent_url) if authenticated else None
|
|
39
|
+
if authenticated and credentials is None:
|
|
40
|
+
log.warning("No saved credentials found for %s", agent_url)
|
|
37
41
|
|
|
38
42
|
async def do_get() -> None:
|
|
39
43
|
output = Output()
|
|
40
44
|
try:
|
|
41
45
|
async with build_http_client() as http_client:
|
|
42
|
-
service = A2AService(http_client, agent_url)
|
|
46
|
+
service = A2AService(http_client, agent_url, credentials=credentials)
|
|
43
47
|
card_data = await service.get_card()
|
|
44
48
|
log.info("Retrieved card for agent: %s", card_data.name)
|
|
45
49
|
|
|
@@ -9,6 +9,7 @@ from typing import AsyncIterator
|
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
from a2a.client import A2ACardResolver, Client, ClientConfig, ClientFactory
|
|
12
|
+
from a2a.client.errors import A2AClientHTTPError
|
|
12
13
|
from a2a.types import (
|
|
13
14
|
AgentCard,
|
|
14
15
|
GetTaskPushNotificationConfigParams,
|
|
@@ -27,6 +28,11 @@ from a2a.types import (
|
|
|
27
28
|
TransportProtocol,
|
|
28
29
|
)
|
|
29
30
|
|
|
31
|
+
from a2a.utils.constants import (
|
|
32
|
+
AGENT_CARD_WELL_KNOWN_PATH,
|
|
33
|
+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
34
|
+
)
|
|
35
|
+
|
|
30
36
|
from a2a_handler.auth import AuthCredentials
|
|
31
37
|
from a2a_handler.common import get_logger
|
|
32
38
|
|
|
@@ -255,18 +261,47 @@ class A2AService:
|
|
|
255
261
|
auth_headers = credentials.to_headers()
|
|
256
262
|
self.http_client.headers.update(auth_headers)
|
|
257
263
|
self._applied_auth_headers = set(auth_headers.keys())
|
|
264
|
+
# Rebuild the SDK client so updated headers are guaranteed to be used.
|
|
265
|
+
self._cached_client = None
|
|
258
266
|
logger.debug("Applied authentication headers: %s", list(auth_headers.keys()))
|
|
259
267
|
|
|
268
|
+
def clear_credentials(self) -> None:
|
|
269
|
+
"""Clear authentication credentials from the service and HTTP client."""
|
|
270
|
+
for header_name in self._applied_auth_headers:
|
|
271
|
+
self.http_client.headers.pop(header_name, None)
|
|
272
|
+
self._applied_auth_headers.clear()
|
|
273
|
+
self.credentials = None
|
|
274
|
+
# Rebuild the SDK client so cleared headers are guaranteed to be used.
|
|
275
|
+
self._cached_client = None
|
|
276
|
+
logger.debug("Cleared authentication headers")
|
|
277
|
+
|
|
260
278
|
async def get_card(self) -> AgentCard:
|
|
261
279
|
"""Fetch and cache the agent card.
|
|
262
280
|
|
|
281
|
+
Tries the standard well-known path first (``agent-card.json``), then
|
|
282
|
+
falls back to the previous path (``agent.json``) used by older ADK
|
|
283
|
+
versions.
|
|
284
|
+
|
|
263
285
|
Returns:
|
|
264
286
|
The agent's card with metadata and capabilities
|
|
265
287
|
"""
|
|
266
288
|
if self._cached_agent_card is None:
|
|
267
289
|
logger.info("Fetching agent card from %s", self.agent_url)
|
|
268
290
|
card_resolver = A2ACardResolver(self.http_client, self.agent_url)
|
|
269
|
-
|
|
291
|
+
try:
|
|
292
|
+
self._cached_agent_card = await card_resolver.get_agent_card()
|
|
293
|
+
except (A2AClientHTTPError, httpx.HTTPStatusError):
|
|
294
|
+
logger.info(
|
|
295
|
+
"Agent card not found at %s, trying %s",
|
|
296
|
+
AGENT_CARD_WELL_KNOWN_PATH,
|
|
297
|
+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
298
|
+
)
|
|
299
|
+
fallback_resolver = A2ACardResolver(
|
|
300
|
+
self.http_client,
|
|
301
|
+
self.agent_url,
|
|
302
|
+
agent_card_path=PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
303
|
+
)
|
|
304
|
+
self._cached_agent_card = await fallback_resolver.get_agent_card()
|
|
270
305
|
logger.info("Connected to agent: %s", self._cached_agent_card.name)
|
|
271
306
|
return self._cached_agent_card
|
|
272
307
|
|
|
@@ -19,6 +19,7 @@ from textual.logging import TextualHandler
|
|
|
19
19
|
from textual.screen import Screen
|
|
20
20
|
from textual.widgets import Button, Footer, Input
|
|
21
21
|
|
|
22
|
+
from a2a_handler.auth import AuthCredentials
|
|
22
23
|
from a2a_handler.common import get_theme, install_tui_log_handler, save_theme
|
|
23
24
|
from a2a_handler.service import A2AService
|
|
24
25
|
from a2a_handler.tui.components import (
|
|
@@ -72,7 +73,7 @@ class HandlerTUI(App[Any]):
|
|
|
72
73
|
return False
|
|
73
74
|
return True
|
|
74
75
|
|
|
75
|
-
def __init__(self, **kwargs: Any) -> None:
|
|
76
|
+
def __init__(self, initial_bearer_token: str | None = None, **kwargs: Any) -> None:
|
|
76
77
|
super().__init__(**kwargs)
|
|
77
78
|
self.current_agent_card: AgentCard | None = None
|
|
78
79
|
self.http_client: httpx.AsyncClient | None = None
|
|
@@ -80,6 +81,7 @@ class HandlerTUI(App[Any]):
|
|
|
80
81
|
self.current_agent_url: str | None = None
|
|
81
82
|
self._agent_service: A2AService | None = None
|
|
82
83
|
self._is_maximized: bool = False
|
|
84
|
+
self._initial_bearer_token = initial_bearer_token
|
|
83
85
|
|
|
84
86
|
def compose(self) -> ComposeResult:
|
|
85
87
|
with Container(id="root-container"):
|
|
@@ -102,6 +104,8 @@ class HandlerTUI(App[Any]):
|
|
|
102
104
|
|
|
103
105
|
messages_panel = self.query_one("#messages-container", TabbedMessagesPanel)
|
|
104
106
|
messages_panel.load_logs(tui_log_handler.get_lines())
|
|
107
|
+
if self._initial_bearer_token:
|
|
108
|
+
messages_panel.set_bearer_token(self._initial_bearer_token)
|
|
105
109
|
|
|
106
110
|
contact_panel = self.query_one("#contact-container", ContactPanel)
|
|
107
111
|
contact_panel.set_version(__version__)
|
|
@@ -125,12 +129,20 @@ class HandlerTUI(App[Any]):
|
|
|
125
129
|
agent_card_panel = self.query_one("#agent-card-container", AgentCardPanel)
|
|
126
130
|
agent_card_panel.refresh_theme()
|
|
127
131
|
|
|
128
|
-
async def _connect_to_agent(
|
|
132
|
+
async def _connect_to_agent(
|
|
133
|
+
self,
|
|
134
|
+
agent_url: str,
|
|
135
|
+
credentials: AuthCredentials | None = None,
|
|
136
|
+
) -> AgentCard:
|
|
129
137
|
if not self.http_client:
|
|
130
138
|
raise RuntimeError("HTTP client not initialized")
|
|
131
139
|
|
|
132
140
|
logger.info("Connecting to agent at %s", agent_url)
|
|
133
|
-
self._agent_service = A2AService(
|
|
141
|
+
self._agent_service = A2AService(
|
|
142
|
+
self.http_client,
|
|
143
|
+
agent_url,
|
|
144
|
+
credentials=credentials,
|
|
145
|
+
)
|
|
134
146
|
return await self._agent_service.get_card()
|
|
135
147
|
|
|
136
148
|
def _update_ui_for_connected_state(self, agent_card: AgentCard) -> None:
|
|
@@ -155,7 +167,8 @@ class HandlerTUI(App[Any]):
|
|
|
155
167
|
messages_panel.add_system_message(f"Connecting to {agent_url}...")
|
|
156
168
|
|
|
157
169
|
try:
|
|
158
|
-
|
|
170
|
+
credentials = messages_panel.get_auth_credentials()
|
|
171
|
+
agent_card = await self._connect_to_agent(agent_url, credentials)
|
|
159
172
|
|
|
160
173
|
self.current_agent_card = agent_card
|
|
161
174
|
self.current_agent_url = agent_url
|
|
@@ -214,6 +227,8 @@ class HandlerTUI(App[Any]):
|
|
|
214
227
|
credentials = messages_panel.get_auth_credentials()
|
|
215
228
|
if credentials:
|
|
216
229
|
self._agent_service.set_credentials(credentials)
|
|
230
|
+
else:
|
|
231
|
+
self._agent_service.clear_credentials()
|
|
217
232
|
|
|
218
233
|
send_result = await self._agent_service.send(
|
|
219
234
|
message_text,
|
|
@@ -97,3 +97,13 @@ class AuthPanel(Vertical):
|
|
|
97
97
|
elif pressed.id == "auth-bearer":
|
|
98
98
|
return AuthType.BEARER
|
|
99
99
|
return None
|
|
100
|
+
|
|
101
|
+
def set_bearer_token(self, token: str) -> None:
|
|
102
|
+
"""Preconfigure bearer token authentication."""
|
|
103
|
+
self.query_one("#bearer-token-input", Input).value = token
|
|
104
|
+
self.query_one("#auth-bearer", RadioButton).value = True
|
|
105
|
+
|
|
106
|
+
# Ensure fields are visible even if no RadioSet event is emitted.
|
|
107
|
+
self.query_one("#api-key-fields", Vertical).add_class("hidden")
|
|
108
|
+
self.query_one("#bearer-fields", Vertical).remove_class("hidden")
|
|
109
|
+
logger.debug("Preconfigured bearer token authentication")
|
|
@@ -251,6 +251,11 @@ class TabbedMessagesPanel(Container):
|
|
|
251
251
|
auth_panel = self._get_auth_panel()
|
|
252
252
|
return auth_panel.get_credentials()
|
|
253
253
|
|
|
254
|
+
def set_bearer_token(self, token: str) -> None:
|
|
255
|
+
"""Preconfigure bearer token authentication in the auth panel."""
|
|
256
|
+
auth_panel = self._get_auth_panel()
|
|
257
|
+
auth_panel.set_bearer_token(token)
|
|
258
|
+
|
|
254
259
|
def add_task(self, task: "Task") -> None:
|
|
255
260
|
"""Add a task to the tasks panel."""
|
|
256
261
|
tasks_panel = self._get_tasks_panel()
|
|
@@ -11,7 +11,12 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
import httpx
|
|
13
13
|
from a2a.client import A2ACardResolver
|
|
14
|
+
from a2a.client.errors import A2AClientHTTPError
|
|
14
15
|
from a2a.types import AgentCard
|
|
16
|
+
from a2a.utils.constants import (
|
|
17
|
+
AGENT_CARD_WELL_KNOWN_PATH,
|
|
18
|
+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
19
|
+
)
|
|
15
20
|
from pydantic import ValidationError
|
|
16
21
|
|
|
17
22
|
from a2a_handler.common import get_logger
|
|
@@ -101,7 +106,20 @@ async def validate_agent_card_from_url(
|
|
|
101
106
|
|
|
102
107
|
try:
|
|
103
108
|
resolver = A2ACardResolver(http_client, agent_url)
|
|
104
|
-
|
|
109
|
+
try:
|
|
110
|
+
agent_card = await resolver.get_agent_card()
|
|
111
|
+
except (A2AClientHTTPError, httpx.HTTPStatusError):
|
|
112
|
+
logger.info(
|
|
113
|
+
"Agent card not found at %s, trying %s",
|
|
114
|
+
AGENT_CARD_WELL_KNOWN_PATH,
|
|
115
|
+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
116
|
+
)
|
|
117
|
+
fallback_resolver = A2ACardResolver(
|
|
118
|
+
http_client,
|
|
119
|
+
agent_url,
|
|
120
|
+
agent_card_path=PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
121
|
+
)
|
|
122
|
+
agent_card = await fallback_resolver.get_agent_card()
|
|
105
123
|
|
|
106
124
|
logger.info("Agent card validation successful for %s", agent_card.name)
|
|
107
125
|
return ValidationResult(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|