Skip to content

Commit 745a674

Browse files
author
Chojan Shang
committed
docs: prepare for 0.3.0
Signed-off-by: Chojan Shang <chojan.shang@vesoft.com>
1 parent b81e4e2 commit 745a674

File tree

8 files changed

+186
-60
lines changed

8 files changed

+186
-60
lines changed

README.md

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A Python implementation of the Agent Client Protocol (ACP). Use it to build agen
55
- Package name: `agent-client-protocol` (import as `acp`)
66
- Repository: https://github.com/psiace/agent-client-protocol-python
77
- Docs: https://psiace.github.io/agent-client-protocol-python/
8+
- Featured: Listed as the first third-party SDK on the official ACP site — see https://agentclientprotocol.com/libraries/community
89

910
## Install
1011

@@ -24,51 +25,54 @@ make test # run tests
2425

2526
## Minimal agent example
2627

28+
See a complete streaming echo example in [examples/echo_agent.py](examples/echo_agent.py). It streams back each text block using `session/update` and ends the turn.
29+
2730
```python
2831
import asyncio
2932

3033
from acp import (
3134
Agent,
3235
AgentSideConnection,
33-
AuthenticateRequest,
34-
CancelNotification,
3536
InitializeRequest,
3637
InitializeResponse,
37-
LoadSessionRequest,
3838
NewSessionRequest,
3939
NewSessionResponse,
4040
PromptRequest,
4141
PromptResponse,
42+
SessionNotification,
4243
stdio_streams,
4344
)
45+
from acp.schema import ContentBlock1, SessionUpdate2
4446

4547

4648
class EchoAgent(Agent):
49+
def __init__(self, conn):
50+
self._conn = conn
51+
4752
async def initialize(self, params: InitializeRequest) -> InitializeResponse:
4853
return InitializeResponse(protocolVersion=params.protocolVersion)
4954

5055
async def newSession(self, params: NewSessionRequest) -> NewSessionResponse:
5156
return NewSessionResponse(sessionId="sess-1")
5257

53-
async def loadSession(self, params: LoadSessionRequest) -> None:
54-
return None
55-
56-
async def authenticate(self, params: AuthenticateRequest) -> None:
57-
return None
58-
5958
async def prompt(self, params: PromptRequest) -> PromptResponse:
60-
# Normally you'd stream updates via sessionUpdate
59+
for block in params.prompt:
60+
text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
61+
await self._conn.sessionUpdate(
62+
SessionNotification(
63+
sessionId=params.sessionId,
64+
update=SessionUpdate2(
65+
sessionUpdate="agent_message_chunk",
66+
content=ContentBlock1(type="text", text=text),
67+
),
68+
)
69+
)
6170
return PromptResponse(stopReason="end_turn")
6271

63-
async def cancel(self, params: CancelNotification) -> None:
64-
return None
65-
6672

6773
async def main() -> None:
6874
reader, writer = await stdio_streams()
69-
# For an agent process, local writes go to client stdin (writer=stdout)
70-
AgentSideConnection(lambda _conn: EchoAgent(), writer, reader)
71-
# Keep running; in a real agent you would await tasks or add your own loop
75+
AgentSideConnection(lambda conn: EchoAgent(conn), writer, reader)
7276
await asyncio.Event().wait()
7377

7478

docs/index.md

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,56 @@ pip install agent-client-protocol
1111
## Minimal usage
1212

1313
```python
14-
from acp import Agent, AgentSideConnection, Client, stdio_streams, PROTOCOL_VERSION, InitializeRequest, InitializeResponse, PromptRequest, PromptResponse
15-
from acp.schema import ContentBlock1, SessionUpdate2, SessionNotification
16-
17-
class MyAgent(Agent):
18-
def __init__(self, client: Client):
19-
self.client = client
20-
async def initialize(self, _p: InitializeRequest) -> InitializeResponse:
21-
return InitializeResponse(protocolVersion=PROTOCOL_VERSION)
22-
async def prompt(self, p: PromptRequest) -> PromptResponse:
23-
await self.client.sessionUpdate(SessionNotification(
24-
sessionId=p.sessionId,
25-
update=SessionUpdate2(sessionUpdate="agent_message_chunk", content=ContentBlock1(type="text", text="Hello from ACP")),
26-
))
14+
import asyncio
15+
16+
from acp import (
17+
Agent,
18+
AgentSideConnection,
19+
InitializeRequest,
20+
InitializeResponse,
21+
NewSessionRequest,
22+
NewSessionResponse,
23+
PromptRequest,
24+
PromptResponse,
25+
SessionNotification,
26+
stdio_streams,
27+
)
28+
from acp.schema import ContentBlock1, SessionUpdate2
29+
30+
31+
class EchoAgent(Agent):
32+
def __init__(self, conn):
33+
self._conn = conn
34+
35+
async def initialize(self, params: InitializeRequest) -> InitializeResponse:
36+
return InitializeResponse(protocolVersion=params.protocolVersion)
37+
38+
async def newSession(self, params: NewSessionRequest) -> NewSessionResponse:
39+
return NewSessionResponse(sessionId="sess-1")
40+
41+
async def prompt(self, params: PromptRequest) -> PromptResponse:
42+
for block in params.prompt:
43+
text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
44+
await self._conn.sessionUpdate(
45+
SessionNotification(
46+
sessionId=params.sessionId,
47+
update=SessionUpdate2(
48+
sessionUpdate="agent_message_chunk",
49+
content=ContentBlock1(type="text", text=text),
50+
),
51+
)
52+
)
2753
return PromptResponse(stopReason="end_turn")
54+
55+
56+
async def main() -> None:
57+
reader, writer = await stdio_streams()
58+
AgentSideConnection(lambda conn: EchoAgent(conn), writer, reader)
59+
await asyncio.Event().wait()
60+
61+
62+
if __name__ == "__main__":
63+
asyncio.run(main())
2864
```
2965

3066
- Quickstart: [quickstart.md](quickstart.md)

docs/mini-swe-agent.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Mini SWE Agent bridge
22

3-
This example wraps mini-swe-agent behind ACP so Zed can run it as an external agent over stdio.
3+
This example wraps mini-swe-agent behind ACP so Zed can run it as an external agent over stdio. It also includes a local Textual UI client connected via a duet launcher.
44

55
## Behavior
66

@@ -15,14 +15,29 @@ Non-terminating events (e.g. user rejection or timeouts) are surfaced both as a
1515

1616
## Configuration
1717

18-
Environment variables set in the Zed server config control the model:
18+
Environment variables control the model:
1919

2020
- `MINI_SWE_MODEL`: model ID (e.g. `openrouter/openai/gpt-4o-mini`)
21-
- `MINI_SWE_MODEL_KWARGS`: JSON string of extra parameters (e.g. `{ "api_base": "https://openrouter.ai/api/v1" }`)
22-
- Vendor API keys (e.g. `OPENROUTER_API_KEY`) must be present in the environment
21+
- `OPENROUTER_API_KEY` for OpenRouter; or `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` for native providers
22+
- Optional `MINI_SWE_MODEL_KWARGS`: JSON, e.g. `{ "api_base": "https://openrouter.ai/api/v1" }` (auto-injected for OpenRouter if missing)
23+
24+
Agent behavior automatically maps the appropriate API key based on the chosen model and available environment variables.
2325

2426
If `mini-swe-agent` is not installed in the venv, the bridge attempts to import a vendored reference copy under `reference/mini-swe-agent/src`.
2527

28+
## How to run
29+
30+
- In Zed (editor integration): configure an agent server to launch `examples/mini_swe_agent/agent.py` and set the environment variables there.
31+
- In terminal (local TUI): run the duet launcher to start both the agent and the Textual client with the same environment and dedicated pipes:
32+
33+
```bash
34+
python examples/mini_swe_agent/duet.py
35+
```
36+
37+
The launcher loads `.env` from the repo root (using python-dotenv) so both processes share the same configuration.
38+
2639
## Files
2740

28-
- Example entry: [`examples/mini_swe_agent/agent.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/mini_swe_agent/agent.py)
41+
- Agent entry: [`examples/mini_swe_agent/agent.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/mini_swe_agent/agent.py)
42+
- Duet launcher: [`examples/mini_swe_agent/duet.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/mini_swe_agent/duet.py)
43+
- Textual client: [`examples/mini_swe_agent/client.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/mini_swe_agent/client.py)

docs/quickstart.md

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,46 @@ import asyncio
1616
from acp import (
1717
Agent,
1818
AgentSideConnection,
19-
AuthenticateRequest,
20-
CancelNotification,
2119
InitializeRequest,
2220
InitializeResponse,
23-
LoadSessionRequest,
2421
NewSessionRequest,
2522
NewSessionResponse,
2623
PromptRequest,
2724
PromptResponse,
25+
SessionNotification,
2826
stdio_streams,
2927
)
28+
from acp.schema import ContentBlock1, SessionUpdate2
3029

3130

3231
class EchoAgent(Agent):
32+
def __init__(self, conn):
33+
self._conn = conn
34+
3335
async def initialize(self, params: InitializeRequest) -> InitializeResponse:
3436
return InitializeResponse(protocolVersion=params.protocolVersion)
3537

3638
async def newSession(self, params: NewSessionRequest) -> NewSessionResponse:
3739
return NewSessionResponse(sessionId="sess-1")
3840

39-
async def loadSession(self, params: LoadSessionRequest) -> None:
40-
return None
41-
42-
async def authenticate(self, params: AuthenticateRequest) -> None:
43-
return None
44-
4541
async def prompt(self, params: PromptRequest) -> PromptResponse:
46-
# Normally you'd stream updates via sessionUpdate
42+
for block in params.prompt:
43+
text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
44+
await self._conn.sessionUpdate(
45+
SessionNotification(
46+
sessionId=params.sessionId,
47+
update=SessionUpdate2(
48+
sessionUpdate="agent_message_chunk",
49+
content=ContentBlock1(type="text", text=text),
50+
),
51+
)
52+
)
4753
return PromptResponse(stopReason="end_turn")
4854

49-
async def cancel(self, params: CancelNotification) -> None:
50-
return None
51-
5255

5356
async def main() -> None:
5457
reader, writer = await stdio_streams()
55-
# For an agent process, local writes go to client stdin (writer=stdout)
56-
AgentSideConnection(lambda _conn: EchoAgent(), writer, reader)
57-
# Keep running; in a real agent you would await tasks or add your own loop
58+
AgentSideConnection(lambda conn: EchoAgent(conn), writer, reader)
5859
await asyncio.Event().wait()
5960

6061

@@ -84,14 +85,26 @@ Add an agent server to Zed’s `settings.json`:
8485
],
8586
"env": {
8687
"MINI_SWE_MODEL": "openrouter/openai/gpt-4o-mini",
87-
"MINI_SWE_MODEL_KWARGS": "{\"api_base\":\"https://openrouter.ai/api/v1\"}",
8888
"OPENROUTER_API_KEY": "sk-or-..."
8989
}
9090
}
9191
}
9292
}
9393
```
9494

95+
- For OpenRouter, `api_base` is set automatically to `https://openrouter.ai/api/v1` if not provided.
96+
- Alternatively, use native providers by setting `MINI_SWE_MODEL` accordingly and providing `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`.
97+
9598
In Zed, open the Agents panel and select "Mini SWE Agent (Python)".
9699

97100
See [mini-swe-agent.md](mini-swe-agent.md) for behavior and message mapping details.
101+
102+
## Run locally with a TUI
103+
104+
Use the duet launcher to run both the agent and the local Textual client over dedicated pipes:
105+
106+
```bash
107+
python examples/mini_swe_agent/duet.py
108+
```
109+
110+
The launcher loads `.env` from the repo root so both processes share the same configuration (requires python-dotenv).

examples/echo_agent.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import asyncio
2+
3+
from acp import (
4+
Agent,
5+
AgentSideConnection,
6+
InitializeRequest,
7+
InitializeResponse,
8+
NewSessionRequest,
9+
NewSessionResponse,
10+
PromptRequest,
11+
PromptResponse,
12+
SessionNotification,
13+
stdio_streams,
14+
)
15+
from acp.schema import ContentBlock1, SessionUpdate2
16+
17+
18+
class EchoAgent(Agent):
19+
def __init__(self, conn):
20+
self._conn = conn
21+
22+
async def initialize(self, params: InitializeRequest) -> InitializeResponse:
23+
return InitializeResponse(protocolVersion=params.protocolVersion)
24+
25+
async def newSession(self, params: NewSessionRequest) -> NewSessionResponse:
26+
return NewSessionResponse(sessionId="sess-1")
27+
28+
async def prompt(self, params: PromptRequest) -> PromptResponse:
29+
for block in params.prompt:
30+
text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "")
31+
await self._conn.sessionUpdate(
32+
SessionNotification(
33+
sessionId=params.sessionId,
34+
update=SessionUpdate2(
35+
sessionUpdate="agent_message_chunk",
36+
content=ContentBlock1(type="text", text=text),
37+
),
38+
)
39+
)
40+
return PromptResponse(stopReason="end_turn")
41+
42+
43+
async def main() -> None:
44+
reader, writer = await stdio_streams()
45+
AgentSideConnection(lambda conn: EchoAgent(conn), writer, reader)
46+
await asyncio.Event().wait()
47+
48+
49+
if __name__ == "__main__":
50+
asyncio.run(main())

examples/mini_swe_agent/README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Mini SWE Agent (Python) — ACP Bridge
22

3-
A minimal Agent Client Protocol (ACP) bridge that wraps mini-swe-agent so it can be run by Zed as an external agent over stdio.
3+
A minimal Agent Client Protocol (ACP) bridge that wraps mini-swe-agent so it can be run by Zed as an external agent over stdio, and also provides a local Textual UI client.
44

5-
## Configure in Zed
5+
## Configure in Zed (recommended for editor integration)
66

77
Add an `agent_servers` entry to Zed’s `settings.json`. Point `command` to the Python interpreter that has both `agent-client-protocol` and `mini-swe-agent` installed, and `args` to this example script:
88

@@ -32,16 +32,24 @@ Notes
3232
- Set `OPENROUTER_API_KEY` to your API key.
3333
- Alternatively, you can use native OpenAI/Anthropic APIs. Set `MINI_SWE_MODEL` accordingly and provide the vendor-specific API key; `MINI_SWE_MODEL_KWARGS` is optional.
3434

35-
## Requirements
35+
## Run locally with a TUI (Textual)
3636

37-
Install mini-swe-agent (or at least its core deps) into the same environment:
37+
Use the duet launcher to run both the ACP agent and the local Textual client connected over dedicated pipes. The client keeps your terminal stdio; ACP messages flow over separate FDs.
3838

3939
```bash
40-
pip install agent-client-protocol mini-swe-agent
41-
# or: pip install litellm jinja2 tenacity
40+
# From repo root
41+
python examples/mini_swe_agent/duet.py
4242
```
4343

44-
Then in Zed, open the Agents panel and select "Mini SWE Agent (Python)" to start a thread.
44+
Environment
45+
- The launcher loads `.env` from the repo root using python-dotenv (override=True) so both child processes inherit the same environment.
46+
- Minimum for OpenRouter:
47+
- `MINI_SWE_MODEL="openrouter/openai/gpt-4o-mini"`
48+
- `OPENROUTER_API_KEY="sk-or-..."`
49+
- Optional: `MINI_SWE_MODEL_KWARGS='{"api_base":"https://openrouter.ai/api/v1"}'` (auto-injected if missing)
50+
51+
Quit behavior
52+
- Quit from the TUI cleanly ends the background loop; duet will terminate both processes gracefully and force-kill after a short timeout if needed.
4553

4654
## Behavior overview
4755

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agent-client-protocol"
3-
version = "0.0.1"
3+
version = "0.3.0"
44
description = "A Python implement of Agent Client Protocol (ACP, by Zed Industries)"
55
authors = [{ name = "Chojan Shang", email = "psiace@apache.org" }]
66
readme = "README.md"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)