codex-a2a 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/PKG-INFO +2 -2
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/README.md +1 -1
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/compatibility.md +1 -1
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/guide.md +61 -3
- codex_a2a-1.3.0/docs/inspection/README.md +17 -0
- codex_a2a-1.3.0/docs/inspection/error_redaction_policy.md +56 -0
- codex_a2a-1.3.0/docs/inspection/release_integrity_policy.md +33 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/publish_github_release.sh +23 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/manager.py +22 -4
- codex_a2a-1.3.0/src/codex_a2a/client/network_policy.py +156 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/config.py +69 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/extension_specs.py +6 -6
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/extensions.py +9 -13
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/exec_runtime.py +3 -2
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/executor.py +3 -1
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/application.py +28 -2
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_payload_mapping.py +4 -9
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/errors.py +14 -6
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/metrics.py +6 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/profile/runtime.py +5 -1
- codex_a2a-1.3.0/src/codex_a2a/redact.py +79 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/agent_card.py +0 -3
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/application.py +158 -5
- codex_a2a-1.3.0/src/codex_a2a/server/database.py +144 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/http_middlewares.py +183 -1
- codex_a2a-1.3.0/src/codex_a2a/server/runtime_limits.py +338 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/discovery_payloads.py +0 -3
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/PKG-INFO +2 -2
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/SOURCES.txt +10 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_client_manager.py +65 -3
- codex_a2a-1.3.0/tests/client/test_network_policy.py +210 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/config/test_settings.py +41 -0
- codex_a2a-1.3.0/tests/execution/test_error_redaction.py +123 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_discovery_extension.py +49 -1
- codex_a2a-1.3.0/tests/jsonrpc/test_error_redaction.py +126 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_protocol_payload_mapping.py +12 -31
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_publish_github_release_script.py +12 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/profile/test_profile.py +13 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_agent_card.py +16 -0
- codex_a2a-1.3.0/tests/server/test_database.py +223 -0
- codex_a2a-1.3.0/tests/server/test_http_boundary.py +282 -0
- codex_a2a-1.3.0/tests/server/test_runtime_limits.py +542 -0
- codex_a2a-1.3.0/tests/test_redact.py +84 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_codex_client_params.py +0 -12
- codex_a2a-1.2.0/src/codex_a2a/server/database.py +0 -47
- codex_a2a-1.2.0/src/codex_a2a/server/runtime_limits.py +0 -167
- codex_a2a-1.2.0/tests/server/test_database.py +0 -27
- codex_a2a-1.2.0/tests/server/test_runtime_limits.py +0 -122
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/dependabot.yml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/ci.yml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/compatibility.yml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/dependency-health.yml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.github/workflows/publish.yml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.gitignore +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.pre-commit-config.yaml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/.secrets.baseline +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/AGENTS.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/CONTRIBUTING.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/LICENSE +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/SECURITY.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/a2a-extension-baseline.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/architecture.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/conformance-triage.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/conformance.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/extension-specifications.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/docs/maintainer-architecture.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/mypy.ini +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/pyproject.toml +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/README.md +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/audit_low_call_sites.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/check_dead_code.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/conformance.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/dependency_health.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/health_common.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/smoke_test_built_cli.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/smoke_test_live_codex.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/sync_codex_docs.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/tck_auth_plugin.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/validate_baseline.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/scripts/validate_runtime_matrix.sh +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/setup.cfg +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/a2a_proto.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/auth.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/cli.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/agent_card.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/auth.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/client.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/config.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/errors.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/extension_negotiation.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/payload_text.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/client/request_context.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/extension_registry.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/contracts/runtime_output.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/discovery_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/output_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/request_metadata.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/request_overrides.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/response_emitter.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/review_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/session_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_chunks.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_interrupts.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_processor.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/stream_state.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/streaming.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/thread_lifecycle_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/tool_call_payloads.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/execution/watch_events.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/input_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_control.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/discovery_query.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/dispatch.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/exec_control.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/exec_control_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/hooks.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_lifecycle.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_recovery.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupt_recovery_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/interrupts.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/owner_guard.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/params_common.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/payload_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/query_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/request_models.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/review_control.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/review_control_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/session_query.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/thread_lifecycle_control.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/thread_lifecycle_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/turn_control.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/jsonrpc/turn_control_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/logging_context.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/media_modes.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/payload_helpers.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/profile/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/protocol_versions.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/py.typed +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/call_context.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/migrations.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/openapi.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/openapi_contract_fragments.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/output_negotiation.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/push_config_store.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/request_handler.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/runtime_state.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/runtime_state_schema.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/server/task_store.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/client.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/conversation_facade.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/interrupt_bridge.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/interrupts.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/models.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/notification_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/request_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/startup.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/stream_bridge.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a/upstream/transport.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/dependency_links.txt +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/entry_points.txt +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/requires.txt +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/src/codex_a2a.egg-info/top_level.txt +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_agent_card.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_cli.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_client_flow.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_errors.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_lifecycle.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_payload_text.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/client/test_request_context.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/config/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/contracts/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/contracts/test_extension_contract_consistency.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/contracts/test_extension_registry.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_agent_errors.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_cancellation.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_codex_agent_session_binding.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_directory_validation.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_discovery_exec_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_metrics.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_request_metadata.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_review_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_session_ownership.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_session_persistence.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_session_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_stream_chunks.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_stream_interrupts.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_streaming_output_contract.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_thread_lifecycle_runtime.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/execution/test_tool_call_payloads.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/fixtures/codex_app_server/command_execution_output_delta.json +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/fixtures/codex_app_server/file_change_output_delta.json +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_exec_extension.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_session_extension.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_thread_lifecycle_extension.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_codex_turn_review_extension.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_dispatch.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_guard_hooks.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_interrupt_lifecycle.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_interrupt_params.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_interrupt_protocol_handlers.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/jsonrpc/test_jsonrpc_models.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_logging.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_release_distribution_contract.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_typing_contract.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/package/test_version.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/profile/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/test_audit_low_call_sites.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/test_script_health_contract.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/scripts/test_tck_auth_plugin.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_auth.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_call_context_builder.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_cli.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_database_app_persistence.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_migrations.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_output_negotiation.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_push_config_store.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_request_handler.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_runtime_state.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_task_store.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/server/test_transport_contract.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/context.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/dummy_clients.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/fixtures.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/http_auth.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/jsonrpc_errors.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/support/settings.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/test_a2a_proto.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/test_input_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/test_protocol_versions.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/__init__.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_interrupt_bridge.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_interrupt_persistence.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_modular_upstream_components.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/tests/upstream/test_request_mapping.py +0 -0
- {codex_a2a-1.2.0 → codex_a2a-1.3.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-a2a
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Codex A2A runtime adapter
|
|
5
5
|
Author: liujuanjuan1984@Intelligent-Internet
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -164,7 +164,7 @@ Authenticated extended card:
|
|
|
164
164
|
- JSON-RPC: `agent/getAuthenticatedExtendedCard`
|
|
165
165
|
- HTTP: `GET /extendedAgentCard`
|
|
166
166
|
|
|
167
|
-
Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; see the Usage Guide for the complete client-side matrix.
|
|
167
|
+
Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; credentials are only sent to hosts listed in `A2A_CLIENT_ALLOWED_HOSTS`. The embedded `a2a_call(...)` tool also rejects private/loopback targets unless `A2A_CLIENT_ALLOW_PRIVATE_HOSTS=true`; see the Usage Guide for the complete client-side matrix.
|
|
168
168
|
|
|
169
169
|
## When To Use It
|
|
170
170
|
|
|
@@ -117,7 +117,7 @@ Authenticated extended card:
|
|
|
117
117
|
- JSON-RPC: `agent/getAuthenticatedExtendedCard`
|
|
118
118
|
- HTTP: `GET /extendedAgentCard`
|
|
119
119
|
|
|
120
|
-
Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; see the Usage Guide for the complete client-side matrix.
|
|
120
|
+
Outbound peer auth is configured with `A2A_CLIENT_BEARER_TOKEN` or `A2A_CLIENT_BASIC_AUTH`; credentials are only sent to hosts listed in `A2A_CLIENT_ALLOWED_HOSTS`. The embedded `a2a_call(...)` tool also rejects private/loopback targets unless `A2A_CLIENT_ALLOW_PRIVATE_HOSTS=true`; see the Usage Guide for the complete client-side matrix.
|
|
121
121
|
|
|
122
122
|
## When To Use It
|
|
123
123
|
|
|
@@ -101,7 +101,7 @@ Execution-environment boundary fields are also published through the runtime pro
|
|
|
101
101
|
- Deployment-conditional methods must be declared as conditional rather than silently disappearing.
|
|
102
102
|
- Rich input mapping is compatibility-sensitive across the core A2A message surface and `codex.turns.steer`. Changes to supported part types, `Part(url|raw)` image handling, or `Part(data)` mention/skill mapping should be treated as wire-level behavior changes.
|
|
103
103
|
- `codex.exec.*` is compatibility-sensitive as the standalone interactive exec contract. Changes to handle shapes, task-stream delivery, or lifecycle method names should be treated as wire-level changes.
|
|
104
|
-
- `codex.discovery.*` is compatibility-sensitive as the stable discovery contract for
|
|
104
|
+
- `codex.discovery.*` is compatibility-sensitive as the stable discovery contract for normalized skill/app/plugin summaries and `mention_path` identifiers. Local paths (`skill.path`, marketplace paths) and raw upstream records are intentionally excluded from discovery responses; changes to normalized item fields, plugin marketplace mapping, or discovery watch task payload kinds should be treated as wire-level changes.
|
|
105
105
|
- `codex.threads.*` is compatibility-sensitive as the provider-private thread lifecycle contract. Changes to lifecycle method names, watch payload kinds, or watch-task bridge event names should be treated as wire-level changes.
|
|
106
106
|
- `codex.turns.*` is compatibility-sensitive as the active-turn control contract. Changes to `expected_turn_id` semantics, same-turn rich-input handling, or rejected override fields should be treated as wire-level changes.
|
|
107
107
|
- `codex.review.*` is compatibility-sensitive as the review control and watch contract. Changes to supported target types, `delivery` semantics, review watch payload kinds, or review watch event names should be treated as wire-level changes.
|
|
@@ -189,15 +189,49 @@ Use the grouped sections below as the deployment-first reading order:
|
|
|
189
189
|
- `A2A_HOST`: bind host, default `127.0.0.1`
|
|
190
190
|
- `A2A_PORT`: bind port, default `8000`
|
|
191
191
|
- `A2A_PUBLIC_URL`: externally reachable A2A URL prefix, default `http://127.0.0.1:8000`
|
|
192
|
+
- `A2A_ALLOWED_ORIGINS`: comma-separated extra browser-origin allowlist for inbound requests. Requests carrying an `Origin` header must match the origin of `A2A_PUBLIC_URL` or an entry here; mismatches are rejected with `403` (CSRF guard). Requests without an `Origin` header (CLI/SDK clients) are unaffected.
|
|
193
|
+
- `A2A_ALLOWED_HOSTS`: comma-separated `Host` header allowlist (exact names or `*.example.com` wildcards). When configured, every inbound request must present a matching `Host` header. Binding to a non-loopback address without this allowlist logs a startup warning (DNS rebinding risk).
|
|
192
194
|
- `A2A_DATABASE_URL`: SQLAlchemy async database URL. Defaults to SQLite under `${CODEX_WORKSPACE_ROOT}/.codex-a2a/codex-a2a.db`.
|
|
193
195
|
- `A2A_DATABASE_URL` also owns the adapter-managed runtime-state schema lifecycle. On startup, `codex-a2a` auto-creates the runtime-state tables, records a schema version for the `runtime_state` scope, and applies in-place migrations for those tables only.
|
|
194
196
|
- The adapter-managed runtime-state schema is limited to `a2a_session_bindings`, `a2a_session_owners`, `a2a_pending_session_claims`, `a2a_pending_interrupt_requests`, and `a2a_schema_version`. It does not own the A2A SDK task-store tables or any upstream Codex/provider-local state.
|
|
195
197
|
- For deployment-specific durability constraints and migration-scope boundaries, see [compatibility.md](./compatibility.md).
|
|
198
|
+
|
|
199
|
+
### SQLite Persistence Hardening
|
|
200
|
+
|
|
201
|
+
File-backed SQLite databases are hardened at startup and on every new connection on POSIX systems:
|
|
202
|
+
|
|
203
|
+
- the database file must be a regular file; symlinks are rejected and fail startup;
|
|
204
|
+
- the file must be owned by the user running `codex-a2a`; a database owned by another user fails startup;
|
|
205
|
+
- the database file is forced to mode `0600`, and existing SQLite sidecar files (`-wal`/`-shm`/`-journal`) are converged to the same mode;
|
|
206
|
+
- the parent directory is created with mode `0700` when it does not exist; keep the `.codex-a2a` directory private (no group/world access);
|
|
207
|
+
- if the database is replaced by a symlink, a special file, or a foreign-owned file while the service is running, the next connection fails closed.
|
|
208
|
+
|
|
209
|
+
`A2A_DATABASE_URL` values that do not map to a plain file path are exempt: `:memory:` and `file:` URI-style database components (including in-memory shared-cache databases) are not hardened. Prefer a plain absolute file path for deployments that need the startup guarantees. Non-POSIX platforms rely on the user account ACLs of the hosting directory; the same private-directory requirement applies there.
|
|
210
|
+
|
|
211
|
+
Deployment requirements:
|
|
212
|
+
|
|
213
|
+
- run `codex-a2a` as a dedicated user and keep the database directory outside world-readable workspace trees;
|
|
214
|
+
- do not place the SQLite file on network-mounted or sync-managed directories;
|
|
215
|
+
- when migrating an existing database, fix ownership and mode before startup: `chown <service-user> <db>` and `chmod 600 <db>`, and ensure the parent directory is not group/world accessible.
|
|
216
|
+
|
|
217
|
+
### Inbound Origin and Host Boundary
|
|
218
|
+
|
|
219
|
+
Browsers store and automatically attach Basic credentials, and they send an `Origin` header with every request. Without an origin boundary, a malicious web page could cross-site trigger `SendMessage`, `CancelTask`, or task subscription against a Basic-protected service. The service therefore enforces:
|
|
220
|
+
|
|
221
|
+
- every request carrying an `Origin` header must match the origin of `A2A_PUBLIC_URL` (scheme, host, and non-default port) or an entry in `A2A_ALLOWED_ORIGINS`; mismatches return `403` before authentication. `Origin: null` (sandboxed iframes) is rejected unless explicitly allowlisted;
|
|
222
|
+
- requests without an `Origin` header — normal CLI/SDK peers — are not subject to origin checks;
|
|
223
|
+
- when `A2A_ALLOWED_HOSTS` is configured, every request must present a matching `Host` header (exact names, optional `host:port`, or `*.example.com` wildcards); mismatches return `403`. This also blocks DNS rebinding, where an attacker-controlled hostname resolves to the service address and the browser sends that hostname as `Host`;
|
|
224
|
+
- binding to a non-loopback address (`0.0.0.0`, `::`, or a LAN/interface address) without `A2A_ALLOWED_HOSTS` logs a startup warning: the service is then exposed to DNS rebinding and should only run behind a trusted network boundary or a reverse proxy that validates `Host`.
|
|
225
|
+
|
|
226
|
+
For browser-based clients, prefer the same origin as `A2A_PUBLIC_URL`, or explicitly allow the dashboard origin via `A2A_ALLOWED_ORIGINS` and treat `A2A_ALLOWED_HOSTS` as part of the deployment contract. Basic authentication should not be relied on as the sole browser-facing boundary: combine it with the origin/host checks above, short-lived credentials, and TLS at the public URL.
|
|
227
|
+
|
|
196
228
|
- `A2A_LOG_LEVEL`: `DEBUG/INFO/WARNING/ERROR`, default `WARNING`
|
|
197
229
|
- `A2A_LOG_PAYLOADS`: log A2A/Codex payload bodies, default `false`
|
|
198
230
|
- `A2A_LOG_BODY_LIMIT`: payload log body size limit, default `0` (no truncation)
|
|
199
231
|
- `A2A_REQUEST_BODY_MAX_BYTES`: hard HTTP request body limit, default `4194304` (4 MiB); set `0` to disable
|
|
200
232
|
- `A2A_MAX_CONCURRENT_OPERATIONS`: process-local active POST and task-subscription limit, default `32`; set `0` to disable
|
|
233
|
+
- `A2A_RATE_LIMIT_ENABLED` / `A2A_RATE_LIMIT_WINDOW_SECONDS` / `A2A_RATE_LIMIT_MAX_REQUESTS`: per-credential sliding-window rate limiting (per-peer-IP for the unauthenticated Agent Card surface). Defaults: `true` / `60` seconds / `120` requests. Exceeding the window returns HTTP `429` with a `Retry-After` header; `A2A_RATE_LIMIT_ENABLED=false` disables the limiter. The limiter is process-local; multi-process deployments should place a shared gateway in front or rely on per-instance limits.
|
|
234
|
+
- `A2A_STREAM_MAX_BYTES` / `A2A_STREAM_MAX_DURATION_SECONDS` / `A2A_STREAM_IDLE_TIMEOUT_SECONDS`: streaming (SSE) response budgets for total serialized bytes, total duration, and idle gap. Defaults: `67108864` bytes / `3600` seconds / `120` seconds; `0` disables the respective budget. When a budget is exceeded the server ends the stream with an SSE `event: error` frame; if the very first event already exceeds the budget, REST requests are rejected with HTTP `429` `RESOURCE_EXHAUSTED` before the SSE stream starts.
|
|
201
235
|
- `A2A_ENABLE_METRICS_ENDPOINT`: enable the authenticated Prometheus `/metrics` endpoint, default `true`
|
|
202
236
|
- `A2A_TITLE`: agent name, default `Codex A2A`
|
|
203
237
|
- `A2A_DESCRIPTION`: agent description exposed on Agent Card and docs surfaces
|
|
@@ -217,6 +251,18 @@ These settings affect peer calls made through `codex-a2a call` or the embedded `
|
|
|
217
251
|
- `A2A_CLIENT_SUPPORTED_TRANSPORTS`: comma-separated outbound transport preference list, default `JSONRPC,HTTP+JSON`
|
|
218
252
|
- `A2A_CLIENT_BEARER_TOKEN`: optional bearer token for the target peer service
|
|
219
253
|
- `A2A_CLIENT_BASIC_AUTH`: optional Basic auth credential for the target peer service when bearer auth is not configured
|
|
254
|
+
- `A2A_CLIENT_ALLOWED_HOSTS`: comma-separated allowlist of outbound target hosts for `a2a_call(...)` (exact names or `*.example.com` wildcards). When configured, outbound calls are restricted to matching hosts, and outbound credentials (`A2A_CLIENT_BEARER_TOKEN` / `A2A_CLIENT_BASIC_AUTH`) are only sent to allowlisted hosts.
|
|
255
|
+
- `A2A_CLIENT_ALLOW_PRIVATE_HOSTS`: default `false`. When `false`, outbound `a2a_call(...)` targets that resolve to private, loopback, link-local, reserved, or multicast addresses are rejected (SSRF / DNS-rebinding guard). Set to `true` only when the deployment intentionally targets A2A peers on the local network.
|
|
256
|
+
|
|
257
|
+
### Outbound Network Policy
|
|
258
|
+
|
|
259
|
+
The embedded `a2a_call(...)` tool lets the upstream model choose the target URL, so the adapter applies a fail-closed network policy before opening any connection:
|
|
260
|
+
|
|
261
|
+
- only `http`/`https` schemes are accepted, and URLs carrying userinfo credentials are rejected;
|
|
262
|
+
- when `A2A_CLIENT_ALLOWED_HOSTS` is set, the target host must match the allowlist (exact or `*.example.com` wildcard);
|
|
263
|
+
- unless `A2A_CLIENT_ALLOW_PRIVATE_HOSTS=true`, the resolved addresses must be public; private/loopback/link-local/reserved addresses are rejected even when the hostname matches the allowlist (DNS rebinding defense);
|
|
264
|
+
- outbound credentials are only attached to allowlisted hosts. If credentials are configured without an allowlist, they are never sent and a warning is logged;
|
|
265
|
+
- the `codex-a2a call` CLI remains a manual operator action and is not subject to the allowlist, but still rejects non-http(s) schemes through the shared client URL handling.
|
|
220
266
|
|
|
221
267
|
### Upstream Codex Configuration
|
|
222
268
|
|
|
@@ -237,6 +283,7 @@ These variables are forwarded to the local `codex app-server` subprocess.
|
|
|
237
283
|
- `A2A_ENABLE_TURN_CONTROL`: expose `codex.turns.steer` on JSON-RPC extensions, default `true`
|
|
238
284
|
- `A2A_ENABLE_REVIEW_CONTROL`: expose `codex.review.start` and `codex.review.watch` on JSON-RPC extensions, default `false`
|
|
239
285
|
- `A2A_ENABLE_EXEC_CONTROL`: expose `codex.exec.*` on JSON-RPC extensions, default `false`
|
|
286
|
+
- `A2A_EXPOSE_WORKSPACE_ROOT_IN_CARD`: default `false`; controls whether the authenticated extended Agent Card includes the local `CODEX_WORKSPACE_ROOT` in the structured profile `runtime_context`. Keep disabled unless trusted callers explicitly need the host path.
|
|
240
287
|
- `A2A_ALLOW_DIRECTORY_OVERRIDE`: allow `metadata.codex.directory` overrides within the configured workspace boundary, default `true`
|
|
241
288
|
- `A2A_SESSION_CACHE_TTL_SECONDS`: in-memory TTL for session mapping, default `3600`
|
|
242
289
|
- `A2A_SESSION_CACHE_MAXSIZE`: max local process session-cache entries, default `10000`
|
|
@@ -274,12 +321,20 @@ These variables are forwarded to the local `codex app-server` subprocess.
|
|
|
274
321
|
| `A2A_HOST` | Bind host |
|
|
275
322
|
| `A2A_PORT` | Bind port |
|
|
276
323
|
| `A2A_PUBLIC_URL` | Public URL prefix |
|
|
324
|
+
| `A2A_ALLOWED_ORIGINS` | Inbound browser-origin allowlist (CSRF guard) |
|
|
325
|
+
| `A2A_ALLOWED_HOSTS` | Inbound Host allowlist (DNS rebinding guard) |
|
|
277
326
|
| `A2A_DATABASE_URL` | Persistence DB URL |
|
|
278
327
|
| `A2A_LOG_LEVEL` | Log level |
|
|
279
328
|
| `A2A_LOG_PAYLOADS` | Log bodies |
|
|
280
329
|
| `A2A_LOG_BODY_LIMIT` | Body log limit |
|
|
281
330
|
| `A2A_REQUEST_BODY_MAX_BYTES` | HTTP request body limit |
|
|
282
331
|
| `A2A_MAX_CONCURRENT_OPERATIONS` | Active operation capacity |
|
|
332
|
+
| `A2A_RATE_LIMIT_ENABLED` | Sliding-window rate limiter toggle |
|
|
333
|
+
| `A2A_RATE_LIMIT_WINDOW_SECONDS` | Rate-limit window length |
|
|
334
|
+
| `A2A_RATE_LIMIT_MAX_REQUESTS` | Max requests per window per key |
|
|
335
|
+
| `A2A_STREAM_MAX_BYTES` | Streaming byte budget |
|
|
336
|
+
| `A2A_STREAM_MAX_DURATION_SECONDS` | Streaming duration budget |
|
|
337
|
+
| `A2A_STREAM_IDLE_TIMEOUT_SECONDS` | Streaming idle timeout |
|
|
283
338
|
| `A2A_TITLE` | Agent title |
|
|
284
339
|
| `A2A_DESCRIPTION` | Agent description |
|
|
285
340
|
| `A2A_VERSION` | Agent version |
|
|
@@ -302,6 +357,8 @@ These variables are forwarded to the local `codex app-server` subprocess.
|
|
|
302
357
|
| `A2A_CLIENT_CARD_FETCH_TIMEOUT_SECONDS` | Card fetch timeout |
|
|
303
358
|
| `A2A_CLIENT_USE_CLIENT_PREFERENCE` | Transport preference |
|
|
304
359
|
| `A2A_CLIENT_SUPPORTED_TRANSPORTS` | Supported transports |
|
|
360
|
+
| `A2A_CLIENT_ALLOWED_HOSTS` | Outbound host allowlist (SSRF / credential binding) |
|
|
361
|
+
| `A2A_CLIENT_ALLOW_PRIVATE_HOSTS` | Allow private/loopback outbound targets |
|
|
305
362
|
| `A2A_INTERRUPT_REQUEST_TTL_SECONDS` | Interrupt TTL |
|
|
306
363
|
| `A2A_EXECUTION_SANDBOX_MODE` | Discovery sandbox mode |
|
|
307
364
|
| `A2A_EXECUTION_SANDBOX_FILESYSTEM_SCOPE` | Discovery FS scope |
|
|
@@ -473,6 +530,7 @@ On the current npm global install layout for Linux x64, the command above resolv
|
|
|
473
530
|
- On the core chat surface, the `application/json` input mode is intentionally narrower than arbitrary JSON: only `Part(data={"type":"mention"|"skill", ...})` is part of the declared stable contract.
|
|
474
531
|
- Image input maps to upstream `turn/start.input[].type=input_image`.
|
|
475
532
|
- `mention.path` and `skill.path` are forwarded verbatim. The service does not guess app or plugin identifiers from display names.
|
|
533
|
+
- Discovery responses no longer expose local `skill.path` values; callers that need rich-input skill items must supply the path from their own context.
|
|
476
534
|
- `local_image` is not part of the current declared stable rich-input contract.
|
|
477
535
|
- Session query projections currently use the upstream Codex `session_id` as the A2A `contextId`. In that projection surface, `contextId` is the canonical session identity and the adapter does not duplicate the same value under `metadata.shared.session.id`.
|
|
478
536
|
- Completed chat turns are persisted as `completed`; `input-required` is reserved for active interrupt asks that still need a reply.
|
|
@@ -654,15 +712,15 @@ This service exposes read-only Codex discovery methods through JSON-RPC:
|
|
|
654
712
|
|
|
655
713
|
Use these methods before constructing rich input:
|
|
656
714
|
|
|
657
|
-
- `skills.list` returns stable
|
|
715
|
+
- `skills.list` returns stable skill `name`/`scope` values; local skill paths are intentionally not exposed
|
|
658
716
|
- `apps.list` returns stable `mention_path=app://<id>` values
|
|
659
717
|
- `plugins.list` and `plugins.read` return stable `mention_path=plugin://<plugin>@<marketplace>` values
|
|
660
718
|
|
|
661
719
|
Result-shape guidance:
|
|
662
720
|
|
|
663
721
|
- use the normalized stable fields declared in Agent Card / OpenAPI first
|
|
664
|
-
-
|
|
665
|
-
- `plugin/list` and `plugin/read` remain upstream experimental; this service exposes a stable minimum subset
|
|
722
|
+
- discovery responses are whitelisted summaries; raw upstream records and local paths are intentionally excluded
|
|
723
|
+
- `plugin/list` and `plugin/read` remain upstream experimental; this service exposes a stable minimum subset without raw passthrough
|
|
666
724
|
|
|
667
725
|
### Skills List (`codex.discovery.skills.list`)
|
|
668
726
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Inspection Deliverables
|
|
2
|
+
|
|
3
|
+
This directory records security-audit decisions and deliverables produced by
|
|
4
|
+
the tracked sub-items of the system security audit ([#331]).
|
|
5
|
+
|
|
6
|
+
Current contents:
|
|
7
|
+
|
|
8
|
+
- [Error redaction policy](./error_redaction_policy.md) — client-facing error
|
|
9
|
+
text masking of absolute local paths (#346).
|
|
10
|
+
- [Release integrity policy](./release_integrity_policy.md) — SHA256 checksum
|
|
11
|
+
manifest for published GitHub release assets (#346).
|
|
12
|
+
|
|
13
|
+
Remaining audit deliverables (full security report, network surface map,
|
|
14
|
+
capability graph, test plan, and architecture invariants) will be added as the
|
|
15
|
+
remaining sub-items are merged.
|
|
16
|
+
|
|
17
|
+
[#331]: https://github.com/liujuanjuan1984/codex-a2a/issues/331
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Error Redaction Policy
|
|
2
|
+
|
|
3
|
+
## Decision
|
|
4
|
+
|
|
5
|
+
Client-visible error text must never expose absolute local filesystem paths.
|
|
6
|
+
All error responses that can leave the process are passed through a single
|
|
7
|
+
deterministic masker (`codex_a2a.redact.redact_absolute_paths`) before
|
|
8
|
+
serialization.
|
|
9
|
+
|
|
10
|
+
Masked output uses the fixed placeholder `<redacted-path>`.
|
|
11
|
+
|
|
12
|
+
## Boundaries covered
|
|
13
|
+
|
|
14
|
+
- JSON-RPC error responses — `codex_a2a.jsonrpc.errors.adapt_jsonrpc_error`
|
|
15
|
+
(message and error metadata, including nested values).
|
|
16
|
+
- REST/HTTP error bodies — `codex_a2a.jsonrpc.errors.build_http_error_body`.
|
|
17
|
+
- Raw exception fallback in
|
|
18
|
+
`codex_a2a.jsonrpc.application._generate_error_response`.
|
|
19
|
+
- Streaming task error messages — `codex_a2a.execution.executor._emit_error`.
|
|
20
|
+
- Interactive exec session failure messages and metadata —
|
|
21
|
+
`codex_a2a.execution.exec_runtime._run_exec_session`.
|
|
22
|
+
- A2A tool-call error results —
|
|
23
|
+
`codex_a2a.execution.executor._handle_a2a_call_tool`; the error text is
|
|
24
|
+
embedded in follow-up turns within the same upstream session, which session
|
|
25
|
+
queries can later expose.
|
|
26
|
+
|
|
27
|
+
## Masking rules
|
|
28
|
+
|
|
29
|
+
Masked:
|
|
30
|
+
|
|
31
|
+
- POSIX absolute paths (`/a/b/c`).
|
|
32
|
+
- Windows drive paths (`C:\a\b`, `C:/a/b`) and UNC paths (`\\server\share`).
|
|
33
|
+
- `file://` URIs that embed a local path (`file:///tmp/x`, `file://C:/tmp/x`).
|
|
34
|
+
- Any other slash-prefixed token (for example API route strings such as
|
|
35
|
+
`/tasks/123`) — masking is deliberately conservative; a slash-prefixed
|
|
36
|
+
token is treated as a potential path rather than risk leaking a real one.
|
|
37
|
+
|
|
38
|
+
Preserved:
|
|
39
|
+
|
|
40
|
+
- Remote URLs (`https://host/path`).
|
|
41
|
+
- Relative paths (`a/b`, `./x`, `../y`).
|
|
42
|
+
- Ordinary prose without absolute paths.
|
|
43
|
+
|
|
44
|
+
## Logging policy
|
|
45
|
+
|
|
46
|
+
Server-side logs intentionally retain full exception context (including
|
|
47
|
+
absolute paths) for diagnosability. Logs are produced on the host that owns
|
|
48
|
+
the filesystem and are not sent to clients. Any pipeline that ships logs off
|
|
49
|
+
the host must apply the same redaction or restrict access before export.
|
|
50
|
+
|
|
51
|
+
## Verification
|
|
52
|
+
|
|
53
|
+
Unit coverage lives in `tests/test_redact.py`, with boundary tests in
|
|
54
|
+
`tests/execution/test_error_redaction.py` and
|
|
55
|
+
`tests/jsonrpc/test_error_redaction.py`. The masker is deterministic and
|
|
56
|
+
idempotent.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Release Integrity Policy
|
|
2
|
+
|
|
3
|
+
## Decision
|
|
4
|
+
|
|
5
|
+
Every GitHub Release must ship a `SHA256SUMS` checksum manifest alongside the
|
|
6
|
+
wheel and sdist artifacts so consumers can verify artifact integrity
|
|
7
|
+
independently of the registry.
|
|
8
|
+
|
|
9
|
+
## Generation
|
|
10
|
+
|
|
11
|
+
`scripts/publish_github_release.sh` regenerates `dist/SHA256SUMS` from the
|
|
12
|
+
release assets at publish time, before upload:
|
|
13
|
+
|
|
14
|
+
- One line per asset in `sha256sum` format: `<sha256-hex> <basename>`.
|
|
15
|
+
- Lines are sorted by basename so the manifest is deterministic.
|
|
16
|
+
- The manifest itself is uploaded as a release asset and is skipped
|
|
17
|
+
idempotently when it is already present on the release.
|
|
18
|
+
|
|
19
|
+
## Verification
|
|
20
|
+
|
|
21
|
+
Consumers verify a downloaded release with:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
sha256sum -c SHA256SUMS
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Scope notes
|
|
28
|
+
|
|
29
|
+
Supply-chain dependency auditing is already covered separately by Dependabot
|
|
30
|
+
and `pip-audit` (part of the baseline validation); this policy covers only the
|
|
31
|
+
published artifact integrity gap from [#346].
|
|
32
|
+
|
|
33
|
+
[#346]: https://github.com/liujuanjuan1984/codex-a2a/issues/346
|
|
@@ -43,6 +43,29 @@ if [[ "${#release_assets[@]}" -eq 0 ]]; then
|
|
|
43
43
|
exit 1
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
+
checksum_manifest_name="SHA256SUMS"
|
|
47
|
+
checksum_path="${dist_dir}/${checksum_manifest_name}"
|
|
48
|
+
|
|
49
|
+
# Regenerate the checksum manifest for the current release assets so every
|
|
50
|
+
# published artifact can be verified against the release (sha256sum -c).
|
|
51
|
+
"${python_bin}" - "${checksum_path}" "${release_assets[@]}" <<'PY'
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
import hashlib
|
|
55
|
+
import sys
|
|
56
|
+
from pathlib import Path
|
|
57
|
+
|
|
58
|
+
manifest_path = Path(sys.argv[1])
|
|
59
|
+
assets = [Path(arg) for arg in sys.argv[2:]]
|
|
60
|
+
lines = []
|
|
61
|
+
for asset in sorted(assets, key=lambda item: item.name):
|
|
62
|
+
digest = hashlib.sha256(asset.read_bytes()).hexdigest()
|
|
63
|
+
lines.append(f"{digest} {asset.name}")
|
|
64
|
+
manifest_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
65
|
+
PY
|
|
66
|
+
|
|
67
|
+
release_assets+=("${checksum_path}")
|
|
68
|
+
|
|
46
69
|
retry() {
|
|
47
70
|
local attempt=1
|
|
48
71
|
local exit_code=0
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import logging
|
|
6
7
|
|
|
7
8
|
from codex_a2a.config import Settings
|
|
8
9
|
from codex_a2a.contracts import extensions as extension_contracts
|
|
9
10
|
|
|
10
11
|
from .client import A2AClient
|
|
11
12
|
from .config import A2AClientConfig
|
|
13
|
+
from .network_policy import validate_agent_url
|
|
12
14
|
from .request_context import build_default_headers
|
|
13
15
|
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
14
18
|
|
|
15
19
|
class A2AClientManager:
|
|
16
20
|
"""Cache `A2AClient` instances by normalized agent URL."""
|
|
@@ -30,6 +34,23 @@ class A2AClientManager:
|
|
|
30
34
|
normalized = agent_url.rstrip("/")
|
|
31
35
|
if not normalized:
|
|
32
36
|
raise ValueError("agent_url is required")
|
|
37
|
+
policy = await validate_agent_url(
|
|
38
|
+
normalized,
|
|
39
|
+
allowed_hosts=self._settings.a2a_client_allowed_hosts,
|
|
40
|
+
allow_private_hosts=self._settings.a2a_client_allow_private_hosts,
|
|
41
|
+
)
|
|
42
|
+
default_headers: dict[str, str] = {}
|
|
43
|
+
if policy.credentials_allowed:
|
|
44
|
+
default_headers = build_default_headers(
|
|
45
|
+
self._settings.a2a_client_bearer_token,
|
|
46
|
+
self._settings.a2a_client_basic_auth,
|
|
47
|
+
)
|
|
48
|
+
elif self._settings.a2a_client_bearer_token or self._settings.a2a_client_basic_auth:
|
|
49
|
+
logger.warning(
|
|
50
|
+
"Outbound A2A credentials are configured but host=%s is not in "
|
|
51
|
+
"A2A_CLIENT_ALLOWED_HOSTS; credentials will not be sent",
|
|
52
|
+
policy.host,
|
|
53
|
+
)
|
|
33
54
|
async with self._lock:
|
|
34
55
|
client = self._clients.get(normalized)
|
|
35
56
|
if client is not None:
|
|
@@ -40,10 +61,7 @@ class A2AClientManager:
|
|
|
40
61
|
request_timeout_seconds=self._settings.a2a_client_timeout_seconds,
|
|
41
62
|
card_fetch_timeout_seconds=self._settings.a2a_client_card_fetch_timeout_seconds,
|
|
42
63
|
use_client_preference=self._settings.a2a_client_use_client_preference,
|
|
43
|
-
default_headers=
|
|
44
|
-
self._settings.a2a_client_bearer_token,
|
|
45
|
-
self._settings.a2a_client_basic_auth,
|
|
46
|
-
),
|
|
64
|
+
default_headers=default_headers,
|
|
47
65
|
supported_transports=list(self._settings.a2a_client_supported_transports),
|
|
48
66
|
accepted_output_modes=["text/plain"],
|
|
49
67
|
extensions=[
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Outbound A2A client network policy (SSRF guard and credential binding).
|
|
2
|
+
|
|
3
|
+
The A2A client is used by the ``a2a_call`` tool, where the target URL is
|
|
4
|
+
produced by the upstream model. Without an explicit policy this creates a
|
|
5
|
+
server-side request forgery surface: the adapter would happily connect to
|
|
6
|
+
private/loopback/link-local addresses (cloud metadata, internal services) and
|
|
7
|
+
would attach globally configured outbound credentials to any endpoint.
|
|
8
|
+
|
|
9
|
+
This module centralizes the checks:
|
|
10
|
+
|
|
11
|
+
- only ``http``/``https`` schemes are accepted;
|
|
12
|
+
- userinfo credentials in the URL are rejected;
|
|
13
|
+
- when ``A2A_CLIENT_ALLOWED_HOSTS`` is configured, the host must match the
|
|
14
|
+
allowlist (exact or ``*.example.com`` wildcard);
|
|
15
|
+
- unless ``A2A_CLIENT_ALLOW_PRIVATE_HOSTS`` is set, DNS resolution results are
|
|
16
|
+
validated and private/loopback/link-local/reserved addresses are rejected
|
|
17
|
+
(this also defends against DNS rebinding at connect time);
|
|
18
|
+
- outbound credentials may only be attached to hosts that match the allowlist.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import ipaddress
|
|
25
|
+
import logging
|
|
26
|
+
import socket
|
|
27
|
+
from collections.abc import Sequence
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from urllib.parse import urlsplit
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
_ALLOWED_SCHEMES = frozenset({"http", "https"})
|
|
34
|
+
|
|
35
|
+
_Address = ipaddress.IPv4Address | ipaddress.IPv6Address
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class A2ANetworkPolicyError(ValueError):
|
|
39
|
+
"""Raised when an outbound agent URL violates the network policy."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class NetworkPolicyDecision:
|
|
44
|
+
"""Outcome of validating an outbound agent URL."""
|
|
45
|
+
|
|
46
|
+
host: str
|
|
47
|
+
allowed_host: bool
|
|
48
|
+
credentials_allowed: bool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def matches_allowed_host(host: str, allowed_hosts: Sequence[str]) -> bool:
|
|
52
|
+
"""Return whether ``host`` matches an allowlist entry.
|
|
53
|
+
|
|
54
|
+
Entries may be exact hostnames or ``*.example.com`` wildcards. A wildcard
|
|
55
|
+
matches any number of subdomain labels but never the bare apex domain.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
normalized = (host or "").strip().lower().rstrip(".")
|
|
59
|
+
if not normalized:
|
|
60
|
+
return False
|
|
61
|
+
for raw_entry in allowed_hosts or ():
|
|
62
|
+
entry = (raw_entry or "").strip().lower().rstrip(".")
|
|
63
|
+
if not entry:
|
|
64
|
+
continue
|
|
65
|
+
if entry.startswith("*."):
|
|
66
|
+
suffix = entry[1:] # ".example.com"
|
|
67
|
+
apex = suffix[1:] # "example.com"
|
|
68
|
+
if normalized != apex and normalized.endswith(suffix):
|
|
69
|
+
return True
|
|
70
|
+
elif normalized == entry:
|
|
71
|
+
return True
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _is_private_address(address: _Address) -> bool:
|
|
76
|
+
return bool(
|
|
77
|
+
address.is_private
|
|
78
|
+
or address.is_loopback
|
|
79
|
+
or address.is_link_local
|
|
80
|
+
or address.is_reserved
|
|
81
|
+
or address.is_multicast
|
|
82
|
+
or address.is_unspecified
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def resolve_host_addresses(host: str) -> tuple[str, ...]:
|
|
87
|
+
"""Resolve ``host`` to a de-duplicated tuple of IP address strings."""
|
|
88
|
+
|
|
89
|
+
loop = asyncio.get_running_loop()
|
|
90
|
+
try:
|
|
91
|
+
infos = await loop.getaddrinfo(host, None, type=socket.SOCK_STREAM)
|
|
92
|
+
except socket.gaierror as exc:
|
|
93
|
+
raise A2ANetworkPolicyError(f"Agent URL host could not be resolved: {host!r}") from exc
|
|
94
|
+
addresses: list[str] = []
|
|
95
|
+
for info in infos:
|
|
96
|
+
ip = str(info[4][0])
|
|
97
|
+
if ip not in addresses:
|
|
98
|
+
addresses.append(ip)
|
|
99
|
+
return tuple(addresses)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def validate_agent_url(
|
|
103
|
+
agent_url: str,
|
|
104
|
+
*,
|
|
105
|
+
allowed_hosts: Sequence[str] | None = None,
|
|
106
|
+
allow_private_hosts: bool = False,
|
|
107
|
+
) -> NetworkPolicyDecision:
|
|
108
|
+
"""Validate an outbound agent URL against the configured network policy."""
|
|
109
|
+
|
|
110
|
+
normalized = (agent_url or "").strip()
|
|
111
|
+
if not normalized:
|
|
112
|
+
raise A2ANetworkPolicyError("agent_url is required")
|
|
113
|
+
|
|
114
|
+
parsed = urlsplit(normalized)
|
|
115
|
+
scheme = (parsed.scheme or "").lower()
|
|
116
|
+
if scheme not in _ALLOWED_SCHEMES:
|
|
117
|
+
raise A2ANetworkPolicyError(
|
|
118
|
+
f"Agent URL scheme must be http or https, got {scheme or 'empty'}"
|
|
119
|
+
)
|
|
120
|
+
host = (parsed.hostname or "").strip().lower()
|
|
121
|
+
if not host:
|
|
122
|
+
raise A2ANetworkPolicyError("Agent URL must include a host")
|
|
123
|
+
if parsed.username or parsed.password:
|
|
124
|
+
raise A2ANetworkPolicyError("Agent URL must not include userinfo credentials")
|
|
125
|
+
|
|
126
|
+
allowlist = tuple(
|
|
127
|
+
(item or "").strip().lower().rstrip(".")
|
|
128
|
+
for item in allowed_hosts or ()
|
|
129
|
+
if (item or "").strip()
|
|
130
|
+
)
|
|
131
|
+
matched = matches_allowed_host(host, allowlist)
|
|
132
|
+
if allowlist and not matched:
|
|
133
|
+
raise A2ANetworkPolicyError(
|
|
134
|
+
f"Agent URL host {host!r} is not allowed by A2A_CLIENT_ALLOWED_HOSTS"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if not allow_private_hosts:
|
|
138
|
+
try:
|
|
139
|
+
addresses = await resolve_host_addresses(host)
|
|
140
|
+
except A2ANetworkPolicyError:
|
|
141
|
+
raise
|
|
142
|
+
except OSError as exc:
|
|
143
|
+
raise A2ANetworkPolicyError(f"Agent URL host could not be resolved: {host!r}") from exc
|
|
144
|
+
private = [
|
|
145
|
+
address for address in addresses if _is_private_address(ipaddress.ip_address(address))
|
|
146
|
+
]
|
|
147
|
+
if private:
|
|
148
|
+
raise A2ANetworkPolicyError(
|
|
149
|
+
f"Agent URL host {host!r} resolves to a private/loopback address: {private[0]}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return NetworkPolicyDecision(
|
|
153
|
+
host=host,
|
|
154
|
+
allowed_host=matched,
|
|
155
|
+
credentials_allowed=bool(allowlist) and matched,
|
|
156
|
+
)
|
|
@@ -320,6 +320,14 @@ class Settings(BaseSettings):
|
|
|
320
320
|
|
|
321
321
|
# A2A settings
|
|
322
322
|
a2a_public_url: str = Field(default="http://127.0.0.1:8000", alias="A2A_PUBLIC_URL")
|
|
323
|
+
a2a_allowed_origins: Annotated[list[str], NoDecode] = Field(
|
|
324
|
+
default_factory=list,
|
|
325
|
+
alias="A2A_ALLOWED_ORIGINS",
|
|
326
|
+
)
|
|
327
|
+
a2a_allowed_hosts: Annotated[list[str], NoDecode] = Field(
|
|
328
|
+
default_factory=list,
|
|
329
|
+
alias="A2A_ALLOWED_HOSTS",
|
|
330
|
+
)
|
|
323
331
|
a2a_project: str | None = Field(default=None, alias="A2A_PROJECT")
|
|
324
332
|
a2a_title: str = Field(default="Codex A2A", alias="A2A_TITLE")
|
|
325
333
|
a2a_description: str = Field(default="A2A wrapper service for Codex", alias="A2A_DESCRIPTION")
|
|
@@ -330,6 +338,10 @@ class Settings(BaseSettings):
|
|
|
330
338
|
a2a_enable_turn_control: bool = Field(default=True, alias="A2A_ENABLE_TURN_CONTROL")
|
|
331
339
|
a2a_enable_review_control: bool = Field(default=False, alias="A2A_ENABLE_REVIEW_CONTROL")
|
|
332
340
|
a2a_enable_exec_control: bool = Field(default=False, alias="A2A_ENABLE_EXEC_CONTROL")
|
|
341
|
+
a2a_expose_workspace_root_in_card: bool = Field(
|
|
342
|
+
default=False,
|
|
343
|
+
alias="A2A_EXPOSE_WORKSPACE_ROOT_IN_CARD",
|
|
344
|
+
)
|
|
333
345
|
a2a_log_level: str = Field(default="WARNING", alias="A2A_LOG_LEVEL")
|
|
334
346
|
a2a_log_payloads: bool = Field(default=False, alias="A2A_LOG_PAYLOADS")
|
|
335
347
|
a2a_log_body_limit: int = Field(default=0, alias="A2A_LOG_BODY_LIMIT")
|
|
@@ -341,6 +353,27 @@ class Settings(BaseSettings):
|
|
|
341
353
|
default=32,
|
|
342
354
|
alias="A2A_MAX_CONCURRENT_OPERATIONS",
|
|
343
355
|
)
|
|
356
|
+
a2a_rate_limit_enabled: bool = Field(default=True, alias="A2A_RATE_LIMIT_ENABLED")
|
|
357
|
+
a2a_rate_limit_window_seconds: float = Field(
|
|
358
|
+
default=60.0,
|
|
359
|
+
alias="A2A_RATE_LIMIT_WINDOW_SECONDS",
|
|
360
|
+
)
|
|
361
|
+
a2a_rate_limit_max_requests: int = Field(
|
|
362
|
+
default=120,
|
|
363
|
+
alias="A2A_RATE_LIMIT_MAX_REQUESTS",
|
|
364
|
+
)
|
|
365
|
+
a2a_stream_max_bytes: int = Field(
|
|
366
|
+
default=64 * 1024 * 1024,
|
|
367
|
+
alias="A2A_STREAM_MAX_BYTES",
|
|
368
|
+
)
|
|
369
|
+
a2a_stream_max_duration_seconds: float = Field(
|
|
370
|
+
default=3600.0,
|
|
371
|
+
alias="A2A_STREAM_MAX_DURATION_SECONDS",
|
|
372
|
+
)
|
|
373
|
+
a2a_stream_idle_timeout_seconds: float = Field(
|
|
374
|
+
default=120.0,
|
|
375
|
+
alias="A2A_STREAM_IDLE_TIMEOUT_SECONDS",
|
|
376
|
+
)
|
|
344
377
|
a2a_documentation_url: str | None = Field(default=None, alias="A2A_DOCUMENTATION_URL")
|
|
345
378
|
a2a_allow_directory_override: bool = Field(default=True, alias="A2A_ALLOW_DIRECTORY_OVERRIDE")
|
|
346
379
|
a2a_host: str = Field(default="127.0.0.1", alias="A2A_HOST")
|
|
@@ -386,6 +419,14 @@ class Settings(BaseSettings):
|
|
|
386
419
|
default_factory=lambda: ["JSONRPC", "HTTP+JSON"],
|
|
387
420
|
alias="A2A_CLIENT_SUPPORTED_TRANSPORTS",
|
|
388
421
|
)
|
|
422
|
+
a2a_client_allowed_hosts: Annotated[list[str], NoDecode] = Field(
|
|
423
|
+
default_factory=list,
|
|
424
|
+
alias="A2A_CLIENT_ALLOWED_HOSTS",
|
|
425
|
+
)
|
|
426
|
+
a2a_client_allow_private_hosts: bool = Field(
|
|
427
|
+
default=False,
|
|
428
|
+
alias="A2A_CLIENT_ALLOW_PRIVATE_HOSTS",
|
|
429
|
+
)
|
|
389
430
|
a2a_interrupt_request_ttl_seconds: int = Field(
|
|
390
431
|
default=3600,
|
|
391
432
|
alias="A2A_INTERRUPT_REQUEST_TTL_SECONDS",
|
|
@@ -448,6 +489,31 @@ class Settings(BaseSettings):
|
|
|
448
489
|
raise ValueError("A2A_STREAM_IDLE_DIAGNOSTIC_SECONDS must be > 0")
|
|
449
490
|
return value
|
|
450
491
|
|
|
492
|
+
@field_validator("a2a_rate_limit_window_seconds")
|
|
493
|
+
@classmethod
|
|
494
|
+
def validate_rate_limit_window_seconds(cls, value: float) -> float:
|
|
495
|
+
if value <= 0:
|
|
496
|
+
raise ValueError("A2A_RATE_LIMIT_WINDOW_SECONDS must be > 0")
|
|
497
|
+
return value
|
|
498
|
+
|
|
499
|
+
@field_validator("a2a_rate_limit_max_requests")
|
|
500
|
+
@classmethod
|
|
501
|
+
def validate_rate_limit_max_requests(cls, value: int) -> int:
|
|
502
|
+
if value <= 0:
|
|
503
|
+
raise ValueError("A2A_RATE_LIMIT_MAX_REQUESTS must be > 0")
|
|
504
|
+
return value
|
|
505
|
+
|
|
506
|
+
@field_validator(
|
|
507
|
+
"a2a_stream_max_bytes",
|
|
508
|
+
"a2a_stream_max_duration_seconds",
|
|
509
|
+
"a2a_stream_idle_timeout_seconds",
|
|
510
|
+
)
|
|
511
|
+
@classmethod
|
|
512
|
+
def validate_stream_budget_values(cls, value: int | float) -> int | float:
|
|
513
|
+
if value < 0:
|
|
514
|
+
raise ValueError("A2A stream budgets must be >= 0")
|
|
515
|
+
return value
|
|
516
|
+
|
|
451
517
|
@field_validator("a2a_interrupt_request_ttl_seconds")
|
|
452
518
|
@classmethod
|
|
453
519
|
def validate_interrupt_request_ttl_seconds(cls, value: int) -> int:
|
|
@@ -459,6 +525,9 @@ class Settings(BaseSettings):
|
|
|
459
525
|
"codex_sandbox_workspace_write_writable_roots",
|
|
460
526
|
"a2a_execution_sandbox_writable_roots",
|
|
461
527
|
"a2a_execution_network_allowed_domains",
|
|
528
|
+
"a2a_client_allowed_hosts",
|
|
529
|
+
"a2a_allowed_origins",
|
|
530
|
+
"a2a_allowed_hosts",
|
|
462
531
|
mode="before",
|
|
463
532
|
)
|
|
464
533
|
@classmethod
|