opencode-a2a 1.3.0__tar.gz → 1.3.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 (222) hide show
  1. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/CONTRIBUTING.md +8 -0
  2. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/PKG-INFO +1 -1
  3. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/pyproject.toml +5 -0
  4. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/request_context.py +9 -12
  5. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/database.py +33 -8
  6. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a.egg-info/PKG-INFO +1 -1
  7. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a.egg-info/SOURCES.txt +1 -0
  8. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_client_facade.py +11 -9
  9. opencode_a2a-1.3.1/tests/client/test_client_http_headers.py +150 -0
  10. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_request_context.py +10 -12
  11. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_opencode_agent_session_binding.py +3 -3
  12. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_database.py +50 -1
  13. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_state_store.py +9 -5
  14. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_task_store_factory.py +15 -7
  15. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.github/dependabot.yml +0 -0
  16. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.github/workflows/dependency-review.yml +0 -0
  17. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.github/workflows/publish.yml +0 -0
  18. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.github/workflows/validate.yml +0 -0
  19. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.gitignore +0 -0
  20. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.pre-commit-config.yaml +0 -0
  21. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/.secrets.baseline +0 -0
  22. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/AGENTS.md +0 -0
  23. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/CODE_OF_CONDUCT.md +0 -0
  24. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/LICENSE +0 -0
  25. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/MANIFEST.in +0 -0
  26. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/README.md +0 -0
  27. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/SECURITY.md +0 -0
  28. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/SUPPORT.md +0 -0
  29. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/architecture.md +0 -0
  30. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/compatibility.md +0 -0
  31. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/conformance-triage.md +0 -0
  32. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/conformance.md +0 -0
  33. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/extension-specifications.md +0 -0
  34. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/guide.md +0 -0
  35. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/maintainer-architecture.md +0 -0
  36. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/docs/security-architecture.md +0 -0
  37. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/README.md +0 -0
  38. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/check_coverage.py +0 -0
  39. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/conformance.sh +0 -0
  40. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/dependency_health.sh +0 -0
  41. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/doctor.sh +0 -0
  42. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/find_thin_wrappers.py +0 -0
  43. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/health_common.sh +0 -0
  44. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/lint.sh +0 -0
  45. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/live_opencode_smoke.sh +0 -0
  46. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/scripts/smoke_test_built_cli.sh +0 -0
  47. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/setup.cfg +0 -0
  48. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/__init__.py +0 -0
  49. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/a2a_protocol.py +0 -0
  50. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/a2a_utils.py +0 -0
  51. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/auth.py +0 -0
  52. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/cli.py +0 -0
  53. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/__init__.py +0 -0
  54. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/agent_card.py +0 -0
  55. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/auth.py +0 -0
  56. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/client.py +0 -0
  57. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/config.py +0 -0
  58. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/error_mapping.py +0 -0
  59. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/errors.py +0 -0
  60. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/network_policy.py +0 -0
  61. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/payload_text.py +0 -0
  62. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/client/polling.py +0 -0
  63. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/config.py +0 -0
  64. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/__init__.py +0 -0
  65. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/__init__.py +0 -0
  66. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/capabilities.py +0 -0
  67. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/catalog.py +0 -0
  68. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/compatibility.py +0 -0
  69. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/contract_docs.py +0 -0
  70. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/identifiers.py +0 -0
  71. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/private_params.py +0 -0
  72. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/contracts/extensions/public_params.py +0 -0
  73. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/__init__.py +0 -0
  74. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/coordinator.py +0 -0
  75. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/event_helpers.py +0 -0
  76. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/executor.py +0 -0
  77. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/metrics.py +0 -0
  78. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/session_manager.py +0 -0
  79. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/stream_events.py +0 -0
  80. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/stream_runtime.py +0 -0
  81. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/stream_state.py +0 -0
  82. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
  83. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/tool_orchestration.py +0 -0
  84. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/execution/upstream_error_translator.py +0 -0
  85. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/extension_negotiation.py +0 -0
  86. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/interrupt_request_tracker.py +0 -0
  87. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
  88. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/application.py +0 -0
  89. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/dispatch.py +0 -0
  90. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
  91. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/__init__.py +0 -0
  92. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/common.py +0 -0
  93. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_callbacks.py +0 -0
  94. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_queries.py +0 -0
  95. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/provider_discovery.py +0 -0
  96. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/session_control.py +0 -0
  97. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/session_lifecycle.py +0 -0
  98. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/session_queries.py +0 -0
  99. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/handlers/workspace_control.py +0 -0
  100. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
  101. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/models.py +0 -0
  102. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
  103. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/metadata_access.py +0 -0
  104. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/opencode_upstream_client.py +0 -0
  105. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/output_modes.py +0 -0
  106. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/parsing.py +0 -0
  107. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/parts/__init__.py +0 -0
  108. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/parts/mapping.py +0 -0
  109. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/parts/text.py +0 -0
  110. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/profile/__init__.py +0 -0
  111. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/profile/runtime.py +0 -0
  112. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/protocol_versions.py +0 -0
  113. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/py.typed +0 -0
  114. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/redact.py +0 -0
  115. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/runtime_state.py +0 -0
  116. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/sandbox_policy.py +0 -0
  117. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/__init__.py +0 -0
  118. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/agent_card.py +0 -0
  119. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/application.py +0 -0
  120. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/client_manager.py +0 -0
  121. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/context_helpers.py +0 -0
  122. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/lifespan.py +0 -0
  123. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/middleware.py +0 -0
  124. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/migrations.py +0 -0
  125. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/openapi.py +0 -0
  126. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/request_parsing.py +0 -0
  127. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/rest_tasks.py +0 -0
  128. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/runtime_limits.py +0 -0
  129. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/state_store.py +0 -0
  130. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/task_store.py +0 -0
  131. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/server/task_store_sdk_compat.py +0 -0
  132. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/task_states.py +0 -0
  133. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/trace_context.py +0 -0
  134. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a/upstream_taxonomy.py +0 -0
  135. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
  136. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
  137. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a.egg-info/requires.txt +0 -0
  138. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
  139. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/__init__.py +0 -0
  140. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/__init__.py +0 -0
  141. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_agent_card.py +0 -0
  142. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_client_config.py +0 -0
  143. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_error_mapping.py +0 -0
  144. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_network_policy.py +0 -0
  145. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_payload_text.py +0 -0
  146. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/client/test_polling.py +0 -0
  147. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/config/__init__.py +0 -0
  148. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/config/test_settings.py +0 -0
  149. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/conftest.py +0 -0
  150. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/contracts/__init__.py +0 -0
  151. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/contracts/test_extension_contract_consistency.py +0 -0
  152. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/__init__.py +0 -0
  153. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_agent_errors.py +0 -0
  154. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_agent_helpers.py +0 -0
  155. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_cancellation.py +0 -0
  156. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_directory_validation.py +0 -0
  157. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_error_redaction.py +0 -0
  158. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_metrics.py +0 -0
  159. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_multipart_input.py +0 -0
  160. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_session_lock_lifecycle.py +0 -0
  161. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_session_ownership.py +0 -0
  162. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
  163. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_streaming_output_contract_core.py +0 -0
  164. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
  165. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
  166. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/__init__.py +0 -0
  167. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_application_dispatch.py +0 -0
  168. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_dispatch_registry.py +0 -0
  169. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_error_redaction.py +0 -0
  170. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_error_responses.py +0 -0
  171. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
  172. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
  173. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
  174. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
  175. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +0 -0
  176. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_opencode_session_extension_lifecycle.py +0 -0
  177. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
  178. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
  179. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/jsonrpc/test_opencode_workspace_control_extension.py +0 -0
  180. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/package/__init__.py +0 -0
  181. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/package/test_logging.py +0 -0
  182. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/package/test_typing_contract.py +0 -0
  183. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/package/test_version.py +0 -0
  184. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/parts/__init__.py +0 -0
  185. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/parts/test_parts_text.py +0 -0
  186. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/profile/__init__.py +0 -0
  187. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/profile/test_profile_runtime.py +0 -0
  188. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/scripts/__init__.py +0 -0
  189. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/scripts/test_script_health_contract.py +0 -0
  190. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/__init__.py +0 -0
  191. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_a2a_client_manager.py +0 -0
  192. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_agent_card.py +0 -0
  193. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_app_behaviors.py +0 -0
  194. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_auth.py +0 -0
  195. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_call_context_builder.py +0 -0
  196. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_cancel_contract.py +0 -0
  197. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_cli.py +0 -0
  198. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_context_helpers.py +0 -0
  199. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_database_app_persistence.py +0 -0
  200. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_http_boundary.py +0 -0
  201. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_output_negotiation.py +0 -0
  202. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_runtime_limits.py +0 -0
  203. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/server/test_transport_contract.py +0 -0
  204. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/__init__.py +0 -0
  205. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/async_iterators.py +0 -0
  206. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/fake_client_errors.py +0 -0
  207. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/helpers.py +0 -0
  208. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/interrupt_clients.py +0 -0
  209. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/jsonrpc_error_assertions.py +0 -0
  210. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/session_extensions.py +0 -0
  211. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/session_query_client.py +0 -0
  212. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/settings.py +0 -0
  213. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/streaming_output.py +0 -0
  214. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/support/workspace_control_client.py +0 -0
  215. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/test_a2a_utils.py +0 -0
  216. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/test_metadata_access.py +0 -0
  217. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/test_parsing.py +0 -0
  218. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/test_redact.py +0 -0
  219. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/test_trace_context.py +0 -0
  220. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/upstream/__init__.py +0 -0
  221. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/tests/upstream/test_opencode_upstream_client_params.py +0 -0
  222. {opencode_a2a-1.3.0 → opencode_a2a-1.3.1}/uv.lock +0 -0
@@ -71,6 +71,14 @@ uv run pytest tests/contracts/test_extension_contract_consistency.py
71
71
  uv run mypy src/opencode_a2a
72
72
  ```
73
73
 
74
+ If you change outbound authentication, tracing, protocol-version, or extension headers, run the SDK transport regression directly:
75
+
76
+ ```bash
77
+ uv run pytest --no-cov tests/client/test_client_http_headers.py
78
+ ```
79
+
80
+ Assertions against a mocked SDK client or `ClientCallContext` alone do not prove that headers reached the HTTP request. Keep at least one regression test across Agent Card resolution, the real SDK transport, and the final `httpx.Request` boundary.
81
+
74
82
  ## Change Expectations
75
83
 
76
84
  - Keep code, comments, and docs in English.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-a2a
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: OpenCode A2A runtime
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -102,3 +102,8 @@ ignore_missing_imports = true
102
102
 
103
103
  [tool.pytest.ini_options]
104
104
  addopts = "--cov=src/opencode_a2a --cov-report=term-missing --cov-report=json:.coverage.json --cov-fail-under=90"
105
+ filterwarnings = [
106
+ "error::pytest.PytestUnhandledThreadExceptionWarning",
107
+ "error::pytest.PytestUnraisableExceptionWarning",
108
+ "always:.*was deleted before being closed.*:ResourceWarning:aiosqlite.core",
109
+ ]
@@ -79,16 +79,13 @@ def build_call_context(
79
79
  if extra_headers:
80
80
  merged_headers.update(extra_headers)
81
81
  normalized_extensions = [value for value in (extensions or ()) if value]
82
- service_parameters = None
83
- if normalized_extensions:
84
- service_parameters = ServiceParametersFactory.create_from(
85
- None,
86
- [with_a2a_extensions(normalized_extensions)],
87
- )
88
- return ClientCallContext(
89
- state={
90
- "headers": dict(merged_headers),
91
- "http_kwargs": {"headers": dict(merged_headers)},
92
- },
93
- service_parameters=service_parameters,
82
+ service_parameter_updates = (
83
+ [with_a2a_extensions(normalized_extensions)] if normalized_extensions else []
94
84
  )
85
+ # a2a-sdk transports serialize service parameters as HTTP headers or gRPC
86
+ # metadata. This is also the channel used by the SDK's AuthInterceptor.
87
+ service_parameters = ServiceParametersFactory.create_from(
88
+ merged_headers,
89
+ service_parameter_updates,
90
+ )
91
+ return ClientCallContext(service_parameters=service_parameters)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import stat
5
+ from contextlib import suppress
5
6
  from functools import partial
6
7
  from pathlib import Path
7
8
  from typing import Any, cast
@@ -112,15 +113,33 @@ def _configure_sqlite_connection(
112
113
  *,
113
114
  database_path: Path | None = None,
114
115
  ) -> None:
115
- if database_path is not None:
116
- _harden_sqlite_file(database_path)
117
- cursor = dbapi_connection.cursor()
118
116
  try:
119
- cursor.execute(f"PRAGMA journal_mode={_SQLITE_JOURNAL_MODE}")
120
- cursor.execute(f"PRAGMA busy_timeout={_SQLITE_BUSY_TIMEOUT_MS}")
121
- cursor.execute(f"PRAGMA synchronous={_SQLITE_SYNCHRONOUS_MODE}")
122
- finally:
123
- cursor.close()
117
+ if database_path is not None:
118
+ _harden_sqlite_file(database_path)
119
+ cursor = dbapi_connection.cursor()
120
+ try:
121
+ cursor.execute(f"PRAGMA journal_mode={_SQLITE_JOURNAL_MODE}")
122
+ cursor.execute(f"PRAGMA busy_timeout={_SQLITE_BUSY_TIMEOUT_MS}")
123
+ cursor.execute(f"PRAGMA synchronous={_SQLITE_SYNCHRONOUS_MODE}")
124
+ finally:
125
+ cursor.close()
126
+ except BaseException:
127
+ # SQLAlchemy does not close a DBAPI connection when a connect listener
128
+ # fails, so release async driver resources before re-raising.
129
+ with suppress(Exception):
130
+ dbapi_connection.close()
131
+ raise
132
+
133
+
134
+ def _harden_sqlite_before_connect(
135
+ _dialect: Any,
136
+ _connection_record: Any,
137
+ _connection_args: tuple[Any, ...],
138
+ _connection_params: dict[str, Any],
139
+ *,
140
+ database_path: Path,
141
+ ) -> None:
142
+ _harden_sqlite_file(database_path)
124
143
 
125
144
 
126
145
  def redact_database_url_for_logs(database_url: str) -> str:
@@ -144,6 +163,12 @@ def build_database_engine(settings: Settings) -> AsyncEngine:
144
163
  _harden_sqlite_file(sqlite_path)
145
164
 
146
165
  engine = create_async_engine(database_url)
166
+ if sqlite_path is not None:
167
+ event.listen(
168
+ engine.sync_engine,
169
+ "do_connect",
170
+ partial(_harden_sqlite_before_connect, database_path=sqlite_path),
171
+ )
147
172
  event.listen(
148
173
  engine.sync_engine,
149
174
  "connect",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-a2a
3
- Version: 1.3.0
3
+ Version: 1.3.1
4
4
  Summary: OpenCode A2A runtime
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -139,6 +139,7 @@ tests/client/__init__.py
139
139
  tests/client/test_agent_card.py
140
140
  tests/client/test_client_config.py
141
141
  tests/client/test_client_facade.py
142
+ tests/client/test_client_http_headers.py
142
143
  tests/client/test_error_mapping.py
143
144
  tests/client/test_network_policy.py
144
145
  tests/client/test_payload_text.py
@@ -333,7 +333,7 @@ async def test_send_message_adds_bearer_token_from_settings(
333
333
  request, _, kwargs = fake_client.send_message_inputs[0]
334
334
  assert request.metadata == {}
335
335
  assert kwargs["context"] is not None
336
- assert kwargs["context"].state["headers"]["Authorization"] == "Bearer peer-token"
336
+ assert kwargs["context"].service_parameters["Authorization"] == "Bearer peer-token"
337
337
 
338
338
 
339
339
  @pytest.mark.asyncio
@@ -354,7 +354,7 @@ async def test_send_message_adds_basic_auth_from_settings(
354
354
  request, _, kwargs = fake_client.send_message_inputs[0]
355
355
  assert request.metadata == {}
356
356
  assert kwargs["context"] is not None
357
- assert kwargs["context"].state["headers"]["Authorization"] == (
357
+ assert kwargs["context"].service_parameters["Authorization"] == (
358
358
  f"Basic {b64encode(b'user:pass').decode()}"
359
359
  )
360
360
 
@@ -382,7 +382,7 @@ async def test_send_message_preserves_explicit_authorization_metadata(
382
382
  assert result[0].HasField("message")
383
383
  request, _, kwargs = fake_client.send_message_inputs[0]
384
384
  assert request.metadata == {"trace_id": "trace-1"}
385
- assert kwargs["context"].state["headers"]["Authorization"] == "Bearer explicit-token"
385
+ assert kwargs["context"].service_parameters["Authorization"] == "Bearer explicit-token"
386
386
 
387
387
 
388
388
  @pytest.mark.asyncio
@@ -405,7 +405,8 @@ async def test_send_message_negotiates_extensions_via_service_parameters(
405
405
  assert len(result) == 1
406
406
  payload, _, kwargs = fake_client.send_message_inputs[0]
407
407
  assert kwargs["context"].service_parameters == {
408
- "A2A-Extensions": "https://example.com/ext-a,https://example.com/ext-b"
408
+ "A2A-Version": "1.0",
409
+ "A2A-Extensions": "https://example.com/ext-a,https://example.com/ext-b",
409
410
  }
410
411
 
411
412
 
@@ -428,7 +429,7 @@ async def test_send_message_prefers_explicit_authorization_without_default_token
428
429
  assert result[0].HasField("message")
429
430
  request, _, kwargs = fake_client.send_message_inputs[0]
430
431
  assert request.metadata == {}
431
- assert kwargs["context"].state["headers"]["Authorization"] == "Bearer explicit-token"
432
+ assert kwargs["context"].service_parameters["Authorization"] == "Bearer explicit-token"
432
433
 
433
434
 
434
435
  @pytest.mark.asyncio
@@ -532,7 +533,7 @@ async def test_get_task_uses_authorization_header_context(
532
533
 
533
534
  params, kwargs = fake_client.task_inputs[0]
534
535
  assert params.id == "task-id"
535
- assert kwargs["context"].state["headers"]["Authorization"] == "Bearer explicit-token"
536
+ assert kwargs["context"].service_parameters["Authorization"] == "Bearer explicit-token"
536
537
  assert "request_metadata" not in kwargs
537
538
 
538
539
 
@@ -551,7 +552,8 @@ async def test_get_task_negotiates_extensions_via_service_parameters(
551
552
 
552
553
  _params, kwargs = fake_client.task_inputs[0]
553
554
  assert kwargs["context"].service_parameters == {
554
- "A2A-Extensions": "https://example.com/ext-a,https://example.com/ext-b"
555
+ "A2A-Version": "1.0",
556
+ "A2A-Extensions": "https://example.com/ext-a,https://example.com/ext-b",
555
557
  }
556
558
 
557
559
 
@@ -570,7 +572,7 @@ async def test_cancel_task_uses_authorization_header_context(
570
572
 
571
573
  params, kwargs = fake_client.cancel_inputs[0]
572
574
  assert params.metadata == {"trace_id": "trace-1"}
573
- assert kwargs["context"].state["headers"]["Authorization"] == "Bearer explicit-token"
575
+ assert kwargs["context"].service_parameters["Authorization"] == "Bearer explicit-token"
574
576
 
575
577
 
576
578
  @pytest.mark.asyncio
@@ -621,7 +623,7 @@ async def test_subscribe_to_task_uses_authorization_header_context(
621
623
  assert [event.task.status.state for event in result] == [TaskState.TASK_STATE_WORKING]
622
624
  params, kwargs = fake_client.subscribe_inputs[0]
623
625
  assert params.id == "task-id"
624
- assert kwargs["context"].state["headers"]["Authorization"] == "Bearer explicit-token"
626
+ assert kwargs["context"].service_parameters["Authorization"] == "Bearer explicit-token"
625
627
  assert "request_metadata" not in kwargs
626
628
 
627
629
 
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from base64 import b64encode
5
+
6
+ import httpx
7
+ import pytest
8
+ from a2a.types import (
9
+ AgentCapabilities,
10
+ AgentCard,
11
+ AgentInterface,
12
+ Message,
13
+ Part,
14
+ Role,
15
+ SendMessageResponse,
16
+ Task,
17
+ TaskState,
18
+ TaskStatus,
19
+ )
20
+ from google.protobuf.json_format import MessageToDict
21
+
22
+ from opencode_a2a.client import A2AClient
23
+ from opencode_a2a.client.config import A2AClientSettings
24
+
25
+ _PEER_URL = "https://peer.example.com"
26
+ _TRACEPARENT = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
27
+
28
+
29
+ def _agent_card(protocol_binding: str) -> AgentCard:
30
+ return AgentCard(
31
+ name="HTTP stub peer",
32
+ description="Exercises the real SDK HTTP transports.",
33
+ version="1.0",
34
+ supported_interfaces=[
35
+ AgentInterface(
36
+ url=f"{_PEER_URL}/",
37
+ protocol_binding=protocol_binding,
38
+ protocol_version="1.0",
39
+ )
40
+ ],
41
+ capabilities=AgentCapabilities(streaming=False),
42
+ default_input_modes=["text/plain"],
43
+ default_output_modes=["text/plain"],
44
+ skills=[],
45
+ )
46
+
47
+
48
+ def _http_stub(
49
+ requests: dict[str, httpx.Request],
50
+ protocol_binding: str,
51
+ ) -> httpx.MockTransport:
52
+ card = _agent_card(protocol_binding)
53
+
54
+ def handle(request: httpx.Request) -> httpx.Response:
55
+ if request.url.path == "/.well-known/agent-card.json":
56
+ requests["GetAgentCard"] = request
57
+ return httpx.Response(200, json=MessageToDict(card))
58
+
59
+ payload = json.loads(request.content) if request.content else None
60
+ if protocol_binding == "JSONRPC":
61
+ method = payload["method"]
62
+ elif request.url.path == "/message:send":
63
+ method = "SendMessage"
64
+ elif request.url.path == "/tasks/task-1":
65
+ method = "GetTask"
66
+ else: # pragma: no cover - keeps unexpected SDK calls visible
67
+ raise AssertionError(f"Unexpected REST request: {request.method} {request.url}")
68
+ requests[method] = request
69
+ if method == "SendMessage":
70
+ result = MessageToDict(
71
+ SendMessageResponse(
72
+ message=Message(
73
+ message_id="reply-1",
74
+ role=Role.ROLE_AGENT,
75
+ parts=[Part(text="ok")],
76
+ )
77
+ )
78
+ )
79
+ elif method == "GetTask":
80
+ result = MessageToDict(
81
+ Task(
82
+ id="task-1",
83
+ context_id="context-1",
84
+ status=TaskStatus(state=TaskState.TASK_STATE_COMPLETED),
85
+ )
86
+ )
87
+ else: # pragma: no cover - keeps unexpected SDK calls visible
88
+ raise AssertionError(f"Unexpected JSON-RPC method: {method}")
89
+ if protocol_binding == "JSONRPC":
90
+ result = {"jsonrpc": "2.0", "id": payload["id"], "result": result}
91
+ return httpx.Response(200, json=result)
92
+
93
+ return httpx.MockTransport(handle)
94
+
95
+
96
+ @pytest.mark.asyncio
97
+ @pytest.mark.parametrize(
98
+ "protocol_binding",
99
+ ("JSONRPC", "HTTP+JSON"),
100
+ ids=("jsonrpc", "rest"),
101
+ )
102
+ @pytest.mark.parametrize(
103
+ ("bearer_token", "basic_auth", "expected_authorization"),
104
+ [
105
+ ("peer-token", None, "Bearer peer-token"),
106
+ (None, "user:pass", f"Basic {b64encode(b'user:pass').decode()}"),
107
+ ],
108
+ ids=("bearer", "basic"),
109
+ )
110
+ async def test_sdk_http_requests_send_configured_headers(
111
+ protocol_binding: str,
112
+ bearer_token: str | None,
113
+ basic_auth: str | None,
114
+ expected_authorization: str,
115
+ ) -> None:
116
+ requests: dict[str, httpx.Request] = {}
117
+ settings = A2AClientSettings(
118
+ bearer_token=bearer_token,
119
+ basic_auth=basic_auth,
120
+ supported_transports=(protocol_binding,),
121
+ )
122
+ async with httpx.AsyncClient(transport=_http_stub(requests, protocol_binding)) as http_client:
123
+ client = A2AClient(_PEER_URL, settings=settings, httpx_client=http_client)
124
+
125
+ await client.send("hello", metadata={"traceparent": _TRACEPARENT})
126
+ await client.get_task("task-1", extensions=["https://example.com/ext"])
127
+
128
+ for method in ("GetAgentCard", "SendMessage", "GetTask"):
129
+ assert requests[method].headers["Authorization"] == expected_authorization
130
+ assert requests[method].headers["A2A-Version"] == "1.0"
131
+ assert requests["SendMessage"].headers["traceparent"] == _TRACEPARENT
132
+ assert requests["GetTask"].headers["A2A-Extensions"] == "https://example.com/ext"
133
+
134
+
135
+ @pytest.mark.asyncio
136
+ async def test_sdk_jsonrpc_request_preserves_explicit_authorization_override() -> None:
137
+ requests: dict[str, httpx.Request] = {}
138
+ settings = A2AClientSettings(
139
+ bearer_token="default-token",
140
+ supported_transports=("JSONRPC",),
141
+ )
142
+ async with httpx.AsyncClient(transport=_http_stub(requests, "JSONRPC")) as http_client:
143
+ client = A2AClient(_PEER_URL, settings=settings, httpx_client=http_client)
144
+
145
+ await client.send(
146
+ "hello",
147
+ metadata={"authorization": "Bearer explicit-token"},
148
+ )
149
+
150
+ assert requests["SendMessage"].headers["Authorization"] == "Bearer explicit-token"
@@ -81,8 +81,8 @@ def test_build_call_context_includes_fixed_protocol_version() -> None:
81
81
  context = build_call_context(None, None)
82
82
 
83
83
  assert context is not None
84
- assert context.state["headers"] == {"A2A-Version": "1.0"}
85
- assert context.state["http_kwargs"]["headers"] == {"A2A-Version": "1.0"}
84
+ assert context.service_parameters == {"A2A-Version": "1.0"}
85
+ assert context.state == {}
86
86
 
87
87
 
88
88
  def test_build_call_context_includes_current_trace_headers() -> None:
@@ -96,7 +96,7 @@ def test_build_call_context_includes_current_trace_headers() -> None:
96
96
  context = build_call_context(None, None)
97
97
 
98
98
  assert context is not None
99
- assert context.state["headers"] == {
99
+ assert context.service_parameters == {
100
100
  "A2A-Version": "1.0",
101
101
  "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
102
102
  "tracestate": "vendor=value",
@@ -120,7 +120,7 @@ def test_build_call_context_preserves_explicit_trace_headers_over_current_contex
120
120
  )
121
121
 
122
122
  assert context is not None
123
- assert context.state["headers"] == {
123
+ assert context.service_parameters == {
124
124
  "Authorization": "Bearer peer-token",
125
125
  "A2A-Version": "1.0",
126
126
  "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
@@ -128,16 +128,11 @@ def test_build_call_context_preserves_explicit_trace_headers_over_current_contex
128
128
  }
129
129
 
130
130
 
131
- def test_build_call_context_carries_default_headers_without_interceptor_layer() -> None:
131
+ def test_build_call_context_carries_headers_via_service_parameters() -> None:
132
132
  context = build_call_context("peer-token", {"X-Trace-Id": "trace-1"})
133
133
 
134
134
  assert isinstance(context, ClientCallContext)
135
- assert context.state["headers"] == {
136
- "Authorization": "Bearer peer-token",
137
- "A2A-Version": "1.0",
138
- "X-Trace-Id": "trace-1",
139
- }
140
- assert context.state["http_kwargs"]["headers"] == {
135
+ assert context.service_parameters == {
141
136
  "Authorization": "Bearer peer-token",
142
137
  "A2A-Version": "1.0",
143
138
  "X-Trace-Id": "trace-1",
@@ -153,7 +148,10 @@ def test_build_call_context_merges_extension_service_parameters() -> None:
153
148
 
154
149
  assert isinstance(context, ClientCallContext)
155
150
  assert context.service_parameters == {
156
- "A2A-Extensions": "https://example.com/ext-a,https://example.com/ext-b"
151
+ "Authorization": "Bearer peer-token",
152
+ "A2A-Version": "1.0",
153
+ "X-Trace-Id": "trace-1",
154
+ "A2A-Extensions": "https://example.com/ext-a,https://example.com/ext-b",
157
155
  }
158
156
 
159
157
 
@@ -588,7 +588,7 @@ async def test_agent_a2a_call_uses_server_side_basic_auth_headers(
588
588
  assert results[0]["output"] == "remote response"
589
589
  _, _, kwargs = fake_sdk_client.send_message_inputs[0]
590
590
  assert kwargs["context"] is not None
591
- assert kwargs["context"].state["headers"]["Authorization"] == (
591
+ assert kwargs["context"].service_parameters["Authorization"] == (
592
592
  f"Basic {b64encode(b'user:pass').decode()}"
593
593
  )
594
594
 
@@ -658,10 +658,10 @@ async def test_agent_a2a_call_propagates_current_trace_headers(
658
658
  assert results[0]["output"] == "remote response"
659
659
  _, _, kwargs = fake_sdk_client.send_message_inputs[0]
660
660
  assert kwargs["context"] is not None
661
- assert kwargs["context"].state["headers"]["traceparent"] == (
661
+ assert kwargs["context"].service_parameters["traceparent"] == (
662
662
  "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
663
663
  )
664
- assert kwargs["context"].state["headers"]["tracestate"] == "vendor=value"
664
+ assert kwargs["context"].service_parameters["tracestate"] == "vendor=value"
665
665
 
666
666
  await manager.close_all()
667
667
 
@@ -3,7 +3,10 @@ from __future__ import annotations
3
3
  import os
4
4
  import stat
5
5
  from pathlib import Path
6
+ from typing import Any
7
+ from unittest.mock import MagicMock
6
8
 
9
+ import aiosqlite
7
10
  import pytest
8
11
 
9
12
  from opencode_a2a.server import database as database_module
@@ -198,7 +201,19 @@ def test_apply_private_sqlite_modes_rejects_foreign_owned_sidecar(tmp_path, monk
198
201
 
199
202
  @pytest.mark.skipif(os.name != "posix", reason="POSIX file permission semantics")
200
203
  @pytest.mark.asyncio
201
- async def test_build_database_engine_rechecks_hardening_on_new_connection(tmp_path) -> None:
204
+ async def test_build_database_engine_rechecks_hardening_on_new_connection(
205
+ tmp_path,
206
+ monkeypatch,
207
+ ) -> None:
208
+ created_connections: list[aiosqlite.Connection] = []
209
+ original_connect = aiosqlite.connect
210
+
211
+ def tracking_connect(*args: Any, **kwargs: Any) -> aiosqlite.Connection:
212
+ connection = original_connect(*args, **kwargs)
213
+ created_connections.append(connection)
214
+ return connection
215
+
216
+ monkeypatch.setattr(aiosqlite, "connect", tracking_connect)
202
217
  settings = _settings_for(tmp_path)
203
218
  engine = build_database_engine(settings)
204
219
  try:
@@ -217,3 +232,37 @@ async def test_build_database_engine_rechecks_hardening_on_new_connection(tmp_pa
217
232
  await conn.exec_driver_sql("SELECT 1")
218
233
  finally:
219
234
  await engine.dispose()
235
+
236
+ assert len(created_connections) == 1
237
+
238
+
239
+ def test_configure_sqlite_connection_closes_connection_on_failure() -> None:
240
+ dbapi_connection = MagicMock()
241
+ cursor = dbapi_connection.cursor.return_value
242
+ cursor.execute.side_effect = RuntimeError("PRAGMA failed")
243
+ dbapi_connection.close.side_effect = OSError("close failed")
244
+
245
+ with pytest.raises(RuntimeError, match="PRAGMA failed"):
246
+ database_module._configure_sqlite_connection(dbapi_connection, MagicMock())
247
+
248
+ cursor.close.assert_called_once_with()
249
+ dbapi_connection.close.assert_called_once_with()
250
+
251
+
252
+ @pytest.mark.skipif(os.name != "posix", reason="POSIX file permission semantics")
253
+ def test_configure_sqlite_connection_closes_connection_on_hardening_failure(tmp_path) -> None:
254
+ victim = tmp_path / "victim.db"
255
+ victim.write_bytes(b"")
256
+ database_path = tmp_path / "runtime.db"
257
+ database_path.symlink_to(victim)
258
+ dbapi_connection = MagicMock()
259
+
260
+ with pytest.raises(RuntimeError, match="symlink"):
261
+ database_module._configure_sqlite_connection(
262
+ dbapi_connection,
263
+ MagicMock(),
264
+ database_path=database_path,
265
+ )
266
+
267
+ dbapi_connection.cursor.assert_not_called()
268
+ dbapi_connection.close.assert_called_once_with()
@@ -667,12 +667,16 @@ async def test_build_runtime_state_runtime_initializes_and_preserves_shared_engi
667
667
  a2a_task_store_database_url=f"sqlite+aiosqlite:///{tmp_path / 'runtime-state.db'}",
668
668
  )
669
669
  engine = build_database_engine(settings)
670
+ original_dispose = engine.dispose
670
671
  dispose_spy = AsyncMock()
671
672
  monkeypatch.setattr(type(engine), "dispose", dispose_spy)
672
673
 
673
- runtime = build_runtime_state_runtime(settings, engine=engine)
674
- await runtime.startup()
675
- await runtime.shutdown()
674
+ try:
675
+ runtime = build_runtime_state_runtime(settings, engine=engine)
676
+ await runtime.startup()
677
+ await runtime.shutdown()
676
678
 
677
- assert await _read_state_store_schema_version(engine) == CURRENT_STATE_STORE_SCHEMA_VERSION
678
- dispose_spy.assert_not_awaited()
679
+ assert await _read_state_store_schema_version(engine) == CURRENT_STATE_STORE_SCHEMA_VERSION
680
+ dispose_spy.assert_not_awaited()
681
+ finally:
682
+ await original_dispose()
@@ -523,13 +523,17 @@ async def test_build_task_store_does_not_dispose_shared_engine(
523
523
  a2a_task_store_database_url=f"sqlite+aiosqlite:///{tmp_path / 'shared-engine.db'}",
524
524
  )
525
525
  engine = build_database_engine(settings)
526
+ original_dispose = engine.dispose
526
527
  dispose_spy = AsyncMock()
527
528
  monkeypatch.setattr(type(engine), "dispose", dispose_spy)
528
529
 
529
- store = build_task_store(settings, engine=engine)
530
- await initialize_task_store(store)
530
+ try:
531
+ store = build_task_store(settings, engine=engine)
532
+ await initialize_task_store(store)
531
533
 
532
- dispose_spy.assert_not_awaited()
534
+ dispose_spy.assert_not_awaited()
535
+ finally:
536
+ await original_dispose()
533
537
 
534
538
 
535
539
  @pytest.mark.asyncio
@@ -542,14 +546,18 @@ async def test_build_task_store_runtime_does_not_dispose_shared_engine(
542
546
  a2a_task_store_database_url=f"sqlite+aiosqlite:///{tmp_path / 'shared-runtime-engine.db'}",
543
547
  )
544
548
  engine = build_database_engine(settings)
549
+ original_dispose = engine.dispose
545
550
  dispose_spy = AsyncMock()
546
551
  monkeypatch.setattr(type(engine), "dispose", dispose_spy)
547
552
 
548
- runtime = build_task_store_runtime(settings, engine=engine)
549
- await runtime.startup()
550
- await runtime.shutdown()
553
+ try:
554
+ runtime = build_task_store_runtime(settings, engine=engine)
555
+ await runtime.startup()
556
+ await runtime.shutdown()
551
557
 
552
- dispose_spy.assert_not_awaited()
558
+ dispose_spy.assert_not_awaited()
559
+ finally:
560
+ await original_dispose()
553
561
 
554
562
 
555
563
  @pytest.mark.asyncio
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes