opencode-a2a 0.4.1__tar.gz → 0.5.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.1 → opencode_a2a-0.5.1}/.github/workflows/ci.yml +13 -10
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.github/workflows/dependency-health.yml +3 -5
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.github/workflows/publish.yml +10 -7
- {opencode_a2a-0.4.1/src/opencode_a2a.egg-info → opencode_a2a-0.5.1}/PKG-INFO +20 -6
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/README.md +16 -5
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/docs/guide.md +33 -5
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/pyproject.toml +3 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/README.md +1 -1
- opencode_a2a-0.5.1/scripts/dependency_health.sh +19 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/cli.py +8 -17
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/agent_card.py +2 -1
- opencode_a2a-0.5.1/src/opencode_a2a/client/auth.py +38 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/client.py +16 -5
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/config.py +14 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/request_context.py +18 -7
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/config.py +20 -3
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/contracts/extensions.py +3 -2
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/executor.py +6 -1
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/session_manager.py +42 -25
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_events.py +1 -1
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_runtime.py +3 -3
- opencode_a2a-0.4.1/src/opencode_a2a/execution/upstream_errors.py → opencode_a2a-0.5.1/src/opencode_a2a/execution/upstream_error_translator.py +3 -3
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/application.py +5 -6
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/opencode_upstream_client.py +32 -79
- opencode_a2a-0.5.1/src/opencode_a2a/runtime_state.py +20 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/application.py +227 -41
- opencode_a2a-0.5.1/src/opencode_a2a/server/state_store.py +656 -0
- opencode_a2a-0.5.1/src/opencode_a2a/server/task_store.py +210 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1/src/opencode_a2a.egg-info}/PKG-INFO +20 -6
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/SOURCES.txt +12 -4
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/requires.txt +3 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_agent_card.py +14 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_client_config.py +17 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_client_facade.py +55 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_request_context.py +25 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/config/test_settings.py +15 -0
- opencode_a2a-0.5.1/tests/conftest.py +36 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/contracts/test_extension_contract_consistency.py +1 -1
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_cancellation.py +22 -2
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_metrics.py +2 -2
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_multipart_input.py +3 -3
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_opencode_agent_session_binding.py +2 -12
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_session_ownership.py +161 -15
- opencode_a2a-0.4.1/tests/jsonrpc/test_error_mapping.py → opencode_a2a-0.5.1/tests/jsonrpc/test_error_responses.py +1 -1
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +7 -7
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_app_behaviors.py +121 -1
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_cli.py +10 -18
- opencode_a2a-0.5.1/tests/server/test_database_app_persistence.py +210 -0
- opencode_a2a-0.5.1/tests/server/test_state_store.py +162 -0
- opencode_a2a-0.5.1/tests/server/test_task_store_factory.py +294 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_transport_contract.py +36 -12
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/helpers.py +12 -7
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/streaming_output.py +3 -3
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/upstream/test_opencode_upstream_client_params.py +21 -21
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/uv.lock +118 -3
- opencode_a2a-0.4.1/scripts/dependency_health.sh +0 -13
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.gitignore +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.pre-commit-config.yaml +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/.secrets.baseline +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/AGENTS.md +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/CONTRIBUTING.md +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/LICENSE +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/SECURITY.md +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/check_coverage.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/doctor.sh +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/health_common.sh +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/lint.sh +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/scripts/smoke_test_built_cli.sh +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/setup.cfg +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/error_mapping.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/errors.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/client/payload_text.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/contracts/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/event_helpers.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/request_context.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_state.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
- /opencode_a2a-0.4.1/src/opencode_a2a/jsonrpc/error_mapping.py → /opencode_a2a-0.5.1/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/mapping.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/text.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/profile/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/profile/runtime.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/sandbox_policy.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/agent_card.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/openapi.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a/server/request_parsing.py +0 -0
- /opencode_a2a-0.4.1/src/opencode_a2a/error_taxonomy.py → /opencode_a2a-0.5.1/src/opencode_a2a/upstream_taxonomy.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_error_mapping.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/client/test_payload_text.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/config/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/contracts/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_agent_errors.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_agent_helpers.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_directory_validation.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_core.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/package/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/package/test_version.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/parts/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/parts/test_parts_text.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/profile/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/profile/test_profile_runtime.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/scripts/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/scripts/test_script_health_contract.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_a2a_client_manager.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_agent_card.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_call_context_builder.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/server/test_cancel_contract.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/__init__.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/support/session_extensions.py +0 -0
- {opencode_a2a-0.4.1 → opencode_a2a-0.5.1}/tests/upstream/__init__.py +0 -0
|
@@ -8,8 +8,6 @@ on:
|
|
|
8
8
|
|
|
9
9
|
permissions:
|
|
10
10
|
contents: read
|
|
11
|
-
env:
|
|
12
|
-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
13
11
|
|
|
14
12
|
jobs:
|
|
15
13
|
quality-gate:
|
|
@@ -17,15 +15,15 @@ jobs:
|
|
|
17
15
|
|
|
18
16
|
steps:
|
|
19
17
|
- name: Checkout
|
|
20
|
-
uses: actions/checkout@
|
|
18
|
+
uses: actions/checkout@v6
|
|
21
19
|
|
|
22
20
|
- name: Setup Python
|
|
23
|
-
uses: actions/setup-python@
|
|
21
|
+
uses: actions/setup-python@v6
|
|
24
22
|
with:
|
|
25
23
|
python-version: "3.13"
|
|
26
24
|
|
|
27
25
|
- name: Setup uv
|
|
28
|
-
uses: astral-sh/setup-uv@
|
|
26
|
+
uses: astral-sh/setup-uv@v7
|
|
29
27
|
with:
|
|
30
28
|
enable-cache: false
|
|
31
29
|
|
|
@@ -41,8 +39,13 @@ jobs:
|
|
|
41
39
|
- name: Enforce coverage policy
|
|
42
40
|
run: uv run python ./scripts/check_coverage.py
|
|
43
41
|
|
|
44
|
-
- name:
|
|
45
|
-
run:
|
|
42
|
+
- name: Export runtime requirements for vulnerability audit
|
|
43
|
+
run: >
|
|
44
|
+
uv export --format requirements.txt --no-dev --locked --no-emit-project
|
|
45
|
+
--output-file /tmp/runtime-requirements.txt >/dev/null
|
|
46
|
+
|
|
47
|
+
- name: Run runtime dependency vulnerability audit
|
|
48
|
+
run: uv run pip-audit --requirement /tmp/runtime-requirements.txt
|
|
46
49
|
|
|
47
50
|
- name: Clean previous build artifacts
|
|
48
51
|
run: rm -rf build dist
|
|
@@ -65,15 +68,15 @@ jobs:
|
|
|
65
68
|
|
|
66
69
|
steps:
|
|
67
70
|
- name: Checkout
|
|
68
|
-
uses: actions/checkout@
|
|
71
|
+
uses: actions/checkout@v6
|
|
69
72
|
|
|
70
73
|
- name: Setup Python
|
|
71
|
-
uses: actions/setup-python@
|
|
74
|
+
uses: actions/setup-python@v6
|
|
72
75
|
with:
|
|
73
76
|
python-version: ${{ matrix.python-version }}
|
|
74
77
|
|
|
75
78
|
- name: Setup uv
|
|
76
|
-
uses: astral-sh/setup-uv@
|
|
79
|
+
uses: astral-sh/setup-uv@v7
|
|
77
80
|
with:
|
|
78
81
|
enable-cache: false
|
|
79
82
|
|
|
@@ -8,8 +8,6 @@ on:
|
|
|
8
8
|
|
|
9
9
|
permissions:
|
|
10
10
|
contents: read
|
|
11
|
-
env:
|
|
12
|
-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
13
11
|
|
|
14
12
|
jobs:
|
|
15
13
|
dependency-health:
|
|
@@ -17,15 +15,15 @@ jobs:
|
|
|
17
15
|
|
|
18
16
|
steps:
|
|
19
17
|
- name: Checkout
|
|
20
|
-
uses: actions/checkout@
|
|
18
|
+
uses: actions/checkout@v6
|
|
21
19
|
|
|
22
20
|
- name: Setup Python
|
|
23
|
-
uses: actions/setup-python@
|
|
21
|
+
uses: actions/setup-python@v6
|
|
24
22
|
with:
|
|
25
23
|
python-version: "3.13"
|
|
26
24
|
|
|
27
25
|
- name: Setup uv
|
|
28
|
-
uses: astral-sh/setup-uv@
|
|
26
|
+
uses: astral-sh/setup-uv@v7
|
|
29
27
|
with:
|
|
30
28
|
enable-cache: false
|
|
31
29
|
|
|
@@ -9,8 +9,6 @@ on:
|
|
|
9
9
|
permissions:
|
|
10
10
|
contents: write
|
|
11
11
|
id-token: write
|
|
12
|
-
env:
|
|
13
|
-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
14
12
|
|
|
15
13
|
jobs:
|
|
16
14
|
publish:
|
|
@@ -18,25 +16,30 @@ jobs:
|
|
|
18
16
|
|
|
19
17
|
steps:
|
|
20
18
|
- name: Checkout
|
|
21
|
-
uses: actions/checkout@
|
|
19
|
+
uses: actions/checkout@v6
|
|
22
20
|
with:
|
|
23
21
|
fetch-depth: 0
|
|
24
22
|
|
|
25
23
|
- name: Setup Python
|
|
26
|
-
uses: actions/setup-python@
|
|
24
|
+
uses: actions/setup-python@v6
|
|
27
25
|
with:
|
|
28
26
|
python-version: "3.13"
|
|
29
27
|
|
|
30
28
|
- name: Setup uv
|
|
31
|
-
uses: astral-sh/setup-uv@
|
|
29
|
+
uses: astral-sh/setup-uv@v7
|
|
32
30
|
with:
|
|
33
31
|
enable-cache: false
|
|
34
32
|
|
|
35
33
|
- name: Run regression baseline
|
|
36
34
|
run: bash ./scripts/doctor.sh
|
|
37
35
|
|
|
38
|
-
- name:
|
|
39
|
-
run:
|
|
36
|
+
- name: Export runtime requirements for vulnerability audit
|
|
37
|
+
run: >
|
|
38
|
+
uv export --format requirements.txt --no-dev --locked --no-emit-project
|
|
39
|
+
--output-file /tmp/runtime-requirements.txt >/dev/null
|
|
40
|
+
|
|
41
|
+
- name: Run runtime dependency vulnerability audit
|
|
42
|
+
run: uv run pip-audit --requirement /tmp/runtime-requirements.txt
|
|
40
43
|
|
|
41
44
|
- name: Clean previous build artifacts
|
|
42
45
|
run: rm -rf build dist
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencode-a2a
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: OpenCode A2A runtime
|
|
5
5
|
Author: liujuanjuan1984@Intelligent-Internet
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -20,10 +20,13 @@ Requires-Python: >=3.11
|
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: a2a-sdk==0.3.25
|
|
23
|
+
Requires-Dist: aiosqlite<1.0,>=0.20
|
|
23
24
|
Requires-Dist: fastapi<1.0,>=0.110
|
|
24
25
|
Requires-Dist: httpx<1.0,>=0.27
|
|
25
26
|
Requires-Dist: pydantic<3.0,>=2.6
|
|
26
27
|
Requires-Dist: pydantic-settings<3.0,>=2.2
|
|
28
|
+
Requires-Dist: requests<3.0,>=2.33.0
|
|
29
|
+
Requires-Dist: sqlalchemy<3.0,>=2.0
|
|
27
30
|
Requires-Dist: sse-starlette<4.0,>=2.1
|
|
28
31
|
Requires-Dist: uvicorn<1.0,>=0.29
|
|
29
32
|
Provides-Extra: dev
|
|
@@ -144,19 +147,30 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
|
|
|
144
147
|
Interact with other A2A agents directly from the command line:
|
|
145
148
|
|
|
146
149
|
```bash
|
|
147
|
-
|
|
150
|
+
# Using the target peer agent's Bearer token via environment injection
|
|
151
|
+
A2A_CLIENT_BEARER_TOKEN=your-outbound-token \
|
|
152
|
+
opencode-a2a call http://other-agent:8000 "How are you?"
|
|
153
|
+
|
|
154
|
+
# Using the target peer agent's Basic auth via environment injection
|
|
155
|
+
# Accepts raw user:pass or its base64-encoded value
|
|
156
|
+
A2A_CLIENT_BASIC_AUTH="user:pass" \
|
|
157
|
+
opencode-a2a call http://other-agent:8000 "How are you?"
|
|
148
158
|
```
|
|
149
159
|
|
|
150
160
|
### Outbound Agent Calls (Tools)
|
|
151
161
|
The server can autonomously execute `a2a_call(url, message)` tool calls emitted by the OpenCode runtime. Results are fetched via A2A and returned to the model as tool results, enabling multi-agent orchestration.
|
|
152
162
|
|
|
153
|
-
When the target peer requires bearer auth, configure
|
|
154
|
-
for server-side outbound calls.
|
|
155
|
-
`
|
|
163
|
+
When the target peer agent requires bearer auth, configure
|
|
164
|
+
`A2A_CLIENT_BEARER_TOKEN` for server-side outbound calls. When the target peer
|
|
165
|
+
agent requires Basic auth, use `A2A_CLIENT_BASIC_AUTH`.
|
|
166
|
+
These outbound credentials apply to the peer specified by `opencode-a2a call`
|
|
167
|
+
or `a2a_call(url, message)`, not to this service's inbound `A2A_BEARER_TOKEN`.
|
|
168
|
+
The CLI intentionally reads outbound credentials from environment variables only,
|
|
169
|
+
so secrets do not appear in shell history or process arguments.
|
|
156
170
|
|
|
157
171
|
Server-side outbound client settings are fully wired through runtime config:
|
|
158
172
|
`A2A_CLIENT_TIMEOUT_SECONDS`, `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS`,
|
|
159
|
-
`A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, and
|
|
173
|
+
`A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, `A2A_CLIENT_BASIC_AUTH`, and
|
|
160
174
|
`A2A_CLIENT_SUPPORTED_TRANSPORTS`.
|
|
161
175
|
|
|
162
176
|
Detailed protocol contracts, examples, and extension docs live in
|
|
@@ -106,19 +106,30 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
|
|
|
106
106
|
Interact with other A2A agents directly from the command line:
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
|
|
109
|
+
# Using the target peer agent's Bearer token via environment injection
|
|
110
|
+
A2A_CLIENT_BEARER_TOKEN=your-outbound-token \
|
|
111
|
+
opencode-a2a call http://other-agent:8000 "How are you?"
|
|
112
|
+
|
|
113
|
+
# Using the target peer agent's Basic auth via environment injection
|
|
114
|
+
# Accepts raw user:pass or its base64-encoded value
|
|
115
|
+
A2A_CLIENT_BASIC_AUTH="user:pass" \
|
|
116
|
+
opencode-a2a call http://other-agent:8000 "How are you?"
|
|
110
117
|
```
|
|
111
118
|
|
|
112
119
|
### Outbound Agent Calls (Tools)
|
|
113
120
|
The server can autonomously execute `a2a_call(url, message)` tool calls emitted by the OpenCode runtime. Results are fetched via A2A and returned to the model as tool results, enabling multi-agent orchestration.
|
|
114
121
|
|
|
115
|
-
When the target peer requires bearer auth, configure
|
|
116
|
-
for server-side outbound calls.
|
|
117
|
-
`
|
|
122
|
+
When the target peer agent requires bearer auth, configure
|
|
123
|
+
`A2A_CLIENT_BEARER_TOKEN` for server-side outbound calls. When the target peer
|
|
124
|
+
agent requires Basic auth, use `A2A_CLIENT_BASIC_AUTH`.
|
|
125
|
+
These outbound credentials apply to the peer specified by `opencode-a2a call`
|
|
126
|
+
or `a2a_call(url, message)`, not to this service's inbound `A2A_BEARER_TOKEN`.
|
|
127
|
+
The CLI intentionally reads outbound credentials from environment variables only,
|
|
128
|
+
so secrets do not appear in shell history or process arguments.
|
|
118
129
|
|
|
119
130
|
Server-side outbound client settings are fully wired through runtime config:
|
|
120
131
|
`A2A_CLIENT_TIMEOUT_SECONDS`, `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS`,
|
|
121
|
-
`A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, and
|
|
132
|
+
`A2A_CLIENT_USE_CLIENT_PREFERENCE`, `A2A_CLIENT_BEARER_TOKEN`, `A2A_CLIENT_BASIC_AUTH`, and
|
|
122
133
|
`A2A_CLIENT_SUPPORTED_TRANSPORTS`.
|
|
123
134
|
|
|
124
135
|
Detailed protocol contracts, examples, and extension docs live in
|
|
@@ -55,10 +55,10 @@ Key variables to understand protocol behavior:
|
|
|
55
55
|
also logged with preview truncation controlled by `A2A_LOG_BODY_LIMIT`.
|
|
56
56
|
- `A2A_MAX_REQUEST_BODY_BYTES`: runtime request-body limit. Oversized requests
|
|
57
57
|
return HTTP `413`.
|
|
58
|
-
- `
|
|
59
|
-
|
|
58
|
+
- `A2A_PENDING_SESSION_CLAIM_TTL_SECONDS`: lease duration for pending preferred
|
|
59
|
+
session claims before they expire and stop blocking other identities.
|
|
60
60
|
- `A2A_INTERRUPT_REQUEST_TTL_SECONDS`: active retention window for the
|
|
61
|
-
|
|
61
|
+
interrupt request binding registry used by `a2a.interrupt.*`
|
|
62
62
|
callback methods. Default: `10800` seconds (`180` minutes).
|
|
63
63
|
- `A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS`: retention window for expired
|
|
64
64
|
interrupt tombstones after active TTL has elapsed. During this window,
|
|
@@ -76,6 +76,10 @@ Key variables to understand protocol behavior:
|
|
|
76
76
|
- `A2A_CLIENT_BEARER_TOKEN`: optional bearer token attached to outbound peer
|
|
77
77
|
calls made by the embedded A2A client and `a2a_call` tool path.
|
|
78
78
|
- `A2A_CLIENT_SUPPORTED_TRANSPORTS`: ordered outbound transport preference list.
|
|
79
|
+
- `A2A_TASK_STORE_BACKEND`: runtime state backend. Supported values: `database`,
|
|
80
|
+
`memory`. Default: `database`.
|
|
81
|
+
- `A2A_TASK_STORE_DATABASE_URL`: database URL used by the default durable
|
|
82
|
+
backend. Default: `sqlite+aiosqlite:///./opencode-a2a.db`.
|
|
79
83
|
- Runtime authentication is bearer-token only via `A2A_BEARER_TOKEN`.
|
|
80
84
|
- The same outbound client flags are also honored by the server-side embedded
|
|
81
85
|
A2A client used for peer calls and `a2a_call` tool execution:
|
|
@@ -157,6 +161,30 @@ OPENCODE_WORKSPACE_ROOT=/abs/path/to/workspace \
|
|
|
157
161
|
opencode-a2a
|
|
158
162
|
```
|
|
159
163
|
|
|
164
|
+
By default, the service uses a SQLite-backed durable state store:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
OPENCODE_BASE_URL=http://127.0.0.1:4096 \
|
|
168
|
+
A2A_BEARER_TOKEN=dev-token \
|
|
169
|
+
A2A_TASK_STORE_DATABASE_URL=sqlite+aiosqlite:///./opencode-a2a.db \
|
|
170
|
+
opencode-a2a
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
With the default `database` backend, the service persists:
|
|
174
|
+
|
|
175
|
+
- task records
|
|
176
|
+
- session binding / ownership state
|
|
177
|
+
- interrupt request bindings and tombstones
|
|
178
|
+
|
|
179
|
+
In-flight asyncio locks, outbound A2A client caches, and stream-local
|
|
180
|
+
aggregation buffers remain process-local runtime state.
|
|
181
|
+
|
|
182
|
+
To opt into an ephemeral development profile, set:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
A2A_TASK_STORE_BACKEND=memory
|
|
186
|
+
```
|
|
187
|
+
|
|
160
188
|
## Troubleshooting Provider Auth State
|
|
161
189
|
|
|
162
190
|
If one deployment works while another fails against the same upstream provider,
|
|
@@ -825,8 +853,8 @@ Notes:
|
|
|
825
853
|
|
|
826
854
|
- `request_id` must be a live interrupt request observed from stream metadata
|
|
827
855
|
(`metadata.shared.interrupt.request_id`).
|
|
828
|
-
- The server keeps an
|
|
829
|
-
|
|
856
|
+
- The server keeps an interrupt binding registry; callbacks with unknown or
|
|
857
|
+
expired `request_id` are rejected.
|
|
830
858
|
- The cache retention windows are controlled by
|
|
831
859
|
`A2A_INTERRUPT_REQUEST_TTL_SECONDS` (default: `10800` seconds / `180`
|
|
832
860
|
minutes) and `A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS` (default: `600`
|
|
@@ -25,10 +25,13 @@ classifiers = [
|
|
|
25
25
|
]
|
|
26
26
|
dependencies = [
|
|
27
27
|
"a2a-sdk==0.3.25",
|
|
28
|
+
"aiosqlite>=0.20,<1.0",
|
|
28
29
|
"fastapi>=0.110,<1.0",
|
|
29
30
|
"httpx>=0.27,<1.0",
|
|
30
31
|
"pydantic>=2.6,<3.0",
|
|
31
32
|
"pydantic-settings>=2.2,<3.0",
|
|
33
|
+
"requests>=2.33.0,<3.0",
|
|
34
|
+
"sqlalchemy>=2.0,<3.0",
|
|
32
35
|
"sse-starlette>=2.1,<4.0",
|
|
33
36
|
"uvicorn>=0.29,<1.0",
|
|
34
37
|
]
|
|
@@ -13,7 +13,7 @@ remaining repository-maintenance helpers.
|
|
|
13
13
|
## Other Scripts
|
|
14
14
|
|
|
15
15
|
- [`doctor.sh`](./doctor.sh): primary local development regression entrypoint (uv sync + lint + tests + coverage)
|
|
16
|
-
- [`dependency_health.sh`](./dependency_health.sh): dependency review entrypoint (`sync`/`pip check` + outdated + audit)
|
|
16
|
+
- [`dependency_health.sh`](./dependency_health.sh): development dependency review entrypoint (`sync`/`pip check` + outdated + dev audit), while blocking CI/publish audits focus on runtime dependencies
|
|
17
17
|
- [`check_coverage.py`](./check_coverage.py): enforces the overall coverage floor and per-file minimums for critical modules
|
|
18
18
|
- [`lint.sh`](./lint.sh): lint helper
|
|
19
19
|
- [`smoke_test_built_cli.sh`](./smoke_test_built_cli.sh): built-artifact smoke test for the released CLI runtime; defaults to the only local wheel, supports explicit wheel/sdist paths, and rejects ambiguous local artifact selection
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# shellcheck source=./health_common.sh
|
|
5
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/health_common.sh"
|
|
6
|
+
|
|
7
|
+
run_shared_repo_health_prerequisites "dependency-health"
|
|
8
|
+
|
|
9
|
+
echo "[dependency-health] list outdated packages"
|
|
10
|
+
uv pip list --outdated
|
|
11
|
+
|
|
12
|
+
dev_requirements="$(mktemp)"
|
|
13
|
+
trap 'rm -f "${dev_requirements}"' EXIT
|
|
14
|
+
|
|
15
|
+
echo "[dependency-health] export dev extra requirements"
|
|
16
|
+
uv export --format requirements.txt --extra dev --no-dev --locked --no-emit-project --output-file "${dev_requirements}" >/dev/null
|
|
17
|
+
|
|
18
|
+
echo "[dependency-health] run dev dependency vulnerability audit"
|
|
19
|
+
uv run pip-audit --requirement "${dev_requirements}"
|
|
@@ -6,23 +6,19 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
|
|
9
|
+
from a2a.types import Message, TaskArtifactUpdateEvent, TaskStatusUpdateEvent
|
|
10
|
+
|
|
9
11
|
from . import __version__
|
|
12
|
+
from .client import A2AClient, load_settings
|
|
10
13
|
from .server.application import main as serve_main
|
|
11
14
|
|
|
12
15
|
|
|
13
|
-
async def run_call(agent_url: str, text: str
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from .client import A2AClient
|
|
17
|
-
|
|
18
|
-
client = A2AClient(agent_url)
|
|
19
|
-
metadata = {}
|
|
20
|
-
if token:
|
|
21
|
-
# Use Authorization header for bearer auth.
|
|
22
|
-
metadata["authorization"] = f"Bearer {token}"
|
|
16
|
+
async def run_call(agent_url: str, text: str) -> int:
|
|
17
|
+
settings = load_settings(os.environ)
|
|
18
|
+
client = A2AClient(agent_url, settings=settings)
|
|
23
19
|
|
|
24
20
|
try:
|
|
25
|
-
async for event in client.send_message(text
|
|
21
|
+
async for event in client.send_message(text):
|
|
26
22
|
if isinstance(event, tuple):
|
|
27
23
|
_, update = event
|
|
28
24
|
if isinstance(update, TaskArtifactUpdateEvent):
|
|
@@ -73,11 +69,6 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
73
69
|
)
|
|
74
70
|
call_parser.add_argument("agent_url", help="URL of the agent to call.")
|
|
75
71
|
call_parser.add_argument("text", help="Text message to send.")
|
|
76
|
-
call_parser.add_argument(
|
|
77
|
-
"--token",
|
|
78
|
-
help="Bearer token for authentication (can also use A2A_CLIENT_BEARER_TOKEN env).",
|
|
79
|
-
default=os.environ.get("A2A_CLIENT_BEARER_TOKEN"),
|
|
80
|
-
)
|
|
81
72
|
|
|
82
73
|
return parser
|
|
83
74
|
|
|
@@ -92,7 +83,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
92
83
|
|
|
93
84
|
namespace = parser.parse_args(args)
|
|
94
85
|
if namespace.command == "call":
|
|
95
|
-
return asyncio.run(run_call(namespace.agent_url, namespace.text
|
|
86
|
+
return asyncio.run(run_call(namespace.agent_url, namespace.text))
|
|
96
87
|
|
|
97
88
|
if namespace.command is None:
|
|
98
89
|
serve_main()
|
|
@@ -66,9 +66,10 @@ def build_resolver_http_kwargs(
|
|
|
66
66
|
*,
|
|
67
67
|
bearer_token: str | None,
|
|
68
68
|
timeout: float,
|
|
69
|
+
basic_auth: str | None = None,
|
|
69
70
|
) -> dict[str, Any]:
|
|
70
71
|
http_kwargs: dict[str, Any] = {"timeout": timeout}
|
|
71
|
-
default_headers = build_default_headers(bearer_token)
|
|
72
|
+
default_headers = build_default_headers(bearer_token, basic_auth)
|
|
72
73
|
if default_headers:
|
|
73
74
|
http_kwargs["headers"] = default_headers
|
|
74
75
|
return http_kwargs
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Authentication helpers for outbound A2A client configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from base64 import b64decode, b64encode
|
|
6
|
+
from binascii import Error as BinasciiError
|
|
7
|
+
|
|
8
|
+
BASIC_AUTH_FORMAT_ERROR = (
|
|
9
|
+
"A2A_CLIENT_BASIC_AUTH must be 'username:password' or a base64-encoded "
|
|
10
|
+
"'username:password' value"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_basic_auth(value: str) -> None:
|
|
15
|
+
"""Validate a raw or pre-encoded Basic Auth credential value."""
|
|
16
|
+
if ":" in value:
|
|
17
|
+
return
|
|
18
|
+
decoded = _decode_basic_auth(value)
|
|
19
|
+
if b":" not in decoded:
|
|
20
|
+
raise ValueError(BASIC_AUTH_FORMAT_ERROR)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def encode_basic_auth(value: str) -> str:
|
|
24
|
+
"""Normalize a raw or pre-encoded Basic Auth value to base64 credentials."""
|
|
25
|
+
if ":" in value:
|
|
26
|
+
return b64encode(value.encode()).decode()
|
|
27
|
+
decoded = _decode_basic_auth(value)
|
|
28
|
+
if b":" not in decoded:
|
|
29
|
+
raise ValueError(BASIC_AUTH_FORMAT_ERROR)
|
|
30
|
+
return b64encode(decoded).decode()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _decode_basic_auth(value: str) -> bytes:
|
|
34
|
+
padded_value = value + ("=" * (-len(value) % 4))
|
|
35
|
+
try:
|
|
36
|
+
return b64decode(padded_value, validate=True)
|
|
37
|
+
except (BinasciiError, ValueError) as exc:
|
|
38
|
+
raise ValueError(BASIC_AUTH_FORMAT_ERROR) from exc
|
|
@@ -83,6 +83,7 @@ class A2AClient:
|
|
|
83
83
|
http_kwargs=build_resolver_http_kwargs(
|
|
84
84
|
bearer_token=self._settings.bearer_token,
|
|
85
85
|
timeout=self._settings.card_fetch_timeout,
|
|
86
|
+
basic_auth=self._settings.basic_auth,
|
|
86
87
|
)
|
|
87
88
|
)
|
|
88
89
|
except (
|
|
@@ -121,7 +122,9 @@ class A2AClient:
|
|
|
121
122
|
try:
|
|
122
123
|
async for event in client.send_message(
|
|
123
124
|
request,
|
|
124
|
-
context=build_call_context(
|
|
125
|
+
context=build_call_context(
|
|
126
|
+
self._settings.bearer_token, extra_headers, self._settings.basic_auth
|
|
127
|
+
),
|
|
125
128
|
request_metadata=request_metadata,
|
|
126
129
|
extensions=extensions,
|
|
127
130
|
):
|
|
@@ -180,7 +183,9 @@ class A2AClient:
|
|
|
180
183
|
history_length=history_length,
|
|
181
184
|
metadata=request_metadata or {},
|
|
182
185
|
),
|
|
183
|
-
context=build_call_context(
|
|
186
|
+
context=build_call_context(
|
|
187
|
+
self._settings.bearer_token, extra_headers, self._settings.basic_auth
|
|
188
|
+
),
|
|
184
189
|
)
|
|
185
190
|
except (
|
|
186
191
|
A2AClientHTTPError,
|
|
@@ -206,7 +211,9 @@ class A2AClient:
|
|
|
206
211
|
try:
|
|
207
212
|
return await client.cancel_task(
|
|
208
213
|
TaskIdParams(id=task_id, metadata=request_metadata or {}),
|
|
209
|
-
context=build_call_context(
|
|
214
|
+
context=build_call_context(
|
|
215
|
+
self._settings.bearer_token, extra_headers, self._settings.basic_auth
|
|
216
|
+
),
|
|
210
217
|
)
|
|
211
218
|
except (
|
|
212
219
|
A2AClientHTTPError,
|
|
@@ -232,7 +239,9 @@ class A2AClient:
|
|
|
232
239
|
try:
|
|
233
240
|
async for event in client.resubscribe(
|
|
234
241
|
TaskIdParams(id=task_id, metadata=request_metadata or {}),
|
|
235
|
-
context=build_call_context(
|
|
242
|
+
context=build_call_context(
|
|
243
|
+
self._settings.bearer_token, extra_headers, self._settings.basic_auth
|
|
244
|
+
),
|
|
236
245
|
):
|
|
237
246
|
yield event
|
|
238
247
|
except (
|
|
@@ -264,7 +273,9 @@ class A2AClient:
|
|
|
264
273
|
factory = ClientFactory(config, consumers=None)
|
|
265
274
|
client = factory.create(
|
|
266
275
|
card,
|
|
267
|
-
interceptors=build_client_interceptors(
|
|
276
|
+
interceptors=build_client_interceptors(
|
|
277
|
+
self._settings.bearer_token, self._settings.basic_auth
|
|
278
|
+
),
|
|
268
279
|
)
|
|
269
280
|
except ValueError as exc:
|
|
270
281
|
raise A2AUnsupportedBindingError(
|
|
@@ -6,6 +6,8 @@ from collections.abc import Iterable, Mapping
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from .auth import validate_basic_auth
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def _read_setting(
|
|
11
13
|
source: Any,
|
|
@@ -106,6 +108,7 @@ class A2AClientSettings:
|
|
|
106
108
|
use_client_preference: bool = False
|
|
107
109
|
card_fetch_timeout: float = 5.0
|
|
108
110
|
bearer_token: str | None = None
|
|
111
|
+
basic_auth: str | None = None
|
|
109
112
|
supported_transports: tuple[str, ...] = (
|
|
110
113
|
"JSONRPC",
|
|
111
114
|
"HTTP+JSON",
|
|
@@ -153,6 +156,16 @@ def load_settings(raw_settings: Any) -> A2AClientSettings:
|
|
|
153
156
|
default=None,
|
|
154
157
|
),
|
|
155
158
|
)
|
|
159
|
+
basic_auth = _coerce_optional_str(
|
|
160
|
+
"A2A_CLIENT_BASIC_AUTH",
|
|
161
|
+
_read_setting(
|
|
162
|
+
raw_settings,
|
|
163
|
+
keys=("A2A_CLIENT_BASIC_AUTH", "a2a_client_basic_auth"),
|
|
164
|
+
default=None,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
if basic_auth is not None:
|
|
168
|
+
validate_basic_auth(basic_auth)
|
|
156
169
|
supported_transports = _parse_transports(
|
|
157
170
|
_read_setting(
|
|
158
171
|
raw_settings,
|
|
@@ -170,6 +183,7 @@ def load_settings(raw_settings: Any) -> A2AClientSettings:
|
|
|
170
183
|
use_client_preference=use_client_preference,
|
|
171
184
|
card_fetch_timeout=card_fetch_timeout,
|
|
172
185
|
bearer_token=bearer_token,
|
|
186
|
+
basic_auth=basic_auth,
|
|
173
187
|
supported_transports=supported_transports,
|
|
174
188
|
)
|
|
175
189
|
|
|
@@ -7,6 +7,8 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from a2a.client.middleware import ClientCallContext, ClientCallInterceptor
|
|
9
9
|
|
|
10
|
+
from .auth import encode_basic_auth
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class HeaderInterceptor(ClientCallInterceptor):
|
|
12
14
|
def __init__(self, default_headers: Mapping[str, str] | None = None) -> None:
|
|
@@ -36,10 +38,15 @@ class HeaderInterceptor(ClientCallInterceptor):
|
|
|
36
38
|
return request_payload, http_kwargs
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
def build_default_headers(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
def build_default_headers(
|
|
42
|
+
bearer_token: str | None,
|
|
43
|
+
basic_auth: str | None = None,
|
|
44
|
+
) -> dict[str, str]:
|
|
45
|
+
if bearer_token:
|
|
46
|
+
return {"Authorization": f"Bearer {bearer_token}"}
|
|
47
|
+
if basic_auth:
|
|
48
|
+
return {"Authorization": f"Basic {encode_basic_auth(basic_auth)}"}
|
|
49
|
+
return {}
|
|
43
50
|
|
|
44
51
|
|
|
45
52
|
def split_request_metadata(
|
|
@@ -59,8 +66,9 @@ def split_request_metadata(
|
|
|
59
66
|
def build_call_context(
|
|
60
67
|
bearer_token: str | None,
|
|
61
68
|
extra_headers: Mapping[str, str] | None,
|
|
69
|
+
basic_auth: str | None = None,
|
|
62
70
|
) -> ClientCallContext | None:
|
|
63
|
-
merged_headers = build_default_headers(bearer_token)
|
|
71
|
+
merged_headers = build_default_headers(bearer_token, basic_auth)
|
|
64
72
|
if extra_headers:
|
|
65
73
|
merged_headers.update(extra_headers)
|
|
66
74
|
if not merged_headers:
|
|
@@ -73,8 +81,11 @@ def build_call_context(
|
|
|
73
81
|
)
|
|
74
82
|
|
|
75
83
|
|
|
76
|
-
def build_client_interceptors(
|
|
77
|
-
|
|
84
|
+
def build_client_interceptors(
|
|
85
|
+
bearer_token: str | None,
|
|
86
|
+
basic_auth: str | None = None,
|
|
87
|
+
) -> list[ClientCallInterceptor]:
|
|
88
|
+
return [HeaderInterceptor(build_default_headers(bearer_token, basic_auth))]
|
|
78
89
|
|
|
79
90
|
|
|
80
91
|
__all__ = [
|
|
@@ -35,6 +35,7 @@ WriteAccessScope = Literal[
|
|
|
35
35
|
"custom",
|
|
36
36
|
]
|
|
37
37
|
OutsideWorkspaceAccess = Literal["unknown", "allowed", "disallowed", "custom"]
|
|
38
|
+
TaskStoreBackend = Literal["memory", "database"]
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def _parse_declared_list(value: Any) -> tuple[str, ...]:
|
|
@@ -132,9 +133,11 @@ class Settings(BaseSettings):
|
|
|
132
133
|
a2a_port: int = Field(default=8000, alias="A2A_PORT")
|
|
133
134
|
a2a_bearer_token: str = Field(..., min_length=1, alias="A2A_BEARER_TOKEN")
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
a2a_pending_session_claim_ttl_seconds: float = Field(
|
|
137
|
+
default=30.0,
|
|
138
|
+
gt=0.0,
|
|
139
|
+
alias="A2A_PENDING_SESSION_CLAIM_TTL_SECONDS",
|
|
140
|
+
)
|
|
138
141
|
a2a_interrupt_request_ttl_seconds: float = Field(
|
|
139
142
|
default=10_800.0,
|
|
140
143
|
ge=0.0,
|
|
@@ -176,7 +179,21 @@ class Settings(BaseSettings):
|
|
|
176
179
|
alias="A2A_CLIENT_SUPPORTED_TRANSPORTS",
|
|
177
180
|
)
|
|
178
181
|
|
|
182
|
+
# Task store settings
|
|
183
|
+
a2a_task_store_backend: TaskStoreBackend = Field(
|
|
184
|
+
default="database",
|
|
185
|
+
alias="A2A_TASK_STORE_BACKEND",
|
|
186
|
+
)
|
|
187
|
+
a2a_task_store_database_url: str | None = Field(
|
|
188
|
+
default="sqlite+aiosqlite:///./opencode-a2a.db",
|
|
189
|
+
alias="A2A_TASK_STORE_DATABASE_URL",
|
|
190
|
+
)
|
|
191
|
+
|
|
179
192
|
@model_validator(mode="after")
|
|
180
193
|
def _validate_sandbox_policy(self) -> Settings:
|
|
181
194
|
SandboxPolicy.from_settings(self).validate_configuration()
|
|
195
|
+
if self.a2a_task_store_backend == "database" and not self.a2a_task_store_database_url:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
"A2A_TASK_STORE_DATABASE_URL is required when A2A_TASK_STORE_BACKEND=database"
|
|
198
|
+
)
|
|
182
199
|
return self
|
|
@@ -450,8 +450,9 @@ def build_session_binding_extension_params(
|
|
|
450
450
|
"message to that upstream session."
|
|
451
451
|
),
|
|
452
452
|
(
|
|
453
|
-
"Otherwise, the server will create a new upstream session and
|
|
454
|
-
"the (identity, contextId)->session_id mapping
|
|
453
|
+
"Otherwise, the server will create a new upstream session and retain "
|
|
454
|
+
"the (identity, contextId)->session_id mapping according to the "
|
|
455
|
+
"configured task/state store backend and TTL policy."
|
|
455
456
|
),
|
|
456
457
|
],
|
|
457
458
|
}
|