Skip to content

Commit f815eba

Browse files
hubwritercrwaters16Copilot
authored
[2026-02-01] ACP Support in Copilot CLI [public preview] (#59333)
Co-authored-by: Claire W <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent d087bfe commit f815eba

File tree

2 files changed

+399
-0
lines changed

2 files changed

+399
-0
lines changed
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
---
2+
title: "Copilot CLI ACP server"
3+
shortTitle: ACP server
4+
intro: "Learn about {% data variables.copilot.copilot_cli %}'s Agent Client Protocol server."
5+
versions:
6+
feature: copilot
7+
topics:
8+
- Copilot
9+
category:
10+
- Configure Copilot
11+
contentType: reference
12+
---
13+
14+
15+
## Overview
16+
17+
Agent Client Protocol (ACP) is a standardized protocol that enables external clients, such as IDEs, editors, and other tools, to communicate with the {% data variables.product.prodname_copilot_short %} agent runtime. It provides a structured way to create and manage agent sessions, send prompts, receive streaming responses, and handle permission requests.
18+
19+
## Architecture
20+
21+
```text
22+
┌─────────────────┐ ┌─────────────────────┐
23+
│ IDE / Editor │ ACP │ Copilot Agent │
24+
│ (ACP Client) │◄───────►│ Runtime │
25+
│ │ stdio/ │ (ACP Server) │
26+
│ │ TCP │ │
27+
└─────────────────┘ └─────────────────────┘
28+
```
29+
30+
The ACP server runs as part of {% data variables.copilot.copilot_cli %} and exposes the agent's capabilities through a well-defined protocol.
31+
32+
## Starting the ACP server
33+
34+
### Stdio Mode (Recommended for IDE Integration)
35+
36+
```bash
37+
copilot --acp --stdio
38+
```
39+
40+
Communication happens over stdin/stdout using newline-delimited JSON (NDJSON).
41+
42+
### TCP Mode
43+
44+
```bash
45+
copilot --acp --port 3000
46+
```
47+
48+
Communication happens over a TCP socket on the specified port.
49+
50+
## Protocol flow
51+
52+
### 1. Initialize connection
53+
54+
```typescript
55+
// Client sends initialize request
56+
const response = await connection.initialize({
57+
clientInfo: {
58+
name: "MyIDE",
59+
version: "1.0"
60+
}
61+
});
62+
63+
// Server responds with capabilities
64+
// {
65+
// serverInfo: { name: "copilot-agent-runtime", version: "x.x.x" },
66+
// capabilities: { ... }
67+
// }
68+
```
69+
70+
### 2. Create a session
71+
72+
```typescript
73+
const session = await connection.newSession({
74+
cwd: "/path/to/project"
75+
});
76+
77+
// Returns: { sessionId: "uuid-..." }
78+
```
79+
80+
### 3. Send prompts
81+
82+
```typescript
83+
await connection.prompt({
84+
sessionId: session.sessionId,
85+
prompt: [
86+
{ type: "text", text: "Fix the authentication bug in login.ts" }
87+
]
88+
});
89+
```
90+
91+
### 4. Receive session updates
92+
93+
The client receives real-time updates via the `sessionUpdate` callback:
94+
95+
```typescript
96+
const client: acp.Client = {
97+
async sessionUpdate(params) {
98+
// params.state: "idle" | "working"
99+
// params.lastUpdateId: number
100+
// params.updates: Array of content updates
101+
102+
for (const update of params.updates) {
103+
switch (update.type) {
104+
case "text":
105+
console.log("Agent says:", update.text);
106+
break;
107+
case "tool":
108+
console.log("Tool execution:", update.name, update.status);
109+
break;
110+
}
111+
}
112+
}
113+
};
114+
```
115+
116+
### 5. Handle permission requests
117+
118+
The agent requests permission for sensitive operations:
119+
120+
```typescript
121+
const client: acp.Client = {
122+
async requestPermission(params) {
123+
// params.request contains details about what permission is needed
124+
// e.g., tool execution, file access, URL fetch
125+
126+
// Display dialog to user and return their choice
127+
return {
128+
outcome: {
129+
outcome: "selected",
130+
optionId: "allow_once" // or "allow_always" or "reject_once"
131+
}
132+
};
133+
}
134+
};
135+
```
136+
137+
## Content types
138+
139+
### Text content
140+
141+
```typescript
142+
{
143+
type: "text",
144+
text: "Your message here"
145+
}
146+
```
147+
148+
### Image content
149+
150+
```typescript
151+
{
152+
type: "image",
153+
data: "<base64-encoded-image>",
154+
mimeType: "image/png" // or "image/jpeg", "image/gif", "image/webp"
155+
}
156+
```
157+
158+
### Embedded resources
159+
160+
```typescript
161+
{
162+
type: "resource",
163+
resource: {
164+
uri: "file:///path/to/file.txt",
165+
text: "file contents",
166+
mimeType: "text/plain"
167+
}
168+
}
169+
```
170+
171+
### Blob data
172+
173+
```typescript
174+
{
175+
type: "blob",
176+
data: "<base64-encoded-data>",
177+
mimeType: "application/octet-stream"
178+
}
179+
```
180+
181+
## Tool execution updates
182+
183+
When the agent executes tools, clients receive detailed updates:
184+
185+
```typescript
186+
{
187+
type: "tool",
188+
id: "tool-execution-id",
189+
name: "edit", // Tool name
190+
status: "running", // "running" | "completed"
191+
kind: "edit", // Tool category
192+
input: { ... }, // Tool parameters
193+
output: { ... }, // Tool result (when completed)
194+
diff: "..." // Diff content for edit operations
195+
}
196+
```
197+
198+
### Tool kinds
199+
200+
| Kind | Description |
201+
|------|-------------|
202+
| `execute` | Command execution (bash) |
203+
| `read` | File reading operations |
204+
| `edit` | File modifications |
205+
| `delete` | File deletions |
206+
| `create` | File creation |
207+
| `search` | Code search (grep, glob) |
208+
| `fetch` | Web fetching |
209+
| `agent` | Sub-agent invocation |
210+
| `github` | GitHub API operations |
211+
| `other` | Other tool types |
212+
213+
## Permission types
214+
215+
### Tool permissions
216+
217+
Requested when the agent wants to execute a tool:
218+
219+
```typescript
220+
{
221+
type: "tool",
222+
toolName: "bash",
223+
input: { command: "npm install" }
224+
}
225+
```
226+
227+
### Path permissions
228+
229+
Requested for file system access:
230+
231+
```typescript
232+
{
233+
type: "path",
234+
path: "/path/to/sensitive/file",
235+
operation: "write" // or "read"
236+
}
237+
```
238+
239+
### URL permissions
240+
241+
Requested for network access:
242+
243+
```typescript
244+
{
245+
type: "url",
246+
url: "https://api.example.com/data",
247+
operation: "fetch"
248+
}
249+
```
250+
251+
## Session lifecycle
252+
253+
```text
254+
┌─────────────┐
255+
│ Created │
256+
└──────┬──────┘
257+
│ prompt()
258+
259+
┌─────────────┐
260+
│ Working │◄───┐
261+
└──────┬──────┘ │
262+
│ │ prompt()
263+
▼ │
264+
┌─────────────┐ │
265+
│ Idle │────┘
266+
└──────┬──────┘
267+
│ destroy()
268+
269+
┌─────────────┐
270+
│ Destroyed │
271+
└─────────────┘
272+
```
273+
274+
## Error handling
275+
276+
Errors are communicated through the protocol's standard error response format:
277+
278+
```typescript
279+
{
280+
error: {
281+
code: -32600,
282+
message: "Invalid request",
283+
data: { ... }
284+
}
285+
}
286+
```
287+
288+
## Example: Full Client Implementation
289+
290+
```typescript
291+
import * as acp from '@anthropic/acp-sdk';
292+
import { spawn } from 'child_process';
293+
294+
async function main() {
295+
// Start the ACP server
296+
const process = spawn('copilot', ['--acp', '--stdio'], {
297+
stdio: ['pipe', 'pipe', 'inherit']
298+
});
299+
300+
// Create transport stream
301+
const stream = acp.ndJsonStream(process.stdin, process.stdout);
302+
303+
// Define client handlers
304+
const client: acp.Client = {
305+
async requestPermission(params) {
306+
console.log('Permission requested:', params.request);
307+
// Auto-approve for this example
308+
return {
309+
outcome: { outcome: "selected", optionId: "allow_once" }
310+
};
311+
},
312+
313+
async sessionUpdate(params) {
314+
console.log(`Session ${params.sessionId} state: ${params.state}`);
315+
for (const update of params.updates) {
316+
if (update.type === 'text') {
317+
process.stdout.write(update.text);
318+
} else if (update.type === 'tool') {
319+
console.log(`Tool: ${update.name} - ${update.status}`);
320+
}
321+
}
322+
}
323+
};
324+
325+
// Establish connection
326+
const connection = new acp.ClientSideConnection(client, stream);
327+
328+
// Initialize
329+
await connection.initialize({
330+
clientInfo: { name: "example-client", version: "1.0.0" }
331+
});
332+
333+
// Create session
334+
const session = await connection.newSession({
335+
cwd: process.cwd()
336+
});
337+
338+
// Send prompt
339+
await connection.prompt({
340+
sessionId: session.sessionId,
341+
prompt: [
342+
{ type: "text", text: "List the files in the current directory" }
343+
]
344+
});
345+
346+
// Wait for completion (in real app, handle via sessionUpdate)
347+
await new Promise(resolve => setTimeout(resolve, 10000));
348+
349+
// Cleanup
350+
process.kill();
351+
}
352+
353+
main().catch(console.error);
354+
```
355+
356+
## Configuration
357+
358+
### Runtime settings
359+
360+
The ACP server inherits settings from the {% data variables.product.prodname_copilot_short %} runtime:
361+
362+
* **Model selection**: Configured via environment or runtime settings
363+
* **Tool permissions**: Managed through the permission service
364+
* **Logging**: Configurable log level and directory
365+
366+
### Environment variables
367+
368+
| Variable | Description |
369+
|----------|-------------|
370+
| `COPILOT_LOG_LEVEL` | Log verbosity (debug, info, warn, error) |
371+
| `COPILOT_LOG_DIR` | Directory for log files |
372+
373+
## Best practices
374+
375+
1. **Use Stdio Mode for IDEs**: More reliable than TCP for local integrations
376+
1. **Handle Permissions Gracefully**: Always implement the `requestPermission` callback
377+
1. **Process Updates Incrementally**: Don't wait for completion to show progress
378+
1. **Clean Up Sessions**: Call destroy when done to free resources
379+
1. **Handle Reconnection**: Implement retry logic for network transports
380+
381+
## Source files
382+
383+
| File | Description |
384+
|------|-------------|
385+
| `src/cli/acp/server.ts` | Main ACP server implementation |
386+
| `src/cli/acp/mapping.ts` | Event transformation layer |
387+
| `src/cli/acp/permissionHandlers.ts` | Permission request handlers |
388+
| `src/cli/acp/permissionMapping.ts` | Permission mapping utilities |
389+
390+
## Testing
391+
392+
ACP has end-to-end tests that can be run with:
393+
394+
```bash
395+
CI=true npm run test:cli-e2e -- test/cli/e2e/acp.test.ts
396+
```
397+
398+
Test captures are available in `test/cli/e2e/captures/acp/` showing various content type handling scenarios.

0 commit comments

Comments
 (0)