opencode-a2a 1.1.2__tar.gz → 1.2.0__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 (212) hide show
  1. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/PKG-INFO +7 -5
  2. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/README.md +4 -2
  3. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/compatibility.md +5 -2
  4. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/guide.md +18 -13
  5. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/maintainer-architecture.md +4 -5
  6. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/pyproject.toml +9 -2
  7. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/README.md +1 -0
  8. opencode_a2a-1.2.0/scripts/live_opencode_smoke.sh +286 -0
  9. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/auth.py +7 -2
  10. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/config.py +15 -3
  11. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/catalog.py +9 -9
  12. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/session_manager.py +74 -42
  13. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/opencode_upstream_client.py +9 -1
  14. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/agent_card.py +1 -1
  15. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/application.py +17 -17
  16. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/database.py +9 -14
  17. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/middleware.py +15 -4
  18. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/openapi.py +2 -2
  19. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/task_store.py +1 -15
  20. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/task_store_sdk_compat.py +1 -21
  21. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a.egg-info/PKG-INFO +7 -5
  22. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a.egg-info/SOURCES.txt +1 -0
  23. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a.egg-info/requires.txt +1 -1
  24. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/config/test_settings.py +48 -0
  25. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/conftest.py +6 -16
  26. opencode_a2a-1.2.0/tests/execution/test_session_lock_lifecycle.py +68 -0
  27. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_agent_card.py +2 -3
  28. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_app_behaviors.py +9 -9
  29. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_call_context_builder.py +2 -2
  30. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_state_store.py +0 -26
  31. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_task_store_factory.py +2 -26
  32. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_transport_contract.py +33 -33
  33. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/upstream/test_opencode_upstream_client_params.py +92 -0
  34. opencode_a2a-1.2.0/uv.lock +1941 -0
  35. opencode_a2a-1.1.2/tests/execution/test_session_lock_lifecycle.py +0 -33
  36. opencode_a2a-1.1.2/uv.lock +0 -1644
  37. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.github/dependabot.yml +0 -0
  38. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.github/workflows/dependency-review.yml +0 -0
  39. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.github/workflows/publish.yml +0 -0
  40. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.github/workflows/validate.yml +0 -0
  41. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.gitignore +0 -0
  42. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.pre-commit-config.yaml +0 -0
  43. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/.secrets.baseline +0 -0
  44. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/AGENTS.md +0 -0
  45. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/CODE_OF_CONDUCT.md +0 -0
  46. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/CONTRIBUTING.md +0 -0
  47. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/LICENSE +0 -0
  48. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/MANIFEST.in +0 -0
  49. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/SECURITY.md +0 -0
  50. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/SUPPORT.md +0 -0
  51. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/architecture.md +0 -0
  52. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/conformance-triage.md +0 -0
  53. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/conformance.md +0 -0
  54. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/docs/extension-specifications.md +0 -0
  55. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/check_coverage.py +0 -0
  56. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/conformance.sh +0 -0
  57. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/dependency_health.sh +0 -0
  58. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/doctor.sh +0 -0
  59. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/find_thin_wrappers.py +0 -0
  60. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/health_common.sh +0 -0
  61. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/lint.sh +0 -0
  62. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/scripts/smoke_test_built_cli.sh +0 -0
  63. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/setup.cfg +0 -0
  64. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/__init__.py +0 -0
  65. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/a2a_protocol.py +0 -0
  66. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/a2a_utils.py +0 -0
  67. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/cli.py +0 -0
  68. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/__init__.py +0 -0
  69. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/agent_card.py +0 -0
  70. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/auth.py +0 -0
  71. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/client.py +0 -0
  72. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/config.py +0 -0
  73. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/error_mapping.py +0 -0
  74. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/errors.py +0 -0
  75. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/payload_text.py +0 -0
  76. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/polling.py +0 -0
  77. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/client/request_context.py +0 -0
  78. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/__init__.py +0 -0
  79. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/__init__.py +0 -0
  80. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/capabilities.py +0 -0
  81. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/compatibility.py +0 -0
  82. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/contract_docs.py +0 -0
  83. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/identifiers.py +0 -0
  84. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/private_params.py +0 -0
  85. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/contracts/extensions/public_params.py +0 -0
  86. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/__init__.py +0 -0
  87. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/coordinator.py +0 -0
  88. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/event_helpers.py +0 -0
  89. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/executor.py +0 -0
  90. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/metrics.py +0 -0
  91. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/stream_events.py +0 -0
  92. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/stream_runtime.py +0 -0
  93. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/stream_state.py +0 -0
  94. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
  95. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/tool_orchestration.py +0 -0
  96. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/execution/upstream_error_translator.py +0 -0
  97. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/extension_negotiation.py +0 -0
  98. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/interrupt_request_tracker.py +0 -0
  99. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
  100. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/application.py +0 -0
  101. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/dispatch.py +0 -0
  102. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
  103. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/__init__.py +0 -0
  104. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/common.py +0 -0
  105. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/interrupt_callbacks.py +0 -0
  106. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/interrupt_queries.py +0 -0
  107. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/provider_discovery.py +0 -0
  108. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/session_control.py +0 -0
  109. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/session_lifecycle.py +0 -0
  110. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/session_queries.py +0 -0
  111. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/handlers/workspace_control.py +0 -0
  112. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/methods.py +0 -0
  113. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/models.py +0 -0
  114. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/jsonrpc/params.py +0 -0
  115. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/metadata_access.py +0 -0
  116. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/output_modes.py +0 -0
  117. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/parsing.py +0 -0
  118. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/parts/__init__.py +0 -0
  119. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/parts/mapping.py +0 -0
  120. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/parts/text.py +0 -0
  121. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/profile/__init__.py +0 -0
  122. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/profile/runtime.py +0 -0
  123. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/protocol_versions.py +0 -0
  124. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/py.typed +0 -0
  125. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/runtime_state.py +0 -0
  126. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/sandbox_policy.py +0 -0
  127. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/__init__.py +0 -0
  128. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/client_manager.py +0 -0
  129. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/context_helpers.py +0 -0
  130. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/lifespan.py +0 -0
  131. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/migrations.py +0 -0
  132. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/request_parsing.py +0 -0
  133. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/rest_tasks.py +0 -0
  134. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/server/state_store.py +0 -0
  135. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/task_states.py +0 -0
  136. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/trace_context.py +0 -0
  137. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a/upstream_taxonomy.py +0 -0
  138. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
  139. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
  140. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/src/opencode_a2a.egg-info/top_level.txt +0 -0
  141. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/__init__.py +0 -0
  142. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/__init__.py +0 -0
  143. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_agent_card.py +0 -0
  144. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_client_config.py +0 -0
  145. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_client_facade.py +0 -0
  146. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_error_mapping.py +0 -0
  147. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_payload_text.py +0 -0
  148. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_polling.py +0 -0
  149. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/client/test_request_context.py +0 -0
  150. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/config/__init__.py +0 -0
  151. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/contracts/__init__.py +0 -0
  152. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/contracts/test_extension_contract_consistency.py +0 -0
  153. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/__init__.py +0 -0
  154. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_agent_errors.py +0 -0
  155. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_agent_helpers.py +0 -0
  156. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_cancellation.py +0 -0
  157. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_directory_validation.py +0 -0
  158. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_metrics.py +0 -0
  159. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_multipart_input.py +0 -0
  160. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_opencode_agent_session_binding.py +0 -0
  161. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_session_ownership.py +0 -0
  162. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
  163. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_streaming_output_contract_core.py +0 -0
  164. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
  165. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/execution/test_streaming_output_contract_logging.py +0 -0
  166. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/__init__.py +0 -0
  167. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_application_dispatch.py +0 -0
  168. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_dispatch_registry.py +0 -0
  169. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_error_responses.py +0 -0
  170. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
  171. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
  172. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
  173. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
  174. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +0 -0
  175. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_opencode_session_extension_lifecycle.py +0 -0
  176. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
  177. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
  178. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/jsonrpc/test_opencode_workspace_control_extension.py +0 -0
  179. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/package/__init__.py +0 -0
  180. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/package/test_logging.py +0 -0
  181. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/package/test_typing_contract.py +0 -0
  182. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/package/test_version.py +0 -0
  183. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/parts/__init__.py +0 -0
  184. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/parts/test_parts_text.py +0 -0
  185. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/profile/__init__.py +0 -0
  186. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/profile/test_profile_runtime.py +0 -0
  187. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/scripts/__init__.py +0 -0
  188. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/scripts/test_script_health_contract.py +0 -0
  189. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/__init__.py +0 -0
  190. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_a2a_client_manager.py +0 -0
  191. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_auth.py +0 -0
  192. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_cancel_contract.py +0 -0
  193. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_cli.py +0 -0
  194. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_context_helpers.py +0 -0
  195. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_database_app_persistence.py +0 -0
  196. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/server/test_output_negotiation.py +0 -0
  197. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/__init__.py +0 -0
  198. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/async_iterators.py +0 -0
  199. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/fake_client_errors.py +0 -0
  200. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/helpers.py +0 -0
  201. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/interrupt_clients.py +0 -0
  202. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/jsonrpc_error_assertions.py +0 -0
  203. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/session_extensions.py +0 -0
  204. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/session_query_client.py +0 -0
  205. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/settings.py +0 -0
  206. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/streaming_output.py +0 -0
  207. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/support/workspace_control_client.py +0 -0
  208. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/test_a2a_utils.py +0 -0
  209. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/test_metadata_access.py +0 -0
  210. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/test_parsing.py +0 -0
  211. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/test_trace_context.py +0 -0
  212. {opencode_a2a-1.1.2 → opencode_a2a-1.2.0}/tests/upstream/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-a2a
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: OpenCode A2A runtime
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/Intelligent-Internet/opencode-a2a
8
8
  Project-URL: Repository, https://github.com/Intelligent-Internet/opencode-a2a
9
9
  Project-URL: Issues, https://github.com/Intelligent-Internet/opencode-a2a/issues
10
10
  Keywords: a2a,opencode,fastapi,json-rpc,sse
11
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.11
@@ -19,7 +19,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
19
19
  Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: a2a-sdk==1.0.2
22
+ Requires-Dist: a2a-sdk==1.1.2
23
23
  Requires-Dist: aiosqlite<1.0,>=0.20
24
24
  Requires-Dist: fastapi<1.0,>=0.139
25
25
  Requires-Dist: httpx<1.0,>=0.27
@@ -133,9 +133,9 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
133
133
 
134
134
  ## Capabilities
135
135
 
136
- - A2A HTTP+JSON endpoints such as `/v1/message:send` and `/v1/message:stream`
136
+ - A2A HTTP+JSON endpoints such as `/message:send` and `/message:stream`
137
137
  - A2A JSON-RPC support on `POST /`
138
- - SDK-owned A2A task surfaces such as `GET /v1/tasks`, task push notification config routes, JSON-RPC `GetExtendedAgentCard`, and HTTP `GET /extendedAgentCard`
138
+ - SDK-owned A2A task surfaces such as `GET /tasks`, task push notification config routes, JSON-RPC `GetExtendedAgentCard`, and HTTP `GET /extendedAgentCard`
139
139
  - Peering capabilities: can act as a client via `opencode-a2a call`
140
140
  - Autonomous tool execution: supports `a2a_call` tool for outbound agent-to-agent communication
141
141
  - SSE streaming with normalized `text`, `reasoning`, and `tool_call` blocks
@@ -201,6 +201,8 @@ This repository improves the service boundary around OpenCode, but it does not t
201
201
  - Provider auth and default model configuration remain on the OpenCode side; deployment-time precedence details and HOME/XDG state impact are documented in [docs/guide.md](docs/guide.md#troubleshooting-provider-auth-state).
202
202
  - Use `A2A_CLIENT_BEARER_TOKEN` for server-side outbound peer calls initiated by `a2a_call`.
203
203
  - Deployment supervision is intentionally BYO. Use `systemd`, Docker, Kubernetes, or another supervisor if you need long-running operation.
204
+ - The supported persistence profile runs one `opencode-a2a` application process against its own local SQLite database. Do not run multiple Uvicorn workers or replicas against the same database file.
205
+ - PostgreSQL and other SQLAlchemy dialects are not part of the supported deployment matrix.
204
206
  - For mutually untrusted tenants, run separate instance pairs with isolated users, containers, workspaces, credentials, and ports.
205
207
 
206
208
  Read before deployment:
@@ -91,9 +91,9 @@ curl http://127.0.0.1:8000/.well-known/agent-card.json
91
91
 
92
92
  ## Capabilities
93
93
 
94
- - A2A HTTP+JSON endpoints such as `/v1/message:send` and `/v1/message:stream`
94
+ - A2A HTTP+JSON endpoints such as `/message:send` and `/message:stream`
95
95
  - A2A JSON-RPC support on `POST /`
96
- - SDK-owned A2A task surfaces such as `GET /v1/tasks`, task push notification config routes, JSON-RPC `GetExtendedAgentCard`, and HTTP `GET /extendedAgentCard`
96
+ - SDK-owned A2A task surfaces such as `GET /tasks`, task push notification config routes, JSON-RPC `GetExtendedAgentCard`, and HTTP `GET /extendedAgentCard`
97
97
  - Peering capabilities: can act as a client via `opencode-a2a call`
98
98
  - Autonomous tool execution: supports `a2a_call` tool for outbound agent-to-agent communication
99
99
  - SSE streaming with normalized `text`, `reasoning`, and `tool_call` blocks
@@ -159,6 +159,8 @@ This repository improves the service boundary around OpenCode, but it does not t
159
159
  - Provider auth and default model configuration remain on the OpenCode side; deployment-time precedence details and HOME/XDG state impact are documented in [docs/guide.md](docs/guide.md#troubleshooting-provider-auth-state).
160
160
  - Use `A2A_CLIENT_BEARER_TOKEN` for server-side outbound peer calls initiated by `a2a_call`.
161
161
  - Deployment supervision is intentionally BYO. Use `systemd`, Docker, Kubernetes, or another supervisor if you need long-running operation.
162
+ - The supported persistence profile runs one `opencode-a2a` application process against its own local SQLite database. Do not run multiple Uvicorn workers or replicas against the same database file.
163
+ - PostgreSQL and other SQLAlchemy dialects are not part of the supported deployment matrix.
162
164
  - For mutually untrusted tenants, run separate instance pairs with isolated users, containers, workspaces, credentials, and ports.
163
165
 
164
166
  Read before deployment:
@@ -7,6 +7,9 @@ This document defines the compatibility promises `opencode-a2a` currently uphold
7
7
  - Python versions: 3.11, 3.12, 3.13
8
8
  - A2A SDK line: `1.x.y`
9
9
  - Supported A2A protocol line: `1.0`
10
+ - OpenCode runtime line: `1.18.x` (verified with `1.18.19`)
11
+
12
+ This is the single canonical place where the supported upstream OpenCode version line is declared. Keep it updated together with the live integration smoke check (`./scripts/live_opencode_smoke.sh`) and do not duplicate the version line elsewhere.
10
13
 
11
14
  The repository currently pins one concrete SDK release in `pyproject.toml` within that v1 line. Upgrade the SDK deliberately rather than relying on floating dependency resolution. The SDK-owned core JSON-RPC method set follows that pinned release and is locked by repository tests so SDK upgrades trigger an explicit compatibility review.
12
15
 
@@ -41,7 +44,7 @@ External TCK runs and local conformance experiments are investigation inputs. Th
41
44
 
42
45
  ## Compatibility-Sensitive Surface
43
46
 
44
- This repository still ships as an alpha project. Within that alpha line, these declared surfaces should not drift silently:
47
+ This repository ships as a beta project. Its documented public contracts are intended to remain stable, while implementation details and explicitly provider-private surfaces may continue to evolve. These declared surfaces should not drift silently:
45
48
 
46
49
  - core A2A send / stream / task methods
47
50
  - v1-only request/response payloads and enum values
@@ -88,7 +91,7 @@ Task-store behavior that should remain stable for clients:
88
91
  - accepted output-mode negotiation for a task is persisted with the task so later reads keep the same filtered output contract
89
92
  - adapter-managed migrations only own adapter state tables; SDK-managed task schema remains SDK-owned
90
93
 
91
- The default SQLite-first profile is intended for local or controlled single-instance deployments. Wider SQLAlchemy dialect compatibility should be treated as implementation latitude rather than a strong public promise unless explicitly documented later.
94
+ The supported persistence profile is one application process using its own local SQLite database. Multiple Uvicorn workers or application replicas must not share that SQLite file. PostgreSQL and other SQLAlchemy dialects are not supported deployment targets; any apparent dialect compatibility is implementation latitude rather than a public promise.
92
95
 
93
96
  ## Extension Stability
94
97
 
@@ -5,9 +5,11 @@ This guide covers configuration, authentication, API behavior, streaming re-subs
5
5
  ## Transport Contracts
6
6
 
7
7
  - The service supports both transports:
8
- - HTTP+JSON (REST endpoints such as `/v1/message:send`)
8
+ - HTTP+JSON (REST endpoints such as `/message:send`)
9
9
  - JSON-RPC (`POST /`)
10
10
  - Agent Card exposes both HTTP+JSON and JSON-RPC through `supportedInterfaces`.
11
+ - HTTP+JSON REST endpoints are served at the root path (e.g. `/message:send`, `/tasks`). A2A 1.0 negotiates protocol version via the `A2A-Version` header, not a URL prefix, so the URL path carries no version segment.
12
+ - Breaking change for earlier releases: versions through v1.1.2 served the HTTP+JSON surface under a `/v1` prefix. Deployments upgrading from those releases must repoint clients, reverse proxies, and any `/v1`-prefixed routes to the root paths.
11
13
  - The public Agent Card is intentionally slimmed to the minimum discovery surface; per-extension disclosure policy is defined in [`extension-specifications.md`](./extension-specifications.md).
12
14
  - Detailed provider-private contracts are served through the authenticated extended card endpoint `/extendedAgentCard`.
13
15
  - Agent Card responses emit weak `ETag` and `Cache-Control`; clients should revalidate cached cards instead of repeatedly fetching full payloads.
@@ -47,9 +49,10 @@ Key variables to understand protocol behavior:
47
49
  - `A2A_INTERRUPT_REQUEST_TTL_SECONDS`: active retention window for the interrupt request binding registry used by `a2a.interrupt.*` callback methods. Default: `10800` seconds (`180` minutes).
48
50
  - `A2A_INTERRUPT_REQUEST_TOMBSTONE_TTL_SECONDS`: retention window for expired interrupt tombstones after active TTL has elapsed. During this window, repeated replies keep returning `INTERRUPT_REQUEST_EXPIRED` instead of falling through to `INTERRUPT_REQUEST_NOT_FOUND`. Default: `600` seconds (`10` minutes).
49
51
  - `A2A_CANCEL_ABORT_TIMEOUT_SECONDS`: best-effort timeout for upstream `session.abort` in cancel flow.
50
- - `OPENCODE_TIMEOUT` / `OPENCODE_TIMEOUT_STREAM`: upstream request timeout and optional stream timeout override.
51
- - `OPENCODE_MAX_CONCURRENT_REQUESTS`: optional fast-fail concurrency limit for unary/control upstream calls. `0` disables the limit.
52
- - `OPENCODE_MAX_CONCURRENT_STREAMS`: optional fast-fail concurrency limit for long-lived upstream `/event` streams. `0` disables the limit.
52
+ - `OPENCODE_TIMEOUT` / `OPENCODE_TIMEOUT_STREAM`: upstream request timeout and stream timeout. Defaults: `120` and `900` seconds.
53
+ - `OPENCODE_MAX_CONCURRENT_REQUESTS`: fast-fail concurrency limit for unary/control upstream calls. Default: `32`; `0` disables the limit explicitly.
54
+ - `OPENCODE_MAX_CONCURRENT_STREAMS`: fast-fail concurrency limit for long-lived upstream `/event` streams. Default: `8`; `0` disables the limit explicitly.
55
+ - `OPENCODE_AUTH_USERNAME` / `OPENCODE_AUTH_PASSWORD`: optional HTTP Basic credentials sent on every upstream OpenCode call. Set both when the upstream `opencode serve` is hardened with `OPENCODE_SERVER_PASSWORD`; the username defaults to `opencode` and must match `OPENCODE_SERVER_USERNAME` when that is overridden upstream. When unset, no `Authorization` header is sent.
53
56
  - `A2A_CLIENT_TIMEOUT_SECONDS`: outbound client timeout. Default: `30` seconds.
54
57
  - `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS`: outbound Agent Card fetch timeout. Default: `5` seconds.
55
58
  - `A2A_CLIENT_USE_CLIENT_PREFERENCE`: whether the outbound client prefers its own transport choices.
@@ -174,11 +177,13 @@ With the default `database` backend, the unified lightweight persistence layer p
174
177
  - pending preferred-session claims
175
178
  - interrupt request bindings and tombstones
176
179
 
177
- This project is SQLite-first for local single-instance deployments. The runtime configures local durability-oriented SQLite connection settings (`WAL`, `busy_timeout`, `synchronous=NORMAL`) and creates missing parent directories for file-backed database paths.
180
+ The supported persistence profile is one `opencode-a2a` application process using its own local SQLite database. Run Uvicorn with one worker and do not share the SQLite file between processes, containers, or replicas. Horizontal application scaling and PostgreSQL deployments are outside the supported deployment matrix. SQLAlchemy remains an internal implementation detail rather than a promise of dialect portability.
181
+
182
+ The runtime configures local durability-oriented SQLite connection settings (`WAL`, `busy_timeout`, `synchronous=NORMAL`) and creates missing parent directories for file-backed database paths.
178
183
 
179
184
  The runtime automatically applies lightweight schema migrations for its custom state tables and records the applied version in `a2a_schema_version`. Schema-version writes are idempotent across concurrent first-start races, pending preferred-session claims now persist absolute `expires_at` timestamps, legacy rows without `expires_at` are pruned during migration instead of being reconstructed from historical TTL assumptions, and the built-in path currently targets the local SQLite deployment profile without requiring Alembic.
180
185
 
181
- Database-backed task persistence also keeps the existing first-terminal-state-wins contract while tightening the SQLite path with an atomic terminal-write guard instead of relying only on process-local read-before-write checks. Any wider SQLAlchemy dialect compatibility should be treated as incidental implementation latitude rather than a documented deployment target.
186
+ Database-backed task persistence also keeps the existing first-terminal-state-wins contract while tightening the SQLite path with an atomic terminal-write guard instead of relying only on process-local read-before-write checks.
182
187
 
183
188
  At startup, the runtime logs a concise persistence summary covering the active backend, the redacted database URL when applicable, the shared persistence scope, and whether the SQLite local durability profile is active.
184
189
 
@@ -223,7 +228,7 @@ If one deployment works while another fails against the same upstream provider,
223
228
  ## Streaming Contract
224
229
 
225
230
  - Streaming is always enabled in this server profile; `message:stream` is part of the stable runtime baseline.
226
- - Streaming (`/v1/message:stream`) emits an initial working `Task`, then incremental `TaskArtifactUpdateEvent` / `TaskStatusUpdateEvent` updates, and finally a terminal `TaskStatusUpdateEvent(final=true)`.
231
+ - Streaming (`/message:stream`) emits an initial working `Task`, then incremental `TaskArtifactUpdateEvent` / `TaskStatusUpdateEvent` updates, and finally a terminal `TaskStatusUpdateEvent(final=true)`.
227
232
  - Stream artifacts carry `artifact.metadata.shared.stream.block_type` with values `text` / `reasoning` / `tool_call`.
228
233
  - Stream artifacts are scoped to logical output lanes rather than one shared catch-all artifact:
229
234
  - text chunks use a stable text artifact ID
@@ -285,7 +290,7 @@ Current behavior:
285
290
  - Shared metadata extension URIs such as session binding and streaming are listed under `extensions.extension_uris`.
286
291
  - `all_jsonrpc_methods` is the runtime truth for the current deployment.
287
292
  - The current SDK-owned core JSON-RPC surface includes `GetExtendedAgentCard` and `tasks/pushNotificationConfig/*`.
288
- - The current SDK-owned REST surface also includes `GET /v1/tasks` and the task push notification config routes.
293
+ - The current SDK-owned REST surface also includes `GET /tasks` and the task push notification config routes.
289
294
  - The SDK-owned core JSON-RPC method set follows the pinned `a2a-sdk` release and is locked by repository tests; review that surface deliberately when upgrading the SDK.
290
295
  - Push notification config routes/methods are currently exposed only because they are part of the SDK-owned core surface. This runtime does not configure a push config store or push sender, so push notification operations remain unsupported. REST routes currently return HTTP `501`, while JSON-RPC methods surface SDK-owned unsupported error envelopes.
291
296
 
@@ -483,7 +488,7 @@ Consumer guidance:
483
488
  Minimal example:
484
489
 
485
490
  ```bash
486
- curl -sS http://127.0.0.1:8000/v1/message:send \
491
+ curl -sS http://127.0.0.1:8000/message:send \
487
492
  -H 'content-type: application/json' \
488
493
  -H 'Authorization: Bearer <your-token>' \
489
494
  -d '{
@@ -535,7 +540,7 @@ Consumer guidance:
535
540
  Minimal example:
536
541
 
537
542
  ```bash
538
- curl -sS http://127.0.0.1:8000/v1/message:send \
543
+ curl -sS http://127.0.0.1:8000/message:send \
539
544
  -H 'content-type: application/json' \
540
545
  -H 'Authorization: Bearer <your-token>' \
541
546
  -d '{
@@ -1222,7 +1227,7 @@ curl -sS http://127.0.0.1:8000/ \
1222
1227
  ## Authentication Example (curl)
1223
1228
 
1224
1229
  ```bash
1225
- curl -sS http://127.0.0.1:8000/v1/message:send \
1230
+ curl -sS http://127.0.0.1:8000/message:send \
1226
1231
  -H 'content-type: application/json' \
1227
1232
  -H 'Authorization: Bearer <your-token>' \
1228
1233
  -d '{
@@ -1256,7 +1261,7 @@ curl -sS http://127.0.0.1:8000/ \
1256
1261
 
1257
1262
  ## Streaming Re-Subscription (`SubscribeToTask`)
1258
1263
 
1259
- If an SSE connection drops, use `GET /v1/tasks/{task_id}:subscribe` to re-subscribe while the task is still non-terminal.
1264
+ If an SSE connection drops, use `GET /tasks/{task_id}:subscribe` to re-subscribe while the task is still non-terminal.
1260
1265
 
1261
1266
  ## Cancellation Semantics (`CancelTask`)
1262
1267
 
@@ -1264,7 +1269,7 @@ If an SSE connection drops, use `GET /v1/tasks/{task_id}:subscribe` to re-subscr
1264
1269
  - For running tasks, the service attempts upstream OpenCode `POST /session/{sessionID}/abort` to stop generation.
1265
1270
  - Upstream interruption is best-effort: if upstream returns 404, network errors, or other HTTP errors, A2A cancellation still completes with `TaskState.TASK_STATE_CANCELED`.
1266
1271
  - Idempotency contract: repeated `CancelTask` on an already `canceled` task returns the current terminal task state without error.
1267
- - Terminal subscribe contract: calling `SubscribeToTask` or `GET /v1/tasks/{task_id}:subscribe` on a terminal task replays one terminal `Task` snapshot and then closes the stream.
1272
+ - Terminal subscribe contract: calling `SubscribeToTask` or `GET /tasks/{task_id}:subscribe` on a terminal task replays one terminal `Task` snapshot and then closes the stream.
1268
1273
  - Terminal persistence contract: once a terminal task snapshot is persisted, this service treats it as immutable. Producers must emit final text and artifact updates before the terminal event, and any final usage or stream metadata must be attached to that terminal event itself. Late terminal-state mutations are rejected by the task-store write policy.
1269
1274
  - These two semantics are also declared as machine-readable `service_behaviors` in the compatibility profile and wire contract extensions.
1270
1275
  - At `A2A_LOG_LEVEL=DEBUG`, the service emits lightweight metric log records (`logger=opencode_a2a.execution.executor`):
@@ -21,12 +21,11 @@ flowchart TD
21
21
 
22
22
  subgraph Upstream["OpenCode Upstream Layer"]
23
23
  UpstreamClient["opencode_upstream_client.py"]
24
- Invocation["invocation.py"]
25
24
  end
26
25
 
27
26
  subgraph Extensions["Extension / Contract Layer"]
28
27
  Jsonrpc["jsonrpc/"]
29
- Contracts["contracts/extensions.py"]
28
+ Contracts["contracts/extensions/"]
30
29
  Profile["profile/runtime.py"]
31
30
  end
32
31
 
@@ -61,7 +60,7 @@ flowchart TD
61
60
 
62
61
  1. `jsonrpc/application.py` owns the adapter-specific JSON-RPC application boundary.
63
62
  2. `jsonrpc/dispatch.py` and handler modules under `jsonrpc/handlers/` route provider-private methods.
64
- 3. `contracts/extensions.py` remains the SSOT for extension metadata exposed through Agent Card and OpenAPI.
63
+ 3. `contracts/extensions/` remains the SSOT for extension metadata exposed through Agent Card and OpenAPI.
65
64
  4. Tests under `tests/contracts/` and `tests/jsonrpc/` guard contract drift.
66
65
 
67
66
  ### Outbound Peer Call Path
@@ -89,7 +88,7 @@ flowchart TD
89
88
 
90
89
  ### Extension and Contract Layer
91
90
 
92
- - `contracts/extensions.py`: SSOT for extension metadata, compatibility profile, and wire-contract payloads
91
+ - `contracts/extensions/`: SSOT for extension metadata, compatibility profile, and wire-contract payloads
93
92
  - `jsonrpc/`: provider-private JSON-RPC extension surface
94
93
  - `profile/runtime.py`: runtime profile that feeds Agent Card, OpenAPI, and compatibility metadata
95
94
  - `protocol_versions.py`: protocol normalization and negotiation helpers
@@ -128,5 +127,5 @@ For maintainers new to the codebase, this order usually gives the fastest payoff
128
127
  2. `docs/architecture.md`
129
128
  3. `src/opencode_a2a/server/application.py`
130
129
  4. `src/opencode_a2a/execution/executor.py`
131
- 5. `src/opencode_a2a/contracts/extensions.py`
130
+ 5. `src/opencode_a2a/contracts/extensions/`
132
131
  6. `docs/guide.md`
@@ -14,7 +14,7 @@ authors = [
14
14
  ]
15
15
  keywords = ["a2a", "opencode", "fastapi", "json-rpc", "sse"]
16
16
  classifiers = [
17
- "Development Status :: 3 - Alpha",
17
+ "Development Status :: 4 - Beta",
18
18
  "Intended Audience :: Developers",
19
19
  "Programming Language :: Python :: 3",
20
20
  "Programming Language :: Python :: 3.11",
@@ -24,7 +24,7 @@ classifiers = [
24
24
  "Topic :: Internet :: WWW/HTTP",
25
25
  ]
26
26
  dependencies = [
27
- "a2a-sdk==1.0.2",
27
+ "a2a-sdk==1.1.2",
28
28
  "aiosqlite>=0.20,<1.0",
29
29
  "fastapi>=0.139,<1.0",
30
30
  "httpx>=0.27,<1.0",
@@ -68,6 +68,13 @@ quote-style = "double"
68
68
 
69
69
  [tool.uv]
70
70
  package = true
71
+ # Keep transitive dependency floors above versions with known vulnerabilities:
72
+ # - click < 8.3.3: PYSEC-2026-2132 (pulled in via uvicorn)
73
+ # - pyasn1 < 0.6.4: PYSEC-2026-3455/3456/3457 (pulled in via google-auth <- a2a-sdk)
74
+ constraint-dependencies = [
75
+ "click>=8.3.3",
76
+ "pyasn1>=0.6.4",
77
+ ]
71
78
 
72
79
  [tool.setuptools_scm]
73
80
  fallback_version = "0+unknown"
@@ -13,6 +13,7 @@ Executable scripts live in this directory. This file is the entry index for the
13
13
 
14
14
  - [`doctor.sh`](./doctor.sh): primary local development regression entrypoint with explicit fix/verify/package phases (uv sync + dependency compatibility + lint + mypy + tests + coverage + built-wheel smoke test)
15
15
  - [`conformance.sh`](./conformance.sh): local/manual external A2A conformance experiment entrypoint; caches the official TCK, can launch a dummy-backed local SUT, and preserves raw artifacts under `run/conformance/`
16
+ - [`live_opencode_smoke.sh`](./live_opencode_smoke.sh): local/manual live integration smoke against a real OpenCode runtime; boots an isolated password-hardened `opencode serve` plus the opencode-a2a runtime in throwaway directories and verifies upstream auth, JSON-RPC extension reads, and a streaming prompt round-trip
16
17
  - [`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
18
  - [`check_coverage.py`](./check_coverage.py): enforces the overall coverage floor and per-file minimums for critical modules
18
19
  - [`find_thin_wrappers.py`](./find_thin_wrappers.py): static analysis helper that reports local functions with low caller counts and flags likely thin forwarding wrappers for manual abstraction review
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env bash
2
+ # Run a live opencode-a2a integration smoke against a real OpenCode runtime.
3
+ # Everything runs inside a temporary, isolated environment: a throwaway HOME/XDG
4
+ # for the upstream OpenCode process, a throwaway workspace directory, and
5
+ # ephemeral ports. No repository data directory or user OpenCode state is read
6
+ # or written, and all artifacts are removed on exit.
7
+ set -euo pipefail
8
+
9
+ # shellcheck source=./health_common.sh
10
+ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/health_common.sh"
11
+
12
+ usage() {
13
+ cat <<'EOF'
14
+ Usage:
15
+ bash ./scripts/live_opencode_smoke.sh
16
+
17
+ Purpose:
18
+ Boot a real `opencode serve` (hardened with a server password), start the
19
+ opencode-a2a runtime against it, and verify the adapter's upstream
20
+ adaptation end-to-end: authenticated upstream connectivity, JSON-RPC
21
+ extension read methods, a streaming prompt round-trip, and that upstream
22
+ auth is actually enforced.
23
+
24
+ Isolation:
25
+ The script creates a fresh temporary root (mktemp -d) used for the OpenCode
26
+ process HOME/XDG directories, the OpenCode workspace, and the adapter's
27
+ working directory (SQLite state). Everything is removed on exit; no valid
28
+ data directory is touched.
29
+
30
+ Requirements:
31
+ - opencode CLI on PATH (or OPENCODE_BIN), any 1.18.x build
32
+ - uv, git, curl, python3
33
+
34
+ Selected environment variables:
35
+ OPENCODE_BIN Override the opencode binary path (default: opencode from PATH)
36
+ LIVE_SMOKE_UPSTREAM_PORT Fixed upstream port (default: ephemeral)
37
+ LIVE_SMOKE_A2A_PORT Fixed adapter port (default: ephemeral)
38
+ LIVE_SMOKE_SKIP_PROMPT=1 Skip the streaming prompt round-trip (network/model dependent)
39
+ LIVE_SMOKE_SKIP_SYNC=1 Skip uv sync for this repository
40
+ LIVE_SMOKE_KEEP_ARTIFACTS=1 Keep the temporary root and print its path
41
+ EOF
42
+ }
43
+
44
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
45
+ usage
46
+ exit 0
47
+ fi
48
+
49
+ if [[ "$#" -gt 0 ]]; then
50
+ echo "Expected no positional arguments" >&2
51
+ exit 1
52
+ fi
53
+
54
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
55
+ ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
56
+
57
+ if ! command -v uv >/dev/null 2>&1; then
58
+ echo "uv not found in PATH" >&2
59
+ exit 1
60
+ fi
61
+
62
+ if ! command -v curl >/dev/null 2>&1; then
63
+ echo "curl not found in PATH" >&2
64
+ exit 1
65
+ fi
66
+
67
+ if ! command -v git >/dev/null 2>&1; then
68
+ echo "git not found in PATH" >&2
69
+ exit 1
70
+ fi
71
+
72
+ if ! command -v python3 >/dev/null 2>&1; then
73
+ echo "python3 not found in PATH" >&2
74
+ exit 1
75
+ fi
76
+
77
+ opencode_bin="${OPENCODE_BIN:-}"
78
+ if [[ -z "${opencode_bin}" ]]; then
79
+ if command -v opencode >/dev/null 2>&1; then
80
+ opencode_bin="$(command -v opencode)"
81
+ else
82
+ echo "opencode CLI not found in PATH; install the 1.18.x line or set OPENCODE_BIN." >&2
83
+ exit 1
84
+ fi
85
+ fi
86
+
87
+ opencode_version="$("${opencode_bin}" --version 2>/dev/null | tr -d '[:space:]')"
88
+ if [[ -z "${opencode_version}" ]]; then
89
+ echo "Failed to read opencode version from ${opencode_bin}" >&2
90
+ exit 1
91
+ fi
92
+ echo "[live-smoke] opencode binary: ${opencode_bin} (${opencode_version})"
93
+
94
+ if [[ "${LIVE_SMOKE_SKIP_SYNC:-0}" != "1" ]]; then
95
+ run_shared_repo_health_prerequisites "live-smoke"
96
+ fi
97
+
98
+ free_port() {
99
+ python3 - <<'PY'
100
+ import socket
101
+
102
+ with socket.socket() as sock:
103
+ sock.bind(("127.0.0.1", 0))
104
+ print(sock.getsockname()[1])
105
+ PY
106
+ }
107
+
108
+ upstream_port="${LIVE_SMOKE_UPSTREAM_PORT:-$(free_port)}"
109
+ a2a_port="${LIVE_SMOKE_A2A_PORT:-$(free_port)}"
110
+ upstream_url="http://127.0.0.1:${upstream_port}"
111
+ a2a_url="http://127.0.0.1:${a2a_port}"
112
+ upstream_username="opencode"
113
+ # Non-secret throwaway credential for the isolated smoke instance only.
114
+ upstream_password="live-smoke-password" # pragma: allowlist secret
115
+ a2a_bearer_token="live-smoke-token" # pragma: allowlist secret
116
+
117
+ run_dir="$(mktemp -d)"
118
+ opencode_home="${run_dir}/opencode-home"
119
+ workspace_dir="${run_dir}/workspace"
120
+ adapter_dir="${run_dir}/adapter"
121
+ upstream_log="${run_dir}/opencode.log"
122
+ adapter_log="${run_dir}/adapter.log"
123
+ stream_result="${run_dir}/stream-result.jsonl"
124
+ mkdir -p "${opencode_home}" "${workspace_dir}" "${adapter_dir}"
125
+
126
+ cleanup() {
127
+ local exit_code="$1"
128
+ if [[ -n "${adapter_pid:-}" ]] && kill -0 "${adapter_pid}" >/dev/null 2>&1; then
129
+ kill "${adapter_pid}" >/dev/null 2>&1 || true
130
+ wait "${adapter_pid}" >/dev/null 2>&1 || true
131
+ fi
132
+ if [[ -n "${upstream_pid:-}" ]] && kill -0 "${upstream_pid}" >/dev/null 2>&1; then
133
+ kill "${upstream_pid}" >/dev/null 2>&1 || true
134
+ wait "${upstream_pid}" >/dev/null 2>&1 || true
135
+ fi
136
+ if [[ "${LIVE_SMOKE_KEEP_ARTIFACTS:-0}" == "1" ]]; then
137
+ echo "[live-smoke] artifacts kept at ${run_dir}"
138
+ else
139
+ rm -rf "${run_dir}"
140
+ fi
141
+ exit "${exit_code}"
142
+ }
143
+
144
+ trap 'cleanup $?' EXIT
145
+
146
+ (
147
+ cd "${workspace_dir}"
148
+ git init -q
149
+ printf 'live smoke workspace\n' > README.md
150
+ git add README.md
151
+ git -c user.name="live-smoke" -c user.email="live-smoke@localhost" commit -qm init
152
+ )
153
+
154
+ echo "[live-smoke] starting upstream opencode on ${upstream_url}"
155
+ HOME="${opencode_home}" \
156
+ XDG_CONFIG_HOME="${opencode_home}/.config" \
157
+ XDG_DATA_HOME="${opencode_home}/.local/share" \
158
+ XDG_CACHE_HOME="${opencode_home}/.cache" \
159
+ OPENCODE_SERVER_PASSWORD="${upstream_password}" \
160
+ "${opencode_bin}" serve --hostname 127.0.0.1 --port "${upstream_port}" \
161
+ >"${upstream_log}" 2>&1 &
162
+ upstream_pid="$!"
163
+
164
+ upstream_ready=""
165
+ for _ in $(seq 1 60); do
166
+ if ! kill -0 "${upstream_pid}" >/dev/null 2>&1; then
167
+ echo "[live-smoke] upstream opencode exited before becoming ready" >&2
168
+ cat "${upstream_log}" >&2
169
+ exit 1
170
+ fi
171
+ if curl -fsS --max-time 5 -u "${upstream_username}:${upstream_password}" \
172
+ "${upstream_url}/doc" >/dev/null 2>&1; then
173
+ upstream_ready="1"
174
+ break
175
+ fi
176
+ sleep 0.5
177
+ done
178
+ if [[ -z "${upstream_ready}" ]]; then
179
+ echo "[live-smoke] upstream opencode did not become ready at ${upstream_url}" >&2
180
+ cat "${upstream_log}" >&2
181
+ exit 1
182
+ fi
183
+ echo "[live-smoke] upstream opencode ready (${opencode_version})"
184
+
185
+ echo "[live-smoke] starting opencode-a2a on ${a2a_url}"
186
+ adapter_bin="${ROOT_DIR}/.venv/bin/opencode-a2a"
187
+ if [[ ! -x "${adapter_bin}" ]]; then
188
+ echo "[live-smoke] ${adapter_bin} not found; run without LIVE_SMOKE_SKIP_SYNC=1 (or sync the repository first)." >&2
189
+ exit 1
190
+ fi
191
+
192
+ OPENCODE_BASE_URL="${upstream_url}" \
193
+ OPENCODE_WORKSPACE_ROOT="${workspace_dir}" \
194
+ OPENCODE_AUTH_USERNAME="${upstream_username}" \
195
+ OPENCODE_AUTH_PASSWORD="${upstream_password}" \
196
+ A2A_STATIC_AUTH_CREDENTIALS="[{\"scheme\":\"bearer\",\"token\":\"${a2a_bearer_token}\",\"principal\":\"automation\"}]" \
197
+ A2A_HOST="127.0.0.1" \
198
+ A2A_PORT="${a2a_port}" \
199
+ A2A_PUBLIC_URL="${a2a_url}" \
200
+ A2A_TASK_STORE_DATABASE_URL="sqlite+aiosqlite:///${run_dir}/adapter/opencode-a2a.db" \
201
+ "${adapter_bin}" serve >"${adapter_log}" 2>&1 &
202
+ adapter_pid="$!"
203
+
204
+ a2a_ready=""
205
+ for _ in $(seq 1 60); do
206
+ if ! kill -0 "${adapter_pid}" >/dev/null 2>&1; then
207
+ echo "[live-smoke] adapter exited before becoming ready" >&2
208
+ cat "${adapter_log}" >&2
209
+ exit 1
210
+ fi
211
+ if curl -fsS --max-time 5 -H "Authorization: Bearer ${a2a_bearer_token}" \
212
+ "${a2a_url}/health" >/dev/null 2>&1; then
213
+ a2a_ready="1"
214
+ break
215
+ fi
216
+ sleep 0.5
217
+ done
218
+ if [[ -z "${a2a_ready}" ]]; then
219
+ echo "[live-smoke] adapter did not become ready at ${a2a_url}" >&2
220
+ cat "${adapter_log}" >&2
221
+ exit 1
222
+ fi
223
+ echo "[live-smoke] adapter ready"
224
+
225
+ auth_header="Authorization: Bearer ${a2a_bearer_token}"
226
+ extension_header="A2A-Extensions: urn:opencode-a2a:extension:session-management:v1, urn:opencode-a2a:extension:workspace-control:v1, urn:opencode-a2a:extension:provider-discovery:v1"
227
+
228
+ jsonrpc_probe() {
229
+ local payload="$1"
230
+ curl -sS --max-time 30 -H "${auth_header}" -H "${extension_header}" \
231
+ -H "Content-Type: application/json" \
232
+ -d "${payload}" "${a2a_url}/"
233
+ }
234
+
235
+ echo "[live-smoke] probe: upstream auth is enforced"
236
+ unauth_status="$(
237
+ curl -sS --max-time 10 -o /dev/null -w '%{http_code}' \
238
+ "${upstream_url}/session/status?directory=${workspace_dir}"
239
+ )"
240
+ if [[ "${unauth_status}" != "401" ]]; then
241
+ echo "[live-smoke] FAIL: upstream /session/status without credentials returned ${unauth_status}, expected 401" >&2
242
+ exit 1
243
+ fi
244
+ echo "[live-smoke] PASS: upstream auth enforced (401 without credentials)"
245
+
246
+ echo "[live-smoke] probe: opencode.projects.current"
247
+ projects_result="$(jsonrpc_probe '{"jsonrpc":"2.0","id":1,"method":"opencode.projects.current","params":{}}')"
248
+ if ! python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit(0 if isinstance(d.get("result", {}).get("item", {}).get("id"), str) else 1)' \
249
+ <<<"${projects_result}"; then
250
+ echo "[live-smoke] FAIL: opencode.projects.current returned unexpected result: ${projects_result}" >&2
251
+ exit 1
252
+ fi
253
+ echo "[live-smoke] PASS: opencode.projects.current"
254
+
255
+ echo "[live-smoke] probe: opencode.sessions.status"
256
+ sessions_result="$(jsonrpc_probe '{"jsonrpc":"2.0","id":2,"method":"opencode.sessions.status","params":{}}')"
257
+ if ! python3 -c 'import json,sys; d=json.load(sys.stdin); sys.exit(0 if "items" in d.get("result", {}) else 1)' \
258
+ <<<"${sessions_result}"; then
259
+ echo "[live-smoke] FAIL: opencode.sessions.status returned unexpected result: ${sessions_result}" >&2
260
+ exit 1
261
+ fi
262
+ echo "[live-smoke] PASS: opencode.sessions.status"
263
+
264
+ if [[ "${LIVE_SMOKE_SKIP_PROMPT:-0}" != "1" ]]; then
265
+ echo "[live-smoke] probe: streaming prompt round-trip"
266
+ curl -sS -N \
267
+ -H "${auth_header}" \
268
+ -H "Content-Type: application/json" \
269
+ -d '{"jsonrpc":"2.0","id":10,"method":"SendStreamingMessage","params":{"message":{"messageId":"live-smoke-msg","role":"ROLE_USER","parts":[{"text":"Reply with exactly: smoke-ok"}]}}}' \
270
+ "${a2a_url}/" --max-time 180 >"${stream_result}"
271
+ if ! grep -q '"TASK_STATE_COMPLETED"' "${stream_result}"; then
272
+ echo "[live-smoke] FAIL: streaming prompt did not reach TASK_STATE_COMPLETED" >&2
273
+ tail -c 2000 "${stream_result}" >&2
274
+ exit 1
275
+ fi
276
+ if ! grep -q 'smoke-ok' "${stream_result}"; then
277
+ echo "[live-smoke] FAIL: streaming prompt reply did not contain the expected text" >&2
278
+ tail -c 2000 "${stream_result}" >&2
279
+ exit 1
280
+ fi
281
+ echo "[live-smoke] PASS: streaming prompt round-trip"
282
+ else
283
+ echo "[live-smoke] skip: streaming prompt round-trip (LIVE_SMOKE_SKIP_PROMPT=1)"
284
+ fi
285
+
286
+ echo "[live-smoke] ALL CHECKS PASSED (opencode ${opencode_version})"
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import base64
4
4
  import binascii
5
+ import secrets
5
6
  from dataclasses import dataclass
6
7
 
7
8
  from starlette.requests import Request
@@ -83,7 +84,7 @@ def authenticate_static_credential(
83
84
  for credential in credentials:
84
85
  if credential.auth_scheme != "bearer" or credential.token is None:
85
86
  continue
86
- if auth_value == credential.token:
87
+ if secrets.compare_digest(auth_value, credential.token):
87
88
  return AuthenticatedPrincipal(
88
89
  identity=credential.principal,
89
90
  auth_scheme="bearer",
@@ -102,7 +103,11 @@ def authenticate_static_credential(
102
103
  for credential in credentials:
103
104
  if credential.auth_scheme != "basic":
104
105
  continue
105
- if credential.username == username and credential.password == password:
106
+ if credential.username is None or credential.password is None:
107
+ continue
108
+ username_matches = secrets.compare_digest(username, credential.username)
109
+ password_matches = secrets.compare_digest(password, credential.password)
110
+ if username_matches and password_matches:
106
111
  return AuthenticatedPrincipal(
107
112
  identity=credential.principal,
108
113
  auth_scheme="basic",
@@ -154,17 +154,23 @@ class Settings(BaseSettings):
154
154
  opencode_system: str | None = Field(default=None, alias="OPENCODE_SYSTEM")
155
155
  opencode_variant: str | None = Field(default=None, alias="OPENCODE_VARIANT")
156
156
  opencode_timeout: float = Field(default=120.0, alias="OPENCODE_TIMEOUT")
157
- opencode_timeout_stream: float | None = Field(default=None, alias="OPENCODE_TIMEOUT_STREAM")
157
+ opencode_timeout_stream: float | None = Field(default=900.0, alias="OPENCODE_TIMEOUT_STREAM")
158
158
  opencode_max_concurrent_requests: int = Field(
159
- default=0,
159
+ default=32,
160
160
  ge=0,
161
161
  alias="OPENCODE_MAX_CONCURRENT_REQUESTS",
162
162
  )
163
163
  opencode_max_concurrent_streams: int = Field(
164
- default=0,
164
+ default=8,
165
165
  ge=0,
166
166
  alias="OPENCODE_MAX_CONCURRENT_STREAMS",
167
167
  )
168
+ opencode_auth_username: str = Field(default="opencode", alias="OPENCODE_AUTH_USERNAME")
169
+ opencode_auth_password: str | None = Field(
170
+ default=None,
171
+ alias="OPENCODE_AUTH_PASSWORD",
172
+ repr=False,
173
+ )
168
174
 
169
175
  # A2A settings
170
176
  a2a_public_url: str = Field(default="http://127.0.0.1:8000", alias="A2A_PUBLIC_URL")
@@ -292,6 +298,12 @@ class Settings(BaseSettings):
292
298
  raise ValueError(
293
299
  "A2A_TASK_STORE_DATABASE_URL is required when A2A_TASK_STORE_BACKEND=database"
294
300
  )
301
+ if (
302
+ self.a2a_task_store_backend == "database"
303
+ and self.a2a_task_store_database_url
304
+ and not self.a2a_task_store_database_url.startswith("sqlite+aiosqlite:")
305
+ ):
306
+ raise ValueError("A2A_TASK_STORE_DATABASE_URL must use the sqlite+aiosqlite scheme")
295
307
  if self.a2a_static_auth_credentials:
296
308
  if not any(credential.enabled for credential in self.a2a_static_auth_credentials):
297
309
  raise ValueError(