opencode-a2a 1.1.0__tar.gz → 1.1.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 (210) hide show
  1. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/AGENTS.md +2 -0
  2. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/PKG-INFO +7 -7
  3. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/README.md +6 -6
  4. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/a2a_utils.py +0 -4
  5. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/cli.py +14 -8
  6. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_queries.py +14 -13
  7. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/output_modes.py +17 -38
  8. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/application.py +16 -13
  9. opencode_a2a-1.1.1/src/opencode_a2a/server/context_helpers.py +76 -0
  10. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/task_store.py +2 -3
  11. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/PKG-INFO +7 -7
  12. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/SOURCES.txt +2 -0
  13. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_agent_helpers.py +33 -0
  14. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_cli.py +28 -6
  15. opencode_a2a-1.1.1/tests/server/test_context_helpers.py +61 -0
  16. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_output_negotiation.py +61 -0
  17. opencode_a2a-1.1.1/tests/test_a2a_utils.py +100 -0
  18. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/uv.lock +3 -3
  19. opencode_a2a-1.1.0/src/opencode_a2a/server/context_helpers.py +0 -97
  20. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/dependabot.yml +0 -0
  21. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/workflows/dependency-review.yml +0 -0
  22. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/workflows/publish.yml +0 -0
  23. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/workflows/validate.yml +0 -0
  24. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.gitignore +0 -0
  25. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.pre-commit-config.yaml +0 -0
  26. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.secrets.baseline +0 -0
  27. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  28. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/CONTRIBUTING.md +0 -0
  29. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/LICENSE +0 -0
  30. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/MANIFEST.in +0 -0
  31. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/SECURITY.md +0 -0
  32. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/SUPPORT.md +0 -0
  33. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/architecture.md +0 -0
  34. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/compatibility.md +0 -0
  35. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/conformance-triage.md +0 -0
  36. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/conformance.md +0 -0
  37. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/extension-specifications.md +0 -0
  38. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/guide.md +0 -0
  39. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/maintainer-architecture.md +0 -0
  40. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/pyproject.toml +0 -0
  41. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/README.md +0 -0
  42. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/check_coverage.py +0 -0
  43. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/conformance.sh +0 -0
  44. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/dependency_health.sh +0 -0
  45. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/doctor.sh +0 -0
  46. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/find_thin_wrappers.py +0 -0
  47. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/health_common.sh +0 -0
  48. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/lint.sh +0 -0
  49. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/smoke_test_built_cli.sh +0 -0
  50. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/setup.cfg +0 -0
  51. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/__init__.py +0 -0
  52. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/a2a_protocol.py +0 -0
  53. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/auth.py +0 -0
  54. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/__init__.py +0 -0
  55. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/agent_card.py +0 -0
  56. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/auth.py +0 -0
  57. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/client.py +0 -0
  58. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/config.py +0 -0
  59. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/error_mapping.py +0 -0
  60. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/errors.py +0 -0
  61. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/payload_text.py +0 -0
  62. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/polling.py +0 -0
  63. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/request_context.py +0 -0
  64. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/config.py +0 -0
  65. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/__init__.py +0 -0
  66. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/__init__.py +0 -0
  67. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/capabilities.py +0 -0
  68. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/catalog.py +0 -0
  69. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/compatibility.py +0 -0
  70. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/contract_docs.py +0 -0
  71. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/identifiers.py +0 -0
  72. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/private_params.py +0 -0
  73. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/public_params.py +0 -0
  74. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/__init__.py +0 -0
  75. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/coordinator.py +0 -0
  76. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/event_helpers.py +0 -0
  77. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/executor.py +0 -0
  78. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/metrics.py +0 -0
  79. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/session_manager.py +0 -0
  80. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/stream_events.py +0 -0
  81. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/stream_runtime.py +0 -0
  82. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/stream_state.py +0 -0
  83. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
  84. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/tool_orchestration.py +0 -0
  85. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/upstream_error_translator.py +0 -0
  86. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/extension_negotiation.py +0 -0
  87. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/interrupt_request_tracker.py +0 -0
  88. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
  89. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/application.py +0 -0
  90. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/dispatch.py +0 -0
  91. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
  92. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/__init__.py +0 -0
  93. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/common.py +0 -0
  94. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_callbacks.py +0 -0
  95. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/provider_discovery.py +0 -0
  96. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/session_control.py +0 -0
  97. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/session_lifecycle.py +0 -0
  98. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/session_queries.py +0 -0
  99. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/workspace_control.py +0 -0
  100. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
  101. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/models.py +0 -0
  102. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
  103. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/metadata_access.py +0 -0
  104. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/opencode_upstream_client.py +0 -0
  105. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parsing.py +0 -0
  106. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parts/__init__.py +0 -0
  107. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parts/mapping.py +0 -0
  108. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parts/text.py +0 -0
  109. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/profile/__init__.py +0 -0
  110. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/profile/runtime.py +0 -0
  111. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/protocol_versions.py +0 -0
  112. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/py.typed +0 -0
  113. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/runtime_state.py +0 -0
  114. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/sandbox_policy.py +0 -0
  115. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/__init__.py +0 -0
  116. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/agent_card.py +0 -0
  117. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/client_manager.py +0 -0
  118. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/database.py +0 -0
  119. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/lifespan.py +0 -0
  120. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/middleware.py +0 -0
  121. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/migrations.py +0 -0
  122. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/openapi.py +0 -0
  123. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/request_parsing.py +0 -0
  124. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/rest_tasks.py +0 -0
  125. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/state_store.py +0 -0
  126. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/task_store_sdk_compat.py +0 -0
  127. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/task_states.py +0 -0
  128. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/trace_context.py +0 -0
  129. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/upstream_taxonomy.py +0 -0
  130. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
  131. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
  132. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/requires.txt +0 -0
  133. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
  134. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/__init__.py +0 -0
  135. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/__init__.py +0 -0
  136. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_agent_card.py +0 -0
  137. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_client_config.py +0 -0
  138. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_client_facade.py +0 -0
  139. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_error_mapping.py +0 -0
  140. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_payload_text.py +0 -0
  141. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_polling.py +0 -0
  142. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_request_context.py +0 -0
  143. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/config/__init__.py +0 -0
  144. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/config/test_settings.py +0 -0
  145. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/conftest.py +0 -0
  146. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/contracts/__init__.py +0 -0
  147. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/contracts/test_extension_contract_consistency.py +0 -0
  148. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/__init__.py +0 -0
  149. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_agent_errors.py +0 -0
  150. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_cancellation.py +0 -0
  151. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_directory_validation.py +0 -0
  152. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_metrics.py +0 -0
  153. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_multipart_input.py +0 -0
  154. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_opencode_agent_session_binding.py +0 -0
  155. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_session_lock_lifecycle.py +0 -0
  156. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_session_ownership.py +0 -0
  157. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
  158. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_core.py +0 -0
  159. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
  160. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
  161. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/__init__.py +0 -0
  162. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_application_dispatch.py +0 -0
  163. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_dispatch_registry.py +0 -0
  164. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_error_responses.py +0 -0
  165. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
  166. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
  167. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
  168. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
  169. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +0 -0
  170. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_lifecycle.py +0 -0
  171. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
  172. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
  173. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_workspace_control_extension.py +0 -0
  174. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/__init__.py +0 -0
  175. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/test_logging.py +0 -0
  176. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/test_typing_contract.py +0 -0
  177. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/test_version.py +0 -0
  178. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/parts/__init__.py +0 -0
  179. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/parts/test_parts_text.py +0 -0
  180. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/profile/__init__.py +0 -0
  181. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/profile/test_profile_runtime.py +0 -0
  182. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/scripts/__init__.py +0 -0
  183. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/scripts/test_script_health_contract.py +0 -0
  184. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/__init__.py +0 -0
  185. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_a2a_client_manager.py +0 -0
  186. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_agent_card.py +0 -0
  187. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_app_behaviors.py +0 -0
  188. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_auth.py +0 -0
  189. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_call_context_builder.py +0 -0
  190. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_cancel_contract.py +0 -0
  191. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_database_app_persistence.py +0 -0
  192. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_state_store.py +0 -0
  193. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_task_store_factory.py +0 -0
  194. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_transport_contract.py +0 -0
  195. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/__init__.py +0 -0
  196. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/async_iterators.py +0 -0
  197. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/fake_client_errors.py +0 -0
  198. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/helpers.py +0 -0
  199. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/interrupt_clients.py +0 -0
  200. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/jsonrpc_error_assertions.py +0 -0
  201. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/session_extensions.py +0 -0
  202. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/session_query_client.py +0 -0
  203. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/settings.py +0 -0
  204. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/streaming_output.py +0 -0
  205. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/workspace_control_client.py +0 -0
  206. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/test_metadata_access.py +0 -0
  207. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/test_parsing.py +0 -0
  208. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/test_trace_context.py +0 -0
  209. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/upstream/__init__.py +0 -0
  210. {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/upstream/test_opencode_upstream_client_params.py +0 -0
@@ -11,6 +11,8 @@ The following rules apply to coding agent collaboration in this repository. Thes
11
11
 
12
12
  - Follow the Git, Issue, and PR workflow defined in [CONTRIBUTING.md](CONTRIBUTING.md).
13
13
  - Use `gh` CLI for all issue/PR operations (reading, writing, and comments). Do not edit through the web UI.
14
+ - Do not use the Codex GitHub connector for any repository operation. Use `gh` CLI exclusively for issue, PR, review, comment, and metadata workflows.
15
+ - If `gh` CLI cannot complete a required GitHub operation because of permissions, authentication, or environment limits, stop and ask the human to perform or unblock the operation. Do not fall back to the Codex GitHub connector.
14
16
  - Create a new tracking issue for any development task that does not already have one.
15
17
  - Link the issue explicitly in PR descriptions (e.g., `Closes #xx`).
16
18
  - Keep status updates synchronized to the relevant issue/PR to avoid duplicate manual work.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-a2a
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: OpenCode A2A runtime
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -43,12 +43,12 @@ Dynamic: license-file
43
43
  # opencode-a2a
44
44
 
45
45
  ```text
46
- ___ ____ _ _ ____ _
47
- / _ \ _ __ ___ _ __ / ___|___ __| | ___ / \ |___ \ / \
48
- | | | | '_ \ / _ \ '_ \| | / _ \ / _` |/ _ \_____ / _ \ __) | / _ \
49
- | |_| | |_) | __/ | | | |__| (_) | (_| | __/_____/ ___ \ / __/ / ___ \
50
- \___/| .__/ \___|_| |_|\____\___/ \__,_|\___| /_/ \_\_____/_/ \_\
51
- |_|
46
+ ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ █████╗ ██████╗ █████╗
47
+ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔══██╗╚════██╗██╔══██╗
48
+ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ██║██║ ██║█████╗█████╗███████║ █████╔╝███████║
49
+ ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██║██║ ██║██╔══╝╚════╝██╔══██║██╔═══╝ ██╔══██║
50
+ ╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗╚██████╔╝██████╔╝███████╗ ██║ ██║███████╗██║ ██║
51
+ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
52
52
  ```
53
53
 
54
54
  > Expose OpenCode through A2A.
@@ -1,12 +1,12 @@
1
1
  # opencode-a2a
2
2
 
3
3
  ```text
4
- ___ ____ _ _ ____ _
5
- / _ \ _ __ ___ _ __ / ___|___ __| | ___ / \ |___ \ / \
6
- | | | | '_ \ / _ \ '_ \| | / _ \ / _` |/ _ \_____ / _ \ __) | / _ \
7
- | |_| | |_) | __/ | | | |__| (_) | (_| | __/_____/ ___ \ / __/ / ___ \
8
- \___/| .__/ \___|_| |_|\____\___/ \__,_|\___| /_/ \_\_____/_/ \_\
9
- |_|
4
+ ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ █████╗ ██████╗ █████╗
5
+ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔══██╗╚════██╗██╔══██╗
6
+ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ██║██║ ██║█████╗█████╗███████║ █████╔╝███████║
7
+ ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██║██║ ██║██╔══╝╚════╝██╔══██║██╔═══╝ ██╔══██║
8
+ ╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗╚██████╔╝██████╔╝███████╗ ██║ ██║███████╗██║ ██║
9
+ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
10
10
  ```
11
11
 
12
12
  > Expose OpenCode through A2A.
@@ -16,10 +16,6 @@ def clone_proto(message: ProtoT) -> ProtoT:
16
16
  return cloned
17
17
 
18
18
 
19
- def proto_equals(left: ProtoMessage, right: ProtoMessage) -> bool:
20
- return bool(left == right)
21
-
22
-
23
19
  def _to_proto_value(value: Any) -> Value:
24
20
  proto_value = Value()
25
21
  if value is None:
@@ -15,12 +15,18 @@ from .config import Settings
15
15
  from .server.application import main as serve_main
16
16
 
17
17
  CLI_BRAND_BANNER = (
18
- " ___ ____ _ _ ____ _ \n"
19
- " / _ \\ _ __ ___ _ __ / ___|___ __| | ___ / \\ |___ \\ / \\ \n"
20
- "| | | | '_ \\ / _ \\ '_ \\| | / _ \\ / _` |/ _ \\_____ / _ \\ __) | / _ \\ \n"
21
- "| |_| | |_) | __/ | | | |__| (_) | (_| | __/_____/ ___ \\ / __/ / ___ \\ \n"
22
- " \\___/| .__/ \\___|_| |_|\\____\\___/ \\__,_|\\___| /_/ \\_\\_____/_/ \\_\\\n"
23
- " |_| "
18
+ " ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗"
19
+ " ██████╗ ███████╗ █████╗ ██████╗ █████╗ \n"
20
+ "██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██╔═══██╗"
21
+ "██╔══██╗██╔════╝ ██╔══██╗╚════██╗██╔══██╗\n"
22
+ "██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ██║"
23
+ "██║ ██║█████╗█████╗███████║ █████╔╝███████║\n"
24
+ "██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██║"
25
+ "██║ ██║██╔══╝╚════╝██╔══██║██╔═══╝ ██╔══██║\n"
26
+ "╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗╚██████╔╝"
27
+ "██████╔╝███████╗ ██║ ██║███████╗██║ ██║\n"
28
+ " ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝"
29
+ " ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝"
24
30
  )
25
31
  PROJECT_REPOSITORY_URL = "https://github.com/Intelligent-Internet/opencode-a2a"
26
32
  HELP_FLAGS = frozenset({"-h", "--help"})
@@ -95,7 +101,7 @@ CALL_HELP = (
95
101
  " Service base URLs also work, but card URLs are the preferred example form."
96
102
  )
97
103
 
98
- ROOT_HELP_EPILOG = f"{OPENCODE_SETUP_HELP}\n\n{SERVE_ENVIRONMENT_HELP}\n\n{CALL_HELP}"
104
+ ROOT_HELP_EPILOG = "Command-specific help:\n opencode-a2a serve --help\n opencode-a2a call --help"
99
105
  SERVE_HELP_EPILOG = f"{OPENCODE_SETUP_HELP}\n\n{SERVE_ENVIRONMENT_HELP}"
100
106
  CALL_HELP_EPILOG = CALL_HELP
101
107
 
@@ -224,7 +230,7 @@ def build_parser() -> argparse.ArgumentParser:
224
230
  CLI_BRAND_BANNER
225
231
  + "\n\n"
226
232
  + f"repo: {PROJECT_REPOSITORY_URL}\n"
227
- + "uv tool install --upgrade opencode-a2a\n"
233
+ + "uv tool install --upgrade opencode-a2a\n\n"
228
234
  + ROOT_DESCRIPTION
229
235
  ),
230
236
  formatter_class=RootHelpFormatter,
@@ -10,18 +10,6 @@ from ..models import JSONRPCRequest
10
10
  from .common import build_internal_error_response, build_success_response, reject_unknown_fields
11
11
 
12
12
 
13
- def _binding_to_result_item(binding: Any) -> dict[str, Any]:
14
- return {
15
- "request_id": binding.request_id,
16
- "session_id": binding.session_id,
17
- "interrupt_type": binding.interrupt_type,
18
- "task_id": binding.task_id,
19
- "context_id": binding.context_id,
20
- "details": dict(binding.details) if isinstance(binding.details, dict) else None,
21
- "expires_at": binding.expires_at,
22
- }
23
-
24
-
25
13
  async def handle_interrupt_query_request(
26
14
  context: ExtensionHandlerContext,
27
15
  base_request: JSONRPCRequest,
@@ -58,5 +46,18 @@ async def handle_interrupt_query_request(
58
46
  return build_success_response(
59
47
  context,
60
48
  base_request.id,
61
- {"items": [_binding_to_result_item(item) for item in items]},
49
+ {
50
+ "items": [
51
+ {
52
+ "request_id": item.request_id,
53
+ "session_id": item.session_id,
54
+ "interrupt_type": item.interrupt_type,
55
+ "task_id": item.task_id,
56
+ "context_id": item.context_id,
57
+ "details": dict(item.details) if isinstance(item.details, dict) else None,
58
+ "expires_at": item.expires_at,
59
+ }
60
+ for item in items
61
+ ]
62
+ },
62
63
  )
@@ -37,30 +37,19 @@ _STREAM_METADATA_STREAM_KEY = "stream"
37
37
  _STREAM_METADATA_BLOCK_TYPE_KEY = "block_type"
38
38
 
39
39
 
40
- def _accepted_output_modes_source(source: Any) -> Iterable[str] | None:
40
+ def normalize_accepted_output_modes(source: Any) -> tuple[str, ...] | None:
41
41
  if source is None:
42
42
  return None
43
-
44
43
  accepted = getattr(source, "accepted_output_modes", None) or getattr(
45
44
  source, "acceptedOutputModes", None
46
45
  )
47
46
  if accepted is not None:
48
47
  source = accepted
49
-
50
- if isinstance(source, str | bytes | bytearray | dict):
51
- return None
52
- if not isinstance(source, Iterable):
53
- return None
54
- return cast(Iterable[str], source)
55
-
56
-
57
- def normalize_accepted_output_modes(source: Any) -> tuple[str, ...] | None:
58
- accepted = _accepted_output_modes_source(source)
59
- if accepted is None:
48
+ if isinstance(source, str | bytes | bytearray | dict) or not isinstance(source, Iterable):
60
49
  return None
61
50
 
62
51
  normalized: list[str] = []
63
- for value in accepted:
52
+ for value in cast(Iterable[str], source):
64
53
  if not isinstance(value, str):
65
54
  continue
66
55
  mode = value.strip().lower()
@@ -194,9 +183,11 @@ def apply_accepted_output_modes(
194
183
  return replace_artifact_event_artifact(payload, artifact)
195
184
 
196
185
  if isinstance(payload, TaskStatusUpdateEvent):
197
- message = None
198
- if payload.status.HasField("message"):
199
- message = _filter_optional_message(payload.status.message, normalized)
186
+ message = (
187
+ _filter_message(payload.status.message, normalized)
188
+ if payload.status.HasField("message")
189
+ else None
190
+ )
200
191
  return replace_status_event_message(payload, message)
201
192
 
202
193
  if isinstance(payload, Task):
@@ -375,7 +366,7 @@ class NegotiatingResultAggregator(ResultAggregator):
375
366
  def _filter_task(task: Task, accepted_output_modes: Collection[str]) -> Task:
376
367
  updated = clone_proto(task)
377
368
  if updated.status.HasField("message"):
378
- filtered_message = _filter_optional_message(updated.status.message, accepted_output_modes)
369
+ filtered_message = _filter_message(updated.status.message, accepted_output_modes)
379
370
  if filtered_message is None:
380
371
  updated.status.ClearField("message")
381
372
  else:
@@ -404,15 +395,6 @@ def _filter_task(task: Task, accepted_output_modes: Collection[str]) -> Task:
404
395
  return updated
405
396
 
406
397
 
407
- def _filter_optional_message(
408
- message: Message | None,
409
- accepted_output_modes: Collection[str],
410
- ) -> Message | None:
411
- if message is None:
412
- return None
413
- return _filter_message(message, accepted_output_modes)
414
-
415
-
416
398
  def _filter_message(
417
399
  message: Message,
418
400
  accepted_output_modes: Collection[str],
@@ -439,7 +421,14 @@ def _filter_parts(
439
421
  ) -> list[Part]:
440
422
  filtered: list[Part] = []
441
423
  for part in parts:
442
- media_type = _part_media_type(part)
424
+ if part.HasField("text"):
425
+ media_type = _TEXT_PLAIN_MEDIA_TYPE
426
+ elif part.HasField("data"):
427
+ media_type = _APPLICATION_JSON_MEDIA_TYPE
428
+ elif part.HasField("raw") or part.HasField("url"):
429
+ media_type = part.media_type or "application/octet-stream"
430
+ else:
431
+ media_type = None
443
432
  if media_type is None or accepts_output_mode(accepted_output_modes, media_type):
444
433
  filtered.append(part)
445
434
  continue
@@ -459,13 +448,3 @@ def _filter_parts(
459
448
  )
460
449
  )
461
450
  return filtered
462
-
463
-
464
- def _part_media_type(part: Part) -> str | None:
465
- if part.HasField("text"):
466
- return _TEXT_PLAIN_MEDIA_TYPE
467
- if part.HasField("data"):
468
- return _APPLICATION_JSON_MEDIA_TYPE
469
- if part.HasField("raw") or part.HasField("url"):
470
- return part.media_type or "application/octet-stream"
471
- return None
@@ -259,17 +259,6 @@ class OpencodeRequestHandler(LegacyRequestHandler):
259
259
  return "Task store unavailable while deleting task state."
260
260
  return "Task store unavailable."
261
261
 
262
- @classmethod
263
- def _task_store_failure_metadata(cls, operation: str) -> dict[str, dict[str, dict[str, str]]]:
264
- return {
265
- "opencode": {
266
- "error": {
267
- "type": TASK_STORE_ERROR_TYPE,
268
- "operation": operation,
269
- }
270
- }
271
- }
272
-
273
262
  @classmethod
274
263
  def _task_store_server_error(cls, exc: TaskStoreOperationError) -> InternalError:
275
264
  return InternalError(message=cls._task_store_failure_message(exc.operation))
@@ -295,7 +284,14 @@ class OpencodeRequestHandler(LegacyRequestHandler):
295
284
  context_id=context_id,
296
285
  status=TaskStatus(state=TaskState.TASK_STATE_FAILED, message=error_message),
297
286
  history=[error_message],
298
- metadata=cls._task_store_failure_metadata(operation),
287
+ metadata={
288
+ "opencode": {
289
+ "error": {
290
+ "type": TASK_STORE_ERROR_TYPE,
291
+ "operation": operation,
292
+ }
293
+ }
294
+ },
299
295
  )
300
296
 
301
297
  @classmethod
@@ -322,7 +318,14 @@ class OpencodeRequestHandler(LegacyRequestHandler):
322
318
  task_id=task_id,
323
319
  context_id=context_id,
324
320
  status=TaskStatus(state=TaskState.TASK_STATE_FAILED),
325
- metadata=cls._task_store_failure_metadata(operation),
321
+ metadata={
322
+ "opencode": {
323
+ "error": {
324
+ "type": TASK_STORE_ERROR_TYPE,
325
+ "operation": operation,
326
+ }
327
+ }
328
+ },
326
329
  ),
327
330
  )
328
331
 
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping, MutableMapping
4
+
5
+ from a2a.auth.user import UnauthenticatedUser, User
6
+ from a2a.server.context import ServerCallContext
7
+
8
+
9
+ class AuthenticatedIdentityUser(User):
10
+ def __init__(self, identity: str) -> None:
11
+ self._identity = identity
12
+
13
+ @property
14
+ def is_authenticated(self) -> bool:
15
+ return True
16
+
17
+ @property
18
+ def user_name(self) -> str:
19
+ return self._identity
20
+
21
+
22
+ def normalize_server_call_context(context: ServerCallContext | None) -> ServerCallContext:
23
+ if context is None:
24
+ return ServerCallContext()
25
+
26
+ raw_state = getattr(context, "state", None)
27
+ raw_requested_extensions = getattr(context, "requested_extensions", None)
28
+ raw_tenant = getattr(context, "tenant", "")
29
+ raw_user = getattr(context, "user", None)
30
+
31
+ if isinstance(raw_state, MutableMapping):
32
+ state = raw_state
33
+ elif isinstance(raw_state, Mapping):
34
+ state = dict(raw_state)
35
+ else:
36
+ state = {}
37
+
38
+ if raw_requested_extensions is None:
39
+ requested_extensions: set[str] = set()
40
+ elif isinstance(raw_requested_extensions, set):
41
+ requested_extensions = raw_requested_extensions
42
+ elif isinstance(raw_requested_extensions, str):
43
+ requested_extensions = {raw_requested_extensions}
44
+ else:
45
+ try:
46
+ requested_extensions = {str(value) for value in raw_requested_extensions}
47
+ except TypeError:
48
+ requested_extensions = set()
49
+
50
+ tenant = raw_tenant if isinstance(raw_tenant, str) else ""
51
+ identity = state.get("identity")
52
+ normalized_identity = identity if isinstance(identity, str) else None
53
+ if normalized_identity and (
54
+ not isinstance(raw_user, User) or isinstance(raw_user, UnauthenticatedUser)
55
+ ):
56
+ user: User = AuthenticatedIdentityUser(normalized_identity)
57
+ elif isinstance(raw_user, User):
58
+ user = raw_user
59
+ else:
60
+ user = UnauthenticatedUser()
61
+
62
+ if (
63
+ isinstance(context, ServerCallContext)
64
+ and raw_state is state
65
+ and raw_user is user
66
+ and raw_tenant == tenant
67
+ and raw_requested_extensions == requested_extensions
68
+ ):
69
+ return context
70
+
71
+ return ServerCallContext(
72
+ state=state,
73
+ user=user,
74
+ tenant=tenant,
75
+ requested_extensions=requested_extensions,
76
+ )
@@ -14,7 +14,6 @@ from a2a.server.tasks.task_store import TaskStore
14
14
  from a2a.types import ListTasksRequest, ListTasksResponse, Task
15
15
  from sqlalchemy.engine import make_url
16
16
 
17
- from ..a2a_utils import proto_equals
18
17
  from ..config import Settings
19
18
  from ..task_states import TERMINAL_TASK_STATES
20
19
  from .context_helpers import normalize_server_call_context
@@ -67,7 +66,7 @@ class FirstTerminalStateWinsPolicy(TaskWritePolicy):
67
66
  persist=False,
68
67
  reason="state_overwrite_after_terminal_persistence",
69
68
  )
70
- if not proto_equals(incoming, existing):
69
+ if incoming != existing:
71
70
  return TaskPersistenceDecision(
72
71
  persist=False,
73
72
  reason="late_mutation_after_terminal_persistence",
@@ -238,7 +237,7 @@ class PolicyAwareTaskStore(TaskStoreDecorator):
238
237
  if (
239
238
  existing is not None
240
239
  and existing.status.state in TERMINAL_TASK_STATES
241
- and proto_equals(existing, task)
240
+ and existing == task
242
241
  ):
243
242
  return
244
243
  raise RuntimeError(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencode-a2a
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: OpenCode A2A runtime
5
5
  Author: liujuanjuan1984@Intelligent-Internet
6
6
  License-Expression: Apache-2.0
@@ -43,12 +43,12 @@ Dynamic: license-file
43
43
  # opencode-a2a
44
44
 
45
45
  ```text
46
- ___ ____ _ _ ____ _
47
- / _ \ _ __ ___ _ __ / ___|___ __| | ___ / \ |___ \ / \
48
- | | | | '_ \ / _ \ '_ \| | / _ \ / _` |/ _ \_____ / _ \ __) | / _ \
49
- | |_| | |_) | __/ | | | |__| (_) | (_| | __/_____/ ___ \ / __/ / ___ \
50
- \___/| .__/ \___|_| |_|\____\___/ \__,_|\___| /_/ \_\_____/_/ \_\
51
- |_|
46
+ ██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ █████╗ ██████╗ █████╗
47
+ ██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔══██╗╚════██╗██╔══██╗
48
+ ██║ ██║██████╔╝█████╗ ██╔██╗ ██║██║ ██║ ██║██║ ██║█████╗█████╗███████║ █████╔╝███████║
49
+ ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██║ ██║ ██║██║ ██║██╔══╝╚════╝██╔══██║██╔═══╝ ██╔══██║
50
+ ╚██████╔╝██║ ███████╗██║ ╚████║╚██████╗╚██████╔╝██████╔╝███████╗ ██║ ██║███████╗██║ ██║
51
+ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
52
52
  ```
53
53
 
54
54
  > Expose OpenCode through A2A.
@@ -125,6 +125,7 @@ src/opencode_a2a/server/task_store.py
125
125
  src/opencode_a2a/server/task_store_sdk_compat.py
126
126
  tests/__init__.py
127
127
  tests/conftest.py
128
+ tests/test_a2a_utils.py
128
129
  tests/test_metadata_access.py
129
130
  tests/test_parsing.py
130
131
  tests/test_trace_context.py
@@ -185,6 +186,7 @@ tests/server/test_auth.py
185
186
  tests/server/test_call_context_builder.py
186
187
  tests/server/test_cancel_contract.py
187
188
  tests/server/test_cli.py
189
+ tests/server/test_context_helpers.py
188
190
  tests/server/test_database_app_persistence.py
189
191
  tests/server/test_output_negotiation.py
190
192
  tests/server/test_state_store.py
@@ -146,6 +146,39 @@ def test_format_upstream_errors_include_detail_and_provider_auth_paths() -> None
146
146
  assert "error=SomeOtherError" in inband.message
147
147
 
148
148
 
149
+ def test_format_upstream_error_helpers_cover_status_and_generic_paths() -> None:
150
+ terminal = _format_stream_terminal_error(detail=None, status=503, error_name=None)
151
+ assert terminal.state == TaskState.TASK_STATE_FAILED
152
+ assert terminal.error_type == "UPSTREAM_SERVER_ERROR"
153
+ assert terminal.upstream_status == 503
154
+
155
+ terminal_with_detail = _format_stream_terminal_error(
156
+ detail="tool crashed",
157
+ status=None,
158
+ error_name=None,
159
+ )
160
+ assert terminal_with_detail.error_type == "UPSTREAM_EXECUTION_ERROR"
161
+ assert "detail=tool crashed" in terminal_with_detail.message
162
+
163
+ inband_with_status = _format_inband_upstream_error(
164
+ source="message.updated",
165
+ detail=None,
166
+ status=429,
167
+ error_name=None,
168
+ )
169
+ assert inband_with_status.error_type == "UPSTREAM_QUOTA_EXCEEDED"
170
+ assert "status=429" in inband_with_status.message
171
+
172
+ provider_auth = _format_inband_upstream_error(
173
+ source="message.updated",
174
+ detail=None,
175
+ status=None,
176
+ error_name="ProviderAuthError",
177
+ )
178
+ assert provider_auth.state == TaskState.TASK_STATE_AUTH_REQUIRED
179
+ assert provider_auth.error_type == "UPSTREAM_UNAUTHORIZED"
180
+
181
+
149
182
  @pytest.mark.asyncio
150
183
  async def test_await_stream_terminal_signal_handles_shortcuts_and_missing_signal() -> None:
151
184
  loop = asyncio.get_running_loop()
@@ -118,13 +118,17 @@ def test_cli_help_does_not_require_runtime_settings(capsys: pytest.CaptureFixtur
118
118
  assert (
119
119
  "OpenCode A2A runtime for explicit service startup and peer calls. A2A Protocol 1.0 only."
120
120
  ) in help_text
121
- assert "___ ____" in help_text
122
- assert "| | | | '_ \\ / _ \\ '_ \\| | / _ \\ / _` |/ _ \\_____ / _ \\" in help_text
121
+ assert "██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗" in help_text
122
+ assert "███████║ █████╔╝███████║" in help_text
123
123
  assert "opencode-a2a <command> [arguments] [options]" in help_text
124
- assert "A2A_STATIC_AUTH_CREDENTIALS" in help_text
125
- assert "opencode serve --hostname 127.0.0.1 --port 4096" in help_text
126
- assert "A2A_CLIENT_BEARER_TOKEN=peer-token" in help_text
127
- assert "/.well-known/agent-card.json" in help_text
124
+ assert "uv tool install --upgrade opencode-a2a\n\nOpenCode A2A runtime" in help_text
125
+ assert "Command-specific help:" in help_text
126
+ assert "opencode-a2a serve --help" in help_text
127
+ assert "opencode-a2a call --help" in help_text
128
+ assert "A2A_STATIC_AUTH_CREDENTIALS" not in help_text
129
+ assert "opencode serve --hostname 127.0.0.1 --port 4096" not in help_text
130
+ assert "A2A_CLIENT_BEARER_TOKEN=peer-token" not in help_text
131
+ assert "/.well-known/agent-card.json" not in help_text
128
132
  assert "{call}" not in help_text
129
133
  assert "serve" in help_text
130
134
  assert "deploy-release" not in help_text
@@ -180,11 +184,29 @@ def test_cli_serve_subcommand_with_invalid_configuration_prints_help(
180
184
 
181
185
  help_text = capsys.readouterr().out
182
186
  assert "Run the OpenCode A2A service. A2A Protocol 1.0 only." in help_text
187
+ assert "A2A_STATIC_AUTH_CREDENTIALS" in help_text
188
+ assert "opencode serve --hostname 127.0.0.1 --port 4096" in help_text
183
189
  assert "configuration errors:" in help_text
184
190
  assert "Configure runtime authentication via A2A_STATIC_AUTH_CREDENTIALS" in help_text
185
191
  serve_mock.assert_not_called()
186
192
 
187
193
 
194
+ def test_cli_serve_help_prints_serve_specific_examples(
195
+ capsys: pytest.CaptureFixture[str],
196
+ ) -> None:
197
+ with mock.patch("opencode_a2a.cli.serve_main") as serve_mock:
198
+ with pytest.raises(SystemExit) as excinfo:
199
+ cli.main(["serve", "--help"])
200
+
201
+ assert excinfo.value.code == 0
202
+ help_text = capsys.readouterr().out
203
+ assert "Run the OpenCode A2A service. A2A Protocol 1.0 only." in help_text
204
+ assert "A2A_STATIC_AUTH_CREDENTIALS" in help_text
205
+ assert "opencode serve --hostname 127.0.0.1 --port 4096" in help_text
206
+ assert "A2A_CLIENT_BEARER_TOKEN=peer-token" not in help_text
207
+ serve_mock.assert_not_called()
208
+
209
+
188
210
  def test_cli_call_without_required_arguments_prints_help(
189
211
  capsys: pytest.CaptureFixture[str],
190
212
  ) -> None:
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from types import MappingProxyType, SimpleNamespace
4
+
5
+ from a2a.auth.user import UnauthenticatedUser
6
+ from a2a.server.context import ServerCallContext
7
+
8
+ from opencode_a2a.server.context_helpers import (
9
+ AuthenticatedIdentityUser,
10
+ normalize_server_call_context,
11
+ )
12
+
13
+
14
+ def test_normalize_server_call_context_preserves_existing_normalized_context() -> None:
15
+ context = ServerCallContext(
16
+ state={"identity": "opaque:test-user"},
17
+ tenant="tenant-1",
18
+ requested_extensions={"ext-a"},
19
+ user=AuthenticatedIdentityUser("opaque:test-user"),
20
+ )
21
+
22
+ normalized = normalize_server_call_context(context)
23
+
24
+ assert normalized is context
25
+ assert normalized.user.is_authenticated is True
26
+ assert normalized.user.user_name == "opaque:test-user"
27
+
28
+
29
+ def test_normalize_server_call_context_coerces_mapping_and_promotes_identity_user() -> None:
30
+ context = SimpleNamespace(
31
+ state=MappingProxyType({"identity": "opaque:test-user", "trace_id": "abc123"}),
32
+ tenant=123,
33
+ requested_extensions=["ext-a", 7, "ext-a"],
34
+ user=UnauthenticatedUser(),
35
+ )
36
+
37
+ normalized = normalize_server_call_context(context)
38
+
39
+ assert normalized is not context
40
+ assert normalized.state == {"identity": "opaque:test-user", "trace_id": "abc123"}
41
+ assert isinstance(normalized.state, dict)
42
+ assert normalized.tenant == ""
43
+ assert normalized.requested_extensions == {"ext-a", "7"}
44
+ assert normalized.user.is_authenticated is True
45
+ assert normalized.user.user_name == "opaque:test-user"
46
+
47
+
48
+ def test_normalize_server_call_context_falls_back_for_invalid_shapes() -> None:
49
+ context = SimpleNamespace(
50
+ state=object(),
51
+ tenant="tenant-3",
52
+ requested_extensions=object(),
53
+ user="not-a-user",
54
+ )
55
+
56
+ normalized = normalize_server_call_context(context)
57
+
58
+ assert normalized.state == {}
59
+ assert normalized.tenant == "tenant-3"
60
+ assert normalized.requested_extensions == set()
61
+ assert isinstance(normalized.user, UnauthenticatedUser)