Skip to content

Commit d03aa97

Browse files
author
Chojan Shang
committed
feat: better gen scripts and rename map for better dev
Signed-off-by: Chojan Shang <chojan.shang@vesoft.com>
1 parent 939158c commit d03aa97

File tree

10 files changed

+641
-456
lines changed

10 files changed

+641
-456
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
The package code lives under `src/acp`, exposing the high-level Agent, transport helpers, and generated protocol schema. Generated artifacts such as `schema/` and `src/acp/schema.py` are refreshed via `scripts/gen_all.py` against the upstream ACP schema. Integration examples are in `examples/`, including `echo_agent.py` and the mini SWE bridge. Tests reside in `tests/` with async fixtures and doctests; documentation sources live in `docs/` and publish via MkDocs. Built distributions drop into `dist/` after builds.
55

66
## Build, Test, and Development Commands
7-
Run `make install` to create a `uv` managed virtualenv and install pre-commit hooks. `make check` executes lock verification, Ruff linting, `ty` static checks, and deptry analysis. `make test` calls `uv run python -m pytest --doctest-modules`. For release prep use `make build` or `make build-and-publish`. `make gen-all` regenerates protocol models; use this before committing schema updates. `make docs` serves MkDocs locally; `make docs-test` ensures clean builds.
7+
Run `make install` to create a `uv` managed virtualenv and install pre-commit hooks. `make check` executes lock verification, Ruff linting, `ty` static checks, and deptry analysis. `make test` calls `uv run python -m pytest --doctest-modules`. For release prep use `make build` or `make build-and-publish`. `make gen-all` regenerates protocol models; export `ACP_SCHEMA_VERSION=<ref>` beforehand to fetch a specific upstream schema (defaults to the cached copy). `make docs` serves MkDocs locally; `make docs-test` ensures clean builds.
88

99
## Coding Style & Naming Conventions
1010
Target Python 3.10+ with type hints and 120-character lines enforced by Ruff (`pyproject.toml`). Prefer dataclasses/pydantic models from the schema modules rather than bare dicts. Tests may ignore security lint (see per-file ignores) but still follow snake_case names. Keep public API modules under `acp/*` lean; place utilities in internal `_`-prefixed modules when needed.

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ uv add agent-client-protocol
1919

2020
```bash
2121
make install # set up venv
22+
ACP_SCHEMA_VERSION=<ref> make gen-all # generate meta.py & schema.py
2223
make check # lint + typecheck
2324
make test # run tests
2425
```
@@ -42,7 +43,7 @@ from acp import (
4243
SessionNotification,
4344
stdio_streams,
4445
)
45-
from acp.schema import ContentBlock1, SessionUpdate2
46+
from acp.schema import TextContentBlock, AgentMessageChunk
4647

4748

4849
class EchoAgent(Agent):
@@ -61,9 +62,9 @@ class EchoAgent(Agent):
6162
await self._conn.sessionUpdate(
6263
SessionNotification(
6364
sessionId=params.sessionId,
64-
update=SessionUpdate2(
65+
update=AgentMessageChunk(
6566
sessionUpdate="agent_message_chunk",
66-
content=ContentBlock1(type="text", text=text),
67+
content=TextContentBlock(type="text", text=text),
6768
),
6869
)
6970
)

schema/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
refs/tags/v0.4.5

scripts/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Utility scripts for generating ACP bindings."""

scripts/gen_all.py

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,152 @@
11
#!/usr/bin/env python3
22
from __future__ import annotations
33

4-
import runpy
4+
import argparse
5+
import json
6+
import os
7+
import re
8+
import sys
9+
import urllib.error
10+
import urllib.request
511
from pathlib import Path
612

7-
SCRIPTS = Path(__file__).resolve().parent
13+
ROOT = Path(__file__).resolve().parents[1]
14+
if str(ROOT) not in sys.path:
15+
sys.path.append(str(ROOT))
16+
17+
from scripts import gen_meta, gen_schema # noqa: E402 pylint: disable=wrong-import-position
18+
19+
SCHEMA_DIR = ROOT / "schema"
20+
SCHEMA_JSON = SCHEMA_DIR / "schema.json"
21+
META_JSON = SCHEMA_DIR / "meta.json"
22+
VERSION_FILE = SCHEMA_DIR / "VERSION"
23+
24+
DEFAULT_REPO = "zed-industries/agent-client-protocol"
25+
26+
27+
def parse_args() -> argparse.Namespace:
28+
parser = argparse.ArgumentParser(description="Regenerate schema.py and meta.py from the ACP schema.")
29+
parser.add_argument(
30+
"--version",
31+
"-v",
32+
help=(
33+
"Git ref (tag/branch) of zed-industries/agent-client-protocol to fetch the schema from. "
34+
"If omitted, uses the cached schema files or falls back to 'main' when missing."
35+
),
36+
)
37+
parser.add_argument(
38+
"--repo",
39+
default=os.environ.get("ACP_SCHEMA_REPO", DEFAULT_REPO),
40+
help="Source repository providing schema.json/meta.json (default: %(default)s)",
41+
)
42+
parser.add_argument(
43+
"--no-download",
44+
action="store_true",
45+
help="Skip downloading schema files even when a version is provided.",
46+
)
47+
parser.add_argument(
48+
"--format",
49+
dest="format_output",
50+
action="store_true",
51+
help="Format generated files with 'uv run ruff format'.",
52+
)
53+
parser.add_argument(
54+
"--no-format",
55+
dest="format_output",
56+
action="store_false",
57+
help="Disable formatting with ruff.",
58+
)
59+
parser.set_defaults(format_output=True)
60+
parser.add_argument(
61+
"--force",
62+
action="store_true",
63+
help="Force schema download even if the requested ref is already cached locally.",
64+
)
65+
return parser.parse_args()
866

967

1068
def main() -> None:
11-
runpy.run_path(str(SCRIPTS / "gen_schema.py"), run_name="__main__")
12-
runpy.run_path(str(SCRIPTS / "gen_meta.py"), run_name="__main__")
69+
args = parse_args()
70+
71+
version = args.version or os.environ.get("ACP_SCHEMA_VERSION")
72+
repo = args.repo
73+
should_download = _should_download(args, version)
74+
75+
if should_download:
76+
ref = resolve_ref(version)
77+
download_schema(repo, ref)
78+
else:
79+
ref = resolve_ref(version) if version else _cached_ref()
80+
81+
if not (SCHEMA_JSON.exists() and META_JSON.exists()):
82+
print("schema/schema.json or schema/meta.json missing; run with --version to fetch them.", file=sys.stderr)
83+
sys.exit(1)
84+
85+
gen_schema.generate_schema(format_output=args.format_output)
86+
gen_meta.generate_meta()
87+
88+
if ref:
89+
print(f"Generated schema using ref: {ref}")
90+
else:
91+
print("Generated schema using local schema files")
92+
93+
94+
def _should_download(args: argparse.Namespace, version: str | None) -> bool:
95+
env_override = os.environ.get("ACP_SCHEMA_DOWNLOAD")
96+
if env_override is not None:
97+
return env_override.lower() in {"1", "true", "yes"}
98+
if args.no_download:
99+
return False
100+
if version:
101+
if not SCHEMA_JSON.exists() or not META_JSON.exists():
102+
return True
103+
cached = _cached_ref()
104+
if args.force:
105+
return True
106+
return cached != resolve_ref(version)
107+
return not (SCHEMA_JSON.exists() and META_JSON.exists())
108+
109+
110+
def resolve_ref(version: str | None) -> str:
111+
if not version:
112+
return "refs/heads/main"
113+
if version.startswith("refs/"):
114+
return version
115+
if re.fullmatch(r"v?\d+\.\d+\.\d+", version):
116+
value = version if version.startswith("v") else f"v{version}"
117+
return f"refs/tags/{value}"
118+
return f"refs/heads/{version}"
119+
120+
121+
def download_schema(repo: str, ref: str) -> None:
122+
SCHEMA_DIR.mkdir(parents=True, exist_ok=True)
123+
schema_url = f"https://raw.githubusercontent.com/{repo}/{ref}/schema/schema.json"
124+
meta_url = f"https://raw.githubusercontent.com/{repo}/{ref}/schema/meta.json"
125+
try:
126+
schema_data = fetch_json(schema_url)
127+
meta_data = fetch_json(meta_url)
128+
except RuntimeError as exc: # pragma: no cover - network error path
129+
print(exc, file=sys.stderr)
130+
sys.exit(1)
131+
132+
SCHEMA_JSON.write_text(json.dumps(schema_data, indent=2), encoding="utf-8")
133+
META_JSON.write_text(json.dumps(meta_data, indent=2), encoding="utf-8")
134+
VERSION_FILE.write_text(ref + "\n", encoding="utf-8")
135+
print(f"Fetched schema and meta from {repo}@{ref}")
136+
137+
138+
def fetch_json(url: str) -> dict:
139+
try:
140+
with urllib.request.urlopen(url) as response: # noqa: S310 - trusted source configured by repo
141+
return json.loads(response.read().decode("utf-8"))
142+
except urllib.error.URLError as exc:
143+
raise RuntimeError(f"Failed to fetch {url}: {exc}") from exc # noqa: TRY003
144+
145+
146+
def _cached_ref() -> str | None:
147+
if VERSION_FILE.exists():
148+
return VERSION_FILE.read_text(encoding="utf-8").strip() or None
149+
return None
13150

14151

15152
if __name__ == "__main__":

scripts/gen_meta.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,38 @@
44
import json
55
from pathlib import Path
66

7-
ROOT = Path.cwd()
7+
ROOT = Path(__file__).resolve().parents[1]
8+
SCHEMA_DIR = ROOT / "schema"
9+
VERSION_FILE = SCHEMA_DIR / "VERSION"
810

911

1012
def main() -> None:
11-
meta_json = ROOT / "schema" / "meta.json"
13+
generate_meta()
14+
15+
16+
def generate_meta() -> None:
17+
meta_json = SCHEMA_DIR / "meta.json"
1218
out_py = ROOT / "src" / "acp" / "meta.py"
13-
data = json.loads(meta_json.read_text())
19+
if not meta_json.exists():
20+
raise SystemExit("schema/meta.json not found. Run gen_schema.py first.") # noqa: TRY003
21+
22+
data = json.loads(meta_json.read_text("utf-8"))
1423
agent_methods = data.get("agentMethods", {})
1524
client_methods = data.get("clientMethods", {})
1625
version = data.get("version", 1)
26+
header_lines = ["# Generated from schema/meta.json. Do not edit by hand."]
27+
if VERSION_FILE.exists():
28+
ref = VERSION_FILE.read_text("utf-8").strip()
29+
if ref:
30+
header_lines.append(f"# Schema ref: {ref}")
31+
1732
out_py.write_text(
18-
"# This file is generated from schema/meta.json. Do not edit by hand.\n"
19-
f"AGENT_METHODS = {agent_methods!r}\n"
20-
f"CLIENT_METHODS = {client_methods!r}\n"
21-
f"PROTOCOL_VERSION = {int(version)}\n"
33+
"\n".join(header_lines)
34+
+ "\n"
35+
+ f"AGENT_METHODS = {agent_methods!r}\n"
36+
+ f"CLIENT_METHODS = {client_methods!r}\n"
37+
+ f"PROTOCOL_VERSION = {int(version)}\n",
38+
encoding="utf-8",
2239
)
2340

2441

0 commit comments

Comments
 (0)