Skip to content

Commit a72d90b

Browse files
committed
First terminal methods
1 parent 5889c19 commit a72d90b

12 files changed

Lines changed: 616 additions & 37 deletions

File tree

AGENTS.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
All paths in the protocol should be absolute
22

3-
When updating JSON-RPC methods, their params, or output:
3+
When I ask you to add a new ACP Agent or Client method:
4+
5+
- Create empty params and output structs in rust/client.rs or rust/agent.rs under the corresponding section. I'll add the fields myself.
6+
- Do not write any tests or docs at all!
7+
- Add constants for the method names
8+
- Add variants to {Agent|Client}{Request|Response} enums
9+
- Add the methods to the Client/Agent impl of {Agent|Client}SideConnection in rust/acp.rs
10+
- Handle the new request in the blanket impl of MessageHandler<{Agent|Client}Side>
11+
- Add the method to markdown_generator.rs SideDocs functions
12+
- Run `npm run generate` and fix any issues that appear
13+
- Add the method to typescript/acp.ts classes and handlers
14+
- Run `npm run check`
15+
16+
When updating existing JSON-RPC methods, their params, or output:
417

518
- Update the mintlify docs and guides in the `docs` directory
619
- Run `npm run check` to make sure the json and zod schemas gets generated properly

docs/protocol/schema.mdx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/ini
7070
<ResponseField name="clientCapabilities" type={<a href="#clientcapabilities">ClientCapabilities</a>} >
7171
Capabilities supported by the client.
7272

73-
- Default: `{"fs":{"readTextFile":false,"writeTextFile":false}}`
73+
- Default: `{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false}`
7474

7575
</ResponseField>
7676
<ResponseField name="protocolVersion" type={<a href="#protocolversion">ProtocolVersion</a>} required>
@@ -517,6 +517,75 @@ See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protoc
517517
The actual update content.
518518
</ResponseField>
519519

520+
<a id="terminal-new"></a>
521+
### <span class="font-mono">terminal/new</span>
522+
523+
#### <span class="font-mono">NewTerminalRequest</span>
524+
525+
**Type:** Object
526+
527+
**Properties:**
528+
529+
<ResponseField name="args" type={<><span>"string"</span><span>[]</span></>} >
530+
</ResponseField>
531+
<ResponseField name="command" type={"string"} required>
532+
</ResponseField>
533+
<ResponseField name="cwd" type={"string | null"} >
534+
</ResponseField>
535+
<ResponseField name="env" type={<><span><a href="#envvariable">EnvVariable</a></span><span>[]</span></>} >
536+
</ResponseField>
537+
<ResponseField name="outputByteLimit" type={"integer | null"} >
538+
539+
- Minimum: `0`
540+
541+
</ResponseField>
542+
<ResponseField name="sessionId" type={<a href="#sessionid">SessionId</a>} required>
543+
</ResponseField>
544+
545+
#### <span class="font-mono">NewTerminalResponse</span>
546+
547+
**Type:** Object
548+
549+
**Properties:**
550+
551+
<ResponseField name="terminalId" type={"string"} required></ResponseField>
552+
553+
<a id="terminal-output"></a>
554+
### <span class="font-mono">terminal/output</span>
555+
556+
#### <span class="font-mono">TerminalOutputRequest</span>
557+
558+
**Type:** Object
559+
560+
**Properties:**
561+
562+
<ResponseField
563+
name="sessionId"
564+
type={<a href="#sessionid">SessionId</a>}
565+
required
566+
></ResponseField>
567+
<ResponseField name="terminalId" type={"string"} required></ResponseField>
568+
569+
#### <span class="font-mono">TerminalOutputResponse</span>
570+
571+
**Type:** Object
572+
573+
**Properties:**
574+
575+
<ResponseField name="exitCode" type={"integer | null"} >
576+
577+
- Minimum: `0`
578+
579+
</ResponseField>
580+
<ResponseField name="finished" type={"boolean"} required>
581+
</ResponseField>
582+
<ResponseField name="output" type={"string"} required>
583+
</ResponseField>
584+
<ResponseField name="signal" type={"string | null"} >
585+
</ResponseField>
586+
<ResponseField name="truncated" type={"boolean"} required>
587+
</ResponseField>
588+
520589
## <span class="font-mono">AgentCapabilities</span>
521590

522591
Capabilities supported by the agent.
@@ -636,6 +705,11 @@ Determines which file operations the agent can request.
636705

637706
- Default: `{"readTextFile":false,"writeTextFile":false}`
638707

708+
</ResponseField>
709+
<ResponseField name="terminal" type={"boolean"} >
710+
711+
- Default: `false`
712+
639713
</ResponseField>
640714

641715
## <span class="font-mono">ContentBlock</span>
@@ -1642,6 +1716,17 @@ File modification shown as a diff.
16421716
</Expandable>
16431717
</ResponseField>
16441718

1719+
<ResponseField name="terminal">
1720+
{""}
1721+
1722+
<Expandable title="Properties">
1723+
1724+
<ResponseField name="terminalId" type={"string"} required></ResponseField>
1725+
<ResponseField name="type" type={"string"} required></ResponseField>
1726+
1727+
</Expandable>
1728+
</ResponseField>
1729+
16451730
## <span class="font-mono">ToolCallId</span>
16461731

16471732
Unique identifier for a tool call within a session.

rust/acp.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ impl Side for ClientSide {
245245
FS_READ_TEXT_FILE_METHOD_NAME => serde_json::from_str(params.get())
246246
.map(AgentRequest::ReadTextFileRequest)
247247
.map_err(Into::into),
248+
TERMINAL_NEW_METHOD_NAME => serde_json::from_str(params.get())
249+
.map(AgentRequest::NewTerminalRequest)
250+
.map_err(Into::into),
251+
TERMINAL_OUTPUT_METHOD_NAME => serde_json::from_str(params.get())
252+
.map(AgentRequest::TerminalOutputRequest)
253+
.map_err(Into::into),
248254
_ => Err(Error::method_not_found()),
249255
}
250256
}
@@ -279,6 +285,14 @@ impl<T: Client> MessageHandler<ClientSide> for T {
279285
let response = self.read_text_file(args).await?;
280286
Ok(ClientResponse::ReadTextFileResponse(response))
281287
}
288+
AgentRequest::NewTerminalRequest(args) => {
289+
let response = self.new_terminal(args).await?;
290+
Ok(ClientResponse::NewTerminalResponse(response))
291+
}
292+
AgentRequest::TerminalOutputRequest(args) => {
293+
let response = self.terminal_output(args).await?;
294+
Ok(ClientResponse::TerminalOutputResponse(response))
295+
}
282296
}
283297
}
284298

@@ -383,6 +397,30 @@ impl Client for AgentSideConnection {
383397
.await
384398
}
385399

400+
async fn new_terminal(
401+
&self,
402+
arguments: NewTerminalRequest,
403+
) -> Result<NewTerminalResponse, Error> {
404+
self.conn
405+
.request(
406+
TERMINAL_NEW_METHOD_NAME,
407+
Some(AgentRequest::NewTerminalRequest(arguments)),
408+
)
409+
.await
410+
}
411+
412+
async fn terminal_output(
413+
&self,
414+
arguments: TerminalOutputRequest,
415+
) -> Result<TerminalOutputResponse, Error> {
416+
self.conn
417+
.request(
418+
TERMINAL_OUTPUT_METHOD_NAME,
419+
Some(AgentRequest::TerminalOutputRequest(arguments)),
420+
)
421+
.await
422+
}
423+
386424
async fn session_notification(&self, notification: SessionNotification) -> Result<(), Error> {
387425
self.conn.notify(
388426
SESSION_UPDATE_NOTIFICATION,

rust/client.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use anyhow::Result;
99
use schemars::JsonSchema;
1010
use serde::{Deserialize, Serialize};
1111

12-
use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
12+
use crate::{ContentBlock, EnvVariable, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
1313

1414
/// Defines the interface that ACP-compliant clients must implement.
1515
///
@@ -54,6 +54,16 @@ pub trait Client {
5454
args: ReadTextFileRequest,
5555
) -> impl Future<Output = Result<ReadTextFileResponse, Error>>;
5656

57+
fn new_terminal(
58+
&self,
59+
args: NewTerminalRequest,
60+
) -> impl Future<Output = Result<NewTerminalResponse, Error>>;
61+
62+
fn terminal_output(
63+
&self,
64+
args: TerminalOutputRequest,
65+
) -> impl Future<Output = Result<TerminalOutputResponse, Error>>;
66+
5767
/// Handles session update notifications from the agent.
5868
///
5969
/// This is a notification endpoint (no response expected) that receives
@@ -244,6 +254,60 @@ pub struct ReadTextFileResponse {
244254
pub content: String,
245255
}
246256

257+
// Terminals
258+
259+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
260+
#[serde(transparent)]
261+
pub struct TerminalId(pub Arc<str>);
262+
263+
impl std::fmt::Display for TerminalId {
264+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265+
write!(f, "{}", self.0)
266+
}
267+
}
268+
269+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
270+
#[schemars(extend("x-side" = "client", "x-method" = "terminal/new"))]
271+
#[serde(rename_all = "camelCase")]
272+
pub struct NewTerminalRequest {
273+
pub session_id: SessionId,
274+
pub command: String,
275+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
276+
pub args: Vec<String>,
277+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
278+
pub env: Vec<EnvVariable>,
279+
#[serde(default, skip_serializing_if = "Option::is_none")]
280+
pub cwd: Option<PathBuf>,
281+
#[serde(default, skip_serializing_if = "Option::is_none")]
282+
pub output_byte_limit: Option<u64>,
283+
}
284+
285+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
286+
#[schemars(extend("x-side" = "client", "x-method" = "terminal/new"))]
287+
#[serde(rename_all = "camelCase")]
288+
pub struct NewTerminalResponse {
289+
pub terminal_id: TerminalId,
290+
}
291+
292+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
293+
#[schemars(extend("x-side" = "client", "x-method" = "terminal/output"))]
294+
#[serde(rename_all = "camelCase")]
295+
pub struct TerminalOutputRequest {
296+
pub session_id: SessionId,
297+
pub terminal_id: TerminalId,
298+
}
299+
300+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
301+
#[schemars(extend("x-side" = "client", "x-method" = "terminal/output"))]
302+
#[serde(rename_all = "camelCase")]
303+
pub struct TerminalOutputResponse {
304+
pub output: String,
305+
pub truncated: bool,
306+
pub finished: bool,
307+
pub exit_code: Option<u32>,
308+
pub signal: Option<String>,
309+
}
310+
247311
// Capabilities
248312

249313
/// Capabilities supported by the client.
@@ -259,6 +323,8 @@ pub struct ClientCapabilities {
259323
/// Determines which file operations the agent can request.
260324
#[serde(default)]
261325
pub fs: FileSystemCapability,
326+
#[serde(default)]
327+
pub terminal: bool,
262328
}
263329

264330
/// File system capabilities that a client may support.
@@ -290,6 +356,10 @@ pub struct ClientMethodNames {
290356
pub fs_write_text_file: &'static str,
291357
/// Method for reading text files.
292358
pub fs_read_text_file: &'static str,
359+
/// Method for creating new terminals.
360+
pub terminal_new: &'static str,
361+
/// Method for getting terminals output.
362+
pub terminal_output: &'static str,
293363
}
294364

295365
/// Constant containing all client method names.
@@ -298,6 +368,8 @@ pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
298368
session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
299369
fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
300370
fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
371+
terminal_new: TERMINAL_NEW_METHOD_NAME,
372+
terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
301373
};
302374

303375
/// Notification name for session updates.
@@ -308,6 +380,10 @@ pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request
308380
pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
309381
/// Method name for reading text files.
310382
pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
383+
/// Method name for creating a new terminal.
384+
pub(crate) const TERMINAL_NEW_METHOD_NAME: &str = "terminal/new";
385+
/// Method for getting terminals output.
386+
pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
311387

312388
/// All possible requests that an agent can send to a client.
313389
///
@@ -322,6 +398,8 @@ pub enum AgentRequest {
322398
WriteTextFileRequest(WriteTextFileRequest),
323399
ReadTextFileRequest(ReadTextFileRequest),
324400
RequestPermissionRequest(RequestPermissionRequest),
401+
NewTerminalRequest(NewTerminalRequest),
402+
TerminalOutputRequest(TerminalOutputRequest),
325403
}
326404

327405
/// All possible responses that a client can send to an agent.
@@ -337,6 +415,8 @@ pub enum ClientResponse {
337415
WriteTextFileResponse,
338416
ReadTextFileResponse(ReadTextFileResponse),
339417
RequestPermissionResponse(RequestPermissionResponse),
418+
NewTerminalResponse(NewTerminalResponse),
419+
TerminalOutputResponse(TerminalOutputResponse),
340420
}
341421

342422
/// All possible notifications that an agent can send to a client.

rust/example_client.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@ impl acp::Client for ExampleClient {
4040
Err(acp::Error::method_not_found())
4141
}
4242

43+
async fn new_terminal(
44+
&self,
45+
_args: acp::NewTerminalRequest,
46+
) -> anyhow::Result<acp::NewTerminalResponse, acp::Error> {
47+
Err(acp::Error::method_not_found())
48+
}
49+
50+
async fn terminal_output(
51+
&self,
52+
_args: acp::TerminalOutputRequest,
53+
) -> anyhow::Result<acp::TerminalOutputResponse, acp::Error> {
54+
Err(acp::Error::method_not_found())
55+
}
56+
4357
async fn session_notification(
4458
&self,
4559
args: acp::SessionNotification,

rust/markdown_generator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,8 @@ impl SideDocs {
644644
"fs/write_text_file" => self.client_methods.get("write_text_file").unwrap(),
645645
"fs/read_text_file" => self.client_methods.get("read_text_file").unwrap(),
646646
"session/update" => self.client_methods.get("session_notification").unwrap(),
647+
"terminal/new" => self.client_methods.get("new_terminal").unwrap(),
648+
"terminal/output" => self.client_methods.get("terminal_output").unwrap(),
647649
_ => panic!("Introduced a method? Add it here :)"),
648650
}
649651
}

rust/rpc_tests.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ impl Client for TestClient {
6767
self.session_notifications.lock().unwrap().push(args);
6868
Ok(())
6969
}
70+
71+
async fn new_terminal(&self, _args: NewTerminalRequest) -> Result<NewTerminalResponse, Error> {
72+
unimplemented!()
73+
}
74+
75+
async fn terminal_output(
76+
&self,
77+
_args: TerminalOutputRequest,
78+
) -> Result<TerminalOutputResponse, Error> {
79+
unimplemented!()
80+
}
7081
}
7182

7283
#[derive(Clone)]

0 commit comments

Comments
 (0)