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.
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/AGENTS.md +2 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/PKG-INFO +7 -7
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/README.md +6 -6
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/a2a_utils.py +0 -4
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/cli.py +14 -8
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_queries.py +14 -13
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/output_modes.py +17 -38
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/application.py +16 -13
- opencode_a2a-1.1.1/src/opencode_a2a/server/context_helpers.py +76 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/task_store.py +2 -3
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/PKG-INFO +7 -7
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/SOURCES.txt +2 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_agent_helpers.py +33 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_cli.py +28 -6
- opencode_a2a-1.1.1/tests/server/test_context_helpers.py +61 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_output_negotiation.py +61 -0
- opencode_a2a-1.1.1/tests/test_a2a_utils.py +100 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/uv.lock +3 -3
- opencode_a2a-1.1.0/src/opencode_a2a/server/context_helpers.py +0 -97
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/dependabot.yml +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/workflows/dependency-review.yml +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/workflows/publish.yml +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.github/workflows/validate.yml +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.gitignore +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.pre-commit-config.yaml +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/.secrets.baseline +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/CODE_OF_CONDUCT.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/CONTRIBUTING.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/LICENSE +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/MANIFEST.in +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/SECURITY.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/SUPPORT.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/architecture.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/compatibility.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/conformance-triage.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/conformance.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/extension-specifications.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/guide.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/docs/maintainer-architecture.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/pyproject.toml +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/README.md +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/check_coverage.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/conformance.sh +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/dependency_health.sh +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/doctor.sh +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/find_thin_wrappers.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/health_common.sh +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/lint.sh +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/scripts/smoke_test_built_cli.sh +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/setup.cfg +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/a2a_protocol.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/auth.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/agent_card.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/auth.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/client.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/config.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/error_mapping.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/errors.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/payload_text.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/polling.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/client/request_context.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/config.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/capabilities.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/catalog.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/compatibility.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/contract_docs.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/identifiers.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/private_params.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/contracts/extensions/public_params.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/coordinator.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/event_helpers.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/executor.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/metrics.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/session_manager.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/stream_events.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/stream_runtime.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/stream_state.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/tool_error_mapping.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/tool_orchestration.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/execution/upstream_error_translator.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/extension_negotiation.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/interrupt_request_tracker.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/application.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/dispatch.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/error_responses.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/common.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_callbacks.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/provider_discovery.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/session_control.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/session_lifecycle.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/session_queries.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/workspace_control.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/methods.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/models.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/params.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/metadata_access.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/opencode_upstream_client.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parsing.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parts/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parts/mapping.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/parts/text.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/profile/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/profile/runtime.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/protocol_versions.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/py.typed +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/runtime_state.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/sandbox_policy.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/agent_card.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/client_manager.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/database.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/lifespan.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/middleware.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/migrations.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/openapi.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/request_parsing.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/rest_tasks.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/state_store.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/server/task_store_sdk_compat.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/task_states.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/trace_context.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/upstream_taxonomy.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/dependency_links.txt +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/entry_points.txt +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/requires.txt +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a.egg-info/top_level.txt +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_agent_card.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_client_config.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_client_facade.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_error_mapping.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_payload_text.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_polling.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/client/test_request_context.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/config/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/config/test_settings.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/conftest.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/contracts/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/contracts/test_extension_contract_consistency.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_agent_errors.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_cancellation.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_directory_validation.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_metrics.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_multipart_input.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_opencode_agent_session_binding.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_session_lock_lifecycle.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_session_ownership.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_blocks.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_core.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_interrupts.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/execution/test_streaming_output_contract_logging.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_application_dispatch.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_dispatch_registry.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_error_responses.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_jsonrpc_methods.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_jsonrpc_params.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_jsonrpc_unsupported_method.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_commands.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_interrupts.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_lifecycle.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_prompt_async.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_session_extension_queries.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/jsonrpc/test_opencode_workspace_control_extension.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/test_logging.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/test_typing_contract.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/package/test_version.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/parts/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/parts/test_parts_text.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/profile/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/profile/test_profile_runtime.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/scripts/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/scripts/test_script_health_contract.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_a2a_client_manager.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_agent_card.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_app_behaviors.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_auth.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_call_context_builder.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_cancel_contract.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_database_app_persistence.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_state_store.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_task_store_factory.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/server/test_transport_contract.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/__init__.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/async_iterators.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/fake_client_errors.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/helpers.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/interrupt_clients.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/jsonrpc_error_assertions.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/session_extensions.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/session_query_client.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/settings.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/streaming_output.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/support/workspace_control_client.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/test_metadata_access.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/test_parsing.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/test_trace_context.py +0 -0
- {opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/tests/upstream/__init__.py +0 -0
- {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.
|
|
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
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
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 =
|
|
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,
|
{opencode_a2a-1.1.0 → opencode_a2a-1.1.1}/src/opencode_a2a/jsonrpc/handlers/interrupt_queries.py
RENAMED
|
@@ -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
|
-
{
|
|
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
|
|
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
|
|
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 =
|
|
198
|
-
|
|
199
|
-
|
|
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 =
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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
|
|
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
|
|
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.
|
|
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 "
|
|
122
|
-
assert "
|
|
121
|
+
assert "██████╗ ██████╗ ███████╗███╗ ██╗ ██████╗" in help_text
|
|
122
|
+
assert "███████║ █████╔╝███████║" in help_text
|
|
123
123
|
assert "opencode-a2a <command> [arguments] [options]" in help_text
|
|
124
|
-
assert "
|
|
125
|
-
assert "
|
|
126
|
-
assert "
|
|
127
|
-
assert "
|
|
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)
|