opencode-a2a 0.4.0__tar.gz → 0.4.1__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.
- {opencode_a2a-0.4.0/src/opencode_a2a.egg-info → opencode_a2a-0.4.1}/PKG-INFO +2 -2
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/README.md +1 -1
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/docs/guide.md +1 -1
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/cli.py +2 -10
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/client/__init__.py +0 -4
- opencode_a2a-0.4.1/src/opencode_a2a/client/agent_card.py +81 -0
- opencode_a2a-0.4.1/src/opencode_a2a/client/client.py +317 -0
- opencode_a2a-0.4.1/src/opencode_a2a/client/error_mapping.py +148 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/client/errors.py +21 -0
- opencode_a2a-0.4.1/src/opencode_a2a/client/payload_text.py +180 -0
- opencode_a2a-0.4.1/src/opencode_a2a/client/request_context.py +86 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/config.py +20 -18
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/contracts/extensions.py +2 -6
- opencode_a2a-0.4.1/src/opencode_a2a/error_taxonomy.py +95 -0
- opencode_a2a-0.4.1/src/opencode_a2a/execution/event_helpers.py +37 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/execution/executor.py +82 -776
- opencode_a2a-0.4.1/src/opencode_a2a/execution/session_manager.py +140 -0
- opencode_a2a-0.4.1/src/opencode_a2a/execution/stream_runtime.py +578 -0
- opencode_a2a-0.4.1/src/opencode_a2a/execution/tool_error_mapping.py +141 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/execution/upstream_errors.py +6 -82
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/jsonrpc/application.py +130 -241
- opencode_a2a-0.4.1/src/opencode_a2a/jsonrpc/error_mapping.py +137 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/parts/text.py +0 -4
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/profile/runtime.py +7 -6
- opencode_a2a-0.4.1/src/opencode_a2a/sandbox_policy.py +111 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/server/application.py +145 -15
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1/src/opencode_a2a.egg-info}/PKG-INFO +2 -2
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a.egg-info/SOURCES.txt +17 -1
- opencode_a2a-0.4.1/tests/client/test_agent_card.py +68 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/client/test_client_facade.py +15 -299
- opencode_a2a-0.4.1/tests/client/test_error_mapping.py +81 -0
- opencode_a2a-0.4.1/tests/client/test_payload_text.py +104 -0
- opencode_a2a-0.4.1/tests/client/test_request_context.py +54 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/config/test_settings.py +34 -4
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_cancellation.py +3 -3
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_directory_validation.py +20 -8
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_metrics.py +0 -2
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_opencode_agent_session_binding.py +148 -25
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_session_ownership.py +20 -17
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_streaming_output_contract_core.py +41 -0
- opencode_a2a-0.4.1/tests/jsonrpc/test_error_mapping.py +79 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +35 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +5 -2
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +8 -7
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/parts/test_parts_text.py +2 -2
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/profile/test_profile_runtime.py +17 -0
- opencode_a2a-0.4.1/tests/server/test_a2a_client_manager.py +199 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/test_agent_card.py +19 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/test_app_behaviors.py +1 -1
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/test_cli.py +6 -15
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/test_transport_contract.py +19 -23
- opencode_a2a-0.4.1/tests/support/session_extensions.py +19 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/upstream/test_opencode_upstream_client_params.py +33 -0
- opencode_a2a-0.4.0/src/opencode_a2a/client/client.py +0 -608
- opencode_a2a-0.4.0/src/opencode_a2a/client/types.py +0 -21
- opencode_a2a-0.4.0/tests/support/session_extensions.py +0 -10
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/.github/workflows/ci.yml +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/.github/workflows/dependency-health.yml +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/.github/workflows/publish.yml +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/.gitignore +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/.pre-commit-config.yaml +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/.secrets.baseline +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/AGENTS.md +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/CONTRIBUTING.md +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/LICENSE +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/SECURITY.md +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/pyproject.toml +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/README.md +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/check_coverage.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/dependency_health.sh +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/doctor.sh +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/health_common.sh +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/lint.sh +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/scripts/smoke_test_built_cli.sh +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/setup.cfg +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/client/config.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/contracts/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/execution/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/execution/request_context.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/execution/stream_events.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/execution/stream_state.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/opencode_upstream_client.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/parts/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/parts/mapping.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/profile/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/server/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/server/agent_card.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/server/openapi.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a/server/request_parsing.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a.egg-info/requires.txt +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/client/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/client/test_client_config.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/config/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/contracts/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/contracts/test_extension_contract_consistency.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_agent_errors.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_agent_helpers.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_multipart_input.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/package/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/package/test_version.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/parts/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/profile/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/scripts/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/scripts/test_script_health_contract.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/test_call_context_builder.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/server/test_cancel_contract.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/support/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/support/helpers.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/support/streaming_output.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/tests/upstream/__init__.py +0 -0
- {opencode_a2a-0.4.0 → opencode_a2a-0.4.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencode-a2a
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: OpenCode A2A runtime
|
|
5
5
|
Author: liujuanjuan1984@Intelligent-Internet
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -115,7 +115,7 @@ A2A_HOST=127.0.0.1 \
|
|
|
115
115
|
A2A_PORT=8000 \
|
|
116
116
|
A2A_PUBLIC_URL=http://127.0.0.1:8000 \
|
|
117
117
|
OPENCODE_WORKSPACE_ROOT=/abs/path/to/workspace \
|
|
118
|
-
opencode-a2a
|
|
118
|
+
opencode-a2a
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
Verify that the service is up:
|
|
@@ -53,7 +53,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
53
53
|
parser = argparse.ArgumentParser(
|
|
54
54
|
prog="opencode-a2a",
|
|
55
55
|
description=(
|
|
56
|
-
"OpenCode A2A runtime.
|
|
56
|
+
"OpenCode A2A runtime. Run without a subcommand to start the service."
|
|
57
|
+
" Deployment supervision is intentionally left to the operator."
|
|
57
58
|
),
|
|
58
59
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
59
60
|
)
|
|
@@ -64,11 +65,6 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
64
65
|
)
|
|
65
66
|
|
|
66
67
|
subparsers = parser.add_subparsers(dest="command")
|
|
67
|
-
subparsers.add_parser(
|
|
68
|
-
"serve",
|
|
69
|
-
help="Start the OpenCode A2A runtime using environment-based settings.",
|
|
70
|
-
description="Start the OpenCode A2A runtime using environment-based settings.",
|
|
71
|
-
)
|
|
72
68
|
|
|
73
69
|
call_parser = subparsers.add_parser(
|
|
74
70
|
"call",
|
|
@@ -95,10 +91,6 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
95
91
|
return 0
|
|
96
92
|
|
|
97
93
|
namespace = parser.parse_args(args)
|
|
98
|
-
if namespace.command == "serve":
|
|
99
|
-
serve_main()
|
|
100
|
-
return 0
|
|
101
|
-
|
|
102
94
|
if namespace.command == "call":
|
|
103
95
|
return asyncio.run(run_call(namespace.agent_url, namespace.text, namespace.token))
|
|
104
96
|
|
|
@@ -10,7 +10,6 @@ from .errors import (
|
|
|
10
10
|
A2AUnsupportedBindingError,
|
|
11
11
|
A2AUnsupportedOperationError,
|
|
12
12
|
)
|
|
13
|
-
from .types import A2AClientEvent, A2AClientEventStream, A2AClientMetadata
|
|
14
13
|
|
|
15
14
|
__all__ = [
|
|
16
15
|
"A2AClient",
|
|
@@ -21,8 +20,5 @@ __all__ = [
|
|
|
21
20
|
"A2AUnsupportedBindingError",
|
|
22
21
|
"A2AUnsupportedOperationError",
|
|
23
22
|
"A2AClientSettings",
|
|
24
|
-
"A2AClientEvent",
|
|
25
|
-
"A2AClientEventStream",
|
|
26
|
-
"A2AClientMetadata",
|
|
27
23
|
"load_settings",
|
|
28
24
|
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Helpers for agent-card URL normalization and resolver bootstrap."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from a2a.client.card_resolver import A2ACardResolver
|
|
10
|
+
from a2a.utils.constants import (
|
|
11
|
+
AGENT_CARD_WELL_KNOWN_PATH,
|
|
12
|
+
EXTENDED_AGENT_CARD_PATH,
|
|
13
|
+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .request_context import build_default_headers
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def normalize_agent_card_endpoint(agent_url: str) -> tuple[str, str]:
|
|
20
|
+
parsed_url = urlsplit(agent_url)
|
|
21
|
+
if not parsed_url.scheme or not parsed_url.netloc:
|
|
22
|
+
raise ValueError(f"agent_url must be absolute URL: {agent_url}")
|
|
23
|
+
|
|
24
|
+
path = parsed_url.path or ""
|
|
25
|
+
normalized_no_leading = path.rstrip("/").lstrip("/")
|
|
26
|
+
candidate_paths = (
|
|
27
|
+
AGENT_CARD_WELL_KNOWN_PATH,
|
|
28
|
+
PREV_AGENT_CARD_WELL_KNOWN_PATH,
|
|
29
|
+
EXTENDED_AGENT_CARD_PATH,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
base_path = normalized_no_leading
|
|
33
|
+
agent_card_path = AGENT_CARD_WELL_KNOWN_PATH
|
|
34
|
+
for candidate_path in candidate_paths:
|
|
35
|
+
card_suffix = candidate_path.lstrip("/")
|
|
36
|
+
if normalized_no_leading.endswith(card_suffix):
|
|
37
|
+
base_path = normalized_no_leading[: -len(card_suffix)].rstrip("/")
|
|
38
|
+
agent_card_path = candidate_path
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
base_url = urlunsplit(
|
|
42
|
+
(
|
|
43
|
+
parsed_url.scheme,
|
|
44
|
+
parsed_url.netloc,
|
|
45
|
+
f"/{base_path}" if base_path else "",
|
|
46
|
+
"",
|
|
47
|
+
"",
|
|
48
|
+
)
|
|
49
|
+
).rstrip("/")
|
|
50
|
+
return base_url, agent_card_path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_agent_card_resolver(
|
|
54
|
+
agent_url: str,
|
|
55
|
+
httpx_client: httpx.AsyncClient,
|
|
56
|
+
) -> A2ACardResolver:
|
|
57
|
+
base_url, agent_card_path = normalize_agent_card_endpoint(agent_url)
|
|
58
|
+
return A2ACardResolver(
|
|
59
|
+
httpx_client=httpx_client,
|
|
60
|
+
base_url=base_url,
|
|
61
|
+
agent_card_path=agent_card_path,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build_resolver_http_kwargs(
|
|
66
|
+
*,
|
|
67
|
+
bearer_token: str | None,
|
|
68
|
+
timeout: float,
|
|
69
|
+
) -> dict[str, Any]:
|
|
70
|
+
http_kwargs: dict[str, Any] = {"timeout": timeout}
|
|
71
|
+
default_headers = build_default_headers(bearer_token)
|
|
72
|
+
if default_headers:
|
|
73
|
+
http_kwargs["headers"] = default_headers
|
|
74
|
+
return http_kwargs
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
"build_agent_card_resolver",
|
|
79
|
+
"build_resolver_http_kwargs",
|
|
80
|
+
"normalize_agent_card_endpoint",
|
|
81
|
+
]
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""A2A client facade for opencode-a2a consumers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import AsyncIterator, Mapping
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from a2a.client import Client, ClientConfig, ClientFactory
|
|
12
|
+
from a2a.client.errors import (
|
|
13
|
+
A2AClientHTTPError,
|
|
14
|
+
A2AClientJSONError,
|
|
15
|
+
A2AClientJSONRPCError,
|
|
16
|
+
)
|
|
17
|
+
from a2a.types import (
|
|
18
|
+
Message,
|
|
19
|
+
Part,
|
|
20
|
+
Role,
|
|
21
|
+
Task,
|
|
22
|
+
TaskArtifactUpdateEvent,
|
|
23
|
+
TaskIdParams,
|
|
24
|
+
TaskQueryParams,
|
|
25
|
+
TaskStatusUpdateEvent,
|
|
26
|
+
TextPart,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from .agent_card import build_agent_card_resolver, build_resolver_http_kwargs
|
|
30
|
+
from .config import A2AClientSettings, load_settings
|
|
31
|
+
from .error_mapping import (
|
|
32
|
+
map_agent_card_error,
|
|
33
|
+
map_operation_error,
|
|
34
|
+
)
|
|
35
|
+
from .errors import A2AUnsupportedBindingError
|
|
36
|
+
from .payload_text import extract_text as extract_text_from_payload
|
|
37
|
+
from .request_context import build_call_context, build_client_interceptors, split_request_metadata
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class A2AClient:
|
|
41
|
+
"""Factory-style facade for lightweight A2A client bootstrap and calls."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
agent_url: str,
|
|
46
|
+
*,
|
|
47
|
+
settings: A2AClientSettings | None = None,
|
|
48
|
+
httpx_client: httpx.AsyncClient | None = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
if not agent_url or not agent_url.strip():
|
|
51
|
+
raise ValueError("agent_url must be non-empty")
|
|
52
|
+
self.agent_url = agent_url.rstrip("/")
|
|
53
|
+
self._settings = settings or load_settings({})
|
|
54
|
+
self._owns_httpx_client = httpx_client is None
|
|
55
|
+
self._httpx_client = httpx_client
|
|
56
|
+
self._client: Client | None = None
|
|
57
|
+
self._agent_card: object | None = None
|
|
58
|
+
self._lock = asyncio.Lock()
|
|
59
|
+
self._request_lock = asyncio.Lock()
|
|
60
|
+
self._active_requests = 0
|
|
61
|
+
|
|
62
|
+
async def close(self) -> None:
|
|
63
|
+
"""Close cached client resources and owned HTTP transport."""
|
|
64
|
+
self._client = None
|
|
65
|
+
if self._httpx_client is not None and self._owns_httpx_client:
|
|
66
|
+
await self._httpx_client.aclose()
|
|
67
|
+
|
|
68
|
+
def is_busy(self) -> bool:
|
|
69
|
+
"""Report whether this facade currently has in-flight work."""
|
|
70
|
+
return self._active_requests > 0
|
|
71
|
+
|
|
72
|
+
async def get_agent_card(self) -> Any:
|
|
73
|
+
"""Fetch and cache peer Agent Card."""
|
|
74
|
+
if self._agent_card is not None:
|
|
75
|
+
return self._agent_card
|
|
76
|
+
|
|
77
|
+
resolver = build_agent_card_resolver(
|
|
78
|
+
self.agent_url,
|
|
79
|
+
await self._get_httpx_client(),
|
|
80
|
+
)
|
|
81
|
+
try:
|
|
82
|
+
card = await resolver.get_agent_card(
|
|
83
|
+
http_kwargs=build_resolver_http_kwargs(
|
|
84
|
+
bearer_token=self._settings.bearer_token,
|
|
85
|
+
timeout=self._settings.card_fetch_timeout,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
except (
|
|
89
|
+
A2AClientHTTPError,
|
|
90
|
+
A2AClientJSONError,
|
|
91
|
+
httpx.TimeoutException,
|
|
92
|
+
httpx.TransportError,
|
|
93
|
+
) as exc:
|
|
94
|
+
raise map_agent_card_error(exc) from exc
|
|
95
|
+
self._agent_card = card
|
|
96
|
+
return card
|
|
97
|
+
|
|
98
|
+
async def send_message(
|
|
99
|
+
self,
|
|
100
|
+
text: str,
|
|
101
|
+
*,
|
|
102
|
+
context_id: str | None = None,
|
|
103
|
+
task_id: str | None = None,
|
|
104
|
+
message_id: str | None = None,
|
|
105
|
+
metadata: Mapping[str, Any] | None = None,
|
|
106
|
+
extensions: list[str] | None = None,
|
|
107
|
+
) -> AsyncIterator[
|
|
108
|
+
Message | tuple[Task, TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None] | None
|
|
109
|
+
]:
|
|
110
|
+
"""Send one user message and stream protocol events."""
|
|
111
|
+
await self._acquire_operation()
|
|
112
|
+
try:
|
|
113
|
+
client = await self._ensure_client()
|
|
114
|
+
request_metadata, extra_headers = split_request_metadata(metadata)
|
|
115
|
+
request = self._build_user_message(
|
|
116
|
+
text=text,
|
|
117
|
+
context_id=context_id,
|
|
118
|
+
task_id=task_id,
|
|
119
|
+
message_id=message_id,
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
async for event in client.send_message(
|
|
123
|
+
request,
|
|
124
|
+
context=build_call_context(self._settings.bearer_token, extra_headers),
|
|
125
|
+
request_metadata=request_metadata,
|
|
126
|
+
extensions=extensions,
|
|
127
|
+
):
|
|
128
|
+
yield event
|
|
129
|
+
except (
|
|
130
|
+
A2AClientHTTPError,
|
|
131
|
+
A2AClientJSONRPCError,
|
|
132
|
+
httpx.TimeoutException,
|
|
133
|
+
httpx.TransportError,
|
|
134
|
+
) as exc:
|
|
135
|
+
raise map_operation_error("message/send", exc) from exc
|
|
136
|
+
finally:
|
|
137
|
+
await self._release_operation()
|
|
138
|
+
|
|
139
|
+
async def send(
|
|
140
|
+
self,
|
|
141
|
+
text: str,
|
|
142
|
+
*,
|
|
143
|
+
context_id: str | None = None,
|
|
144
|
+
task_id: str | None = None,
|
|
145
|
+
message_id: str | None = None,
|
|
146
|
+
metadata: Mapping[str, Any] | None = None,
|
|
147
|
+
extensions: list[str] | None = None,
|
|
148
|
+
) -> Message | tuple[Task, TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None] | None:
|
|
149
|
+
"""Send a message and return the terminal response/event."""
|
|
150
|
+
last_event: (
|
|
151
|
+
Message | tuple[Task, TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None] | None
|
|
152
|
+
) = None
|
|
153
|
+
async for event in self.send_message(
|
|
154
|
+
text,
|
|
155
|
+
context_id=context_id,
|
|
156
|
+
task_id=task_id,
|
|
157
|
+
message_id=message_id,
|
|
158
|
+
metadata=metadata,
|
|
159
|
+
extensions=extensions,
|
|
160
|
+
):
|
|
161
|
+
last_event = event
|
|
162
|
+
return last_event
|
|
163
|
+
|
|
164
|
+
async def get_task(
|
|
165
|
+
self,
|
|
166
|
+
task_id: str,
|
|
167
|
+
*,
|
|
168
|
+
history_length: int | None = None,
|
|
169
|
+
metadata: Mapping[str, Any] | None = None,
|
|
170
|
+
) -> Task:
|
|
171
|
+
"""Fetch one task by id."""
|
|
172
|
+
await self._acquire_operation()
|
|
173
|
+
try:
|
|
174
|
+
client = await self._ensure_client()
|
|
175
|
+
request_metadata, extra_headers = split_request_metadata(metadata)
|
|
176
|
+
try:
|
|
177
|
+
return await client.get_task(
|
|
178
|
+
TaskQueryParams(
|
|
179
|
+
id=task_id,
|
|
180
|
+
history_length=history_length,
|
|
181
|
+
metadata=request_metadata or {},
|
|
182
|
+
),
|
|
183
|
+
context=build_call_context(self._settings.bearer_token, extra_headers),
|
|
184
|
+
)
|
|
185
|
+
except (
|
|
186
|
+
A2AClientHTTPError,
|
|
187
|
+
A2AClientJSONRPCError,
|
|
188
|
+
httpx.TimeoutException,
|
|
189
|
+
httpx.TransportError,
|
|
190
|
+
) as exc:
|
|
191
|
+
raise map_operation_error("tasks/get", exc) from exc
|
|
192
|
+
finally:
|
|
193
|
+
await self._release_operation()
|
|
194
|
+
|
|
195
|
+
async def cancel_task(
|
|
196
|
+
self,
|
|
197
|
+
task_id: str,
|
|
198
|
+
*,
|
|
199
|
+
metadata: Mapping[str, Any] | None = None,
|
|
200
|
+
) -> Task:
|
|
201
|
+
"""Cancel one task by id."""
|
|
202
|
+
await self._acquire_operation()
|
|
203
|
+
try:
|
|
204
|
+
client = await self._ensure_client()
|
|
205
|
+
request_metadata, extra_headers = split_request_metadata(metadata)
|
|
206
|
+
try:
|
|
207
|
+
return await client.cancel_task(
|
|
208
|
+
TaskIdParams(id=task_id, metadata=request_metadata or {}),
|
|
209
|
+
context=build_call_context(self._settings.bearer_token, extra_headers),
|
|
210
|
+
)
|
|
211
|
+
except (
|
|
212
|
+
A2AClientHTTPError,
|
|
213
|
+
A2AClientJSONRPCError,
|
|
214
|
+
httpx.TimeoutException,
|
|
215
|
+
httpx.TransportError,
|
|
216
|
+
) as exc:
|
|
217
|
+
raise map_operation_error("tasks/cancel", exc) from exc
|
|
218
|
+
finally:
|
|
219
|
+
await self._release_operation()
|
|
220
|
+
|
|
221
|
+
async def resubscribe_task(
|
|
222
|
+
self,
|
|
223
|
+
task_id: str,
|
|
224
|
+
*,
|
|
225
|
+
metadata: Mapping[str, Any] | None = None,
|
|
226
|
+
) -> AsyncIterator[tuple[Task, TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None]]:
|
|
227
|
+
"""Resubscribe to task updates."""
|
|
228
|
+
await self._acquire_operation()
|
|
229
|
+
try:
|
|
230
|
+
client = await self._ensure_client()
|
|
231
|
+
request_metadata, extra_headers = split_request_metadata(metadata)
|
|
232
|
+
try:
|
|
233
|
+
async for event in client.resubscribe(
|
|
234
|
+
TaskIdParams(id=task_id, metadata=request_metadata or {}),
|
|
235
|
+
context=build_call_context(self._settings.bearer_token, extra_headers),
|
|
236
|
+
):
|
|
237
|
+
yield event
|
|
238
|
+
except (
|
|
239
|
+
A2AClientHTTPError,
|
|
240
|
+
A2AClientJSONRPCError,
|
|
241
|
+
httpx.TimeoutException,
|
|
242
|
+
httpx.TransportError,
|
|
243
|
+
) as exc:
|
|
244
|
+
raise map_operation_error("tasks/resubscribe", exc) from exc
|
|
245
|
+
finally:
|
|
246
|
+
await self._release_operation()
|
|
247
|
+
|
|
248
|
+
async def _ensure_client(self) -> Client:
|
|
249
|
+
async with self._lock:
|
|
250
|
+
if self._client is not None:
|
|
251
|
+
return self._client
|
|
252
|
+
return await self._build_client()
|
|
253
|
+
|
|
254
|
+
async def _build_client(self) -> Client:
|
|
255
|
+
card = await self.get_agent_card()
|
|
256
|
+
config = ClientConfig(
|
|
257
|
+
streaming=True,
|
|
258
|
+
polling=False,
|
|
259
|
+
httpx_client=await self._get_httpx_client(),
|
|
260
|
+
supported_transports=list(self._settings.supported_transports),
|
|
261
|
+
use_client_preference=self._settings.use_client_preference,
|
|
262
|
+
)
|
|
263
|
+
try:
|
|
264
|
+
factory = ClientFactory(config, consumers=None)
|
|
265
|
+
client = factory.create(
|
|
266
|
+
card,
|
|
267
|
+
interceptors=build_client_interceptors(self._settings.bearer_token),
|
|
268
|
+
)
|
|
269
|
+
except ValueError as exc:
|
|
270
|
+
raise A2AUnsupportedBindingError(
|
|
271
|
+
f"No supported transport found for {self.agent_url}"
|
|
272
|
+
) from exc
|
|
273
|
+
self._client = client
|
|
274
|
+
return client
|
|
275
|
+
|
|
276
|
+
async def _get_httpx_client(self) -> httpx.AsyncClient:
|
|
277
|
+
if self._httpx_client is not None:
|
|
278
|
+
return self._httpx_client
|
|
279
|
+
self._httpx_client = httpx.AsyncClient(timeout=self._settings.default_timeout)
|
|
280
|
+
return self._httpx_client
|
|
281
|
+
|
|
282
|
+
async def _acquire_operation(self) -> None:
|
|
283
|
+
async with self._request_lock:
|
|
284
|
+
self._active_requests += 1
|
|
285
|
+
|
|
286
|
+
async def _release_operation(self) -> None:
|
|
287
|
+
async with self._request_lock:
|
|
288
|
+
if self._active_requests > 0:
|
|
289
|
+
self._active_requests -= 1
|
|
290
|
+
|
|
291
|
+
def _build_user_message(
|
|
292
|
+
self,
|
|
293
|
+
*,
|
|
294
|
+
text: str,
|
|
295
|
+
context_id: str | None,
|
|
296
|
+
task_id: str | None,
|
|
297
|
+
message_id: str | None,
|
|
298
|
+
) -> Message:
|
|
299
|
+
return Message(
|
|
300
|
+
role=Role.user,
|
|
301
|
+
message_id=message_id or str(uuid4()),
|
|
302
|
+
context_id=context_id,
|
|
303
|
+
task_id=task_id,
|
|
304
|
+
parts=self._normalize_parts(text),
|
|
305
|
+
metadata=None,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def extract_text(cls, payload: Any) -> str | None:
|
|
310
|
+
return extract_text_from_payload(payload)
|
|
311
|
+
|
|
312
|
+
# keep parts construction explicitly typed for mypy compatibility in older stubs
|
|
313
|
+
def _normalize_parts(self, text: str) -> list[Part]:
|
|
314
|
+
return [cast(Part, TextPart(text=text))]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
__all__ = ["A2AClient"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Centralized error mapping for outbound A2A client operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from a2a.client.errors import A2AClientHTTPError, A2AClientJSONError, A2AClientJSONRPCError
|
|
7
|
+
|
|
8
|
+
from .errors import (
|
|
9
|
+
A2AAgentUnavailableError,
|
|
10
|
+
A2AAuthenticationError,
|
|
11
|
+
A2AClientError,
|
|
12
|
+
A2AClientResetRequiredError,
|
|
13
|
+
A2APeerProtocolError,
|
|
14
|
+
A2APermissionDeniedError,
|
|
15
|
+
A2ATimeoutError,
|
|
16
|
+
A2AUnsupportedOperationError,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _attach_http_status(error: A2AClientError, status: int | None) -> A2AClientError:
|
|
21
|
+
error.http_status = status
|
|
22
|
+
return error
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _extract_jsonrpc_error_payload(
|
|
26
|
+
exc: A2AClientJSONRPCError,
|
|
27
|
+
) -> tuple[str | None, int | None, object]:
|
|
28
|
+
error = getattr(exc, "error", None)
|
|
29
|
+
if error is None:
|
|
30
|
+
return None, None, None
|
|
31
|
+
return (
|
|
32
|
+
getattr(error, "message", None),
|
|
33
|
+
getattr(error, "code", None),
|
|
34
|
+
getattr(error, "data", None),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def map_jsonrpc_error(exc: A2AClientJSONRPCError) -> A2AClientError:
|
|
39
|
+
_message, code, data = _extract_jsonrpc_error_payload(exc)
|
|
40
|
+
if code == -32601:
|
|
41
|
+
parsed_error = A2AUnsupportedOperationError(
|
|
42
|
+
"Remote A2A peer does not support the requested operation"
|
|
43
|
+
)
|
|
44
|
+
parsed_error.error_code = "method_not_supported"
|
|
45
|
+
parsed_error.code = code
|
|
46
|
+
parsed_error.data = data
|
|
47
|
+
return parsed_error
|
|
48
|
+
if code == -32602:
|
|
49
|
+
return A2APeerProtocolError(
|
|
50
|
+
"Remote A2A peer rejected the request payload",
|
|
51
|
+
error_code="invalid_params",
|
|
52
|
+
rpc_code=code,
|
|
53
|
+
data=data,
|
|
54
|
+
)
|
|
55
|
+
if code == -32603:
|
|
56
|
+
reset_required = A2AClientResetRequiredError(
|
|
57
|
+
"Remote A2A peer entered an unstable state and requires a fresh client session"
|
|
58
|
+
)
|
|
59
|
+
reset_required.code = code
|
|
60
|
+
reset_required.data = data
|
|
61
|
+
return reset_required
|
|
62
|
+
return A2APeerProtocolError(
|
|
63
|
+
"Remote A2A peer returned a protocol error",
|
|
64
|
+
error_code="peer_protocol_error",
|
|
65
|
+
rpc_code=code,
|
|
66
|
+
data=data,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def map_http_error(operation: str, exc: A2AClientHTTPError) -> A2AClientError:
|
|
71
|
+
status = exc.status_code
|
|
72
|
+
if status == 401:
|
|
73
|
+
return _attach_http_status(
|
|
74
|
+
A2AAuthenticationError(
|
|
75
|
+
f"Remote A2A peer rejected {operation} due to authentication failure"
|
|
76
|
+
),
|
|
77
|
+
status,
|
|
78
|
+
)
|
|
79
|
+
if status == 403:
|
|
80
|
+
return _attach_http_status(
|
|
81
|
+
A2APermissionDeniedError(
|
|
82
|
+
f"Remote A2A peer rejected {operation} due to insufficient permissions"
|
|
83
|
+
),
|
|
84
|
+
status,
|
|
85
|
+
)
|
|
86
|
+
if status in {404, 405, 409, 501}:
|
|
87
|
+
return _attach_http_status(
|
|
88
|
+
A2AUnsupportedOperationError(f"Remote A2A peer does not support {operation}"),
|
|
89
|
+
status,
|
|
90
|
+
)
|
|
91
|
+
if status == 408:
|
|
92
|
+
return _attach_http_status(
|
|
93
|
+
A2ATimeoutError(f"Remote A2A peer timed out during {operation}"),
|
|
94
|
+
status,
|
|
95
|
+
)
|
|
96
|
+
if status in {429, 502, 503, 504}:
|
|
97
|
+
return _attach_http_status(
|
|
98
|
+
A2AClientResetRequiredError(
|
|
99
|
+
f"Remote A2A peer is temporarily unstable during {operation}"
|
|
100
|
+
),
|
|
101
|
+
status,
|
|
102
|
+
)
|
|
103
|
+
return _attach_http_status(
|
|
104
|
+
A2AAgentUnavailableError(f"Remote A2A peer is unavailable for {operation}"),
|
|
105
|
+
status,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def map_transport_error(
|
|
110
|
+
operation: str,
|
|
111
|
+
exc: httpx.TimeoutException | httpx.TransportError,
|
|
112
|
+
) -> A2AClientError:
|
|
113
|
+
if isinstance(exc, httpx.TimeoutException):
|
|
114
|
+
return A2ATimeoutError(f"Remote A2A peer timed out during {operation}")
|
|
115
|
+
return A2AAgentUnavailableError(f"Remote A2A peer is unreachable for {operation}")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def map_operation_error(
|
|
119
|
+
operation: str,
|
|
120
|
+
exc: A2AClientHTTPError | A2AClientJSONRPCError | httpx.TimeoutException | httpx.TransportError,
|
|
121
|
+
) -> A2AClientError:
|
|
122
|
+
if isinstance(exc, A2AClientHTTPError):
|
|
123
|
+
return map_http_error(operation, exc)
|
|
124
|
+
if isinstance(exc, A2AClientJSONRPCError):
|
|
125
|
+
return map_jsonrpc_error(exc)
|
|
126
|
+
return map_transport_error(operation, exc)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def map_agent_card_error(
|
|
130
|
+
exc: A2AClientHTTPError | A2AClientJSONError | httpx.TimeoutException | httpx.TransportError,
|
|
131
|
+
) -> A2AClientError:
|
|
132
|
+
if isinstance(exc, A2AClientHTTPError):
|
|
133
|
+
return map_http_error("agent-card/fetch", exc)
|
|
134
|
+
if isinstance(exc, A2AClientJSONError):
|
|
135
|
+
return A2APeerProtocolError(
|
|
136
|
+
"Remote A2A peer returned an invalid agent card payload",
|
|
137
|
+
error_code="invalid_agent_card",
|
|
138
|
+
)
|
|
139
|
+
return map_transport_error("agent-card/fetch", exc)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__all__ = [
|
|
143
|
+
"map_agent_card_error",
|
|
144
|
+
"map_http_error",
|
|
145
|
+
"map_jsonrpc_error",
|
|
146
|
+
"map_operation_error",
|
|
147
|
+
"map_transport_error",
|
|
148
|
+
]
|
|
@@ -18,12 +18,30 @@ class A2AAgentUnavailableError(A2AClientError):
|
|
|
18
18
|
error_code = "agent_unavailable"
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
class A2AAuthenticationError(A2AClientError):
|
|
22
|
+
"""Raised when a remote A2A peer rejects authentication."""
|
|
23
|
+
|
|
24
|
+
error_code = "authentication_failed"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class A2APermissionDeniedError(A2AClientError):
|
|
28
|
+
"""Raised when a remote A2A peer rejects authorization."""
|
|
29
|
+
|
|
30
|
+
error_code = "permission_denied"
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
class A2AClientResetRequiredError(A2AAgentUnavailableError):
|
|
22
34
|
"""Raised when the cached transport should be rebuilt."""
|
|
23
35
|
|
|
24
36
|
error_code = "reset_required"
|
|
25
37
|
|
|
26
38
|
|
|
39
|
+
class A2ATimeoutError(A2AAgentUnavailableError):
|
|
40
|
+
"""Raised when an outbound call to a remote A2A peer times out."""
|
|
41
|
+
|
|
42
|
+
error_code = "timeout"
|
|
43
|
+
|
|
44
|
+
|
|
27
45
|
class A2AUnsupportedBindingError(A2AClientError):
|
|
28
46
|
"""Raised when local and remote transport configuration has no overlap."""
|
|
29
47
|
|
|
@@ -58,7 +76,10 @@ class A2APeerProtocolError(A2AClientError):
|
|
|
58
76
|
__all__ = [
|
|
59
77
|
"A2AClientError",
|
|
60
78
|
"A2AAgentUnavailableError",
|
|
79
|
+
"A2AAuthenticationError",
|
|
80
|
+
"A2APermissionDeniedError",
|
|
61
81
|
"A2AClientResetRequiredError",
|
|
82
|
+
"A2ATimeoutError",
|
|
62
83
|
"A2AUnsupportedBindingError",
|
|
63
84
|
"A2AUnsupportedOperationError",
|
|
64
85
|
"A2APeerProtocolError",
|