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
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ jobs:
- name: Install dependencies
run: uv sync --all-extras --dev

# Node + pnpm are required by tests/test_conformance_parity.py, which shells
# out to `pnpm exec tsx runner_ts.ts` to cross-validate the TS runner against
# the Python runner. Without these, the conformance parity tests fail with
# FileNotFoundError: 'pnpm'.
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 9

- name: Install conformance TS deps (for parity tests)
working-directory: wrappers/conformance
run: pnpm install --frozen-lockfile || pnpm install

- name: ruff (lint)
run: uv run ruff check src/ tests/

Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ coverage.xml
htmlcov/

# uv
uv.lock

# Editors
.idea/
Expand Down
153 changes: 79 additions & 74 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = 'amplifier-agent'
version = '0.3.0'
version = '0.4.0'
requires-python = '>=3.12'
license = 'MIT'
dependencies = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: >
surface paths.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/final]
Expand All @@ -24,7 +24,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down Expand Up @@ -68,7 +68,7 @@ script:
method: initialize
id: 3
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down Expand Up @@ -112,7 +112,7 @@ script:
method: initialize
id: 5
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: >
tool/started even if internal hook would.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/final]
Expand All @@ -16,7 +16,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ description: >
wire must be an intentional, reviewed addition, not silent drift.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/final]
Expand All @@ -22,7 +22,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >
this fixture exercises only the client wire plumbing.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/final]
Expand All @@ -18,7 +18,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: >
other fixture and is therefore not duplicated here.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/final]
Expand All @@ -17,7 +17,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >
continuity is tested in tests/test_resume_continuity.py.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/final, tool/started, tool/completed]
Expand All @@ -19,7 +19,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down Expand Up @@ -73,7 +73,7 @@ script:
method: initialize
id: 3
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: >
second turn that can reference first-turn context (design §5.3).

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities: {display: {events: [result/final]}}

script:
Expand All @@ -14,7 +14,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities: {display: {events: [result/final]}}
sessionId: sess-resume-1
Expand Down Expand Up @@ -42,7 +42,7 @@ script:
method: initialize
id: 3
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities: {display: {events: [result/final]}}
sessionId: sess-resume-1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: >
field on existing notification payloads.

setup:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientCapabilities:
display:
events: [result/delta, result/final, progress]
Expand All @@ -16,7 +16,7 @@ script:
method: initialize
id: 1
params:
protocolVersion: "0.2.0"
protocolVersion: "0.3.0"
clientInfo: {name: conformance-harness, version: "0.0.0"}
capabilities:
display: {events: [result/delta, result/final, progress]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ script:
data:
code: protocol_version_mismatch
clientVersion: "2099-12-future-vN"
serverVersion: "0.2.0"
serverVersion: "0.3.0"
remediation: "Reinstall the matching amplifier-agent and amplifier-agent-client packages, or set allowProtocolSkew: true in the host config file (--config)."

assertions:
Expand Down
2 changes: 1 addition & 1 deletion src/amplifier_agent_lib/protocol/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from typing import Any, NotRequired, TypedDict

PROTOCOL_VERSION = "0.2.0"
PROTOCOL_VERSION = "0.3.0"
"""Wire protocol version. Bump on breaking changes; semver applies.

0.2.0 — MCP config delivery changed from inline ``mcpServers`` dict to a
Expand Down
2 changes: 1 addition & 1 deletion src/amplifier_agent_lib/protocol/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# Amplifier Agent — Wire Spec

**Protocol version:** `0.2.0`
**Protocol version:** `0.3.0`

**Framing:** JSON-RPC 2.0 over NDJSON over stdio.
Stdout carries frames only; stderr is free-form log output.
Expand Down
12 changes: 10 additions & 2 deletions tests/cli/test_config_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,20 @@ def test_config_show_reports_xdg_config_home_from_env(runner: CliRunner, tmp_pat
assert parsed["xdg_config_home"]["source"] == "env:XDG_CONFIG_HOME"


def test_config_show_reports_default_when_env_absent(runner: CliRunner, tmp_path: Path) -> None:
def test_config_show_reports_default_when_env_absent(
runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""When XDG_CONFIG_HOME is unset, xdg_config_home.source=='default'."""
# CliRunner.invoke(env=...) MERGES with os.environ rather than REPLACING it.
# GitHub Actions runners set XDG_CONFIG_HOME by default, which would leak into
# the test and make source='env:XDG_CONFIG_HOME' instead of the 'default' this
# test is asserting. Explicitly delete the XDG_* env vars to ensure hermeticity.
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
monkeypatch.delenv("XDG_STATE_HOME", raising=False)
env = {
"HOME": str(tmp_path),
"ANTHROPIC_API_KEY": "sk-test",
# Ensure XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_STATE_HOME are absent.
}
result = runner.invoke(cli, ["config", "show"], env=env, catch_exceptions=False)
assert result.exit_code == 0, result.output
Expand Down
77 changes: 6 additions & 71 deletions tests/cli/test_mode_a_v2_envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,77 +65,12 @@ def test_output_text_emits_reply_only() -> None:
json.loads(result.stdout)


def test_mcp_servers_inline_json_parsed() -> None:
"""--mcp-servers '<json>' parses into the engine's _TurnSpec."""
runner = CliRunner()
captured: dict = {}

async def fake_execute(spec):
captured["mcp_servers"] = spec.mcp_servers
return _mock_turn_result("ok")

with (
patch("amplifier_agent_cli.modes.single_turn._execute_turn", side_effect=fake_execute),
patch("amplifier_agent_cli.modes.single_turn._read_bundle_default_provider", return_value="anthropic"),
):
result = runner.invoke(
run,
[
"--session-id",
"sid-1",
"--mcp-servers",
'{"nc_send":{"transport":"stdio","command":"node","args":["/x.js"]}}',
"hello",
],
)

assert result.exit_code == 0, result.output
assert captured["mcp_servers"] == {"nc_send": {"transport": "stdio", "command": "node", "args": ["/x.js"]}}


def test_mcp_servers_at_path_form(tmp_path) -> None:
"""--mcp-servers @<path> reads JSON from a file."""
cfg = tmp_path / "mcp.json"
cfg.write_text(
'{"server":{"transport":"stdio","command":"node","args":[]}}',
encoding="utf-8",
)
runner = CliRunner()
captured: dict = {}

async def fake_execute(spec):
captured["mcp_servers"] = spec.mcp_servers
return _mock_turn_result("ok")

with (
patch("amplifier_agent_cli.modes.single_turn._execute_turn", side_effect=fake_execute),
patch("amplifier_agent_cli.modes.single_turn._read_bundle_default_provider", return_value="anthropic"),
):
result = runner.invoke(run, ["--session-id", "sid-1", "--mcp-servers", f"@{cfg}", "hello"])

assert result.exit_code == 0, result.output
assert captured["mcp_servers"] == {"server": {"transport": "stdio", "command": "node", "args": []}}


def test_mcp_servers_malformed_json_yields_argv_envelope() -> None:
"""Malformed JSON in --mcp-servers maps to AaaError(argv_json_malformed). O2'."""
runner = CliRunner()
with patch("amplifier_agent_cli.modes.single_turn._read_bundle_default_provider", return_value="anthropic"):
result = runner.invoke(
run,
[
"--session-id",
"sid-1",
"--mcp-servers",
"{not json",
"hello",
],
)

assert result.exit_code == 2, result.output
envelope = json.loads(result.stdout)
assert envelope["error"]["code"] == "argv_json_malformed"
assert envelope["error"]["classification"] == "protocol"
# NOTE: test_mcp_servers_{inline_json_parsed,at_path_form,malformed_json_yields_argv_envelope}
# were removed when the --mcp-servers argv flag was retired. The flag was first
# renamed to --mcp-config-path by PR #24 and then fully removed by PR #29. The
# MCP catalog path now lives in host config (mcp.configPath) or in the
# AMPLIFIER_MCP_CONFIG environment variable; the removal-guardrail for the
# argv flag lives in tests/cli/test_drop_mcp_config_path_flag.py.


def test_protocol_version_mismatch_yields_envelope() -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _make_engine(buf: io.StringIO | None = None) -> Engine:
def _boot_params(**kwargs: object) -> dict:
"""Return a minimal valid InitializeParams dict with all 9 canonical events."""
params: dict = {
"protocolVersion": "0.2.0",
"protocolVersion": "0.3.0",
"clientInfo": {"name": "test-client", "version": "0.0.0"},
"capabilities": {
"display": {"events": list(CANONICAL_DISPLAY_EVENTS)},
Expand Down
2 changes: 1 addition & 1 deletion tests/test_engine_with_real_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def _make_engine() -> Engine:
def _boot_params(**kwargs: object) -> dict:
"""Return a minimal InitializeParams dict."""
params: dict = {
"protocolVersion": "0.2.0",
"protocolVersion": "0.3.0",
"clientInfo": {"name": "test-client", "version": "0.0.0"},
"capabilities": {
"display": {"events": []},
Expand Down
2 changes: 1 addition & 1 deletion tests/test_protocol_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def test_gen_emits_spec_md_with_required_sections(tmp_path: Path) -> None:

spec = (tmp_path / "spec.md").read_text()
assert "DO NOT HAND-EDIT" in spec
assert "0.2.0" in spec, "PROTOCOL_VERSION must appear"
assert "0.3.0" in spec, "PROTOCOL_VERSION must appear"
for required_section in ("## Methods", "## Notifications", "## Errors", "## Capabilities"):
assert required_section in spec, f"missing section: {required_section}"
# Schema links must point at the schemas/ subdir
Expand Down
Loading
Loading