Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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"]

Expand All @@ -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
Expand Down Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -84,7 +84,7 @@ Key variables to understand protocol behavior:
`Task.metadata.shared.usage` with the same field schema.
- Requests require `Authorization: Bearer <token>`; 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:
Expand Down Expand Up @@ -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`
Expand All @@ -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
Expand All @@ -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
```

Expand All @@ -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
```

Expand Down
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -64,12 +64,12 @@ 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
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"
2 changes: 1 addition & 1 deletion scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
4 changes: 2 additions & 2 deletions scripts/deploy/enable_instance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion scripts/deploy/install_units.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
8 changes: 4 additions & 4 deletions scripts/deploy/run_a2a.sh
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion scripts/deploy/setup_instance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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=<a2a-bearer-token>
EOF
Expand Down
4 changes: 2 additions & 2 deletions scripts/deploy/update_a2a.sh
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions scripts/deploy_light.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions scripts/deploy_light_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
12 changes: 6 additions & 6 deletions scripts/deploy_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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. |
Expand Down Expand Up @@ -204,21 +204,21 @@ Status:

```bash
sudo systemctl status opencode@<project>.service
sudo systemctl status opencode-a2a@<project>.service
sudo systemctl status opencode-a2a-server@<project>.service
```

Recent logs:

```bash
sudo journalctl -u opencode@<project>.service -n 200 --no-pager
sudo journalctl -u opencode-a2a@<project>.service -n 200 --no-pager
sudo journalctl -u opencode-a2a-server@<project>.service -n 200 --no-pager
```

Follow logs:

```bash
sudo journalctl -u opencode@<project>.service -f
sudo journalctl -u opencode-a2a@<project>.service -f
sudo journalctl -u opencode-a2a-server@<project>.service -f
```

Remove one instance:
Expand Down
6 changes: 3 additions & 3 deletions scripts/init_system.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions scripts/start_services.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &
Expand All @@ -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..."
Expand Down
2 changes: 1 addition & 1 deletion scripts/start_services_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion scripts/uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
File renamed without changes.
Loading
Loading