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