opencode-a2a 0.5.0__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.
Files changed (137) hide show
  1. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/.github/workflows/ci.yml +13 -10
  2. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/.github/workflows/dependency-health.yml +3 -5
  3. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/.github/workflows/publish.yml +10 -7
  4. {opencode_a2a-0.5.0/src/opencode_a2a.egg-info → opencode_a2a-0.5.1}/PKG-INFO +18 -6
  5. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/README.md +16 -5
  6. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/docs/guide.md +0 -2
  7. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/pyproject.toml +1 -0
  8. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/scripts/README.md +1 -1
  9. opencode_a2a-0.5.1/scripts/dependency_health.sh +19 -0
  10. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/cli.py +8 -17
  11. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/agent_card.py +2 -1
  12. opencode_a2a-0.5.1/src/opencode_a2a/client/auth.py +38 -0
  13. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/client.py +16 -5
  14. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/config.py +14 -0
  15. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/request_context.py +18 -7
  16. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/config.py +0 -9
  17. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/application.py +0 -1
  18. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/server/application.py +175 -36
  19. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/server/state_store.py +4 -12
  20. opencode_a2a-0.5.1/src/opencode_a2a/server/task_store.py +210 -0
  21. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1/src/opencode_a2a.egg-info}/PKG-INFO +18 -6
  22. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/SOURCES.txt +1 -0
  23. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/requires.txt +1 -0
  24. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/test_agent_card.py +14 -0
  25. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/test_client_config.py +17 -0
  26. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/test_client_facade.py +55 -0
  27. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/test_request_context.py +25 -0
  28. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_app_behaviors.py +121 -1
  29. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_cli.py +10 -18
  30. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_state_store.py +0 -31
  31. opencode_a2a-0.5.1/tests/server/test_task_store_factory.py +294 -0
  32. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_transport_contract.py +0 -6
  33. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/uv.lock +5 -3
  34. opencode_a2a-0.5.0/scripts/dependency_health.sh +0 -13
  35. opencode_a2a-0.5.0/src/opencode_a2a/server/task_store.py +0 -116
  36. opencode_a2a-0.5.0/tests/server/test_task_store_factory.py +0 -78
  37. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/.gitignore +0 -0
  38. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/.pre-commit-config.yaml +0 -0
  39. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/.secrets.baseline +0 -0
  40. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/AGENTS.md +0 -0
  41. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/CONTRIBUTING.md +0 -0
  42. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/LICENSE +0 -0
  43. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/SECURITY.md +0 -0
  44. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/scripts/check_coverage.py +0 -0
  45. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/scripts/doctor.sh +0 -0
  46. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/scripts/health_common.sh +0 -0
  47. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/scripts/lint.sh +0 -0
  48. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/scripts/smoke_test_built_cli.sh +0 -0
  49. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/setup.cfg +0 -0
  50. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/__init__.py +0 -0
  51. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/__init__.py +0 -0
  52. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/error_mapping.py +0 -0
  53. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/errors.py +0 -0
  54. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/client/payload_text.py +0 -0
  55. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/contracts/__init__.py +0 -0
  56. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/contracts/extensions.py +0 -0
  57. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/__init__.py +0 -0
  58. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/event_helpers.py +0 -0
  59. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/executor.py +0 -0
  60. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/request_context.py +0 -0
  61. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/session_manager.py +0 -0
  62. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_events.py +0 -0
  63. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_runtime.py +0 -0
  64. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/stream_state.py +0 -0
  65. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
  66. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/execution/upstream_error_translator.py +0 -0
  67. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
  68. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
  69. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
  70. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
  71. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/opencode_upstream_client.py +0 -0
  72. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/__init__.py +0 -0
  73. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/mapping.py +0 -0
  74. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/parts/text.py +0 -0
  75. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/profile/__init__.py +0 -0
  76. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/profile/runtime.py +0 -0
  77. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/runtime_state.py +0 -0
  78. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/sandbox_policy.py +0 -0
  79. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/server/__init__.py +0 -0
  80. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/server/agent_card.py +0 -0
  81. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/server/openapi.py +0 -0
  82. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/server/request_parsing.py +0 -0
  83. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a/upstream_taxonomy.py +0 -0
  84. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
  85. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
  86. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
  87. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/__init__.py +0 -0
  88. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/__init__.py +0 -0
  89. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/test_error_mapping.py +0 -0
  90. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/client/test_payload_text.py +0 -0
  91. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/config/__init__.py +0 -0
  92. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/config/test_settings.py +0 -0
  93. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/conftest.py +0 -0
  94. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/contracts/__init__.py +0 -0
  95. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/contracts/test_extension_contract_consistency.py +0 -0
  96. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/__init__.py +0 -0
  97. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_agent_errors.py +0 -0
  98. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_agent_helpers.py +0 -0
  99. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_cancellation.py +0 -0
  100. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_directory_validation.py +0 -0
  101. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_metrics.py +0 -0
  102. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_multipart_input.py +0 -0
  103. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_opencode_agent_session_binding.py +0 -0
  104. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_session_ownership.py +0 -0
  105. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
  106. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_core.py +0 -0
  107. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
  108. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
  109. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/__init__.py +0 -0
  110. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_error_responses.py +0 -0
  111. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
  112. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
  113. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
  114. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
  115. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +0 -0
  116. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
  117. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
  118. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/package/__init__.py +0 -0
  119. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/package/test_version.py +0 -0
  120. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/parts/__init__.py +0 -0
  121. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/parts/test_parts_text.py +0 -0
  122. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/profile/__init__.py +0 -0
  123. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/profile/test_profile_runtime.py +0 -0
  124. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/scripts/__init__.py +0 -0
  125. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/scripts/test_script_health_contract.py +0 -0
  126. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/__init__.py +0 -0
  127. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_a2a_client_manager.py +0 -0
  128. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_agent_card.py +0 -0
  129. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_call_context_builder.py +0 -0
  130. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_cancel_contract.py +0 -0
  131. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/server/test_database_app_persistence.py +0 -0
  132. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/support/__init__.py +0 -0
  133. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/support/helpers.py +0 -0
  134. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/support/session_extensions.py +0 -0
  135. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/support/streaming_output.py +0 -0
  136. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/upstream/__init__.py +0 -0
  137. {opencode_a2a-0.5.0 → opencode_a2a-0.5.1}/tests/upstream/test_opencode_upstream_client_params.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@v4
18
+ uses: actions/checkout@v6
21
19
 
22
20
  - name: Setup Python
23
- uses: actions/setup-python@v5
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@v4
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: Run dependency vulnerability audit
45
- run: uv run pip-audit
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@v4
71
+ uses: actions/checkout@v6
69
72
 
70
73
  - name: Setup Python
71
- uses: actions/setup-python@v5
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@v4
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@v4
18
+ uses: actions/checkout@v6
21
19
 
22
20
  - name: Setup Python
23
- uses: actions/setup-python@v5
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@v4
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@v4
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@v5
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@v4
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: Run dependency vulnerability audit
39
- run: uv run pip-audit
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.5.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
@@ -25,6 +25,7 @@ Requires-Dist: fastapi<1.0,>=0.110
25
25
  Requires-Dist: httpx<1.0,>=0.27
26
26
  Requires-Dist: pydantic<3.0,>=2.6
27
27
  Requires-Dist: pydantic-settings<3.0,>=2.2
28
+ Requires-Dist: requests<3.0,>=2.33.0
28
29
  Requires-Dist: sqlalchemy<3.0,>=2.0
29
30
  Requires-Dist: sse-starlette<4.0,>=2.1
30
31
  Requires-Dist: uvicorn<1.0,>=0.29
@@ -146,19 +147,30 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
146
147
  Interact with other A2A agents directly from the command line:
147
148
 
148
149
  ```bash
149
- opencode-a2a call http://other-agent:8000 "How are you?" --token your-outbound-token
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?"
150
158
  ```
151
159
 
152
160
  ### Outbound Agent Calls (Tools)
153
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.
154
162
 
155
- When the target peer requires bearer auth, configure `A2A_CLIENT_BEARER_TOKEN`
156
- for server-side outbound calls. CLI calls can continue using `--token` or
157
- `A2A_CLIENT_BEARER_TOKEN`.
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.
158
170
 
159
171
  Server-side outbound client settings are fully wired through runtime config:
160
172
  `A2A_CLIENT_TIMEOUT_SECONDS`, `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS`,
161
- `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
162
174
  `A2A_CLIENT_SUPPORTED_TRANSPORTS`.
163
175
 
164
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
- opencode-a2a call http://other-agent:8000 "How are you?" --token your-outbound-token
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 `A2A_CLIENT_BEARER_TOKEN`
116
- for server-side outbound calls. CLI calls can continue using `--token` or
117
- `A2A_CLIENT_BEARER_TOKEN`.
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
@@ -80,8 +80,6 @@ Key variables to understand protocol behavior:
80
80
  `memory`. Default: `database`.
81
81
  - `A2A_TASK_STORE_DATABASE_URL`: database URL used by the default durable
82
82
  backend. Default: `sqlite+aiosqlite:///./opencode-a2a.db`.
83
- - `A2A_TASK_STORE_TABLE_NAME` / `A2A_TASK_STORE_CREATE_TABLE`: database task
84
- store table name and whether to auto-create database tables on startup.
85
83
  - Runtime authentication is bearer-token only via `A2A_BEARER_TOKEN`.
86
84
  - The same outbound client flags are also honored by the server-side embedded
87
85
  A2A client used for peer calls and `a2a_call` tool execution:
@@ -30,6 +30,7 @@ dependencies = [
30
30
  "httpx>=0.27,<1.0",
31
31
  "pydantic>=2.6,<3.0",
32
32
  "pydantic-settings>=2.2,<3.0",
33
+ "requests>=2.33.0,<3.0",
33
34
  "sqlalchemy>=2.0,<3.0",
34
35
  "sse-starlette>=2.1,<4.0",
35
36
  "uvicorn>=0.29,<1.0",
@@ -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, token: str | None = None) -> int:
14
- from a2a.types import Message, TaskArtifactUpdateEvent, TaskStatusUpdateEvent
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, metadata=metadata):
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, namespace.token))
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(self._settings.bearer_token, extra_headers),
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(self._settings.bearer_token, extra_headers),
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(self._settings.bearer_token, extra_headers),
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(self._settings.bearer_token, extra_headers),
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(self._settings.bearer_token),
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(bearer_token: str | None) -> dict[str, str]:
40
- if not bearer_token:
41
- return {}
42
- return {"Authorization": f"Bearer {bearer_token}"}
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(bearer_token: str | None) -> list[ClientCallInterceptor]:
77
- return [HeaderInterceptor(build_default_headers(bearer_token))]
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__ = [
@@ -188,15 +188,6 @@ class Settings(BaseSettings):
188
188
  default="sqlite+aiosqlite:///./opencode-a2a.db",
189
189
  alias="A2A_TASK_STORE_DATABASE_URL",
190
190
  )
191
- a2a_task_store_table_name: str = Field(
192
- default="tasks",
193
- min_length=1,
194
- alias="A2A_TASK_STORE_TABLE_NAME",
195
- )
196
- a2a_task_store_create_table: bool = Field(
197
- default=True,
198
- alias="A2A_TASK_STORE_CREATE_TABLE",
199
- )
200
191
 
201
192
  @model_validator(mode="after")
202
193
  def _validate_sandbox_policy(self) -> Settings:
@@ -74,7 +74,6 @@ __all__ = [
74
74
 
75
75
  ERR_SESSION_NOT_FOUND = SESSION_QUERY_ERROR_BUSINESS_CODES["SESSION_NOT_FOUND"]
76
76
  ERR_SESSION_FORBIDDEN = SESSION_QUERY_ERROR_BUSINESS_CODES["SESSION_FORBIDDEN"]
77
- ERR_METHOD_NOT_SUPPORTED = -32601
78
77
  ERR_UPSTREAM_UNREACHABLE = SESSION_QUERY_ERROR_BUSINESS_CODES["UPSTREAM_UNREACHABLE"]
79
78
  ERR_UPSTREAM_HTTP_ERROR = SESSION_QUERY_ERROR_BUSINESS_CODES["UPSTREAM_HTTP_ERROR"]
80
79
  ERR_INTERRUPT_NOT_FOUND = INTERRUPT_ERROR_BUSINESS_CODES["INTERRUPT_REQUEST_NOT_FOUND"]