From 070a8bedb16d72c891a775a4654267b332b77942 Mon Sep 17 00:00:00 2001 From: "helen@cloud" Date: Fri, 13 Mar 2026 03:21:37 -0400 Subject: [PATCH] chore: rename project to opencode-a2a-server #170 --- .github/workflows/ci.yml | 2 +- README.md | 12 +-- SECURITY.md | 2 +- docs/guide.md | 12 +-- pyproject.toml | 14 +-- scripts/deploy.sh | 2 +- scripts/deploy/enable_instance.sh | 4 +- scripts/deploy/install_units.sh | 2 +- scripts/deploy/run_a2a.sh | 8 +- scripts/deploy/setup_instance.sh | 2 +- scripts/deploy/update_a2a.sh | 4 +- scripts/deploy_light.sh | 6 +- scripts/deploy_light_readme.md | 4 +- scripts/deploy_readme.md | 12 +-- scripts/init_system.sh | 6 +- scripts/start_services.sh | 6 +- scripts/start_services_readme.md | 2 +- scripts/uninstall.sh | 2 +- .../__init__.py | 0 .../agent.py | 0 .../app.py | 2 +- .../config.py | 0 .../extension_contracts.py | 0 .../jsonrpc_ext.py | 0 .../opencode_client.py | 0 .../text_parts.py | 0 tests/helpers.py | 4 +- tests/test_agent_card.py | 4 +- tests/test_agent_errors.py | 2 +- tests/test_call_context_builder.py | 2 +- tests/test_cancel_contract.py | 2 +- tests/test_cancellation.py | 8 +- tests/test_deploy_security_contract.py | 22 +++-- tests/test_directory_validation.py | 4 +- tests/test_extension_contract_consistency.py | 8 +- tests/test_opencode_agent_session_binding.py | 4 +- tests/test_opencode_client_params.py | 2 +- tests/test_opencode_session_extension.py | 98 +++++++++---------- tests/test_session_ownership.py | 4 +- tests/test_settings.py | 2 +- tests/test_streaming_output_contract.py | 4 +- tests/test_transport_contract.py | 34 +++---- uv.lock | 2 +- 43 files changed, 157 insertions(+), 153 deletions(-) rename src/{opencode_a2a_serve => opencode_a2a_server}/__init__.py (100%) rename src/{opencode_a2a_serve => opencode_a2a_server}/agent.py (100%) rename src/{opencode_a2a_serve => opencode_a2a_server}/app.py (99%) rename src/{opencode_a2a_serve => opencode_a2a_server}/config.py (100%) rename src/{opencode_a2a_serve => opencode_a2a_server}/extension_contracts.py (100%) rename src/{opencode_a2a_serve => opencode_a2a_server}/jsonrpc_ext.py (100%) rename src/{opencode_a2a_serve => opencode_a2a_server}/opencode_client.py (100%) rename src/{opencode_a2a_serve => opencode_a2a_server}/text_parts.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14ef202..25ece15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: run: bash ./scripts/lint.sh - name: Run mypy - run: uv run mypy src/opencode_a2a_serve + run: uv run mypy src/opencode_a2a_server - name: Run pytest run: uv run pytest diff --git a/README.md b/README.md index 6bbab41..7134139 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# opencode-a2a-serve +# opencode-a2a-server > Turn OpenCode into a stateful A2A service with a clear runtime boundary and production-friendly deployment workflow. -`opencode-a2a-serve` exposes OpenCode through standard A2A interfaces and adds +`opencode-a2a-server` exposes OpenCode through standard A2A interfaces and adds the operational pieces that raw agent runtimes usually do not provide by default: authentication, session continuity, streaming contracts, interrupt handling, deployment tooling, and explicit security guidance. @@ -30,7 +30,7 @@ need a stable service layer around it. This repository provides that layer by: ## Design Principle -One `OpenCode + opencode-a2a-serve` instance pair is treated as a +One `OpenCode + opencode-a2a-server` instance pair is treated as a single-tenant trust boundary. - OpenCode may manage multiple projects/directories, but one deployed instance @@ -45,7 +45,7 @@ single-tenant trust boundary. ```mermaid flowchart TD - Hub["A2A client / a2a-client-hub / app"] --> Api["opencode-a2a-serve transport"] + Hub["A2A client / a2a-client-hub / app"] --> Api["opencode-a2a-server transport"] Api --> Mapping["Task / session / interrupt mapping"] Mapping --> Runtime["OpenCode HTTP runtime"] @@ -64,7 +64,7 @@ If you need a client-side integration layer to consume this service, prefer It is a better place for client concerns such as A2A consumption, upstream adapter normalization, and application-facing integration, while -`opencode-a2a-serve` stays focused on the server/runtime boundary around +`opencode-a2a-server` stays focused on the server/runtime boundary around OpenCode. ## Security Model @@ -101,7 +101,7 @@ uv sync --all-extras 3. Start this service: ```bash -A2A_BEARER_TOKEN=dev-token uv run opencode-a2a-serve +A2A_BEARER_TOKEN=dev-token uv run opencode-a2a-server ``` Default address: `http://127.0.0.1:8000` diff --git a/SECURITY.md b/SECURITY.md index cce854c..8cebcf5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,7 +11,7 @@ not fully isolate upstream model credentials from OpenCode runtime behavior. - `A2A_BEARER_TOKEN` protects access to the A2A surface, but it is not a tenant-isolation boundary inside one deployed instance. -- One `OpenCode + opencode-a2a-serve` instance pair is treated as a +- One `OpenCode + opencode-a2a-server` instance pair is treated as a single-tenant trust boundary by design. - Within one instance, consumers share the same underlying OpenCode workspace/environment by default. diff --git a/docs/guide.md b/docs/guide.md index 98a4f0d..e500ac9 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -18,7 +18,7 @@ and JSON-RPC extension details (README stays at overview level). This section keeps only the protocol-relevant variables. For the full runtime variable catalog and defaults, see -[`../src/opencode_a2a_serve/config.py`](../src/opencode_a2a_serve/config.py). +[`../src/opencode_a2a_server/config.py`](../src/opencode_a2a_server/config.py). For deploy-time inputs and systemd-oriented parameters, see [`../scripts/deploy_readme.md`](../scripts/deploy_readme.md). @@ -84,7 +84,7 @@ Key variables to understand protocol behavior: `Task.metadata.shared.usage` with the same field schema. - Requests require `Authorization: Bearer `; otherwise `401` is returned. Agent Card endpoints are public. -- Within one `opencode-a2a-serve` instance, all consumers share the same +- Within one `opencode-a2a-server` instance, all consumers share the same underlying OpenCode workspace/environment. This deployment model is not tenant-isolated by default. - Error handling: @@ -503,7 +503,7 @@ If an SSE connection drops, use `GET /v1/tasks/{task_id}:subscribe` to re-subscr - Upstream interruption is best-effort: if upstream returns 404, network errors, or other HTTP errors, A2A cancellation still completes with `TaskState.canceled`. - Idempotency contract: repeated `tasks/cancel` on an already `canceled` task returns the current terminal task state without error. - Terminal subscribe contract: calling `subscribe` on a terminal task replays one terminal `Task` snapshot and then closes the stream. -- The cancel path emits metric log records (`logger=opencode_a2a_serve.agent`): +- The cancel path emits metric log records (`logger=opencode_a2a_server.agent`): - `a2a_cancel_requests_total` - `a2a_cancel_abort_attempt_total` - `a2a_cancel_abort_success_total` @@ -516,7 +516,7 @@ If an SSE connection drops, use `GET /v1/tasks/{task_id}:subscribe` to re-subscr When changing extension methods/errors or extension metadata, validate the single-source contract and generated surfaces together: -1. Update `src/opencode_a2a_serve/extension_contracts.py` first (SSOT). +1. Update `src/opencode_a2a_server/extension_contracts.py` first (SSOT). 2. Run focused contract checks: ```bash @@ -527,7 +527,7 @@ uv run pytest tests/test_extension_contract_consistency.py ```bash uv run pre-commit run --all-files -uv run mypy src/opencode_a2a_serve +uv run mypy src/opencode_a2a_server uv run pytest ``` @@ -541,7 +541,7 @@ The contract check fails when any of these drift: ```bash uv run pre-commit install -uv run mypy src/opencode_a2a_serve +uv run mypy src/opencode_a2a_server uv run pytest ``` diff --git a/pyproject.toml b/pyproject.toml index a333651..9c42216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "opencode-a2a-serve" +name = "opencode-a2a-server" version = "0.1.0" description = "A2A wrapper service for opencode" readme = "README.md" @@ -42,12 +42,12 @@ dev = [ ] [project.scripts] -opencode-a2a-serve = "opencode_a2a_serve.app:main" +opencode-a2a-server = "opencode_a2a_server.app:main" [project.urls] -Homepage = "https://github.com/Intelligent-Internet/opencode-a2a-serve" -Repository = "https://github.com/Intelligent-Internet/opencode-a2a-serve" -Issues = "https://github.com/Intelligent-Internet/opencode-a2a-serve/issues" +Homepage = "https://github.com/Intelligent-Internet/opencode-a2a-server" +Repository = "https://github.com/Intelligent-Internet/opencode-a2a-server" +Issues = "https://github.com/Intelligent-Internet/opencode-a2a-server/issues" [tool.ruff] line-length = 100 @@ -64,7 +64,7 @@ package = true [tool.mypy] python_version = "3.11" -files = ["src/opencode_a2a_serve"] +files = ["src/opencode_a2a_server"] ignore_missing_imports = true show_error_codes = true pretty = true @@ -72,4 +72,4 @@ warn_redundant_casts = true warn_unused_ignores = true [tool.pytest.ini_options] -addopts = "--cov=src/opencode_a2a_serve --cov-report=term-missing --cov-fail-under=80" +addopts = "--cov=src/opencode_a2a_server --cov-report=term-missing --cov-fail-under=80" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index a7adebf..4c7242b 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -159,7 +159,7 @@ USAGE exit 1 fi -export OPENCODE_A2A_DIR="${OPENCODE_A2A_DIR:-/opt/opencode-a2a/opencode-a2a-serve}" +export OPENCODE_A2A_DIR="${OPENCODE_A2A_DIR:-/opt/opencode-a2a/opencode-a2a-server}" export OPENCODE_CORE_DIR="${OPENCODE_CORE_DIR:-/opt/.opencode}" export UV_PYTHON_DIR="${UV_PYTHON_DIR:-/opt/uv-python}" export UV_PYTHON_DIR_GROUP="${UV_PYTHON_DIR_GROUP-opencode}" diff --git a/scripts/deploy/enable_instance.sh b/scripts/deploy/enable_instance.sh index 2a13b53..cbef64c 100755 --- a/scripts/deploy/enable_instance.sh +++ b/scripts/deploy/enable_instance.sh @@ -29,6 +29,6 @@ start_or_restart() { } start_or_restart "opencode@${PROJECT_NAME}.service" -start_or_restart "opencode-a2a@${PROJECT_NAME}.service" +start_or_restart "opencode-a2a-server@${PROJECT_NAME}.service" -sudo systemctl status "opencode-a2a@${PROJECT_NAME}.service" --no-pager +sudo systemctl status "opencode-a2a-server@${PROJECT_NAME}.service" --no-pager diff --git a/scripts/deploy/install_units.sh b/scripts/deploy/install_units.sh index f207e40..67f2c7d 100755 --- a/scripts/deploy/install_units.sh +++ b/scripts/deploy/install_units.sh @@ -11,7 +11,7 @@ set -euo pipefail UNIT_DIR="/etc/systemd/system" OPENCODE_UNIT="${UNIT_DIR}/opencode@.service" -A2A_UNIT="${UNIT_DIR}/opencode-a2a@.service" +A2A_UNIT="${UNIT_DIR}/opencode-a2a-server@.service" sudo install -d -m 755 "$UNIT_DIR" diff --git a/scripts/deploy/run_a2a.sh b/scripts/deploy/run_a2a.sh index 5ee43fc..0e4bff6 100755 --- a/scripts/deploy/run_a2a.sh +++ b/scripts/deploy/run_a2a.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash -# Wrapper to run opencode-a2a-serve from the shared venv. +# Wrapper to run opencode-a2a-server from the shared venv. set -euo pipefail -OPENCODE_A2A_DIR="${OPENCODE_A2A_DIR:-/opt/opencode-a2a/opencode-a2a-serve}" -A2A_BIN="${A2A_BIN:-${OPENCODE_A2A_DIR}/.venv/bin/opencode-a2a-serve}" +OPENCODE_A2A_DIR="${OPENCODE_A2A_DIR:-/opt/opencode-a2a/opencode-a2a-server}" +A2A_BIN="${A2A_BIN:-${OPENCODE_A2A_DIR}/.venv/bin/opencode-a2a-server}" if [[ ! -x "$A2A_BIN" ]]; then - echo "opencode-a2a-serve entrypoint not found at $A2A_BIN" >&2 + echo "opencode-a2a-server entrypoint not found at $A2A_BIN" >&2 exit 1 fi diff --git a/scripts/deploy/setup_instance.sh b/scripts/deploy/setup_instance.sh index ec2234b..dc6e31f 100755 --- a/scripts/deploy/setup_instance.sh +++ b/scripts/deploy/setup_instance.sh @@ -172,7 +172,7 @@ rm -f "$opencode_auth_example_tmp" a2a_secret_example_tmp="$(mktemp)" cat <<'EOF' >"$a2a_secret_example_tmp" -# Root-only runtime secret file for opencode-a2a@.service. +# Root-only runtime secret file for opencode-a2a-server@.service. # Populate A2A_BEARER_TOKEN here if ENABLE_SECRET_PERSISTENCE is not enabled during deploy. A2A_BEARER_TOKEN= EOF diff --git a/scripts/deploy/update_a2a.sh b/scripts/deploy/update_a2a.sh index 0e3f836..c8061e3 100755 --- a/scripts/deploy/update_a2a.sh +++ b/scripts/deploy/update_a2a.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Update the shared opencode-a2a-serve environment (no git operations). +# Update the shared opencode-a2a-server environment (no git operations). # Requires env: OPENCODE_A2A_DIR. set -euo pipefail @@ -20,7 +20,7 @@ if ! command -v uv >/dev/null 2>&1; then exit 1 fi -echo "Syncing opencode-a2a-serve venv in ${OPENCODE_A2A_DIR}..." +echo "Syncing opencode-a2a-server venv in ${OPENCODE_A2A_DIR}..." ( cd "$OPENCODE_A2A_DIR" uv sync --all-extras diff --git a/scripts/deploy_light.sh b/scripts/deploy_light.sh index a1d5c29..4c5910f 100755 --- a/scripts/deploy_light.sh +++ b/scripts/deploy_light.sh @@ -245,7 +245,7 @@ is_opencode_running() { is_a2a_running() { local pid pid="$(a2a_pid 2>/dev/null || true)" - pid_matches_tokens "$pid" "opencode-a2a-serve" + pid_matches_tokens "$pid" "opencode-a2a-server" } cleanup_stale_pidfiles() { @@ -477,7 +477,7 @@ start_a2a() { if [[ -n "$OPENCODE_TIMEOUT_STREAM" ]]; then export OPENCODE_TIMEOUT_STREAM fi - exec uv run opencode-a2a-serve + exec uv run opencode-a2a-server ) >>"$A2A_LOG_FILE" 2>&1 & echo "$!" >"$A2A_PID_FILE" } @@ -505,7 +505,7 @@ stop_instance() { fi if [[ -n "$a2a_current_pid" ]]; then - terminate_pid "$a2a_current_pid" "opencode-a2a-serve" + terminate_pid "$a2a_current_pid" "opencode-a2a-server" fi if [[ -n "$opencode_current_pid" ]]; then terminate_pid "$opencode_current_pid" "opencode serve" diff --git a/scripts/deploy_light_readme.md b/scripts/deploy_light_readme.md index 301530b..94ab20b 100644 --- a/scripts/deploy_light_readme.md +++ b/scripts/deploy_light_readme.md @@ -8,7 +8,7 @@ This script does **not** replace the systemd deployment flow: - It keeps the current two-process runtime model: - `opencode serve` - - `opencode-a2a-serve` + - `opencode-a2a-server` - It does not create system users, isolated data roots, or systemd units. - It is best suited for single-user or small-team environments that already trust the current host user and workspace. @@ -93,7 +93,7 @@ On `start`, the script: 1. validates required inputs and local commands 2. starts `opencode serve` 3. waits until `GET /session` succeeds on the configured OpenCode bind address -4. starts `opencode-a2a-serve` +4. starts `opencode-a2a-server` 5. waits until the local Agent Card endpoint responds successfully If either process fails readiness, the script stops any already-started child process and exits non-zero. diff --git a/scripts/deploy_readme.md b/scripts/deploy_readme.md index 8c26835..dc932f1 100644 --- a/scripts/deploy_readme.md +++ b/scripts/deploy_readme.md @@ -20,8 +20,8 @@ For the overall threat model, see [`../SECURITY.md`](../SECURITY.md). - `systemd` and `sudo` available - OpenCode core path prepared (default `/opt/.opencode`) -- repo path prepared (default `/opt/opencode-a2a/opencode-a2a-serve`) -- A2A venv prepared (default `${OPENCODE_A2A_DIR}/.venv/bin/opencode-a2a-serve`) +- repo path prepared (default `/opt/opencode-a2a/opencode-a2a-server`) +- A2A venv prepared (default `${OPENCODE_A2A_DIR}/.venv/bin/opencode-a2a-server`) - uv python pool prepared (default `/opt/uv-python`) For one-time host bootstrap, see [`init_system_readme.md`](./init_system_readme.md). @@ -119,7 +119,7 @@ For values that support both environment variables and CLI keys: | ENV Name | CLI Key | Required | Default | Notes | | --- | --- | --- | --- | --- | -| `OPENCODE_A2A_DIR` | - | Optional | `/opt/opencode-a2a/opencode-a2a-serve` | Repo path. | +| `OPENCODE_A2A_DIR` | - | Optional | `/opt/opencode-a2a/opencode-a2a-server` | Repo path. | | `OPENCODE_CORE_DIR` | - | Optional | `/opt/.opencode` | OpenCode core path. | | `UV_PYTHON_DIR` | - | Optional | `/opt/uv-python` | uv python pool path. | | `UV_PYTHON_DIR_GROUP` | - | Optional | `opencode` | Optional shared-group access control. | @@ -204,21 +204,21 @@ Status: ```bash sudo systemctl status opencode@.service -sudo systemctl status opencode-a2a@.service +sudo systemctl status opencode-a2a-server@.service ``` Recent logs: ```bash sudo journalctl -u opencode@.service -n 200 --no-pager -sudo journalctl -u opencode-a2a@.service -n 200 --no-pager +sudo journalctl -u opencode-a2a-server@.service -n 200 --no-pager ``` Follow logs: ```bash sudo journalctl -u opencode@.service -f -sudo journalctl -u opencode-a2a@.service -f +sudo journalctl -u opencode-a2a-server@.service -f ``` Remove one instance: diff --git a/scripts/init_system.sh b/scripts/init_system.sh index 738bd73..f35bbed 100755 --- a/scripts/init_system.sh +++ b/scripts/init_system.sh @@ -5,14 +5,14 @@ set -euo pipefail OPENCODE_CORE_DIR="/opt/.opencode" SHARED_WRAPPER_DIR="/opt/opencode-a2a" -OPENCODE_A2A_DIR="${SHARED_WRAPPER_DIR}/opencode-a2a-serve" +OPENCODE_A2A_DIR="${SHARED_WRAPPER_DIR}/opencode-a2a-server" UV_PYTHON_DIR="/opt/uv-python" UV_PYTHON_DIR_MODE="770" UV_PYTHON_DIR_FINAL_MODE="750" UV_PYTHON_DIR_GROUP="opencode" UV_PYTHON_INSTALL_DIR="$UV_PYTHON_DIR" DATA_ROOT="/data/opencode-a2a" -OPENCODE_A2A_REPO="https://github.com/Intelligent-Internet/opencode-a2a-serve.git" +OPENCODE_A2A_REPO="https://github.com/Intelligent-Internet/opencode-a2a-server.git" OPENCODE_A2A_BRANCH="main" OPENCODE_INSTALLER_URL="https://opencode.ai/install" OPENCODE_INSTALLER_VERSION="1.2.5" @@ -586,7 +586,7 @@ fi log_done "Repository check completed." log_start "Checking A2A virtual environment..." -if [[ -x "${OPENCODE_A2A_DIR}/.venv/bin/opencode-a2a-serve" ]]; then +if [[ -x "${OPENCODE_A2A_DIR}/.venv/bin/opencode-a2a-server" ]]; then log_done "A2A venv already initialized; skip." else if ! command -v uv >/dev/null 2>&1; then diff --git a/scripts/start_services.sh b/scripts/start_services.sh index e0adae9..bacefba 100755 --- a/scripts/start_services.sh +++ b/scripts/start_services.sh @@ -71,7 +71,7 @@ if ! command -v uv >/dev/null 2>&1; then fi kill_existing "${OPENCODE_CMD} serve" "opencode serve" -kill_existing "uv run opencode-a2a-serve" "opencode-a2a-serve" +kill_existing "uv run opencode-a2a-server" "opencode-a2a-server" echo "Starting opencode serve..." "$OPENCODE_CMD" serve --log-level "$OPENCODE_LOG_LEVEL" --print-logs >"$OPENCODE_LOG" 2>&1 & @@ -83,9 +83,9 @@ A2A_HOST="$A2A_HOST" \ A2A_PUBLIC_URL="$A2A_PUBLIC_URL" \ A2A_LOG_LEVEL="$A2A_LOG_LEVEL" \ OTEL_INSTRUMENTATION_A2A_SDK_ENABLED="$OTEL_INSTRUMENTATION_A2A_SDK_ENABLED" \ -uv run opencode-a2a-serve >"$A2A_LOG" 2>&1 & +uv run opencode-a2a-server >"$A2A_LOG" 2>&1 & A2A_PID=$! -echo "opencode-a2a-serve pid: ${A2A_PID} (log: $A2A_LOG)" +echo "opencode-a2a-server pid: ${A2A_PID} (log: $A2A_LOG)" cleanup() { echo "Stopping services..." diff --git a/scripts/start_services_readme.md b/scripts/start_services_readme.md index cf12556..0b604c1 100644 --- a/scripts/start_services_readme.md +++ b/scripts/start_services_readme.md @@ -19,7 +19,7 @@ Then run: The script starts: - `opencode serve` -- `uv run opencode-a2a-serve` +- `uv run opencode-a2a-server` It also stops previous matching local processes before startup. diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 8c225e2..4d31140 100755 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -63,7 +63,7 @@ if [[ "$DATA_ROOT" =~ [[:space:]] ]]; then fi UNIT_OPENCODE="opencode@${PROJECT_NAME}.service" -UNIT_A2A="opencode-a2a@${PROJECT_NAME}.service" +UNIT_A2A="opencode-a2a-server@${PROJECT_NAME}.service" APPLY="false" if [[ "$CONFIRM_INPUT" == "UNINSTALL" ]]; then diff --git a/src/opencode_a2a_serve/__init__.py b/src/opencode_a2a_server/__init__.py similarity index 100% rename from src/opencode_a2a_serve/__init__.py rename to src/opencode_a2a_server/__init__.py diff --git a/src/opencode_a2a_serve/agent.py b/src/opencode_a2a_server/agent.py similarity index 100% rename from src/opencode_a2a_serve/agent.py rename to src/opencode_a2a_server/agent.py diff --git a/src/opencode_a2a_serve/app.py b/src/opencode_a2a_server/app.py similarity index 99% rename from src/opencode_a2a_serve/app.py rename to src/opencode_a2a_server/app.py index d997555..9b496be 100644 --- a/src/opencode_a2a_serve/app.py +++ b/src/opencode_a2a_server/app.py @@ -284,7 +284,7 @@ def _build_agent_card_description( ) parts: list[str] = [base, summary] parts.append( - "Within one opencode-a2a-serve instance, all consumers share the same " + "Within one opencode-a2a-server instance, all consumers share the same " "underlying OpenCode workspace/environment." ) project = deployment_context.get("project") diff --git a/src/opencode_a2a_serve/config.py b/src/opencode_a2a_server/config.py similarity index 100% rename from src/opencode_a2a_serve/config.py rename to src/opencode_a2a_server/config.py diff --git a/src/opencode_a2a_serve/extension_contracts.py b/src/opencode_a2a_server/extension_contracts.py similarity index 100% rename from src/opencode_a2a_serve/extension_contracts.py rename to src/opencode_a2a_server/extension_contracts.py diff --git a/src/opencode_a2a_serve/jsonrpc_ext.py b/src/opencode_a2a_server/jsonrpc_ext.py similarity index 100% rename from src/opencode_a2a_serve/jsonrpc_ext.py rename to src/opencode_a2a_server/jsonrpc_ext.py diff --git a/src/opencode_a2a_serve/opencode_client.py b/src/opencode_a2a_server/opencode_client.py similarity index 100% rename from src/opencode_a2a_serve/opencode_client.py rename to src/opencode_a2a_server/opencode_client.py diff --git a/src/opencode_a2a_serve/text_parts.py b/src/opencode_a2a_server/text_parts.py similarity index 100% rename from src/opencode_a2a_serve/text_parts.py rename to src/opencode_a2a_server/text_parts.py diff --git a/tests/helpers.py b/tests/helpers.py index f70fa98..6eb233f 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -7,8 +7,8 @@ from a2a.server.context import ServerCallContext from a2a.types import Message, MessageSendParams, Role, TextPart -from opencode_a2a_serve.config import Settings -from opencode_a2a_serve.opencode_client import OpencodeMessage +from opencode_a2a_server.config import Settings +from opencode_a2a_server.opencode_client import OpencodeMessage def make_settings(**overrides: Any) -> Settings: diff --git a/tests/test_agent_card.py b/tests/test_agent_card.py index b2afbe0..8f5f8c7 100644 --- a/tests/test_agent_card.py +++ b/tests/test_agent_card.py @@ -1,4 +1,4 @@ -from opencode_a2a_serve.app import ( +from opencode_a2a_server.app import ( INTERRUPT_CALLBACK_EXTENSION_URI, MODEL_SELECTION_EXTENSION_URI, PROVIDER_DISCOVERY_EXTENSION_URI, @@ -7,7 +7,7 @@ STREAMING_EXTENSION_URI, build_agent_card, ) -from opencode_a2a_serve.jsonrpc_ext import SESSION_CONTEXT_PREFIX +from opencode_a2a_server.jsonrpc_ext import SESSION_CONTEXT_PREFIX from tests.helpers import make_settings diff --git a/tests/test_agent_errors.py b/tests/test_agent_errors.py index 386955b..88bc788 100644 --- a/tests/test_agent_errors.py +++ b/tests/test_agent_errors.py @@ -5,7 +5,7 @@ from a2a.server.events.event_queue import EventQueue from a2a.types import Task, TaskState, TaskStatusUpdateEvent -from opencode_a2a_serve.agent import OpencodeAgentExecutor +from opencode_a2a_server.agent import OpencodeAgentExecutor from tests.helpers import configure_mock_client_runtime, make_request_context_mock diff --git a/tests/test_call_context_builder.py b/tests/test_call_context_builder.py index 6a43e09..cc18a9b 100644 --- a/tests/test_call_context_builder.py +++ b/tests/test_call_context_builder.py @@ -1,6 +1,6 @@ from starlette.requests import Request -from opencode_a2a_serve.app import IdentityAwareCallContextBuilder +from opencode_a2a_server.app import IdentityAwareCallContextBuilder def _request(path: str, *, raw_path: bytes | None = None) -> Request: diff --git a/tests/test_cancel_contract.py b/tests/test_cancel_contract.py index 0448c22..69ac692 100644 --- a/tests/test_cancel_contract.py +++ b/tests/test_cancel_contract.py @@ -13,7 +13,7 @@ ) from a2a.utils.errors import ServerError -from opencode_a2a_serve.app import OpencodeRequestHandler +from opencode_a2a_server.app import OpencodeRequestHandler def _task(*, task_id: str, context_id: str, state: TaskState) -> Task: diff --git a/tests/test_cancellation.py b/tests/test_cancellation.py index 1b1daad..e207258 100644 --- a/tests/test_cancellation.py +++ b/tests/test_cancellation.py @@ -7,8 +7,8 @@ from a2a.server.events.event_queue import EventQueue from a2a.types import TaskState, TaskStatusUpdateEvent -from opencode_a2a_serve.agent import OpencodeAgentExecutor -from opencode_a2a_serve.opencode_client import OpencodeClient +from opencode_a2a_server.agent import OpencodeAgentExecutor +from opencode_a2a_server.opencode_client import OpencodeClient from tests.helpers import configure_mock_client_runtime, make_request_context_mock @@ -63,7 +63,7 @@ async def send_message( ) cancel_queue = AsyncMock(spec=EventQueue) - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.agent"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.agent"): await asyncio.wait_for(executor.cancel(cancel_context, cancel_queue), timeout=1.0) cancel_events = [call.args[0] for call in cancel_queue.enqueue_event.call_args_list] @@ -233,7 +233,7 @@ async def slow_abort_session( call_context_enabled=False, ) cancel_queue = AsyncMock(spec=EventQueue) - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.agent"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.agent"): await asyncio.wait_for(executor.cancel(cancel_context, cancel_queue), timeout=0.5) cancel_events = [call.args[0] for call in cancel_queue.enqueue_event.call_args_list] diff --git a/tests/test_deploy_security_contract.py b/tests/test_deploy_security_contract.py index 3307087..29a4501 100644 --- a/tests/test_deploy_security_contract.py +++ b/tests/test_deploy_security_contract.py @@ -1,6 +1,5 @@ from pathlib import Path - DEPLOY_SH_TEXT = Path("scripts/deploy.sh").read_text() SETUP_INSTANCE_TEXT = Path("scripts/deploy/setup_instance.sh").read_text() INSTALL_UNITS_TEXT = Path("scripts/deploy/install_units.sh").read_text() @@ -10,10 +9,11 @@ def test_deploy_defaults_to_operator_provisioned_runtime_secrets() -> None: - assert 'export ENABLE_SECRET_PERSISTENCE="${ENABLE_SECRET_PERSISTENCE:-false}"' in DEPLOY_SH_TEXT - assert 'enable_secret_persistence)' in DEPLOY_SH_TEXT + expected_default = 'export ENABLE_SECRET_PERSISTENCE="${ENABLE_SECRET_PERSISTENCE:-false}"' + assert expected_default in DEPLOY_SH_TEXT + assert "enable_secret_persistence)" in DEPLOY_SH_TEXT assert 'if [[ -z "$PROJECT_NAME" ]]; then' in DEPLOY_SH_TEXT - assert 'GH_TOKEN= A2A_BEARER_TOKEN=' not in DEPLOY_SH_TEXT + assert "GH_TOKEN= A2A_BEARER_TOKEN=" not in DEPLOY_SH_TEXT def test_systemd_units_split_secret_and_non_secret_env_files() -> None: @@ -25,13 +25,17 @@ def test_systemd_units_split_secret_and_non_secret_env_files() -> None: def test_setup_instance_generates_examples_and_requires_runtime_secret_files() -> None: + required_a2a_secret = 'require_runtime_secret_file "$A2A_SECRET_ENV_FILE" "A2A_BEARER_TOKEN"' + secret_persistence_notice = ( + "deploy will not write GH_TOKEN, A2A_BEARER_TOKEN, or provider keys to disk" + ) assert ': "${ENABLE_SECRET_PERSISTENCE:=false}"' in SETUP_INSTANCE_TEXT - assert 'opencode.auth.env.example' in SETUP_INSTANCE_TEXT - assert 'a2a.secret.env.example' in SETUP_INSTANCE_TEXT - assert 'opencode.secret.env.example' in SETUP_INSTANCE_TEXT + assert "opencode.auth.env.example" in SETUP_INSTANCE_TEXT + assert "a2a.secret.env.example" in SETUP_INSTANCE_TEXT + assert "opencode.secret.env.example" in SETUP_INSTANCE_TEXT assert 'require_runtime_secret_file "$OPENCODE_AUTH_ENV_FILE" "GH_TOKEN"' in SETUP_INSTANCE_TEXT - assert 'require_runtime_secret_file "$A2A_SECRET_ENV_FILE" "A2A_BEARER_TOKEN"' in SETUP_INSTANCE_TEXT - assert "deploy will not write GH_TOKEN, A2A_BEARER_TOKEN, or provider keys to disk" in SETUP_INSTANCE_TEXT + assert required_a2a_secret in SETUP_INSTANCE_TEXT + assert secret_persistence_notice in SETUP_INSTANCE_TEXT assert "Value for ${key} contains a newline or carriage return" in SETUP_INSTANCE_TEXT diff --git a/tests/test_directory_validation.py b/tests/test_directory_validation.py index 0221ce0..0f1eb08 100644 --- a/tests/test_directory_validation.py +++ b/tests/test_directory_validation.py @@ -4,8 +4,8 @@ import pytest from a2a.server.events.event_queue import EventQueue -from opencode_a2a_serve.agent import OpencodeAgentExecutor -from opencode_a2a_serve.opencode_client import OpencodeClient +from opencode_a2a_server.agent import OpencodeAgentExecutor +from opencode_a2a_server.opencode_client import OpencodeClient from tests.helpers import make_request_context_mock, make_settings diff --git a/tests/test_extension_contract_consistency.py b/tests/test_extension_contract_consistency.py index 63949ee..5d8be4d 100644 --- a/tests/test_extension_contract_consistency.py +++ b/tests/test_extension_contract_consistency.py @@ -1,7 +1,7 @@ import httpx import pytest -from opencode_a2a_serve.app import ( +from opencode_a2a_server.app import ( INTERRUPT_CALLBACK_EXTENSION_URI, MODEL_SELECTION_EXTENSION_URI, PROVIDER_DISCOVERY_EXTENSION_URI, @@ -11,7 +11,7 @@ build_agent_card, create_app, ) -from opencode_a2a_serve.extension_contracts import ( +from opencode_a2a_server.extension_contracts import ( INTERRUPT_CALLBACK_METHODS, PROVIDER_DISCOVERY_METHODS, SESSION_QUERY_METHODS, @@ -22,7 +22,7 @@ build_session_query_extension_params, build_streaming_extension_params, ) -from opencode_a2a_serve.jsonrpc_ext import SESSION_CONTEXT_PREFIX +from opencode_a2a_server.jsonrpc_ext import SESSION_CONTEXT_PREFIX from tests.helpers import DummySessionQueryOpencodeClient as DummyOpencodeClient from tests.helpers import make_settings @@ -212,7 +212,7 @@ async def test_extension_notification_contracts_return_204( params: dict[str, object], interrupt_type: str | None, ) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient(make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False)) if interrupt_type is not None: diff --git a/tests/test_opencode_agent_session_binding.py b/tests/test_opencode_agent_session_binding.py index 3a342c3..61f4ede 100644 --- a/tests/test_opencode_agent_session_binding.py +++ b/tests/test_opencode_agent_session_binding.py @@ -3,8 +3,8 @@ import pytest from a2a.types import Task -from opencode_a2a_serve.agent import OpencodeAgentExecutor -from opencode_a2a_serve.opencode_client import OpencodeMessage +from opencode_a2a_server.agent import OpencodeAgentExecutor +from opencode_a2a_server.opencode_client import OpencodeMessage from tests.helpers import DummyChatOpencodeClient, DummyEventQueue, make_request_context diff --git a/tests/test_opencode_client_params.py b/tests/test_opencode_client_params.py index e523792..d2e08c8 100644 --- a/tests/test_opencode_client_params.py +++ b/tests/test_opencode_client_params.py @@ -1,7 +1,7 @@ import httpx import pytest -from opencode_a2a_serve.opencode_client import OpencodeClient, UpstreamContractError +from opencode_a2a_server.opencode_client import OpencodeClient, UpstreamContractError from tests.helpers import make_settings diff --git a/tests/test_opencode_session_extension.py b/tests/test_opencode_session_extension.py index d9fbaee..de2c39c 100644 --- a/tests/test_opencode_session_extension.py +++ b/tests/test_opencode_session_extension.py @@ -3,8 +3,8 @@ import httpx import pytest -from opencode_a2a_serve.config import Settings -from opencode_a2a_serve.opencode_client import UpstreamContractError +from opencode_a2a_server.config import Settings +from opencode_a2a_server.opencode_client import UpstreamContractError from tests.helpers import DummySessionQueryOpencodeClient as DummyOpencodeClient from tests.helpers import make_settings @@ -20,7 +20,7 @@ def _session_meta(payload: dict) -> dict: @pytest.mark.asyncio async def test_session_query_extension_requires_bearer_token(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyOpencodeClient) app = app_module.create_app( @@ -49,7 +49,7 @@ async def test_session_query_extension_requires_bearer_token(monkeypatch): @pytest.mark.asyncio async def test_session_query_extension_returns_jsonrpc_result(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -122,7 +122,7 @@ async def test_session_query_extension_returns_jsonrpc_result(monkeypatch): @pytest.mark.asyncio async def test_provider_discovery_extension_returns_normalized_catalog(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -182,7 +182,7 @@ async def test_provider_discovery_extension_returns_normalized_catalog(monkeypat @pytest.mark.asyncio async def test_provider_discovery_extension_rejects_invalid_provider_id(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -212,7 +212,7 @@ async def test_provider_discovery_extension_rejects_invalid_provider_id(monkeypa @pytest.mark.asyncio async def test_provider_discovery_extension_maps_payload_mismatch(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -243,7 +243,7 @@ async def test_provider_discovery_extension_maps_payload_mismatch(monkeypatch): @pytest.mark.asyncio async def test_session_query_extension_rejects_non_array_upstream_payload(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class WeirdPayloadClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -276,7 +276,7 @@ def __init__(self, _settings: Settings) -> None: @pytest.mark.asyncio async def test_session_query_extension_session_title_is_extracted_or_placeholder(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class TitlePayloadClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -304,7 +304,7 @@ def __init__(self, _settings: Settings) -> None: @pytest.mark.asyncio async def test_session_query_extension_keeps_session_with_empty_title(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class EmptyTitlePayloadClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -332,7 +332,7 @@ def __init__(self, _settings: Settings) -> None: @pytest.mark.asyncio async def test_session_query_extension_message_role_and_id_from_info(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InfoRoleClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -371,7 +371,7 @@ def __init__(self, _settings: Settings) -> None: @pytest.mark.asyncio async def test_session_query_extension_accepts_top_level_list_payload(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class ListPayloadClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -420,7 +420,7 @@ def __init__(self, _settings: Settings) -> None: @pytest.mark.asyncio async def test_session_query_extension_rejects_non_list_wrapped_payload(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class AltKeyPayloadClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -462,7 +462,7 @@ def __init__(self, _settings: Settings) -> None: @pytest.mark.asyncio async def test_session_query_extension_rejects_cursor_limit(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -504,7 +504,7 @@ async def test_session_query_extension_rejects_cursor_limit(monkeypatch): @pytest.mark.asyncio async def test_session_query_extension_rejects_page_size_pagination(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -545,7 +545,7 @@ async def test_session_query_extension_rejects_page_size_pagination(monkeypatch) @pytest.mark.asyncio async def test_session_query_extension_maps_404_to_session_not_found(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class NotFoundOpencodeClient(DummyOpencodeClient): async def list_messages(self, session_id: str, *, params=None): @@ -580,10 +580,10 @@ async def list_messages(self, session_id: str, *, params=None): @pytest.mark.asyncio async def test_session_query_extension_does_not_log_response_bodies(monkeypatch, caplog): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyOpencodeClient) - caplog.set_level(logging.DEBUG, logger="opencode_a2a_serve.app") + caplog.set_level(logging.DEBUG, logger="opencode_a2a_server.app") app = app_module.create_app( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=True, **_BASE_SETTINGS) @@ -611,7 +611,7 @@ async def test_session_query_extension_does_not_log_response_bodies(monkeypatch, @pytest.mark.asyncio async def test_session_prompt_async_extension_success(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -663,7 +663,7 @@ async def test_session_prompt_async_extension_success(monkeypatch): @pytest.mark.asyncio async def test_session_prompt_async_extension_rejects_invalid_params(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -762,7 +762,7 @@ async def test_session_prompt_async_extension_rejects_invalid_params(monkeypatch @pytest.mark.asyncio async def test_session_prompt_async_extension_rejects_owner_mismatch(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -797,7 +797,7 @@ async def test_session_prompt_async_extension_rejects_owner_mismatch(monkeypatch @pytest.mark.asyncio async def test_session_prompt_async_extension_reuses_directory_boundary_validation(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -842,7 +842,7 @@ async def test_session_prompt_async_extension_reuses_directory_boundary_validati @pytest.mark.asyncio async def test_session_prompt_async_extension_honors_directory_override_switch(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -889,7 +889,7 @@ async def test_session_prompt_async_extension_honors_directory_override_switch(m @pytest.mark.asyncio async def test_session_prompt_async_extension_maps_404_to_session_not_found(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class NotFoundPromptAsyncClient(DummyOpencodeClient): async def session_prompt_async(self, session_id: str, request: dict, *, directory=None): @@ -926,7 +926,7 @@ async def session_prompt_async(self, session_id: str, request: dict, *, director @pytest.mark.asyncio async def test_session_prompt_async_extension_maps_non_204_to_payload_error(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InvalidPromptAsyncStatusClient(DummyOpencodeClient): async def session_prompt_async(self, session_id: str, request: dict, *, directory=None): @@ -963,7 +963,7 @@ async def session_prompt_async(self, session_id: str, request: dict, *, director @pytest.mark.asyncio async def test_session_prompt_async_extension_maps_500_to_upstream_http_error(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class UpstreamErrorPromptAsyncClient(DummyOpencodeClient): async def session_prompt_async(self, session_id: str, request: dict, *, directory=None): @@ -1001,7 +1001,7 @@ async def session_prompt_async(self, session_id: str, request: dict, *, director @pytest.mark.asyncio async def test_session_prompt_async_extension_maps_network_error_to_unreachable(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class NetworkErrorPromptAsyncClient(DummyOpencodeClient): async def session_prompt_async(self, session_id: str, request: dict, *, directory=None): @@ -1037,8 +1037,8 @@ async def session_prompt_async(self, session_id: str, request: dict, *, director @pytest.mark.asyncio async def test_session_prompt_async_release_failure_does_not_override_response(monkeypatch, caplog): - import opencode_a2a_serve.app as app_module - from opencode_a2a_serve.agent import OpencodeAgentExecutor + import opencode_a2a_server.app as app_module + from opencode_a2a_server.agent import OpencodeAgentExecutor class NetworkErrorPromptAsyncClient(DummyOpencodeClient): async def session_prompt_async(self, session_id: str, request: dict, *, directory=None): @@ -1086,7 +1086,7 @@ async def _release_raises( @pytest.mark.asyncio async def test_session_prompt_async_extension_notification_returns_204(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -1117,7 +1117,7 @@ async def test_session_prompt_async_extension_notification_returns_204(monkeypat @pytest.mark.asyncio async def test_session_command_extension_success(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -1167,7 +1167,7 @@ async def test_session_command_extension_success(monkeypatch): @pytest.mark.asyncio async def test_session_command_extension_accepts_request_model(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -1210,7 +1210,7 @@ async def test_session_command_extension_accepts_request_model(monkeypatch): @pytest.mark.asyncio async def test_session_command_extension_rejects_invalid_params(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -1260,7 +1260,7 @@ async def test_session_command_extension_rejects_invalid_params(monkeypatch): @pytest.mark.asyncio async def test_session_command_extension_maps_404_to_session_not_found(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class NotFoundCommandClient(DummyOpencodeClient): async def session_command(self, session_id: str, request: dict, *, directory=None): @@ -1297,7 +1297,7 @@ async def session_command(self, session_id: str, request: dict, *, directory=Non @pytest.mark.asyncio async def test_session_shell_extension_disabled_by_default(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -1331,7 +1331,7 @@ async def test_session_shell_extension_disabled_by_default(monkeypatch): @pytest.mark.asyncio async def test_session_shell_extension_success_when_enabled(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -1382,7 +1382,7 @@ async def test_session_shell_extension_success_when_enabled(monkeypatch): @pytest.mark.asyncio async def test_session_shell_extension_rejects_invalid_params(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -1446,7 +1446,7 @@ async def test_session_shell_extension_rejects_invalid_params(monkeypatch): @pytest.mark.asyncio async def test_session_shell_extension_rejects_owner_mismatch(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings( @@ -1491,7 +1491,7 @@ async def test_session_shell_extension_rejects_owner_mismatch(monkeypatch): @pytest.mark.asyncio async def test_session_command_extension_maps_500_to_upstream_http_error(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class UpstreamErrorCommandClient(DummyOpencodeClient): async def session_command(self, session_id: str, request: dict, *, directory=None): @@ -1529,7 +1529,7 @@ async def session_command(self, session_id: str, request: dict, *, directory=Non @pytest.mark.asyncio async def test_session_shell_extension_maps_network_error_to_unreachable(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class NetworkErrorShellClient(DummyOpencodeClient): async def session_shell(self, session_id: str, request: dict, *, directory=None): @@ -1570,7 +1570,7 @@ async def session_shell(self, session_id: str, request: dict, *, directory=None) @pytest.mark.asyncio async def test_interrupt_callback_extension_permission_reply(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InterruptClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -1645,7 +1645,7 @@ async def permission_reply( @pytest.mark.asyncio async def test_interrupt_callback_extension_rejects_legacy_permission_fields(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -1674,7 +1674,7 @@ async def test_interrupt_callback_extension_rejects_legacy_permission_fields(mon @pytest.mark.asyncio async def test_interrupt_callback_extension_rejects_legacy_metadata_directory(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module dummy = DummyOpencodeClient( make_settings(a2a_bearer_token="t-1", a2a_log_payloads=False, **_BASE_SETTINGS) @@ -1710,7 +1710,7 @@ async def test_interrupt_callback_extension_rejects_legacy_metadata_directory(mo @pytest.mark.asyncio async def test_interrupt_callback_extension_question_reply_and_reject(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InterruptClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -1810,7 +1810,7 @@ async def question_reject( @pytest.mark.asyncio async def test_interrupt_callback_extension_maps_404_to_interrupt_not_found(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class NotFoundInterruptClient(DummyOpencodeClient): async def permission_reply( @@ -1858,7 +1858,7 @@ async def permission_reply( @pytest.mark.asyncio async def test_interrupt_callback_extension_rejects_expired_request(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class ExpiredInterruptClient(DummyOpencodeClient): def resolve_interrupt_request(self, request_id: str): @@ -1890,7 +1890,7 @@ def resolve_interrupt_request(self, request_id: str): @pytest.mark.asyncio async def test_interrupt_callback_extension_rejects_unknown_request_id(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InterruptClient(DummyOpencodeClient): def __init__(self, _settings: Settings) -> None: @@ -1938,7 +1938,7 @@ async def permission_reply( @pytest.mark.asyncio async def test_interrupt_callback_extension_rejects_interrupt_type_mismatch(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InterruptClient(DummyOpencodeClient): pass @@ -1976,7 +1976,7 @@ class InterruptClient(DummyOpencodeClient): @pytest.mark.asyncio async def test_interrupt_callback_extension_rejects_identity_mismatch(monkeypatch): - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class InterruptClient(DummyOpencodeClient): pass diff --git a/tests/test_session_ownership.py b/tests/test_session_ownership.py index 5bd764a..26d7293 100644 --- a/tests/test_session_ownership.py +++ b/tests/test_session_ownership.py @@ -4,8 +4,8 @@ import pytest from a2a.server.events.event_queue import EventQueue -from opencode_a2a_serve.agent import OpencodeAgentExecutor, _TTLCache -from opencode_a2a_serve.opencode_client import OpencodeClient +from opencode_a2a_server.agent import OpencodeAgentExecutor, _TTLCache +from opencode_a2a_server.opencode_client import OpencodeClient from tests.helpers import configure_mock_client_runtime, make_request_context_mock diff --git a/tests/test_settings.py b/tests/test_settings.py index 8debeda..821cae5 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -4,7 +4,7 @@ import pytest from pydantic import ValidationError -from opencode_a2a_serve.config import Settings +from opencode_a2a_server.config import Settings def test_settings_missing_required(): diff --git a/tests/test_streaming_output_contract.py b/tests/test_streaming_output_contract.py index f71c23e..c9ba342 100644 --- a/tests/test_streaming_output_contract.py +++ b/tests/test_streaming_output_contract.py @@ -3,14 +3,14 @@ import pytest from a2a.types import Task, TaskArtifactUpdateEvent, TaskState, TaskStatusUpdateEvent -from opencode_a2a_serve.agent import ( +from opencode_a2a_server.agent import ( BlockType, OpencodeAgentExecutor, _extract_token_usage, _extract_tool_part_payload, _StreamOutputState, ) -from opencode_a2a_serve.opencode_client import OpencodeMessage +from opencode_a2a_server.opencode_client import OpencodeMessage from tests.helpers import DummyEventQueue, make_request_context, make_settings diff --git a/tests/test_transport_contract.py b/tests/test_transport_contract.py index 2013da9..cf20dea 100644 --- a/tests/test_transport_contract.py +++ b/tests/test_transport_contract.py @@ -4,7 +4,7 @@ import pytest from a2a.types import TransportProtocol -from opencode_a2a_serve.app import _normalize_log_level, build_agent_card, create_app +from opencode_a2a_server.app import _normalize_log_level, build_agent_card, create_app from tests.helpers import DummyChatOpencodeClient, make_settings @@ -67,7 +67,7 @@ def test_openapi_jsonrpc_examples_include_core_message_methods() -> None: @pytest.mark.asyncio async def test_dual_stack_send_accepts_transport_native_payloads(monkeypatch) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app(make_settings(a2a_bearer_token="test-token")) @@ -105,7 +105,7 @@ async def test_dual_stack_send_accepts_transport_native_payloads(monkeypatch) -> @pytest.mark.asyncio async def test_dual_stack_send_rejects_cross_transport_payload_shapes(monkeypatch) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app(make_settings(a2a_bearer_token="test-token")) @@ -194,14 +194,14 @@ def _jsonrpc_message_send_payload(text: str) -> dict: @pytest.mark.asyncio async def test_log_payloads_keeps_body_for_rest_handler(monkeypatch, caplog) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app(make_settings(a2a_bearer_token="test-token", a2a_log_payloads=True)) transport = httpx.ASGITransport(app=app) headers = {"Authorization": "Bearer test-token"} - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.app"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.app"): async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: resp = await client.post( "/v1/message:send", @@ -218,14 +218,14 @@ async def test_log_payloads_keeps_body_for_rest_handler(monkeypatch, caplog) -> @pytest.mark.asyncio async def test_log_payloads_streaming_response_path(monkeypatch, caplog) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app(make_settings(a2a_bearer_token="test-token", a2a_log_payloads=True)) transport = httpx.ASGITransport(app=app) headers = {"Authorization": "Bearer test-token"} - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.app"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.app"): async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: async with client.stream( "POST", "/v1/message:stream", headers=headers, json=_rest_message_payload() @@ -245,7 +245,7 @@ async def test_log_payloads_streaming_response_path(monkeypatch, caplog) -> None @pytest.mark.asyncio async def test_log_payloads_omits_non_json_request_body(monkeypatch, caplog) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app(make_settings(a2a_bearer_token="test-token", a2a_log_payloads=True)) @@ -255,7 +255,7 @@ async def test_log_payloads_omits_non_json_request_body(monkeypatch, caplog) -> "Content-Type": "application/octet-stream", } - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.app"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.app"): async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: resp = await client.post("/", headers=headers, content=b"\x00\x01\x02\x03") assert resp.status_code < 500 @@ -269,7 +269,7 @@ async def test_log_payloads_omits_non_json_request_body(monkeypatch, caplog) -> @pytest.mark.asyncio async def test_log_payloads_omits_text_plain_request_body(monkeypatch, caplog) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app(make_settings(a2a_bearer_token="test-token", a2a_log_payloads=True)) @@ -283,7 +283,7 @@ async def test_log_payloads_omits_text_plain_request_body(monkeypatch, caplog) - '{"messageId":"m","role":"user","parts":[{"kind":"text","text":"secret"}]}}}' ) - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.app"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.app"): async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: resp = await client.post("/", headers=headers, content=body) assert resp.status_code < 500 @@ -297,7 +297,7 @@ async def test_log_payloads_omits_text_plain_request_body(monkeypatch, caplog) - @pytest.mark.asyncio async def test_log_payloads_omits_when_content_length_missing(monkeypatch, caplog) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app( @@ -320,7 +320,7 @@ async def test_log_payloads_omits_when_content_length_missing(monkeypatch, caplo async def _body_stream(): yield body - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.app"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.app"): async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: resp = await client.post( "/", @@ -342,7 +342,7 @@ async def _body_stream(): @pytest.mark.asyncio async def test_log_payloads_omits_oversized_request_body(monkeypatch, caplog) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module monkeypatch.setattr(app_module, "OpencodeClient", DummyChatOpencodeClient) app = app_module.create_app( @@ -356,7 +356,7 @@ async def test_log_payloads_omits_oversized_request_body(monkeypatch, caplog) -> headers = {"Authorization": "Bearer test-token"} oversized_text = "x" * 512 - with caplog.at_level(logging.DEBUG, logger="opencode_a2a_serve.app"): + with caplog.at_level(logging.DEBUG, logger="opencode_a2a_server.app"): async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: resp = await client.post( "/", @@ -373,7 +373,7 @@ async def test_log_payloads_omits_oversized_request_body(monkeypatch, caplog) -> def test_create_app_propagates_cancel_abort_timeout(monkeypatch) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module captured: dict[str, float | bool | int] = {} @@ -429,7 +429,7 @@ async def release_session_for_control(self, *, identity: str, session_id: str) - def test_create_app_requires_control_guard_hooks(monkeypatch) -> None: - import opencode_a2a_serve.app as app_module + import opencode_a2a_server.app as app_module class _BrokenExecutor: def __init__( diff --git a/uv.lock b/uv.lock index 06a5d69..ef935d8 100644 --- a/uv.lock +++ b/uv.lock @@ -686,7 +686,7 @@ wheels = [ ] [[package]] -name = "opencode-a2a-serve" +name = "opencode-a2a-server" version = "0.1.0" source = { editable = "." } dependencies = [