Skip to content

Commit 2dafefb

Browse files
authored
feat(serve): add Zed ACP adapter for exposing agents over stdio (#1428)
* feat(serve): add Zed ACP adapter for exposing agents over stdio Introduces `beeai_framework.adapters.acp_zed`, a new serve adapter that exposes any BeeAI agent as a program speaking Zed's Agent Client Protocol (https://agentclientprotocol.com) over stdio JSON-RPC. - `ACPZedServer` subclasses the generic `Server` and wires a single registered agent into `acp.run_agent()`. Factories ship for `RequirementAgent`, `ToolCallingAgent`, and `ReActAgent`; each maps BeeAI run events onto ACP `session/update` notifications (assistant text as `agent_message_chunk`, internal reasoning as `agent_thought_chunk`). - `ACPZedServerAgent` implements `initialize`, `new_session`, `prompt`, `cancel`, and `close_session`, clones the template agent per session, and routes memory through the existing `MemoryManager`. - `ACPZedReadFileTool` / `ACPZedWriteFileTool` expose the client-side filesystem methods (`fs/read_text_file`, `fs/write_text_file`) as BeeAI tools — absolute-path validated, capability-gated, and scoped to the active session via a ContextVar so tool code stays free of ACP plumbing. - Optional dependency `agent-client-protocol`; installable via `pip install "beeai-framework[acp-zed]"`. - Example at `examples/serve/acp_zed.py` plus Zed `settings.json` launch snippet in the module docstring. Drive-bys to clear pre-commit hook (pyrefly + ruff) that was already failing on main from #1408 / #1409: - Guard `parse_module().entity_id` against `None` in `DocumentLoader` and `DocumentProcessor`, mirroring `VectorStore` / `TextSplitter`. - Sort imports in three `adapters/langchain/backend/*.py` files. - Add `-> None` return annotations in `tests/utils/test_strings.py`. - Reformat `tools/tool.py` and `tests/utils/test_strings.py` per ruff. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * fix(acp_zed): address review feedback on concurrency, logging, tool calls - `prompt()` now cancels and drains any in-flight task for the same `session_id` before starting a new one, so back-to-back prompts don't leave an orphan producing interleaved `session/update` frames. - `_redirect_stdout_logging` walks every named logger (including those with `propagate=False`), not just the root logger, since any stray stdout write corrupts the ACP JSON-RPC framing. - `_stream_messages_since` surfaces tool-call entries on an `AssistantMessage` even when the message carries no text, so Zed shows the invocation immediately rather than waiting for the tool result. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * docs(acp_zed): add Kiwi.com MCP and LiteAgent examples Two runnable ACP stdio entrypoints showcasing different adapter paths: - `examples/serve/acp_zed_kiwi.py` — wraps the flight-search `RequirementAgent` from `examples/playground/tests/kiwi.py`, pulling its toolset from the public Kiwi.com MCP server. - `examples/serve/acp_zed_lite.py` — exposes a `LiteAgent` (not one of the three pre-registered agent types) by registering a custom factory inline. Bridges `LiteAgent`'s emitter-based `final_answer` chunks to `session_update` via an `asyncio.Queue` so chunks flush to Zed as they arrive. Also serves as a template for adapting any custom BeeAI agent. Both include Zed `settings.json` launch snippets in their docstrings. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * test(examples): exclude acp_zed stdio examples from runpy sweep Each `serve/acp_zed*.py` entrypoint calls `.serve()` which hands control to `acp.run_agent()` and blocks on stdin forever. `runpy` them and the test suite hangs. Matches the existing treatment of `serve/acp.py` and the other serve entrypoints. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * feat(acp_zed): register LiteAgent as a native agent type `LiteAgent` joins `RequirementAgent`, `ToolCallingAgent`, and `ReActAgent` as a pre-registered factory. It streams through an emitter `final_answer` callback rather than an async iterator, so the factory bridges the synchronous callback to the async ACP connection via an `asyncio.Queue`. After the turn settles, tool-call entries and tool results accumulated in memory are drained through `_stream_messages_since` so they surface in Zed's agent panel. The `examples/serve/acp_zed_lite.py` example drops its inline factory registration — users now just `.register(LiteAgent(...)).serve()`. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * fix(acp_zed): move examples into subdir to avoid acp.py import shadow Running `python examples/serve/acp_zed_lite.py` puts `examples/serve/` on `sys.path[0]`, where `from acp import ...` inside the adapter resolves to the sibling `examples/serve/acp.py` (the IBM/BeeAI ACP example) instead of the Zed SDK's `acp` package. The shadowed module then drags in `acp_sdk`, which has a separate pre-existing uvicorn incompatibility, and the whole import chain collapses with `AttributeError: module 'uvicorn.config' has no attribute 'LoopSetupType'`. Move the three Zed ACP entrypoints into `examples/serve/acp_zed/`: - `examples/serve/acp_zed.py` → `examples/serve/acp_zed/simple.py` - `examples/serve/acp_zed_kiwi.py` → `examples/serve/acp_zed/kiwi.py` - `examples/serve/acp_zed_lite.py` → `examples/serve/acp_zed/lite.py` With this layout, running the script places `examples/serve/acp_zed/` on `sys.path[0]` instead, so `acp` resolves to the installed SDK and no shadow can occur. Docstring launch snippets and the `test_examples.py` exclusion glob are updated to match. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * feat(tools): add coding-agent tool primitives + ACP terminal Five new tools closing the biggest gaps for coding agents running through Zed ACP or any other serve adapter. Framework-level (reusable from any agent): - `beeai_framework.tools.code.ShellTool` — subprocess runner with timeout, list-form argv (no shell injection), returns exit code + stdout/stderr. - `beeai_framework.tools.filesystem.GlobTool` — pathlib-based file discovery with hidden-file gating and truncation cap. - `beeai_framework.tools.filesystem.GrepTool` — recursive regex search, shells out to `rg` when available (JSON stream parsing, gitignore-aware), falls back to stdlib `re` + `rglob` otherwise. - `beeai_framework.tools.filesystem.FilePatchTool` — two modes: unified-diff via `patch-ng`, and single-file exact-text replace keyed on expected occurrence count (prevents silent over-matching, the most common agent edit footgun). New optional `[filesystem]` extra. Adapter-level (Zed ACP): - `ACPZedTerminalTool` — routes commands through ACP `terminal/*` so live output renders in Zed's terminal widget and the process lifetime is editor-controlled. Extends `FsBridge` with `create_terminal`, `terminal_output`, `wait_for_terminal_exit`, `release_terminal`, `kill_terminal` passthroughs and a `can_terminal` capability gate. Wiring: - `examples/serve/acp_zed/lite.py` now registers all five tools alongside the existing FS read/write. - 23 new unit tests covering happy paths, timeouts, capability gating, path validation, rg-present / rg-missing grep branches, and diff round-trips. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * feat(acp_zed): route io_confirm through session/request_permission Wire the framework's existing `io_confirm` / `setup_io_context` ContextVar pattern into the ACP Zed adapter, mirroring how A2A and IBM/BeeAI-ACP already override it in their own scopes. Any agent code that calls `io_confirm(...)` — most notably `AskPermissionRequirement` — now delivers the prompt to the editor user via ACP's `session/request_permission` and maps `AllowedOutcome` / `DeniedOutcome` back to `bool`, instead of hanging on stdin (which is owned by the JSON-RPC transport under ACP). - `ACPZedIOContext` (new `adapters/acp_zed/serve/io.py`) installs `confirm` and `read` handlers on enter, restores on exit. - `FsBridge.request_permission` passthrough. - `ACPZedServerAgent.prompt` creates the turn task *inside* the io context so the ContextVar propagates to `run_turn` and any tool / requirement code it calls. - `io_read` raises a clear `RuntimeError` under the adapter — ACP has no free-form text-input method, and silently hanging on the transport stdin is worse than failing loudly. - 4 new tests: allow / deny round-trips, read-raises-under-context, and context teardown restoration. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * refactor(tools): protocol-agnostic Shell/Read/Edit via ContextVar backends Instead of shipping ACP-specific tool subclasses that thread a server handle through every agent definition, move protocol awareness behind a ContextVar- based backend — same shape as `beeai_framework/utils/io.py` already uses for `io_confirm`. One generic tool, one implementation, behavior adapts to whoever is serving. New generic tools (replace the ACP subclasses): - `ShellTool` — delegates to `ShellBackend` from `tools/code/_shell_backend.py`. Default is local `asyncio.create_subprocess_exec`. - `FileReadTool` — delegates to `FileBackend` from `tools/filesystem/_file_backend.py`. - `FileEditTool` — generic edit with `overwrite` and `replace` modes, both via `FileBackend.read_text` + `write_text`. Replaces the old `FilePatchTool`. ACP Zed backends (`adapters/acp_zed/serve/backends.py`): - `ACPShellBackend` routes commands through `terminal/create` + `wait_for_terminal_exit` + `terminal_output` + `release_terminal`. - `ACPFileBackend` routes reads + writes through `fs/read_text_file` / `fs/write_text_file`. Turn context (`adapters/acp_zed/serve/io.py`): - `ACPZedIOContext` now installs all three overrides on enter (shell + file + io_confirm) and restores on exit. Wired into `ACPZedServerAgent.prompt` around the `asyncio.create_task` so the ContextVars propagate to `run_turn`. Removed (superseded by the generic tools): - `ACPZedReadFileTool`, `ACPZedWriteFileTool`, `ACPZedTerminalTool` - `FilePatchTool` (patch-ng) and the `[filesystem]` extra Example: `acp_zed/lite.py` + `acp_zed/simple.py` now use only generic tools — same agent definition would run unchanged on any other serve mode. 30 unit tests pass: local defaults for each tool, stub-backend round-trips, ACP backend capability gating, full end-to-end through `ACPZedIOContext`. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * refactor(tools): tighten backend boundaries and error handling Review cleanups on the ContextVar-backend layer: - `LocalShellBackend.run` owns the `FileNotFoundError → ToolError("Command not found")` translation; `ShellTool._run` loses its backend-specific try/except. Tool stays generic, error surface stays consistent across backends. - `ACPShellBackend.run` stops silently dropping unsupported fields: - `input_text` now raises `ToolError` (ACP terminal has no stdin path; silently ignoring would let an agent submit piped input and watch the command run without it). - `timeout_seconds` is enforced via `asyncio.wait_for` + `kill_terminal` + `release_terminal` on timeout. Previously the wait could hang forever. - Drop the dead `output_byte_limit` parameter from `ShellBackend` — the local backend never enforced it and the ACP backend no longer plumbs it through; easy to add back when a backend actually honors it. - Remove the `_ = Any` dead-code marker and the unused `typing.Any` import from `_shell_backend.py`. - `FileEditTool._run` simplifies the missing-file-on-overwrite flow — one clear branch instead of the `original = "" if ... else None; if original is None: raise` shuffle. - `FsBridge.request_permission` typed against the concrete ACP schema (`list[PermissionOption]`, `ToolCallUpdate`, `RequestPermissionResponse`) instead of `list[Any] / Any / Any`. Two new tests cover the ACP backend's stricter contract: - `test_shell_backend_rejects_stdin` — confirms `ToolError` for input_text. - `test_shell_backend_timeout_kills_terminal` — monkeypatches `wait_for_terminal_exit` to hang and asserts kill + release fire and `timed_out` is set. 32 unit tests pass; ruff + pyrefly at 0 errors. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * docs(acp_zed): tidy the three examples Consistent spine across all three (`load_dotenv`, async `_build_agent`, guarded `main`), and each one now has a distinct, obvious role: - `simple.py` — smallest workable coding agent: `FileReadTool` + `FileEditTool` + `ShellTool` on a `RequirementAgent`. Zero ACP-specific code in the agent definition; the adapter installs ACP-routed backends for the turn. Previously sync, missing `ShellTool`, no `load_dotenv`. - `lite.py` — full coding-tool loadout on `LiteAgent` (file r/w, shell, glob, grep). Dropped the five `# noqa: F401` placeholder imports and the commented-out tool entries inside `tools=[...]`; docstring now points readers at `beeai_framework.tools.{search,think,weather}` for extras. - `kiwi.py` — docstring updated to make its specialized-MCP role clear and to point readers at `simple.py`/`lite.py` for the coding patterns. No code changes. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> * docs(acp_zed): document the Zed ACP adapter and coding-tool primitives Adds a dedicated integrations/acp-zed page covering setup, the per-turn backend swap, supported agent types, and ACP-specific tool caveats. Documents the new Shell/FileRead/FileEdit/Glob/Grep tools and the ContextVar-pluggable shell + file backends in modules/tools, and lists the new server in modules/serve. Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com> --------- Signed-off-by: Tomáš Dvořák <toomas2d@gmail.com>
1 parent 2523a8a commit 2dafefb

35 files changed

Lines changed: 2626 additions & 4 deletions

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"integrations/agent-stack",
6464
"integrations/mcp",
6565
"integrations/a2a",
66+
"integrations/acp-zed",
6667
"integrations/watsonx-orchestrate",
6768
"integrations/openai-api"
6869
]

docs/integrations/acp-zed.mdx

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
---
2+
title: "ACP (Zed)"
3+
icon: "code"
4+
---
5+
6+
The **ACP Zed adapter** exposes a single BeeAI agent as a [Zed Agent Client Protocol (ACP)](https://github.com/zed-industries/agent-client-protocol) program over stdio, so it shows up directly inside [Zed](https://zed.dev/)'s agent panel.
7+
8+
Unlike the HTTP-based [ACP integration](./acp.mdx), Zed's ACP runs **one agent per stdio process**: Zed launches your Python script, talks JSON-RPC over stdin/stdout, and renders responses, tool calls, and terminal output in the editor UI.
9+
10+
The adapter also routes BeeAI's generic coding tools (`ShellTool`, `FileReadTool`, `FileEditTool`) through Zed's filesystem and terminal capabilities for the duration of a turn — so file edits show up as diffs in unsaved buffers, and shell commands open in Zed's terminal widget.
11+
12+
---
13+
14+
## Prerequisites
15+
16+
- **[Zed](https://zed.dev/)** editor installed
17+
- **BeeAI Framework** installed with the `acp-zed` extra:
18+
19+
```bash
20+
pip install "beeai-framework[acp-zed]"
21+
```
22+
23+
- A configured LLM backend (Ollama, OpenAI, watsonx, …)
24+
25+
---
26+
27+
## Quick start
28+
29+
### 1. Build and serve an agent
30+
31+
The smallest viable coding agent — three generic tools, no ACP-specific code:
32+
33+
{/* <!-- embedme python/examples/serve/acp_zed/simple.py --> */}
34+
```py Python [expandable]
35+
"""Smallest viable coding agent over Zed's Agent Client Protocol (stdio).
36+
37+
Three generic tools — `FileReadTool`, `FileEditTool`, `ShellTool` — and nothing
38+
ACP-specific in the agent definition. When served through `ACPZedServer`, the
39+
adapter installs ACP-routed backends for the turn, so file reads/writes flow
40+
through `fs/read_text_file` / `fs/write_text_file` and shell commands open in
41+
Zed's terminal widget. The same agent definition would run unchanged as a CLI
42+
script or over any other serve mode.
43+
44+
Launch from Zed (`~/.config/zed/settings.json`):
45+
46+
{
47+
"agent_servers": {
48+
"beeai": {
49+
"command": "python",
50+
"args": ["/absolute/path/to/examples/serve/acp_zed/simple.py"]
51+
}
52+
}
53+
}
54+
55+
Prereqs: `pip install "beeai-framework[acp-zed]"`, Ollama running with
56+
`granite4:micro`.
57+
"""
58+
59+
from __future__ import annotations
60+
61+
import asyncio
62+
import sys
63+
64+
from dotenv import load_dotenv
65+
66+
from beeai_framework.adapters.acp_zed import ACPZedServer
67+
from beeai_framework.agents.requirement import RequirementAgent
68+
from beeai_framework.backend import ChatModel
69+
from beeai_framework.memory import UnconstrainedMemory
70+
from beeai_framework.tools.code import ShellTool
71+
from beeai_framework.tools.filesystem import FileEditTool, FileReadTool
72+
73+
load_dotenv()
74+
75+
76+
async def _build_agent() -> RequirementAgent:
77+
return RequirementAgent(
78+
llm=ChatModel.from_name("ollama:granite4:micro"),
79+
tools=[FileReadTool(), FileEditTool(), ShellTool()],
80+
memory=UnconstrainedMemory(),
81+
name="beeai-coder",
82+
description="A BeeAI RequirementAgent running as a Zed ACP agent.",
83+
)
84+
85+
86+
def main() -> None:
87+
agent = asyncio.run(_build_agent())
88+
ACPZedServer().register(agent).serve()
89+
90+
91+
if __name__ == "__main__":
92+
try:
93+
main()
94+
except KeyboardInterrupt:
95+
sys.exit(0)
96+
97+
```
98+
99+
### 2. Register the agent in Zed
100+
101+
Add the entry to `~/.config/zed/settings.json`:
102+
103+
```json
104+
{
105+
"agent_servers": {
106+
"beeai": {
107+
"command": "python",
108+
"args": ["/absolute/path/to/examples/serve/acp_zed/simple.py"]
109+
}
110+
}
111+
}
112+
```
113+
114+
Open Zed's agent panel and pick **beeai** from the dropdown — the script is launched on demand and JSON-RPC frames flow over stdio.
115+
116+
---
117+
118+
## How it works
119+
120+
`ACPZedServer` wraps the agent in an `ACPZedServerAgent` (one process, one agent) and runs it under the [`acp` Python SDK](https://github.com/zed-industries/agent-client-protocol). For every `session/prompt` call from the editor it:
121+
122+
1. Creates a session-scoped clone of the agent (so memory persists across prompts in the same chat but is isolated between chats).
123+
2. Installs ACP-routed backends for the duration of the turn — `ShellBackend`, `FileBackend`, and `io_confirm` are swapped onto implementations that talk to the editor.
124+
3. Streams assistant text via `agent_message_chunk`, tool calls and tool results via `agent_thought_chunk`, so users see the agent's intermediate steps live.
125+
126+
The wiring lives in three places:
127+
128+
- `ACPZedServer` / `ACPZedServerAgent` — the server + per-session lifecycle.
129+
- `FsBridge` — agent-side handle to the editor's `fs/*` and `terminal/*` methods, with the active `session_id` carried through a `ContextVar`.
130+
- `ACPZedIOContext` — the per-turn context manager that swaps backends in and out.
131+
132+
### Supported agent types
133+
134+
The adapter ships factories for every BeeAI agent type, so `register(agent)` works out of the box:
135+
136+
- `RequirementAgent`
137+
- `ToolCallingAgent`
138+
- `ReActAgent`
139+
- `LiteAgent`
140+
141+
Each factory streams the right events for that agent's emitter shape (e.g. `final_answer` for `LiteAgent`, `partial_update` for `ReActAgent`).
142+
143+
### stdio framing
144+
145+
Zed's ACP transport uses **stdout exclusively for JSON-RPC frames**. A stray `print()` or log line on stdout will corrupt the stream. The adapter rebinds any logging handler bound to `sys.stdout` onto `sys.stderr` at startup; if you have your own logging configuration, make sure it follows suit.
146+
147+
---
148+
149+
## Auto-routed coding tools
150+
151+
The headline benefit of running under `ACPZedServer` is that the [generic coding tools](/modules/tools/#coding-tools) become **editor-aware** with no code changes. The same `ShellTool()` / `FileReadTool()` / `FileEditTool()` that runs against local disk in a CLI script will, when served through this adapter, transparently route through:
152+
153+
| Tool | Local default | Under `ACPZedServer` |
154+
|:----------------|:---------------------------|:----------------------------------------------------|
155+
| `FileReadTool` | `pathlib.Path.read_text` | `fs/read_text_file` — respects unsaved buffers |
156+
| `FileEditTool` | `pathlib.Path.write_text` | `fs/write_text_file` — editor renders diffs |
157+
| `ShellTool` | local `asyncio` subprocess | `terminal/*` — opens Zed's terminal widget |
158+
| `io_confirm` | console prompt | `session/request_permission` — modal in editor |
159+
160+
Tools see this through a `ContextVar`-backed dispatch — no ACP-specific subclasses required. Outside an ACP turn, every tool falls back to its local default.
161+
162+
```py
163+
from beeai_framework.adapters.acp_zed import ACPZedServer
164+
from beeai_framework.agents.lite import LiteAgent
165+
from beeai_framework.backend import ChatModel
166+
from beeai_framework.tools.code import ShellTool
167+
from beeai_framework.tools.filesystem import FileEditTool, FileReadTool, GlobTool, GrepTool
168+
169+
agent = LiteAgent(
170+
llm=ChatModel.from_name("openai:gpt-4o-mini"),
171+
tools=[FileReadTool(), FileEditTool(), ShellTool(), GlobTool(), GrepTool()],
172+
)
173+
174+
ACPZedServer().register(agent).serve()
175+
```
176+
177+
`GlobTool` and `GrepTool` are not routed — they hit the local filesystem directly, since the ACP capability set has no equivalent. They still work, but they don't see Zed's unsaved buffers.
178+
179+
### ACP-specific behavior of routed backends
180+
181+
A few things worth knowing about the ACP backends:
182+
183+
- **`ShellTool` stdin**`input_text` raises `ToolError` under the ACP terminal backend; the protocol has no way to feed stdin to a terminal.
184+
- **`ShellTool` streams** — Zed's terminal conflates stdout and stderr. The ACP backend returns the combined output in `stdout` and leaves `stderr` empty.
185+
- **`ShellTool` timeouts** — enforced by `asyncio.wait_for` around `wait_for_terminal_exit`, with an explicit `kill_terminal` + `release_terminal` on timeout.
186+
- **`io_read` is unsupported** — ACP has no free-form text-input method; calls raise `RuntimeError`. Use a tool call or `io_confirm` (which routes through `session/request_permission`) instead.
187+
- **Capability gating** — every routed call checks the client's advertised `ClientCapabilities` before dispatching. Reads/writes/terminals fail fast with a `ToolError` if Zed didn't advertise the matching capability.
188+
189+
---
190+
191+
## Configuration
192+
193+
`ACPZedServerConfig` is intentionally minimal — there's no port or host because Zed manages the process:
194+
195+
```py
196+
from beeai_framework.adapters.acp_zed import ACPZedServer, ACPZedServerConfig
197+
198+
ACPZedServer(
199+
config=ACPZedServerConfig(
200+
agent_name="beeai-coder",
201+
agent_description="A coding agent backed by BeeAI.",
202+
)
203+
).register(agent).serve()
204+
```
205+
206+
If `agent_name` / `agent_description` are omitted, the adapter uses the agent's own metadata.
207+
208+
---
209+
210+
## More examples
211+
212+
<CardGroup cols={2}>
213+
<Card
214+
title="LiteAgent + full toolset"
215+
icon="hammer"
216+
href="https://github.com/i-am-bee/beeai-framework/blob/main/python/examples/serve/acp_zed/lite.py"
217+
>
218+
Streams tokens via `LiteAgent`'s emitter callback and bridges through an `asyncio.Queue` to ACP.
219+
</Card>
220+
<Card
221+
title="Kiwi.com flight-search"
222+
icon="plane"
223+
href="https://github.com/i-am-bee/beeai-framework/blob/main/python/examples/serve/acp_zed/kiwi.py"
224+
>
225+
Domain-specific agent that pulls its toolset from a public MCP server. No file/shell tools.
226+
</Card>
227+
</CardGroup>
228+
229+
---
230+
231+
## Limitations
232+
233+
- **One agent per process**`serve()` raises if more than one agent is registered. Run multiple agents as separate `agent_servers` entries in `settings.json`.
234+
- **No image/audio prompt blocks** — the prompt converter currently extracts text and embedded-resource text only; image and audio blocks are dropped pending stable multi-part `UserMessage` support across all backends.
235+
- **`MCPTool` from Zed-managed servers**`new_session` accepts `mcp_servers` from the editor but the adapter does not currently spin them up. Configure MCP servers on the agent itself instead.

docs/modules/serve.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The following table lists the currently supported providers:
2121
| [A2A](/integrations/a2a/#server) | `pip install beeai-framework[a2a]` |
2222
| [Agent Stack](/integrations/agent-stack/#server) | `pip install beeai-framework[agentstack]` |
2323
| [MCP](/integrations/mcp/#server) | `pip install beeai-framework[mcp]` |
24+
| [ACP (Zed editor, stdio)](/integrations/acp-zed) | `pip install beeai-framework[acp-zed]` |
2425
| [IBM watsonx Orchestrate](/integrations/watsonx-orchestrate/#server) | `pip install beeai-framework` |
2526
| [OpenAI Chat Completion API](/integrations/openai-api/#server) | `pip install beeai-framework` |
2627
| [OpenAI Responses API](/integrations/openai-api/#server) | `pip install beeai-framework` |

docs/modules/tools.mdx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ Ready-to-use tools that provide immediate functionality for common agent tasks:
2626
| [VectorStoreSearch](#vector-store-search) | Search for documents in a vector database |
2727
| [Python](#python) | Let agent run an arbitrary Python code in a sandboxed environment |
2828
| [Sandbox](#sandbox) | Run custom Python functions in a sandboxed environment |
29+
| [Shell](#shell) | Run a command and capture its output (with pluggable backend) |
30+
| [FileRead](#file-read) | Read a text file via a pluggable file backend |
31+
| [FileEdit](#file-edit) | Overwrite or search-and-replace a text file with a unified diff |
32+
| [Glob](#glob) | List files matching a glob pattern |
33+
| [Grep](#grep) | Recursively search files for a regex (uses ripgrep when available) |
2934

3035
[Request additional built-in tools](https://github.com/i-am-bee/beeai-framework/discussions)
3136

@@ -1143,6 +1148,120 @@ Environmental variables can be overridden (or defined) in the following ways:
11431148
Only `PythonTool` can access files.
11441149
</Note>
11451150

1151+
<Heading level={3} id="coding-tools">Coding-agent tool primitives</Heading>
1152+
1153+
The framework ships a set of protocol-agnostic primitives for building coding agents. These tools work without any extra setup against the local filesystem and a local subprocess — but their I/O is dispatched through `ContextVar`-backed backends, which means a serve adapter (like the [Zed ACP adapter](/integrations/acp-zed)) can transparently route them through an editor's filesystem and terminal capabilities for the duration of a turn.
1154+
1155+
| Primitive | Default backend | Pluggable via |
1156+
|:----------------|:-------------------------------------------|:----------------------------------------------|
1157+
| `ShellTool` | `LocalShellBackend` (`asyncio.subprocess`) | `setup_shell_backend(...)` |
1158+
| `FileReadTool` | `LocalFileBackend` (`pathlib`) | `setup_file_backend(...)` |
1159+
| `FileEditTool` | `LocalFileBackend` (`pathlib`) | `setup_file_backend(...)` |
1160+
| `GlobTool` | local — `pathlib.Path.glob` | not pluggable |
1161+
| `GrepTool` | local — `ripgrep` if on `$PATH`, else stdlib | not pluggable |
1162+
1163+
Tool code stays the same regardless of where it runs; protocol-awareness is the backend's responsibility.
1164+
1165+
<Heading level={4} id="shell">Shell tool</Heading>
1166+
1167+
Run a command (as a list of arguments — **not** through a shell) and return its exit code, stdout, stderr, and timeout/truncation flags.
1168+
1169+
```py Python [expandable]
1170+
from beeai_framework.tools.code import ShellTool, ShellToolInput
1171+
1172+
result = await ShellTool().run(
1173+
ShellToolInput(command=["git", "status", "--porcelain"], timeout_seconds=10)
1174+
)
1175+
print(result.result["stdout"])
1176+
```
1177+
1178+
Inputs:
1179+
1180+
- `command` *(list[str], required)* — argv list. Use `shlex.split` if you only have a string.
1181+
- `cwd`, `env` — working directory and extra env vars merged over `os.environ`.
1182+
- `timeout_seconds` *(default `60`)* — kills the process if it runs longer; set to `None` to disable.
1183+
- `input_text` — piped to stdin. Some backends (e.g. ACP terminals) don't support stdin and will raise.
1184+
1185+
To swap the backend yourself (e.g. for a sandbox or remote runner):
1186+
1187+
```py
1188+
from beeai_framework.tools.code import setup_shell_backend, ShellBackend
1189+
1190+
class MyBackend(ShellBackend): ...
1191+
1192+
cleanup = setup_shell_backend(MyBackend())
1193+
try:
1194+
...
1195+
finally:
1196+
cleanup()
1197+
```
1198+
1199+
<Heading level={4} id="file-read">FileRead tool</Heading>
1200+
1201+
Read a text file via the active `FileBackend`. Path must be absolute. Optional `line` (1-based) and `limit` slice the file without loading the whole thing.
1202+
1203+
```py Python
1204+
from beeai_framework.tools.filesystem import FileReadTool, FileReadToolInput
1205+
1206+
text = await FileReadTool().run(
1207+
FileReadToolInput(path="/abs/path/to/file.py", line=1, limit=80)
1208+
)
1209+
```
1210+
1211+
<Heading level={4} id="file-edit">FileEdit tool</Heading>
1212+
1213+
Edit a file in one of two modes, returning a unified diff in the output:
1214+
1215+
- `mode="overwrite"` — write `content` verbatim. Creates the file if missing.
1216+
- `mode="replace"` — exact-text search-and-replace. Fails (without writing) if the number of occurrences of `old` doesn't match `expected_occurrences` (default `1`), preventing the most common agent edit footgun: silent over-matching.
1217+
1218+
```py Python [expandable]
1219+
from beeai_framework.tools.filesystem import FileEditTool, FileEditToolInput
1220+
1221+
result = await FileEditTool().run(
1222+
FileEditToolInput(
1223+
mode="replace",
1224+
path="/abs/path/to/config.py",
1225+
old="DEBUG = False",
1226+
new="DEBUG = True",
1227+
expected_occurrences=1,
1228+
)
1229+
)
1230+
print(result.result["diff"])
1231+
```
1232+
1233+
<Heading level={4} id="glob">Glob tool</Heading>
1234+
1235+
List paths matching a glob pattern under a root directory. Hidden files (`.foo`) are excluded by default.
1236+
1237+
```py Python
1238+
from beeai_framework.tools.filesystem import GlobTool, GlobToolInput
1239+
1240+
result = await GlobTool().run(GlobToolInput(pattern="**/*.py", root="src", limit=200))
1241+
for path in result.result["matches"]:
1242+
print(path)
1243+
```
1244+
1245+
<Heading level={4} id="grep">Grep tool</Heading>
1246+
1247+
Recursively search files for a regex. Uses `ripgrep` (`rg`) when it's on `$PATH` for speed and `.gitignore` honoring; falls back to a pure-Python walk that skips common ignored directories (`.git`, `node_modules`, `.venv`, …) when it isn't.
1248+
1249+
```py Python [expandable]
1250+
from beeai_framework.tools.filesystem import GrepTool, GrepToolInput
1251+
1252+
result = await GrepTool().run(
1253+
GrepToolInput(pattern=r"def\s+main\b", root="src", glob="*.py", context_lines=2)
1254+
)
1255+
for hit in result.result["matches"]:
1256+
print(f"{hit['path']}:{hit['line']}: {hit['text']}")
1257+
```
1258+
1259+
The output shape is the same regardless of which backend ran — the `used_ripgrep` flag tells you which one did.
1260+
1261+
<Tip>
1262+
Combine these tools with the [ACP Zed adapter](/integrations/acp-zed) to expose a coding agent inside the [Zed](https://zed.dev/) editor — file reads/writes route through Zed's unsaved buffers, shell commands open in Zed's terminal widget, all without changing your tool code.
1263+
</Tip>
1264+
11461265
---
11471266

11481267
## Creating custom tools
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright 2025 © BeeAI a Series of LF Projects, LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from beeai_framework.adapters.acp_zed.serve.backends import ACPFileBackend, ACPShellBackend
5+
from beeai_framework.adapters.acp_zed.serve.server import ACPZedServer, ACPZedServerConfig
6+
7+
__all__ = [
8+
"ACPFileBackend",
9+
"ACPShellBackend",
10+
"ACPZedServer",
11+
"ACPZedServerConfig",
12+
]

0 commit comments

Comments
 (0)