From 437087cd4c14e8d95b98a79d8149a2be8be3c86f Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Fri, 16 Oct 2026 15:23:00 +0200 Subject: [PATCH 1/8] feat: add ACP agents example (TypeScript) Drive OpenCode, goose or Claude Code inside an E2B sandbox with the official ACP client through sandbox.acp. One client for all agents; the agent's tools run inside the sandbox. --- examples/acp-agents-js/.env.template | 5 ++ examples/acp-agents-js/README.md | 38 ++++++++++ examples/acp-agents-js/package.json | 18 +++++ examples/acp-agents-js/src/index.ts | 105 +++++++++++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 examples/acp-agents-js/.env.template create mode 100644 examples/acp-agents-js/README.md create mode 100644 examples/acp-agents-js/package.json create mode 100644 examples/acp-agents-js/src/index.ts diff --git a/examples/acp-agents-js/.env.template b/examples/acp-agents-js/.env.template new file mode 100644 index 00000000..11cbf9ca --- /dev/null +++ b/examples/acp-agents-js/.env.template @@ -0,0 +1,5 @@ +E2B_API_KEY= +# For the opencode and goose agents +OPENAI_API_KEY= +# For the claude agent +ANTHROPIC_API_KEY= diff --git a/examples/acp-agents-js/README.md b/examples/acp-agents-js/README.md new file mode 100644 index 00000000..84642d0a --- /dev/null +++ b/examples/acp-agents-js/README.md @@ -0,0 +1,38 @@ +# ACP coding agents in E2B Sandbox (TypeScript) + +Run a coding agent that speaks the [Agent Client Protocol](https://agentclientprotocol.com) (ACP), such as OpenCode, goose or Claude Code, inside an E2B sandbox and drive it from your own code with the official ACP client. + +One client works for every agent: only the sandbox template and the command that starts the agent change. + +| `AGENT` | Template | Command | Key | +|---|---|---|---| +| `opencode` (default) | `opencode` | `opencode acp` | `OPENAI_API_KEY` | +| `goose` | `goose` | `goose acp --with-builtin developer` | `OPENAI_API_KEY` | +| `claude` | `claude` | `claude-agent-acp` | `ANTHROPIC_API_KEY` | + +## How it works + +`sandbox.acp.start(command)` starts the agent inside the sandbox and talks ACP over its stdin and stdout. `agent.stream` plugs straight into `ClientSideConnection` from `@agentclientprotocol/sdk`: + +```ts +const agent = await sandbox.acp.start('opencode acp', { cwd, envs }) +const connection = new ClientSideConnection(() => client, agent.stream) + +await connection.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) +const { sessionId } = await connection.newSession({ cwd, mcpServers: [] }) +await connection.prompt({ sessionId, prompt: [{ type: 'text', text: '...' }] }) + +await agent.close() +``` + +The agent's tools always run inside the sandbox. `sandbox.acp` turns off the client filesystem and terminal capabilities in `initialize` and rejects any agent request for them, so the agent never reads or writes files on the machine running your client. + +Your client still answers permission requests and receives the agent's messages and tool calls. This example approves every permission request and prints the agent's progress. + +## How to run + +1. Copy `.env.template` to `.env` and fill in `E2B_API_KEY` and the key for the agent you use. +2. Install dependencies with `npm install`. +3. Run `npm start`, or pick another agent with `AGENT=goose npm start` or `AGENT=claude npm start`. + +The agent writes and runs `fizzbuzz.py` in the sandbox, then the example runs the same file again to show that the result lives in the sandbox. diff --git a/examples/acp-agents-js/package.json b/examples/acp-agents-js/package.json new file mode 100644 index 00000000..9c5fc74c --- /dev/null +++ b/examples/acp-agents-js/package.json @@ -0,0 +1,18 @@ +{ + "name": "acp-agents", + "version": "1.0.0", + "type": "module", + "scripts": { + "start": "tsx src/index.ts" + }, + "dependencies": { + "@agentclientprotocol/sdk": "^1.8.0", + "dotenv": "^17.4.2", + "e2b": "^2.57.0", + "tsx": "^4.19.4" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "typescript": "^5.9.3" + } +} diff --git a/examples/acp-agents-js/src/index.ts b/examples/acp-agents-js/src/index.ts new file mode 100644 index 00000000..f867d6a7 --- /dev/null +++ b/examples/acp-agents-js/src/index.ts @@ -0,0 +1,105 @@ +import 'dotenv/config' +import { Sandbox } from 'e2b' +import { + ClientSideConnection, + PROTOCOL_VERSION, + type Client, +} from '@agentclientprotocol/sdk' + +// Each agent is a sandbox template plus the command that starts it in ACP mode. +const agents = { + opencode: { + template: 'opencode', + command: 'opencode acp', + envs: { OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '' }, + // OpenCode reads its model from its config file. + files: { + '/home/user/.config/opencode/opencode.json': JSON.stringify({ + model: 'openai/gpt-5.6-luna', + }), + }, + }, + goose: { + template: 'goose', + command: 'goose acp --with-builtin developer', + envs: { + OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '', + GOOSE_PROVIDER: 'openai', + GOOSE_MODEL: 'gpt-5.6-luna', + }, + files: {}, + }, + claude: { + template: 'claude', + command: 'claude-agent-acp', + envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? '' }, + files: {}, + }, +} + +const name = (process.env.AGENT ?? 'opencode') as keyof typeof agents +const agentConfig = agents[name] +if (!agentConfig) { + throw new Error(`Unknown AGENT "${name}", use one of: ${Object.keys(agents).join(', ')}`) +} + +// The ACP client: it shows the agent's progress and answers permission requests. +// File edits and shell commands happen inside the sandbox, not on this machine. +const client: Client = { + async requestPermission({ toolCall, options }) { + const allow = options.find((o) => o.kind === 'allow_once') ?? options[0] + console.log(`\n[permission] ${toolCall.title} -> ${allow.name}`) + return { outcome: { outcome: 'selected', optionId: allow.optionId } } + }, + async sessionUpdate({ update }) { + if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') { + process.stdout.write(update.content.text) + } else if (update.sessionUpdate === 'tool_call') { + console.log(`\n[tool] ${update.title}`) + } + }, +} + +const sandbox = await Sandbox.create(agentConfig.template, { timeoutMs: 10 * 60_000 }) +console.log(`Sandbox ${sandbox.sandboxId} with ${name}`) + +try { + for (const [path, content] of Object.entries(agentConfig.files)) { + await sandbox.files.write(path, content) + } + const cwd = '/home/user/project' + await sandbox.files.makeDir(cwd) + + // Start the agent inside the sandbox and connect an ACP client to it. + const agent = await sandbox.acp.start(agentConfig.command, { + cwd, + envs: agentConfig.envs, + }) + const connection = new ClientSideConnection(() => client, agent.stream) + + const { agentInfo } = await connection.initialize({ + protocolVersion: PROTOCOL_VERSION, + clientCapabilities: {}, + }) + console.log(`Connected to ${agentInfo?.name} ${agentInfo?.version}`) + + const { sessionId } = await connection.newSession({ cwd, mcpServers: [] }) + const { stopReason } = await connection.prompt({ + sessionId, + prompt: [ + { + type: 'text', + text: 'Create fizzbuzz.py that prints FizzBuzz for 1 to 15, run it with python3 and show the output.', + }, + ], + }) + console.log(`\n\nTurn finished: ${stopReason}`) + + // The agent worked inside the sandbox, so its result is there. + const result = await sandbox.commands.run(`python3 ${cwd}/fizzbuzz.py`) + console.log(`fizzbuzz.py in the sandbox prints:\n${result.stdout}`) + + await agent.close() +} finally { + await sandbox.kill() +} From f0fcd624b15a0b9e8c598e60aa25b6d2a38d8fb9 Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Fri, 16 Oct 2026 15:34:00 +0200 Subject: [PATCH 2/8] feat: add ACP agents example (Python) Python twin of acp-agents-js using AsyncSandbox.acp with agent-client-protocol. --- examples/acp-agents-python/.env.template | 5 + examples/acp-agents-python/.gitignore | 10 ++ examples/acp-agents-python/.python-version | 1 + examples/acp-agents-python/README.md | 39 ++++++++ examples/acp-agents-python/main.py | 108 +++++++++++++++++++++ examples/acp-agents-python/pyproject.toml | 11 +++ 6 files changed, 174 insertions(+) create mode 100644 examples/acp-agents-python/.env.template create mode 100644 examples/acp-agents-python/.gitignore create mode 100644 examples/acp-agents-python/.python-version create mode 100644 examples/acp-agents-python/README.md create mode 100644 examples/acp-agents-python/main.py create mode 100644 examples/acp-agents-python/pyproject.toml diff --git a/examples/acp-agents-python/.env.template b/examples/acp-agents-python/.env.template new file mode 100644 index 00000000..11cbf9ca --- /dev/null +++ b/examples/acp-agents-python/.env.template @@ -0,0 +1,5 @@ +E2B_API_KEY= +# For the opencode and goose agents +OPENAI_API_KEY= +# For the claude agent +ANTHROPIC_API_KEY= diff --git a/examples/acp-agents-python/.gitignore b/examples/acp-agents-python/.gitignore new file mode 100644 index 00000000..685485c2 --- /dev/null +++ b/examples/acp-agents-python/.gitignore @@ -0,0 +1,10 @@ +# Virtual environments +.venv + +# Environment files +.env + +# Python caches +__pycache__ +.pytest_cache +.ruff_cache diff --git a/examples/acp-agents-python/.python-version b/examples/acp-agents-python/.python-version new file mode 100644 index 00000000..e4fba218 --- /dev/null +++ b/examples/acp-agents-python/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/examples/acp-agents-python/README.md b/examples/acp-agents-python/README.md new file mode 100644 index 00000000..dd3f4f93 --- /dev/null +++ b/examples/acp-agents-python/README.md @@ -0,0 +1,39 @@ +# ACP coding agents in E2B Sandbox (Python) + +Run a coding agent that speaks the [Agent Client Protocol](https://agentclientprotocol.com) (ACP), such as OpenCode, goose or Claude Code, inside an E2B sandbox and drive it from your own code with the official ACP client (`agent-client-protocol`). + +One client works for every agent: only the sandbox template and the command that starts the agent change. + +| `AGENT` | Template | Command | Key | +|---|---|---|---| +| `opencode` (default) | `opencode` | `opencode acp` | `OPENAI_API_KEY` | +| `goose` | `goose` | `goose acp --with-builtin developer` | `OPENAI_API_KEY` | +| `claude` | `claude` | `claude-agent-acp` | `ANTHROPIC_API_KEY` | + +## How it works + +`sandbox.acp.start(command)` starts the agent inside the sandbox and talks ACP over its stdin and stdout. The returned agent is an ACP transport, so it goes straight into `acp.connect_to_agent()`: + +```python +agent = await sandbox.acp.start("opencode acp", cwd=cwd, envs=envs) +connection = acp.connect_to_agent(client, agent) + +await connection.initialize(protocol_version=acp.PROTOCOL_VERSION) +session = await connection.new_session(cwd=cwd, mcp_servers=[]) +await connection.prompt(session_id=session.session_id, prompt=[acp.text_block("...")]) + +await agent.close() +``` + +The agent's tools always run inside the sandbox. `sandbox.acp` turns off the client filesystem and terminal capabilities in `initialize` and rejects any agent request for them, so the agent never reads or writes files on the machine running your client. + +Your client still answers permission requests and receives the agent's messages and tool calls. This example approves every permission request and prints the agent's progress. + +The ACP Python SDK is asynchronous, so the example uses `AsyncSandbox`. + +## How to run + +1. Copy `.env.template` to `.env` and fill in `E2B_API_KEY` and the key for the agent you use. +2. Run `uv run main.py`, or pick another agent with `AGENT=goose uv run main.py` or `AGENT=claude uv run main.py`. + +The agent writes and runs `fizzbuzz.py` in the sandbox, then the example runs the same file again to show that the result lives in the sandbox. diff --git a/examples/acp-agents-python/main.py b/examples/acp-agents-python/main.py new file mode 100644 index 00000000..bb7b3401 --- /dev/null +++ b/examples/acp-agents-python/main.py @@ -0,0 +1,108 @@ +import asyncio +import json +import os + +import acp +from acp import schema +from dotenv import load_dotenv + +from e2b import AsyncSandbox + +load_dotenv() + +# Each agent is a sandbox template plus the command that starts it in ACP mode. +AGENTS = { + "opencode": { + "template": "opencode", + "command": "opencode acp", + "envs": {"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", "")}, + # OpenCode reads its model from its config file. + "files": { + "/home/user/.config/opencode/opencode.json": json.dumps( + {"model": "openai/gpt-5.6-luna"} + ) + }, + }, + "goose": { + "template": "goose", + "command": "goose acp --with-builtin developer", + "envs": { + "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", ""), + "GOOSE_PROVIDER": "openai", + "GOOSE_MODEL": "gpt-5.6-luna", + }, + "files": {}, + }, + "claude": { + "template": "claude", + "command": "claude-agent-acp", + "envs": {"ANTHROPIC_API_KEY": os.getenv("ANTHROPIC_API_KEY", "")}, + "files": {}, + }, +} + + +class Client: + """The ACP client: it shows the agent's progress and answers permission requests. + + File edits and shell commands happen inside the sandbox, not on this machine. + """ + + async def request_permission(self, session_id, tool_call, options, **kwargs): + allow = next((o for o in options if o.kind == "allow_once"), options[0]) + print(f"\n[permission] {tool_call.title} -> {allow.name}") + return schema.RequestPermissionResponse( + outcome=schema.AllowedOutcome(outcome="selected", option_id=allow.option_id) + ) + + async def session_update(self, session_id, update, **kwargs): + if isinstance(update, schema.AgentMessageChunk) and update.content.type == "text": + print(update.content.text, end="", flush=True) + elif isinstance(update, schema.ToolCallStart): + print(f"\n[tool] {update.title}") + + +async def main(): + name = os.getenv("AGENT", "opencode") + if name not in AGENTS: + raise SystemExit(f'Unknown AGENT "{name}", use one of: {", ".join(AGENTS)}') + config = AGENTS[name] + + sandbox = await AsyncSandbox.create(config["template"], timeout=600) + print(f"Sandbox {sandbox.sandbox_id} with {name}") + try: + for path, content in config["files"].items(): + await sandbox.files.write(path, content) + cwd = "/home/user/project" + await sandbox.files.make_dir(cwd) + + # Start the agent inside the sandbox and connect an ACP client to it. + agent = await sandbox.acp.start(config["command"], cwd=cwd, envs=config["envs"]) + connection = acp.connect_to_agent(Client(), agent) + + init = await connection.initialize(protocol_version=acp.PROTOCOL_VERSION) + print(f"Connected to {init.agent_info.name} {init.agent_info.version}") + + session = await connection.new_session(cwd=cwd, mcp_servers=[]) + response = await connection.prompt( + session_id=session.session_id, + prompt=[ + acp.text_block( + "Create fizzbuzz.py that prints FizzBuzz for 1 to 15, " + "run it with python3 and show the output." + ) + ], + ) + print(f"\n\nTurn finished: {response.stop_reason}") + + # The agent worked inside the sandbox, so its result is there. + result = await sandbox.commands.run(f"python3 {cwd}/fizzbuzz.py") + print(f"fizzbuzz.py in the sandbox prints:\n{result.stdout}") + + await agent.close() + finally: + await sandbox.kill() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/acp-agents-python/pyproject.toml b/examples/acp-agents-python/pyproject.toml new file mode 100644 index 00000000..ef6db6a5 --- /dev/null +++ b/examples/acp-agents-python/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "acp-agents-python" +version = "0.1.0" +description = "Drive ACP coding agents (OpenCode, goose, Claude Code) running inside an E2B sandbox" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "agent-client-protocol>=0.12.1,<0.13", + "e2b>=2.57.0", + "python-dotenv>=1.1,<2", +] From 532231700484a6e43bb877484e4bdd9ae068f30f Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Fri, 16 Oct 2026 15:45:00 +0200 Subject: [PATCH 3/8] chore: list the ACP agents examples in the index and example runner --- README.md | 6 ++++++ tests/run-examples.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 2401d7d5..07768232 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,12 @@ Read more about E2B on the [E2B website](https://e2b.dev/?utm_source=github&utm_ TypeScript + Agent Client Protocol + Drive OpenCode, goose or Claude Code inside a Sandbox with an ACP client + Python + TypeScript + + Sandbox Agent SDK Run Sandbox Agent inside E2B and connect with the SDK - diff --git a/tests/run-examples.ts b/tests/run-examples.ts index 53546c5f..456c49d5 100644 --- a/tests/run-examples.ts +++ b/tests/run-examples.ts @@ -72,6 +72,8 @@ const scripts: { // openai 7.33 (the first release with openai/helpers/computer) requires Node 22. { name: 'openai-computer-use-python', interpreter: 'uv', file: './examples/openai-computer-use-python/', entrypoint: '1-run.py' }, { name: 'openai-computer-use-js', interpreter: 'npm', file: './examples/openai-computer-use-js/', nodeVersion: '22.22.0' }, + { name: 'acp-agents-js', interpreter: 'npm', file: './examples/acp-agents-js/', nodeVersion: '22.22.0' }, + { name: 'acp-agents-python', interpreter: 'uv', file: './examples/acp-agents-python/' }, { name: 'playwright-in-e2b', interpreter: 'npm', file: './examples/playwright-in-e2b/' }, { name: 'anthropic-claude-code-in-sandbox-js', interpreter: 'npm', file: './examples/anthropic-claude-code-in-sandbox-js/' }, { name: 'anthropic-claude-code-in-sandbox-python', interpreter: 'uv', file: './examples/anthropic-claude-code-in-sandbox-python/', entrypoint: 'python -m anthropic_claude_code_in_sandbox.main' }, From 5f9a29e8de3f3553ca7e473d5ba3e32ae2ac7e48 Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Fri, 16 Oct 2026 15:56:00 +0200 Subject: [PATCH 4/8] refactor: pass the OpenCode model through OPENCODE_CONFIG_CONTENT Drop the per-agent config-file maps and the remote write loop; OpenCode reads the same JSON from the environment. --- examples/acp-agents-js/src/index.ts | 14 +++----------- examples/acp-agents-python/main.py | 13 +++---------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/examples/acp-agents-js/src/index.ts b/examples/acp-agents-js/src/index.ts index f867d6a7..0a634dd0 100644 --- a/examples/acp-agents-js/src/index.ts +++ b/examples/acp-agents-js/src/index.ts @@ -11,12 +11,9 @@ const agents = { opencode: { template: 'opencode', command: 'opencode acp', - envs: { OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '' }, - // OpenCode reads its model from its config file. - files: { - '/home/user/.config/opencode/opencode.json': JSON.stringify({ - model: 'openai/gpt-5.6-luna', - }), + envs: { + OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '', + OPENCODE_CONFIG_CONTENT: JSON.stringify({ model: 'openai/gpt-5.6-luna' }), }, }, goose: { @@ -27,13 +24,11 @@ const agents = { GOOSE_PROVIDER: 'openai', GOOSE_MODEL: 'gpt-5.6-luna', }, - files: {}, }, claude: { template: 'claude', command: 'claude-agent-acp', envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? '' }, - files: {}, }, } @@ -64,9 +59,6 @@ const sandbox = await Sandbox.create(agentConfig.template, { timeoutMs: 10 * 60_ console.log(`Sandbox ${sandbox.sandboxId} with ${name}`) try { - for (const [path, content] of Object.entries(agentConfig.files)) { - await sandbox.files.write(path, content) - } const cwd = '/home/user/project' await sandbox.files.makeDir(cwd) diff --git a/examples/acp-agents-python/main.py b/examples/acp-agents-python/main.py index bb7b3401..63cff413 100644 --- a/examples/acp-agents-python/main.py +++ b/examples/acp-agents-python/main.py @@ -15,12 +15,9 @@ "opencode": { "template": "opencode", "command": "opencode acp", - "envs": {"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", "")}, - # OpenCode reads its model from its config file. - "files": { - "/home/user/.config/opencode/opencode.json": json.dumps( - {"model": "openai/gpt-5.6-luna"} - ) + "envs": { + "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", ""), + "OPENCODE_CONFIG_CONTENT": json.dumps({"model": "openai/gpt-5.6-luna"}), }, }, "goose": { @@ -31,13 +28,11 @@ "GOOSE_PROVIDER": "openai", "GOOSE_MODEL": "gpt-5.6-luna", }, - "files": {}, }, "claude": { "template": "claude", "command": "claude-agent-acp", "envs": {"ANTHROPIC_API_KEY": os.getenv("ANTHROPIC_API_KEY", "")}, - "files": {}, }, } @@ -71,8 +66,6 @@ async def main(): sandbox = await AsyncSandbox.create(config["template"], timeout=600) print(f"Sandbox {sandbox.sandbox_id} with {name}") try: - for path, content in config["files"].items(): - await sandbox.files.write(path, content) cwd = "/home/user/project" await sandbox.files.make_dir(cwd) From 8f4444b30bf17221148abed2b897676c00e051a5 Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Fri, 16 Oct 2026 16:07:00 +0200 Subject: [PATCH 5/8] feat: use @e2b/acp and e2b-acp in the ACP examples connectAcpAgent / connect_acp_agent replace the hand-built transport. The examples now fail if fizzbuzz.py in the sandbox does not print FizzBuzz, and the READMEs state the exact tool boundary. --- examples/acp-agents-js/README.md | 19 +++++++++---------- examples/acp-agents-js/package.json | 3 ++- examples/acp-agents-js/src/index.ts | 16 +++++++++------- examples/acp-agents-python/README.md | 19 +++++++++---------- examples/acp-agents-python/main.py | 14 +++++++++++--- examples/acp-agents-python/pyproject.toml | 3 ++- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/examples/acp-agents-js/README.md b/examples/acp-agents-js/README.md index 84642d0a..4cf95569 100644 --- a/examples/acp-agents-js/README.md +++ b/examples/acp-agents-js/README.md @@ -2,7 +2,7 @@ Run a coding agent that speaks the [Agent Client Protocol](https://agentclientprotocol.com) (ACP), such as OpenCode, goose or Claude Code, inside an E2B sandbox and drive it from your own code with the official ACP client. -One client works for every agent: only the sandbox template and the command that starts the agent change. +The same client works for all three agents: only the sandbox template and the command that starts the agent change. | `AGENT` | Template | Command | Key | |---|---|---|---| @@ -12,22 +12,21 @@ One client works for every agent: only the sandbox template and the command that ## How it works -`sandbox.acp.start(command)` starts the agent inside the sandbox and talks ACP over its stdin and stdout. `agent.stream` plugs straight into `ClientSideConnection` from `@agentclientprotocol/sdk`: +The example creates a sandbox from the agent's template, then `connectAcpAgent()` from [`@e2b/acp`](https://www.npmjs.com/package/@e2b/acp) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: ```ts -const agent = await sandbox.acp.start('opencode acp', { cwd, envs }) -const connection = new ClientSideConnection(() => client, agent.stream) +const agent = await connectAcpAgent(sandbox, { command: 'opencode acp', client, cwd, envs }) -await connection.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) -const { sessionId } = await connection.newSession({ cwd, mcpServers: [] }) -await connection.prompt({ sessionId, prompt: [{ type: 'text', text: '...' }] }) +await agent.connection.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) +const { sessionId } = await agent.connection.newSession({ cwd, mcpServers: [] }) +await agent.connection.prompt({ sessionId, prompt: [{ type: 'text', text: '...' }] }) await agent.close() ``` -The agent's tools always run inside the sandbox. `sandbox.acp` turns off the client filesystem and terminal capabilities in `initialize` and rejects any agent request for them, so the agent never reads or writes files on the machine running your client. +`connectAcpAgent()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. -Your client still answers permission requests and receives the agent's messages and tool calls. This example approves every permission request and prints the agent's progress. +Your client still answers permission requests and receives the agent's messages and tool calls. This example approves each request with its `allow_once` option and prints the agent's progress. ## How to run @@ -35,4 +34,4 @@ Your client still answers permission requests and receives the agent's messages 2. Install dependencies with `npm install`. 3. Run `npm start`, or pick another agent with `AGENT=goose npm start` or `AGENT=claude npm start`. -The agent writes and runs `fizzbuzz.py` in the sandbox, then the example runs the same file again to show that the result lives in the sandbox. +The agent writes and runs `fizzbuzz.py` in the sandbox, then the example runs the same file again and checks that it prints `FizzBuzz`, which shows the result lives in the sandbox. diff --git a/examples/acp-agents-js/package.json b/examples/acp-agents-js/package.json index 9c5fc74c..40d6e1fb 100644 --- a/examples/acp-agents-js/package.json +++ b/examples/acp-agents-js/package.json @@ -7,8 +7,9 @@ }, "dependencies": { "@agentclientprotocol/sdk": "^1.8.0", + "@e2b/acp": "^0.1.0", "dotenv": "^17.4.2", - "e2b": "^2.57.0", + "e2b": "^2.56.1", "tsx": "^4.19.4" }, "devDependencies": { diff --git a/examples/acp-agents-js/src/index.ts b/examples/acp-agents-js/src/index.ts index 0a634dd0..c87d425b 100644 --- a/examples/acp-agents-js/src/index.ts +++ b/examples/acp-agents-js/src/index.ts @@ -1,10 +1,7 @@ import 'dotenv/config' import { Sandbox } from 'e2b' -import { - ClientSideConnection, - PROTOCOL_VERSION, - type Client, -} from '@agentclientprotocol/sdk' +import { connectAcpAgent } from '@e2b/acp' +import { PROTOCOL_VERSION, type Client } from '@agentclientprotocol/sdk' // Each agent is a sandbox template plus the command that starts it in ACP mode. const agents = { @@ -63,11 +60,13 @@ try { await sandbox.files.makeDir(cwd) // Start the agent inside the sandbox and connect an ACP client to it. - const agent = await sandbox.acp.start(agentConfig.command, { + const agent = await connectAcpAgent(sandbox, { + command: agentConfig.command, + client, cwd, envs: agentConfig.envs, }) - const connection = new ClientSideConnection(() => client, agent.stream) + const connection = agent.connection const { agentInfo } = await connection.initialize({ protocolVersion: PROTOCOL_VERSION, @@ -90,6 +89,9 @@ try { // The agent worked inside the sandbox, so its result is there. const result = await sandbox.commands.run(`python3 ${cwd}/fizzbuzz.py`) console.log(`fizzbuzz.py in the sandbox prints:\n${result.stdout}`) + if (!result.stdout.includes('FizzBuzz')) { + throw new Error('fizzbuzz.py did not print FizzBuzz') + } await agent.close() } finally { diff --git a/examples/acp-agents-python/README.md b/examples/acp-agents-python/README.md index dd3f4f93..e7ce59bb 100644 --- a/examples/acp-agents-python/README.md +++ b/examples/acp-agents-python/README.md @@ -2,7 +2,7 @@ Run a coding agent that speaks the [Agent Client Protocol](https://agentclientprotocol.com) (ACP), such as OpenCode, goose or Claude Code, inside an E2B sandbox and drive it from your own code with the official ACP client (`agent-client-protocol`). -One client works for every agent: only the sandbox template and the command that starts the agent change. +The same client works for all three agents: only the sandbox template and the command that starts the agent change. | `AGENT` | Template | Command | Key | |---|---|---|---| @@ -12,22 +12,21 @@ One client works for every agent: only the sandbox template and the command that ## How it works -`sandbox.acp.start(command)` starts the agent inside the sandbox and talks ACP over its stdin and stdout. The returned agent is an ACP transport, so it goes straight into `acp.connect_to_agent()`: +The example creates a sandbox from the agent's template, then `connect_acp_agent()` from [`e2b-acp`](https://pypi.org/project/e2b-acp/) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: ```python -agent = await sandbox.acp.start("opencode acp", cwd=cwd, envs=envs) -connection = acp.connect_to_agent(client, agent) +agent = await connect_acp_agent(sandbox, "opencode acp", client, cwd=cwd, envs=envs) -await connection.initialize(protocol_version=acp.PROTOCOL_VERSION) -session = await connection.new_session(cwd=cwd, mcp_servers=[]) -await connection.prompt(session_id=session.session_id, prompt=[acp.text_block("...")]) +await agent.connection.initialize(protocol_version=acp.PROTOCOL_VERSION) +session = await agent.connection.new_session(cwd=cwd, mcp_servers=[]) +await agent.connection.prompt(session_id=session.session_id, prompt=[acp.text_block("...")]) await agent.close() ``` -The agent's tools always run inside the sandbox. `sandbox.acp` turns off the client filesystem and terminal capabilities in `initialize` and rejects any agent request for them, so the agent never reads or writes files on the machine running your client. +`connect_acp_agent()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. -Your client still answers permission requests and receives the agent's messages and tool calls. This example approves every permission request and prints the agent's progress. +Your client still answers permission requests and receives the agent's messages and tool calls. This example approves each request with its `allow_once` option and prints the agent's progress. The ACP Python SDK is asynchronous, so the example uses `AsyncSandbox`. @@ -36,4 +35,4 @@ The ACP Python SDK is asynchronous, so the example uses `AsyncSandbox`. 1. Copy `.env.template` to `.env` and fill in `E2B_API_KEY` and the key for the agent you use. 2. Run `uv run main.py`, or pick another agent with `AGENT=goose uv run main.py` or `AGENT=claude uv run main.py`. -The agent writes and runs `fizzbuzz.py` in the sandbox, then the example runs the same file again to show that the result lives in the sandbox. +The agent writes and runs `fizzbuzz.py` in the sandbox, then the example runs the same file again and checks that it prints `FizzBuzz`, which shows the result lives in the sandbox. diff --git a/examples/acp-agents-python/main.py b/examples/acp-agents-python/main.py index 63cff413..8cb2e388 100644 --- a/examples/acp-agents-python/main.py +++ b/examples/acp-agents-python/main.py @@ -7,6 +7,7 @@ from dotenv import load_dotenv from e2b import AsyncSandbox +from e2b_acp import connect_acp_agent load_dotenv() @@ -51,7 +52,10 @@ async def request_permission(self, session_id, tool_call, options, **kwargs): ) async def session_update(self, session_id, update, **kwargs): - if isinstance(update, schema.AgentMessageChunk) and update.content.type == "text": + if ( + isinstance(update, schema.AgentMessageChunk) + and update.content.type == "text" + ): print(update.content.text, end="", flush=True) elif isinstance(update, schema.ToolCallStart): print(f"\n[tool] {update.title}") @@ -70,8 +74,10 @@ async def main(): await sandbox.files.make_dir(cwd) # Start the agent inside the sandbox and connect an ACP client to it. - agent = await sandbox.acp.start(config["command"], cwd=cwd, envs=config["envs"]) - connection = acp.connect_to_agent(Client(), agent) + agent = await connect_acp_agent( + sandbox, config["command"], Client(), cwd=cwd, envs=config["envs"] + ) + connection = agent.connection init = await connection.initialize(protocol_version=acp.PROTOCOL_VERSION) print(f"Connected to {init.agent_info.name} {init.agent_info.version}") @@ -91,6 +97,8 @@ async def main(): # The agent worked inside the sandbox, so its result is there. result = await sandbox.commands.run(f"python3 {cwd}/fizzbuzz.py") print(f"fizzbuzz.py in the sandbox prints:\n{result.stdout}") + if "FizzBuzz" not in result.stdout: + raise RuntimeError("fizzbuzz.py did not print FizzBuzz") await agent.close() finally: diff --git a/examples/acp-agents-python/pyproject.toml b/examples/acp-agents-python/pyproject.toml index ef6db6a5..9d4abfc5 100644 --- a/examples/acp-agents-python/pyproject.toml +++ b/examples/acp-agents-python/pyproject.toml @@ -6,6 +6,7 @@ readme = "README.md" requires-python = ">=3.11" dependencies = [ "agent-client-protocol>=0.12.1,<0.13", - "e2b>=2.57.0", + "e2b>=2.56.1", + "e2b-acp>=0.1.0", "python-dotenv>=1.1,<2", ] From cbfbf83b0c56f05a0a08fa6e0f53c8567e6df701 Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Mon, 19 Oct 2026 10:05:54 +0200 Subject: [PATCH 6/8] refactor: use connectAcpAgent(sandbox, command, client) and stop() in the ACP examples --- examples/acp-agents-js/README.md | 4 ++-- examples/acp-agents-js/src/index.ts | 6 ++---- examples/acp-agents-python/README.md | 2 +- examples/acp-agents-python/main.py | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/acp-agents-js/README.md b/examples/acp-agents-js/README.md index 4cf95569..897cea09 100644 --- a/examples/acp-agents-js/README.md +++ b/examples/acp-agents-js/README.md @@ -15,13 +15,13 @@ The same client works for all three agents: only the sandbox template and the co The example creates a sandbox from the agent's template, then `connectAcpAgent()` from [`@e2b/acp`](https://www.npmjs.com/package/@e2b/acp) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: ```ts -const agent = await connectAcpAgent(sandbox, { command: 'opencode acp', client, cwd, envs }) +const agent = await connectAcpAgent(sandbox, 'opencode acp', client, { cwd, envs }) await agent.connection.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) const { sessionId } = await agent.connection.newSession({ cwd, mcpServers: [] }) await agent.connection.prompt({ sessionId, prompt: [{ type: 'text', text: '...' }] }) -await agent.close() +await agent.stop() ``` `connectAcpAgent()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. diff --git a/examples/acp-agents-js/src/index.ts b/examples/acp-agents-js/src/index.ts index c87d425b..169465f4 100644 --- a/examples/acp-agents-js/src/index.ts +++ b/examples/acp-agents-js/src/index.ts @@ -60,9 +60,7 @@ try { await sandbox.files.makeDir(cwd) // Start the agent inside the sandbox and connect an ACP client to it. - const agent = await connectAcpAgent(sandbox, { - command: agentConfig.command, - client, + const agent = await connectAcpAgent(sandbox, agentConfig.command, client, { cwd, envs: agentConfig.envs, }) @@ -93,7 +91,7 @@ try { throw new Error('fizzbuzz.py did not print FizzBuzz') } - await agent.close() + await agent.stop() } finally { await sandbox.kill() } diff --git a/examples/acp-agents-python/README.md b/examples/acp-agents-python/README.md index e7ce59bb..1000f553 100644 --- a/examples/acp-agents-python/README.md +++ b/examples/acp-agents-python/README.md @@ -21,7 +21,7 @@ await agent.connection.initialize(protocol_version=acp.PROTOCOL_VERSION) session = await agent.connection.new_session(cwd=cwd, mcp_servers=[]) await agent.connection.prompt(session_id=session.session_id, prompt=[acp.text_block("...")]) -await agent.close() +await agent.stop() ``` `connect_acp_agent()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. diff --git a/examples/acp-agents-python/main.py b/examples/acp-agents-python/main.py index 8cb2e388..7242f635 100644 --- a/examples/acp-agents-python/main.py +++ b/examples/acp-agents-python/main.py @@ -100,7 +100,7 @@ async def main(): if "FizzBuzz" not in result.stdout: raise RuntimeError("fizzbuzz.py did not print FizzBuzz") - await agent.close() + await agent.stop() finally: await sandbox.kill() From 09a6c05b44fd6816f82b0472d1d0649c34cc85cd Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Thu, 29 Oct 2026 15:07:02 +0100 Subject: [PATCH 7/8] refactor: use AcpAgent.start and the ACP client() app in the ACP examples --- examples/acp-agents-js/README.md | 13 +++++++------ examples/acp-agents-js/src/index.ts | 27 +++++++++++++-------------- examples/acp-agents-python/README.md | 6 +++--- examples/acp-agents-python/main.py | 4 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/acp-agents-js/README.md b/examples/acp-agents-js/README.md index 897cea09..393cb95b 100644 --- a/examples/acp-agents-js/README.md +++ b/examples/acp-agents-js/README.md @@ -12,19 +12,20 @@ The same client works for all three agents: only the sandbox template and the co ## How it works -The example creates a sandbox from the agent's template, then `connectAcpAgent()` from [`@e2b/acp`](https://www.npmjs.com/package/@e2b/acp) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: +The example creates a sandbox from the agent's template, then `AcpAgent.start()` from [`@e2b/acp`](https://www.npmjs.com/package/@e2b/acp) starts the agent inside it and connects your ACP client, built with `client()` from `@agentclientprotocol/sdk`, over the agent's stdin and stdout: ```ts -const agent = await connectAcpAgent(sandbox, 'opencode acp', client, { cwd, envs }) +const agent = await AcpAgent.start(sandbox, 'opencode acp', app, { cwd, envs }) -await agent.connection.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) -const { sessionId } = await agent.connection.newSession({ cwd, mcpServers: [] }) -await agent.connection.prompt({ sessionId, prompt: [{ type: 'text', text: '...' }] }) +const acp = agent.connection.agent +await acp.request(methods.agent.initialize, { protocolVersion: PROTOCOL_VERSION }) +const { sessionId } = await acp.request(methods.agent.session.new, { cwd, mcpServers: [] }) +await acp.request(methods.agent.session.prompt, { sessionId, prompt: [{ type: 'text', text: '...' }] }) await agent.stop() ``` -`connectAcpAgent()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. +`AcpAgent.start()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. Your client still answers permission requests and receives the agent's messages and tool calls. This example approves each request with its `allow_once` option and prints the agent's progress. diff --git a/examples/acp-agents-js/src/index.ts b/examples/acp-agents-js/src/index.ts index 169465f4..6d7c1ed3 100644 --- a/examples/acp-agents-js/src/index.ts +++ b/examples/acp-agents-js/src/index.ts @@ -1,7 +1,7 @@ import 'dotenv/config' import { Sandbox } from 'e2b' -import { connectAcpAgent } from '@e2b/acp' -import { PROTOCOL_VERSION, type Client } from '@agentclientprotocol/sdk' +import { AcpAgent } from '@e2b/acp' +import { client, methods, PROTOCOL_VERSION } from '@agentclientprotocol/sdk' // Each agent is a sandbox template plus the command that starts it in ACP mode. const agents = { @@ -37,20 +37,19 @@ if (!agentConfig) { // The ACP client: it shows the agent's progress and answers permission requests. // File edits and shell commands happen inside the sandbox, not on this machine. -const client: Client = { - async requestPermission({ toolCall, options }) { +const app = client({ name: 'e2b-acp-cookbook' }) + .onRequest(methods.client.session.requestPermission, async ({ params: { toolCall, options } }) => { const allow = options.find((o) => o.kind === 'allow_once') ?? options[0] console.log(`\n[permission] ${toolCall.title} -> ${allow.name}`) return { outcome: { outcome: 'selected', optionId: allow.optionId } } - }, - async sessionUpdate({ update }) { + }) + .onNotification(methods.client.session.update, ({ params: { update } }) => { if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') { process.stdout.write(update.content.text) } else if (update.sessionUpdate === 'tool_call') { console.log(`\n[tool] ${update.title}`) } - }, -} + }) const sandbox = await Sandbox.create(agentConfig.template, { timeoutMs: 10 * 60_000 }) console.log(`Sandbox ${sandbox.sandboxId} with ${name}`) @@ -60,20 +59,20 @@ try { await sandbox.files.makeDir(cwd) // Start the agent inside the sandbox and connect an ACP client to it. - const agent = await connectAcpAgent(sandbox, agentConfig.command, client, { + const agent = await AcpAgent.start(sandbox, agentConfig.command, app, { cwd, envs: agentConfig.envs, }) - const connection = agent.connection + // Requests to the agent go through agent.connection.agent. + const acp = agent.connection.agent - const { agentInfo } = await connection.initialize({ + const { agentInfo } = await acp.request(methods.agent.initialize, { protocolVersion: PROTOCOL_VERSION, - clientCapabilities: {}, }) console.log(`Connected to ${agentInfo?.name} ${agentInfo?.version}`) - const { sessionId } = await connection.newSession({ cwd, mcpServers: [] }) - const { stopReason } = await connection.prompt({ + const { sessionId } = await acp.request(methods.agent.session.new, { cwd, mcpServers: [] }) + const { stopReason } = await acp.request(methods.agent.session.prompt, { sessionId, prompt: [ { diff --git a/examples/acp-agents-python/README.md b/examples/acp-agents-python/README.md index 1000f553..7deb578f 100644 --- a/examples/acp-agents-python/README.md +++ b/examples/acp-agents-python/README.md @@ -12,10 +12,10 @@ The same client works for all three agents: only the sandbox template and the co ## How it works -The example creates a sandbox from the agent's template, then `connect_acp_agent()` from [`e2b-acp`](https://pypi.org/project/e2b-acp/) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: +The example creates a sandbox from the agent's template, then `AcpAgent.start()` from [`e2b-acp`](https://pypi.org/project/e2b-acp/) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: ```python -agent = await connect_acp_agent(sandbox, "opencode acp", client, cwd=cwd, envs=envs) +agent = await AcpAgent.start(sandbox, "opencode acp", client, cwd=cwd, envs=envs) await agent.connection.initialize(protocol_version=acp.PROTOCOL_VERSION) session = await agent.connection.new_session(cwd=cwd, mcp_servers=[]) @@ -24,7 +24,7 @@ await agent.connection.prompt(session_id=session.session_id, prompt=[acp.text_bl await agent.stop() ``` -`connect_acp_agent()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. +`AcpAgent.start()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. Your client still answers permission requests and receives the agent's messages and tool calls. This example approves each request with its `allow_once` option and prints the agent's progress. diff --git a/examples/acp-agents-python/main.py b/examples/acp-agents-python/main.py index 7242f635..b8d6a819 100644 --- a/examples/acp-agents-python/main.py +++ b/examples/acp-agents-python/main.py @@ -7,7 +7,7 @@ from dotenv import load_dotenv from e2b import AsyncSandbox -from e2b_acp import connect_acp_agent +from e2b_acp import AcpAgent load_dotenv() @@ -74,7 +74,7 @@ async def main(): await sandbox.files.make_dir(cwd) # Start the agent inside the sandbox and connect an ACP client to it. - agent = await connect_acp_agent( + agent = await AcpAgent.start( sandbox, config["command"], Client(), cwd=cwd, envs=config["envs"] ) connection = agent.connection From cf19000f236c297e6f2fa3086252736d12b6d5e9 Mon Sep 17 00:00:00 2001 From: Maksim Obukhov Date: Fri, 30 Oct 2026 10:05:41 +0100 Subject: [PATCH 8/8] refactor: rename AcpAgent to ACPAgent and AsyncACPAgent in the ACP examples --- examples/acp-agents-js/README.md | 6 +++--- examples/acp-agents-js/src/index.ts | 4 ++-- examples/acp-agents-python/README.md | 6 +++--- examples/acp-agents-python/main.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/acp-agents-js/README.md b/examples/acp-agents-js/README.md index 393cb95b..ac067db9 100644 --- a/examples/acp-agents-js/README.md +++ b/examples/acp-agents-js/README.md @@ -12,10 +12,10 @@ The same client works for all three agents: only the sandbox template and the co ## How it works -The example creates a sandbox from the agent's template, then `AcpAgent.start()` from [`@e2b/acp`](https://www.npmjs.com/package/@e2b/acp) starts the agent inside it and connects your ACP client, built with `client()` from `@agentclientprotocol/sdk`, over the agent's stdin and stdout: +The example creates a sandbox from the agent's template, then `ACPAgent.start()` from [`@e2b/acp`](https://www.npmjs.com/package/@e2b/acp) starts the agent inside it and connects your ACP client, built with `client()` from `@agentclientprotocol/sdk`, over the agent's stdin and stdout: ```ts -const agent = await AcpAgent.start(sandbox, 'opencode acp', app, { cwd, envs }) +const agent = await ACPAgent.start(sandbox, 'opencode acp', app, { cwd, envs }) const acp = agent.connection.agent await acp.request(methods.agent.initialize, { protocolVersion: PROTOCOL_VERSION }) @@ -25,7 +25,7 @@ await acp.request(methods.agent.session.prompt, { sessionId, prompt: [{ type: 't await agent.stop() ``` -`AcpAgent.start()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. +`ACPAgent.start()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. Your client still answers permission requests and receives the agent's messages and tool calls. This example approves each request with its `allow_once` option and prints the agent's progress. diff --git a/examples/acp-agents-js/src/index.ts b/examples/acp-agents-js/src/index.ts index 6d7c1ed3..46e71ff0 100644 --- a/examples/acp-agents-js/src/index.ts +++ b/examples/acp-agents-js/src/index.ts @@ -1,6 +1,6 @@ import 'dotenv/config' import { Sandbox } from 'e2b' -import { AcpAgent } from '@e2b/acp' +import { ACPAgent } from '@e2b/acp' import { client, methods, PROTOCOL_VERSION } from '@agentclientprotocol/sdk' // Each agent is a sandbox template plus the command that starts it in ACP mode. @@ -59,7 +59,7 @@ try { await sandbox.files.makeDir(cwd) // Start the agent inside the sandbox and connect an ACP client to it. - const agent = await AcpAgent.start(sandbox, agentConfig.command, app, { + const agent = await ACPAgent.start(sandbox, agentConfig.command, app, { cwd, envs: agentConfig.envs, }) diff --git a/examples/acp-agents-python/README.md b/examples/acp-agents-python/README.md index 7deb578f..2f5583be 100644 --- a/examples/acp-agents-python/README.md +++ b/examples/acp-agents-python/README.md @@ -12,10 +12,10 @@ The same client works for all three agents: only the sandbox template and the co ## How it works -The example creates a sandbox from the agent's template, then `AcpAgent.start()` from [`e2b-acp`](https://pypi.org/project/e2b-acp/) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: +The example creates a sandbox from the agent's template, then `AsyncACPAgent.start()` from [`e2b-acp`](https://pypi.org/project/e2b-acp/) starts the agent inside it and connects your ACP client over the agent's stdin and stdout: ```python -agent = await AcpAgent.start(sandbox, "opencode acp", client, cwd=cwd, envs=envs) +agent = await AsyncACPAgent.start(sandbox, "opencode acp", client, cwd=cwd, envs=envs) await agent.connection.initialize(protocol_version=acp.PROTOCOL_VERSION) session = await agent.connection.new_session(cwd=cwd, mcp_servers=[]) @@ -24,7 +24,7 @@ await agent.connection.prompt(session_id=session.session_id, prompt=[acp.text_bl await agent.stop() ``` -`AcpAgent.start()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. +`AsyncACPAgent.start()` turns off the client filesystem and terminal capabilities in `initialize` and rejects agent requests for them, so the agent edits files and runs commands with its own tools inside the sandbox, never on the machine running your client. It does not restrict the agent's network access or the MCP servers you pass to it. Your client still answers permission requests and receives the agent's messages and tool calls. This example approves each request with its `allow_once` option and prints the agent's progress. diff --git a/examples/acp-agents-python/main.py b/examples/acp-agents-python/main.py index b8d6a819..99b867dc 100644 --- a/examples/acp-agents-python/main.py +++ b/examples/acp-agents-python/main.py @@ -7,7 +7,7 @@ from dotenv import load_dotenv from e2b import AsyncSandbox -from e2b_acp import AcpAgent +from e2b_acp import AsyncACPAgent load_dotenv() @@ -74,7 +74,7 @@ async def main(): await sandbox.files.make_dir(cwd) # Start the agent inside the sandbox and connect an ACP client to it. - agent = await AcpAgent.start( + agent = await AsyncACPAgent.start( sandbox, config["command"], Client(), cwd=cwd, envs=config["envs"] ) connection = agent.connection