a2a-handler 0.1.20__tar.gz → 0.1.22__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.
Files changed (62) hide show
  1. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/PKG-INFO +6 -4
  2. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/README.md +3 -1
  3. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/pyproject.toml +3 -3
  4. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/__init__.py +139 -5
  5. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/_helpers.py +102 -3
  6. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/card.py +9 -2
  7. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/message.py +285 -14
  8. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/schema.py +3 -4
  9. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/server.py +2 -2
  10. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/task.py +4 -1
  11. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/common/logging.py +35 -13
  12. a2a_handler-0.1.22/src/a2a_handler/common/output.py +113 -0
  13. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/server/__init__.py +1 -1
  14. a2a_handler-0.1.22/src/a2a_handler/server/agent.py +133 -0
  15. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/server/card.py +58 -3
  16. a2a_handler-0.1.22/src/a2a_handler/server/tools/__init__.py +23 -0
  17. a2a_handler-0.1.22/src/a2a_handler/server/tools/a2a_docs.py +140 -0
  18. a2a_handler-0.1.22/src/a2a_handler/server/tools/handler_docs.py +44 -0
  19. a2a_handler-0.1.22/src/a2a_handler/server/tools/source.py +109 -0
  20. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/servers.py +35 -0
  21. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/service.py +3 -1
  22. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/app.py +48 -10
  23. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/app.tcss +154 -12
  24. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/artifacts.py +6 -19
  25. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/card.py +6 -4
  26. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/input.py +29 -1
  27. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/messages.py +188 -19
  28. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/tasks.py +99 -32
  29. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/tab.py +199 -0
  30. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/views.py +13 -4
  31. a2a_handler-0.1.20/src/a2a_handler/common/output.py +0 -66
  32. a2a_handler-0.1.20/src/a2a_handler/server/agent.py +0 -76
  33. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/LICENSE +0 -0
  34. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/__init__.py +0 -0
  35. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/auth.py +0 -0
  36. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/mcp.py +0 -0
  37. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/cli/session.py +0 -0
  38. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/common/__init__.py +0 -0
  39. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/common/config.py +0 -0
  40. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/common/dotenv.py +0 -0
  41. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/common/input_validation.py +0 -0
  42. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/mcp/__init__.py +0 -0
  43. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/mcp/server.py +0 -0
  44. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/server/app.py +0 -0
  45. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/server/ollama.py +0 -0
  46. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/session.py +0 -0
  47. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/__init__.py +0 -0
  48. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/commands/__init__.py +0 -0
  49. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/commands/palette.py +0 -0
  50. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/commands/screens.py +0 -0
  51. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/__init__.py +0 -0
  52. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/auth.py +0 -0
  53. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/headers.py +0 -0
  54. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/components/logs.py +0 -0
  55. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/__init__.py +0 -0
  56. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/save.py +0 -0
  57. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/session.py +0 -0
  58. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/tabs.py +0 -0
  59. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/types.py +0 -0
  60. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/tui/server/workspace.py +0 -0
  61. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/src/a2a_handler/validation.py +0 -0
  62. {a2a_handler-0.1.20 → a2a_handler-0.1.22}/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.20
3
+ Version: 0.1.22
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
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Dist: a2a-sdk>=0.3.24,<1.0.0
22
22
  Requires-Dist: click>=8.0.0
23
- Requires-Dist: google-adk>=1.26.0
23
+ Requires-Dist: google-adk>=2.0.0
24
24
  Requires-Dist: httpx>=0.27.0
25
25
  Requires-Dist: litellm>=1.0.0
26
26
  Requires-Dist: python-dotenv>=1.0.0
@@ -28,7 +28,7 @@ Requires-Dist: rich>=13.0.0
28
28
  Requires-Dist: textual>=0.47.0
29
29
  Requires-Dist: uvicorn>=0.30.0
30
30
  Requires-Dist: textual-serve>=1.1.0
31
- Requires-Dist: mcp[cli]>=1.2.0
31
+ Requires-Dist: mcp[cli]>=1.27.1
32
32
  Requires-Dist: truststore>=0.9.0
33
33
  Requires-Dist: platformdirs>=4.0.0
34
34
  Requires-Python: >=3.11
@@ -75,7 +75,9 @@ handler tui # launch the interactive TUI
75
75
  handler message send URL "hello" # send a message from the CLI
76
76
  handler card get URL # fetch an agent card
77
77
  handler mcp # start the MCP server
78
- handler server run agent # run the bundled reference agent
78
+ handler server run agent # run the bundled embedded agent
79
+ handler docs # open the hosted documentation
80
+ handler update # upgrade Handler to the latest release
79
81
  ```
80
82
 
81
83
  ## Run Without Installing
@@ -34,7 +34,9 @@ handler tui # launch the interactive TUI
34
34
  handler message send URL "hello" # send a message from the CLI
35
35
  handler card get URL # fetch an agent card
36
36
  handler mcp # start the MCP server
37
- handler server run agent # run the bundled reference agent
37
+ handler server run agent # run the bundled embedded agent
38
+ handler docs # open the hosted documentation
39
+ handler update # upgrade Handler to the latest release
38
40
  ```
39
41
 
40
42
  ## Run Without Installing
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "a2a-handler"
3
- version = "0.1.20"
3
+ version = "0.1.22"
4
4
  description = "An A2A Protocol client TUI and CLI."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -34,7 +34,7 @@ classifiers = [
34
34
  dependencies = [
35
35
  "a2a-sdk>=0.3.24,<1.0.0",
36
36
  "click>=8.0.0",
37
- "google-adk>=1.26.0",
37
+ "google-adk>=2.0.0",
38
38
  "httpx>=0.27.0",
39
39
  "litellm>=1.0.0",
40
40
  "python-dotenv>=1.0.0",
@@ -42,7 +42,7 @@ dependencies = [
42
42
  "textual>=0.47.0",
43
43
  "uvicorn>=0.30.0",
44
44
  "textual-serve>=1.1.0",
45
- "mcp[cli]>=1.2.0",
45
+ "mcp[cli]>=1.27.1",
46
46
  "truststore>=0.9.0",
47
47
  "platformdirs>=4.0.0",
48
48
  ]
@@ -19,6 +19,11 @@ truststore.inject_into_ssl()
19
19
 
20
20
  import logging
21
21
  import os
22
+ import shutil
23
+ import subprocess
24
+ import webbrowser
25
+ from collections.abc import Sequence
26
+ from typing import Any
22
27
 
23
28
  logging.getLogger().setLevel(logging.WARNING)
24
29
 
@@ -30,6 +35,7 @@ from a2a_handler.common.dotenv import load_runtime_dotenv
30
35
  from a2a_handler.common.output import OutputFormat
31
36
  from a2a_handler.tui import HandlerTUI
32
37
 
38
+ from ._helpers import configure_http_timeouts
33
39
  from .card import card
34
40
  from .mcp import mcp
35
41
  from .message import message
@@ -40,24 +46,81 @@ from .task import task
40
46
 
41
47
  log = get_logger(__name__)
42
48
 
49
+ DOCS_URL = "https://handler.alduncanson.com/"
50
+ PACKAGE_NAME = "a2a-handler"
43
51
 
44
- @click.group()
52
+
53
+ class RuntimeDotenvGroup(click.Group):
54
+ """Click group that loads workspace .env before option envvars resolve."""
55
+
56
+ def main(
57
+ self,
58
+ args: Sequence[str] | None = None,
59
+ prog_name: str | None = None,
60
+ complete_var: str | None = None,
61
+ standalone_mode: bool = True,
62
+ windows_expand_args: bool = True,
63
+ **extra: Any,
64
+ ) -> Any:
65
+ """Load runtime dotenv before Click parses envvar-backed options."""
66
+ load_runtime_dotenv()
67
+ return super().main(
68
+ args=args,
69
+ prog_name=prog_name,
70
+ complete_var=complete_var,
71
+ standalone_mode=standalone_mode,
72
+ windows_expand_args=windows_expand_args,
73
+ **extra,
74
+ )
75
+
76
+
77
+ @click.group(cls=RuntimeDotenvGroup)
45
78
  @click.version_option(version=__version__, prog_name="handler")
46
79
  @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
47
80
  @click.option("--debug", "-d", is_flag=True, help="Enable debug logging")
48
81
  @click.option(
49
82
  "--output",
50
83
  "output_format",
51
- type=click.Choice(["json", "ndjson"]),
52
- default="json",
84
+ type=click.Choice(["text", "json", "ndjson"]),
85
+ default="text",
53
86
  show_default=True,
54
- help="Output format",
87
+ help="Output format (use json/ndjson for structured output)",
55
88
  )
56
89
  @click.option(
57
90
  "--quiet",
58
91
  is_flag=True,
59
92
  help="Suppress non-error output",
60
93
  )
94
+ @click.option(
95
+ "--connect-timeout",
96
+ envvar="HANDLER_CONNECT_TIMEOUT",
97
+ metavar="SECONDS|none",
98
+ help="HTTP connect timeout in seconds (env: HANDLER_CONNECT_TIMEOUT)",
99
+ )
100
+ @click.option(
101
+ "--read-timeout",
102
+ envvar="HANDLER_READ_TIMEOUT",
103
+ metavar="SECONDS|none",
104
+ help="HTTP read timeout for non-streaming calls (env: HANDLER_READ_TIMEOUT)",
105
+ )
106
+ @click.option(
107
+ "--write-timeout",
108
+ envvar="HANDLER_WRITE_TIMEOUT",
109
+ metavar="SECONDS|none",
110
+ help="HTTP write timeout in seconds (env: HANDLER_WRITE_TIMEOUT)",
111
+ )
112
+ @click.option(
113
+ "--pool-timeout",
114
+ envvar="HANDLER_POOL_TIMEOUT",
115
+ metavar="SECONDS|none",
116
+ help="HTTP connection pool timeout in seconds (env: HANDLER_POOL_TIMEOUT)",
117
+ )
118
+ @click.option(
119
+ "--stream-read-timeout",
120
+ envvar="HANDLER_STREAM_READ_TIMEOUT",
121
+ metavar="SECONDS|none",
122
+ help="HTTP read timeout for streaming calls; default is none (env: HANDLER_STREAM_READ_TIMEOUT)",
123
+ )
61
124
  @click.pass_context
62
125
  def cli(
63
126
  ctx: click.Context,
@@ -65,13 +128,24 @@ def cli(
65
128
  debug: bool,
66
129
  output_format: OutputFormat,
67
130
  quiet: bool,
131
+ connect_timeout: str | None,
132
+ read_timeout: str | None,
133
+ write_timeout: str | None,
134
+ pool_timeout: str | None,
135
+ stream_read_timeout: str | None,
68
136
  ) -> None:
69
137
  """Handler - A2A protocol client CLI."""
70
- load_runtime_dotenv()
71
138
  ctx.ensure_object(dict)
72
139
  ctx.obj["output_format"] = output_format
73
140
  ctx.obj["quiet"] = quiet
74
141
  configure_output(output_format=output_format, quiet=quiet)
142
+ configure_http_timeouts(
143
+ connect_timeout=connect_timeout,
144
+ read_timeout=read_timeout,
145
+ write_timeout=write_timeout,
146
+ pool_timeout=pool_timeout,
147
+ stream_read_timeout=stream_read_timeout,
148
+ )
75
149
 
76
150
  if debug:
77
151
  setup_logging(level="DEBUG")
@@ -103,6 +177,66 @@ def version() -> None:
103
177
  output.json({"version": __version__})
104
178
 
105
179
 
180
+ @cli.command()
181
+ def docs() -> None:
182
+ """Open the Handler documentation in your browser.
183
+
184
+ \b
185
+ Examples:
186
+ $ handler docs
187
+ """
188
+ opened = webbrowser.open(DOCS_URL)
189
+ output = Output()
190
+ output.json({"url": DOCS_URL, "opened": opened})
191
+
192
+
193
+ def _upgrade_command() -> list[str] | None:
194
+ """Return the preferred package manager command for upgrading Handler."""
195
+ if shutil.which("uv"):
196
+ return ["uv", "tool", "upgrade", PACKAGE_NAME]
197
+ if shutil.which("pipx"):
198
+ return ["pipx", "upgrade", PACKAGE_NAME]
199
+ return None
200
+
201
+
202
+ @cli.command()
203
+ def update() -> None:
204
+ """Update Handler to the latest published version.
205
+
206
+ \b
207
+ Examples:
208
+ $ handler update
209
+ $ handler upgrade
210
+ """
211
+ output = Output()
212
+ command = _upgrade_command()
213
+ if command is None:
214
+ output.error(
215
+ code="installer_not_found",
216
+ message="Could not find uv or pipx to update Handler.",
217
+ suggestion=(
218
+ "Install uv or pipx, then run `uv tool upgrade a2a-handler` "
219
+ "or `pipx upgrade a2a-handler`."
220
+ ),
221
+ )
222
+ raise SystemExit(1)
223
+
224
+ result = subprocess.run(command, check=False)
225
+ if result.returncode != 0:
226
+ output.error(
227
+ code="update_failed",
228
+ message="Handler update command failed.",
229
+ details={
230
+ "command": command,
231
+ "returncode": result.returncode,
232
+ },
233
+ )
234
+ raise SystemExit(result.returncode)
235
+
236
+
237
+ cli.add_command(update, name="upgrade")
238
+
239
+
106
240
  @cli.command()
107
241
  @click.option(
108
242
  "--bearer-env", "-b", help="Env var containing bearer token for agent auth"
@@ -27,6 +27,13 @@ from a2a_handler.servers import (
27
27
  )
28
28
 
29
29
  TIMEOUT = 120
30
+ _TIMEOUT_NONE_VALUES = {"none", "null", "off", "disabled"}
31
+
32
+ _connect_timeout: float | None = float(TIMEOUT)
33
+ _read_timeout: float | None = float(TIMEOUT)
34
+ _write_timeout: float | None = float(TIMEOUT)
35
+ _pool_timeout: float | None = float(TIMEOUT)
36
+ _stream_read_timeout: float | None = None
30
37
  log = get_logger(__name__)
31
38
 
32
39
 
@@ -39,17 +46,109 @@ class AgentSelection:
39
46
 
40
47
 
41
48
  def build_http_client(
42
- timeout: int = TIMEOUT,
49
+ timeout: int | float | httpx.Timeout | None = None,
43
50
  credentials: AuthCredentials | None = None,
44
51
  ) -> httpx.AsyncClient:
45
52
  """Build an HTTP client with the specified timeout."""
53
+ effective_timeout = timeout if timeout is not None else _standard_timeout()
46
54
  if credentials and credentials.auth_type == AuthType.MTLS:
47
55
  return httpx.AsyncClient(
48
- timeout=timeout,
56
+ timeout=effective_timeout,
49
57
  verify=credentials.build_ssl_context(),
50
58
  trust_env=False,
51
59
  )
52
- return httpx.AsyncClient(timeout=timeout, trust_env=False)
60
+ return httpx.AsyncClient(timeout=effective_timeout, trust_env=False)
61
+
62
+
63
+ def build_streaming_http_client(
64
+ credentials: AuthCredentials | None = None,
65
+ ) -> httpx.AsyncClient:
66
+ """Build an HTTP client for long-lived streaming responses.
67
+
68
+ Streaming keeps the usual finite connect/write/pool timeouts but disables
69
+ the read timeout so an active long-running A2A task is not aborted just
70
+ because the server has no SSE event ready for a while.
71
+ """
72
+ return build_http_client(
73
+ timeout=httpx.Timeout(
74
+ connect=_connect_timeout,
75
+ read=_stream_read_timeout,
76
+ write=_write_timeout,
77
+ pool=_pool_timeout,
78
+ ),
79
+ credentials=credentials,
80
+ )
81
+
82
+
83
+ def configure_http_timeouts(
84
+ *,
85
+ connect_timeout: str | int | float | None = None,
86
+ read_timeout: str | int | float | None = None,
87
+ write_timeout: str | int | float | None = None,
88
+ pool_timeout: str | int | float | None = None,
89
+ stream_read_timeout: str | int | float | None = None,
90
+ ) -> None:
91
+ """Configure default CLI HTTP timeouts.
92
+
93
+ Passing no values resets the defaults. String values may be seconds or
94
+ ``none`` to disable that timeout.
95
+ """
96
+ global _connect_timeout, _read_timeout, _write_timeout, _pool_timeout
97
+ global _stream_read_timeout
98
+
99
+ _connect_timeout = _parse_timeout_option(
100
+ connect_timeout, "connect-timeout", float(TIMEOUT)
101
+ )
102
+ _read_timeout = _parse_timeout_option(read_timeout, "read-timeout", float(TIMEOUT))
103
+ _write_timeout = _parse_timeout_option(
104
+ write_timeout, "write-timeout", float(TIMEOUT)
105
+ )
106
+ _pool_timeout = _parse_timeout_option(pool_timeout, "pool-timeout", float(TIMEOUT))
107
+ _stream_read_timeout = _parse_timeout_option(
108
+ stream_read_timeout, "stream-read-timeout", None
109
+ )
110
+
111
+
112
+ def _standard_timeout() -> httpx.Timeout:
113
+ """Return the configured timeout for regular request/response calls."""
114
+ return httpx.Timeout(
115
+ connect=_connect_timeout,
116
+ read=_read_timeout,
117
+ write=_write_timeout,
118
+ pool=_pool_timeout,
119
+ )
120
+
121
+
122
+ def _parse_timeout_option(
123
+ value: str | int | float | None,
124
+ field_name: str,
125
+ default: float | None,
126
+ ) -> float | None:
127
+ """Parse a CLI/env timeout value."""
128
+ if value is None:
129
+ return default
130
+ if isinstance(value, str):
131
+ normalized = value.strip().lower()
132
+ if not normalized:
133
+ return default
134
+ if normalized in _TIMEOUT_NONE_VALUES:
135
+ return None
136
+ try:
137
+ parsed = float(normalized)
138
+ except ValueError as exc:
139
+ raise click.BadParameter(
140
+ "must be a number of seconds or 'none'",
141
+ param_hint=f"--{field_name}",
142
+ ) from exc
143
+ else:
144
+ parsed = float(value)
145
+
146
+ if parsed < 0:
147
+ raise click.BadParameter(
148
+ "must be greater than or equal to 0",
149
+ param_hint=f"--{field_name}",
150
+ )
151
+ return parsed
53
152
 
54
153
 
55
154
  def handle_client_error(e: Exception, agent_url: str, output: Output | None) -> None:
@@ -11,8 +11,8 @@ from a2a_handler.common.input_validation import InputValidationError, validate_a
11
11
  from a2a_handler.service import A2AService
12
12
  from a2a_handler.validation import (
13
13
  ValidationResult,
14
+ ValidationSource,
14
15
  validate_agent_card_from_file,
15
- validate_agent_card_from_url,
16
16
  )
17
17
 
18
18
  from ._helpers import (
@@ -155,7 +155,14 @@ def card_validate(
155
155
 
156
156
  async def do_validate() -> None:
157
157
  async with build_http_client(credentials=credentials) as http_client:
158
- result = await validate_agent_card_from_url(resolved_url, http_client)
158
+ service = A2AService(http_client, resolved_url, credentials=credentials)
159
+ agent_card = await service.get_card()
160
+ result = ValidationResult(
161
+ valid=True,
162
+ source=resolved_url,
163
+ source_type=ValidationSource.URL,
164
+ agent_card=agent_card,
165
+ )
159
166
 
160
167
  _format_validation_result(result, output)
161
168