Skip to content

Commit c898f59

Browse files
author
Chojan Shang
committed
feat: intro helpers
Signed-off-by: Chojan Shang <chojan.shang@vesoft.com>
1 parent f2b80ca commit c898f59

File tree

12 files changed

+519
-65
lines changed

12 files changed

+519
-65
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Target Python 3.10+ with four-space indentation and type hints on public APIs.
1818
- Ruff enforces formatting and lint rules (`uv run ruff check`, `uv run ruff format`); keep both clean before publishing.
1919
- Prefer dataclasses or generated Pydantic models from `acp.schema` over ad-hoc dicts. Place shared utilities in `_`-prefixed internal modules.
20+
- When constructing ACP payloads, use the builders in `acp.helpers` (for example `text_block`, `start_tool_call`). These helpers keep the generated Pydantic models authoritative while hiding required literal fields, and the golden tests (`tests/test_golden.py`) ensure they always match the schema.
2021

2122
## Testing Guidelines
2223
- Tests live in `tests/` and must be named `test_*.py`. Use `pytest.mark.asyncio` for coroutine coverage.

README.md

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import asyncio
4444
import sys
4545
from pathlib import Path
4646

47-
from acp import spawn_agent_process
48-
from acp.schema import InitializeRequest, NewSessionRequest, PromptRequest, TextContentBlock
47+
from acp import spawn_agent_process, text_block
48+
from acp.schema import InitializeRequest, NewSessionRequest, PromptRequest
4949

5050

5151
async def main() -> None:
@@ -56,7 +56,7 @@ async def main() -> None:
5656
await conn.prompt(
5757
PromptRequest(
5858
sessionId=session.sessionId,
59-
prompt=[TextContentBlock(type="text", text="Hello!")],
59+
prompt=[text_block("Hello!")],
6060
)
6161
)
6262

@@ -80,10 +80,11 @@ from acp import (
8080
NewSessionResponse,
8181
PromptRequest,
8282
PromptResponse,
83-
SessionNotification,
83+
session_notification,
8484
stdio_streams,
85+
text_block,
86+
update_agent_message,
8587
)
86-
from acp.schema import TextContentBlock, AgentMessageChunk
8788

8889

8990
class EchoAgent(Agent):
@@ -100,12 +101,9 @@ class EchoAgent(Agent):
100101
for block in params.prompt:
101102
text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
102103
await self._conn.sessionUpdate(
103-
SessionNotification(
104-
sessionId=params.sessionId,
105-
update=AgentMessageChunk(
106-
sessionUpdate="agent_message_chunk",
107-
content=TextContentBlock(type="text", text=text),
108-
),
104+
session_notification(
105+
params.sessionId,
106+
update_agent_message(text_block(text)),
109107
)
110108
)
111109
return PromptResponse(stopReason="end_turn")
@@ -131,6 +129,23 @@ Full example with streaming and lifecycle hooks lives in [examples/echo_agent.py
131129
- `examples/duet.py`: launches both example agent and client using `spawn_agent_process`
132130
- `examples/gemini.py`: connects to the Gemini CLI in `--experimental-acp` mode, with optional auto-approval and sandbox flags
133131

132+
## Helper APIs
133+
134+
Use `acp.helpers` to build protocol payloads without manually shaping dictionaries:
135+
136+
```python
137+
from acp import start_tool_call, text_block, tool_content, update_tool_call
138+
139+
start = start_tool_call("call-1", "Inspect config", kind="read", status="pending")
140+
update = update_tool_call(
141+
"call-1",
142+
status="completed",
143+
content=[tool_content(text_block("Inspection finished."))],
144+
)
145+
```
146+
147+
Helpers cover content blocks (`text_block`, `resource_link_block`), embedded resources, tool calls (`start_edit_tool_call`, `update_tool_call`), and session updates (`update_agent_message_text`, `session_notification`).
148+
134149
## Documentation
135150

136151
- Project docs (MkDocs): https://psiace.github.io/agent-client-protocol-python/

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Welcome to the Python SDK for the Agent Client Protocol (ACP). The package ships
1111
- Pydantic models generated from the upstream ACP schema (`acp.schema`)
1212
- Async agent/client wrappers with JSON-RPC task supervision built in
1313
- Process helpers (`spawn_agent_process`, `spawn_client_process`) for embedding ACP nodes inside Python applications
14+
- Helper APIs in `acp.helpers` that mirror the Go/TS SDK builders for content blocks, tool calls, and session updates
1415
- Examples that showcase streaming updates, file operations, permission flows, and even a Gemini CLI bridge (`examples/gemini.py`)
1516

1617
## Getting started

docs/quickstart.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ import asyncio
5757
import sys
5858
from pathlib import Path
5959

60-
from acp import spawn_agent_process
60+
from acp import spawn_agent_process, text_block
6161
from acp.interfaces import Client
62-
from acp.schema import InitializeRequest, NewSessionRequest, PromptRequest, SessionNotification, TextContentBlock
62+
from acp.schema import InitializeRequest, NewSessionRequest, PromptRequest, SessionNotification
6363

6464

6565
class SimpleClient(Client):
@@ -78,7 +78,7 @@ async def main() -> None:
7878
await conn.prompt(
7979
PromptRequest(
8080
sessionId=session.sessionId,
81-
prompt=[TextContentBlock(type="text", text="Hello from spawn!")],
81+
prompt=[text_block("Hello from spawn!")],
8282
)
8383
)
8484

@@ -108,6 +108,19 @@ Hook it up with `AgentSideConnection` inside an async entrypoint and wire it to
108108
- [`examples/duet.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/duet.py) to see `spawn_agent_process` in action alongside the interactive client
109109
- [`examples/gemini.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/gemini.py) to drive the Gemini CLI (`--experimental-acp`) directly from Python
110110

111+
Need builders for common payloads? `acp.helpers` mirrors the Go/TS helper APIs:
112+
113+
```python
114+
from acp import start_tool_call, update_tool_call, text_block, tool_content
115+
116+
start_update = start_tool_call("call-42", "Open file", kind="read", status="pending")
117+
finish_update = update_tool_call(
118+
"call-42",
119+
status="completed",
120+
content=[tool_content(text_block("File opened."))],
121+
)
122+
```
123+
111124
## 5. Optional: Talk to the Gemini CLI
112125

113126
If you have the Gemini CLI installed and authenticated:

examples/agent.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,13 @@
1818
PromptResponse,
1919
SetSessionModeRequest,
2020
SetSessionModeResponse,
21+
session_notification,
2122
stdio_streams,
23+
text_block,
24+
update_agent_message,
2225
PROTOCOL_VERSION,
2326
)
24-
from acp.schema import (
25-
AgentCapabilities,
26-
AgentMessageChunk,
27-
McpCapabilities,
28-
PromptCapabilities,
29-
SessionNotification,
30-
TextContentBlock,
31-
)
27+
from acp.schema import AgentCapabilities, McpCapabilities, PromptCapabilities
3228

3329

3430
class ExampleAgent(Agent):
@@ -38,12 +34,9 @@ def __init__(self, conn: AgentSideConnection) -> None:
3834

3935
async def _send_chunk(self, session_id: str, content: Any) -> None:
4036
await self._conn.sessionUpdate(
41-
SessionNotification(
42-
sessionId=session_id,
43-
update=AgentMessageChunk(
44-
sessionUpdate="agent_message_chunk",
45-
content=content,
46-
),
37+
session_notification(
38+
session_id,
39+
update_agent_message(content),
4740
)
4841
)
4942

@@ -85,7 +78,7 @@ async def prompt(self, params: PromptRequest) -> PromptResponse:
8578
# Notify the client what it just sent and then echo each content block back.
8679
await self._send_chunk(
8780
params.sessionId,
88-
TextContentBlock(type="text", text="Client sent:"),
81+
text_block("Client sent:"),
8982
)
9083
for block in params.prompt:
9184
await self._send_chunk(params.sessionId, block)

examples/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
PromptRequest,
1515
RequestError,
1616
SessionNotification,
17+
text_block,
1718
PROTOCOL_VERSION,
1819
)
19-
from acp.schema import TextContentBlock
2020

2121

2222
class ExampleClient(Client):
@@ -91,7 +91,7 @@ async def interactive_loop(conn: ClientSideConnection, session_id: str) -> None:
9191
await conn.prompt(
9292
PromptRequest(
9393
sessionId=session_id,
94-
prompt=[TextContentBlock(type="text", text=line)],
94+
prompt=[text_block(line)],
9595
)
9696
)
9797
except Exception as exc: # noqa: BLE001

examples/echo_agent.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
NewSessionResponse,
1111
PromptRequest,
1212
PromptResponse,
13-
SessionNotification,
13+
session_notification,
1414
stdio_streams,
15+
text_block,
16+
update_agent_message,
1517
)
16-
from acp.schema import AgentMessageChunk, TextContentBlock
1718

1819

1920
class EchoAgent(Agent):
@@ -30,12 +31,9 @@ async def prompt(self, params: PromptRequest) -> PromptResponse:
3031
for block in params.prompt:
3132
text = getattr(block, "text", "")
3233
await self._conn.sessionUpdate(
33-
SessionNotification(
34-
sessionId=params.sessionId,
35-
update=AgentMessageChunk(
36-
sessionUpdate="agent_message_chunk",
37-
content=TextContentBlock(type="text", text=text),
38-
),
34+
session_notification(
35+
params.sessionId,
36+
update_agent_message(text_block(text)),
3937
)
4038
)
4139
return PromptResponse(stopReason="end_turn")

examples/gemini.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
ClientSideConnection,
1717
PROTOCOL_VERSION,
1818
RequestError,
19+
text_block,
1920
)
2021
from acp.schema import (
2122
AgentMessageChunk,
@@ -252,7 +253,7 @@ async def interactive_loop(conn: ClientSideConnection, session_id: str) -> None:
252253
await conn.prompt(
253254
PromptRequest(
254255
sessionId=session_id,
255-
prompt=[TextContentBlock(type="text", text=line)],
256+
prompt=[text_block(line)],
256257
)
257258
)
258259
except RequestError as err:

src/acp/__init__.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@
66
RequestError,
77
TerminalHandle,
88
)
9+
from .helpers import (
10+
audio_block,
11+
embedded_blob_resource,
12+
embedded_text_resource,
13+
image_block,
14+
plan_entry,
15+
resource_block,
16+
resource_link_block,
17+
session_notification,
18+
start_edit_tool_call,
19+
start_read_tool_call,
20+
start_tool_call,
21+
text_block,
22+
tool_content,
23+
tool_diff_content,
24+
tool_terminal_ref,
25+
update_agent_message,
26+
update_agent_message_text,
27+
update_agent_thought,
28+
update_agent_thought_text,
29+
update_plan,
30+
update_tool_call,
31+
update_user_message,
32+
update_user_message_text,
33+
)
934
from .meta import (
1035
AGENT_METHODS,
1136
CLIENT_METHODS,
@@ -101,4 +126,28 @@
101126
"spawn_client_process",
102127
"default_environment",
103128
"spawn_stdio_transport",
129+
# helpers
130+
"text_block",
131+
"image_block",
132+
"audio_block",
133+
"resource_link_block",
134+
"embedded_text_resource",
135+
"embedded_blob_resource",
136+
"resource_block",
137+
"tool_content",
138+
"tool_diff_content",
139+
"tool_terminal_ref",
140+
"plan_entry",
141+
"update_plan",
142+
"update_user_message",
143+
"update_user_message_text",
144+
"update_agent_message",
145+
"update_agent_message_text",
146+
"update_agent_thought",
147+
"update_agent_thought_text",
148+
"session_notification",
149+
"start_tool_call",
150+
"start_read_tool_call",
151+
"start_edit_tool_call",
152+
"update_tool_call",
104153
]

0 commit comments

Comments
 (0)