Skip to content

Commit b3f9aa4

Browse files
committed
Release terminals
1 parent a72d90b commit b3f9aa4

11 files changed

Lines changed: 146 additions & 7 deletions

File tree

AGENTS.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
All paths in the protocol should be absolute
22

3-
When I ask you to add a new ACP Agent or Client method:
3+
## Adding new methods
44

55
- 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+
- If the protocol method name is `noun/verb`, use `verb_noun` for the user facing methods and structs.
7+
8+
Example 1 (`noun/noun`):
9+
Protocol method: `terminal/output`
10+
Trait method name: `terminal_output`
11+
Request/Response structs: `TerminalOutputRequest` / `TerminalOutputResponse`
12+
Method names struct: `terminal_output: &'static str`
13+
14+
Example 2 (`noun/verb`):
15+
Protocol method: `terminal/new`
16+
Trait method name: `new_terminal`
17+
Request/Response structs: `NewTerminalRequest` / `NewTerminalResponse`
18+
Method names struct: `terminal_new: &'static str`
19+
620
- Do not write any tests or docs at all!
721
- Add constants for the method names
822
- Add variants to {Agent|Client}{Request|Response} enums
923
- Add the methods to the Client/Agent impl of {Agent|Client}SideConnection in rust/acp.rs
24+
- Handle the method in the decoders
1025
- Handle the new request in the blanket impl of MessageHandler<{Agent|Client}Side>
1126
- Add the method to markdown_generator.rs SideDocs functions
1227
- Run `npm run generate` and fix any issues that appear
1328
- Add the method to typescript/acp.ts classes and handlers
1429
- Run `npm run check`
30+
- Update the example agents and clients in tests and examples in both libraries
1531

16-
When updating existing JSON-RPC methods, their params, or output:
32+
## Updating existing methods, their params, or output
1733

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

docs/protocol/schema.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,22 @@ See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protoc
586586
<ResponseField name="truncated" type={"boolean"} required>
587587
</ResponseField>
588588

589+
<a id="terminal-release"></a>
590+
### <span class="font-mono">terminal/release</span>
591+
592+
#### <span class="font-mono">ReleaseTerminalRequest</span>
593+
594+
**Type:** Object
595+
596+
**Properties:**
597+
598+
<ResponseField
599+
name="sessionId"
600+
type={<a href="#sessionid">SessionId</a>}
601+
required
602+
></ResponseField>
603+
<ResponseField name="terminalId" type={"string"} required></ResponseField>
604+
589605
## <span class="font-mono">AgentCapabilities</span>
590606

591607
Capabilities supported by the agent.

rust/acp.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ impl Side for ClientSide {
251251
TERMINAL_OUTPUT_METHOD_NAME => serde_json::from_str(params.get())
252252
.map(AgentRequest::TerminalOutputRequest)
253253
.map_err(Into::into),
254+
TERMINAL_RELEASE_METHOD_NAME => serde_json::from_str(params.get())
255+
.map(AgentRequest::ReleaseTerminalRequest)
256+
.map_err(Into::into),
254257
_ => Err(Error::method_not_found()),
255258
}
256259
}
@@ -293,6 +296,10 @@ impl<T: Client> MessageHandler<ClientSide> for T {
293296
let response = self.terminal_output(args).await?;
294297
Ok(ClientResponse::TerminalOutputResponse(response))
295298
}
299+
AgentRequest::ReleaseTerminalRequest(args) => {
300+
self.release_terminal(args).await?;
301+
Ok(ClientResponse::ReleaseTerminalResponse)
302+
}
296303
}
297304
}
298305

@@ -421,6 +428,15 @@ impl Client for AgentSideConnection {
421428
.await
422429
}
423430

431+
async fn release_terminal(&self, arguments: ReleaseTerminalRequest) -> Result<(), Error> {
432+
self.conn
433+
.request(
434+
TERMINAL_RELEASE_METHOD_NAME,
435+
Some(AgentRequest::ReleaseTerminalRequest(arguments)),
436+
)
437+
.await
438+
}
439+
424440
async fn session_notification(&self, notification: SessionNotification) -> Result<(), Error> {
425441
self.conn.notify(
426442
SESSION_UPDATE_NOTIFICATION,

rust/client.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ pub trait Client {
6464
args: TerminalOutputRequest,
6565
) -> impl Future<Output = Result<TerminalOutputResponse, Error>>;
6666

67+
fn release_terminal(
68+
&self,
69+
args: ReleaseTerminalRequest,
70+
) -> impl Future<Output = Result<(), Error>>;
71+
6772
/// Handles session update notifications from the agent.
6873
///
6974
/// This is a notification endpoint (no response expected) that receives
@@ -308,6 +313,14 @@ pub struct TerminalOutputResponse {
308313
pub signal: Option<String>,
309314
}
310315

316+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
317+
#[schemars(extend("x-side" = "client", "x-method" = "terminal/release"))]
318+
#[serde(rename_all = "camelCase")]
319+
pub struct ReleaseTerminalRequest {
320+
pub session_id: SessionId,
321+
pub terminal_id: TerminalId,
322+
}
323+
311324
// Capabilities
312325

313326
/// Capabilities supported by the client.
@@ -360,6 +373,8 @@ pub struct ClientMethodNames {
360373
pub terminal_new: &'static str,
361374
/// Method for getting terminals output.
362375
pub terminal_output: &'static str,
376+
/// Method for releasing terminals.
377+
pub terminal_release: &'static str,
363378
}
364379

365380
/// Constant containing all client method names.
@@ -370,6 +385,7 @@ pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
370385
fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
371386
terminal_new: TERMINAL_NEW_METHOD_NAME,
372387
terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
388+
terminal_release: TERMINAL_RELEASE_METHOD_NAME,
373389
};
374390

375391
/// Notification name for session updates.
@@ -384,6 +400,8 @@ pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
384400
pub(crate) const TERMINAL_NEW_METHOD_NAME: &str = "terminal/new";
385401
/// Method for getting terminals output.
386402
pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
403+
/// Method for releasing a terminal.
404+
pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
387405

388406
/// All possible requests that an agent can send to a client.
389407
///
@@ -400,6 +418,7 @@ pub enum AgentRequest {
400418
RequestPermissionRequest(RequestPermissionRequest),
401419
NewTerminalRequest(NewTerminalRequest),
402420
TerminalOutputRequest(TerminalOutputRequest),
421+
ReleaseTerminalRequest(ReleaseTerminalRequest),
403422
}
404423

405424
/// All possible responses that a client can send to an agent.
@@ -417,6 +436,7 @@ pub enum ClientResponse {
417436
RequestPermissionResponse(RequestPermissionResponse),
418437
NewTerminalResponse(NewTerminalResponse),
419438
TerminalOutputResponse(TerminalOutputResponse),
439+
ReleaseTerminalResponse,
420440
}
421441

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

rust/example_client.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ impl acp::Client for ExampleClient {
5454
Err(acp::Error::method_not_found())
5555
}
5656

57+
async fn release_terminal(
58+
&self,
59+
_args: acp::ReleaseTerminalRequest,
60+
) -> anyhow::Result<(), acp::Error> {
61+
Err(acp::Error::method_not_found())
62+
}
63+
5764
async fn session_notification(
5865
&self,
5966
args: acp::SessionNotification,

rust/markdown_generator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ impl SideDocs {
646646
"session/update" => self.client_methods.get("session_notification").unwrap(),
647647
"terminal/new" => self.client_methods.get("new_terminal").unwrap(),
648648
"terminal/output" => self.client_methods.get("terminal_output").unwrap(),
649+
"terminal/release" => self.client_methods.get("release_terminal").unwrap(),
649650
_ => panic!("Introduced a method? Add it here :)"),
650651
}
651652
}

rust/rpc_tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ impl Client for TestClient {
7878
) -> Result<TerminalOutputResponse, Error> {
7979
unimplemented!()
8080
}
81+
82+
async fn release_terminal(&self, _args: ReleaseTerminalRequest) -> Result<(), Error> {
83+
unimplemented!()
84+
}
8185
}
8286

8387
#[derive(Clone)]

schema/meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"session_request_permission": "session/request_permission",
1414
"session_update": "session/update",
1515
"terminal_new": "terminal/new",
16-
"terminal_output": "terminal/output"
16+
"terminal_output": "terminal/output",
17+
"terminal_release": "terminal/release"
1718
},
1819
"version": 1
1920
}

schema/schema.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
{
5252
"$ref": "#/$defs/TerminalOutputRequest",
5353
"title": "TerminalOutputRequest"
54+
},
55+
{
56+
"$ref": "#/$defs/ReleaseTerminalRequest",
57+
"title": "ReleaseTerminalRequest"
5458
}
5559
],
5660
"description": "All possible requests that an agent can send to a client.\n\nThis enum is used internally for routing RPC requests. You typically won't need\nto use this directly - instead, use the methods on the [`Client`] trait.\n\nThis enum encompasses all method calls from agent to client.",
@@ -264,6 +268,10 @@
264268
{
265269
"$ref": "#/$defs/TerminalOutputResponse",
266270
"title": "TerminalOutputResponse"
271+
},
272+
{
273+
"title": "ReleaseTerminalResponse",
274+
"type": "null"
267275
}
268276
],
269277
"description": "All possible responses that a client can send to an agent.\n\nThis enum is used internally for routing RPC responses. You typically won't need\nto use this directly - the responses are handled automatically by the connection.\n\nThese are responses to the corresponding AgentRequest variants.",
@@ -918,6 +926,20 @@
918926
"required": ["content"],
919927
"type": "object"
920928
},
929+
"ReleaseTerminalRequest": {
930+
"properties": {
931+
"sessionId": {
932+
"$ref": "#/$defs/SessionId"
933+
},
934+
"terminalId": {
935+
"type": "string"
936+
}
937+
},
938+
"required": ["sessionId", "terminalId"],
939+
"type": "object",
940+
"x-method": "terminal/release",
941+
"x-side": "client"
942+
},
921943
"RequestPermissionOutcome": {
922944
"description": "The outcome of a permission request.",
923945
"oneOf": [

typescript/acp.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ export class AgentSideConnection implements Client {
173173
);
174174
}
175175

176+
async releaseTerminal(params: schema.ReleaseTerminalRequest): Promise<void> {
177+
return await this.#connection.sendRequest(
178+
schema.CLIENT_METHODS.terminal_release,
179+
params,
180+
);
181+
}
182+
176183
/**
177184
* Writes content to a text file in the client's file system.
178185
*
@@ -269,6 +276,13 @@ export class ClientSideConnection implements Agent {
269276
validatedParams as schema.TerminalOutputRequest,
270277
);
271278
}
279+
case schema.CLIENT_METHODS.terminal_release: {
280+
const validatedParams =
281+
schema.releaseTerminalRequestSchema.parse(params);
282+
return client.releaseTerminal(
283+
validatedParams as schema.ReleaseTerminalRequest,
284+
);
285+
}
272286
default:
273287
throw RequestError.methodNotFound(method);
274288
}
@@ -771,6 +785,7 @@ export interface Client {
771785
terminalOutput(
772786
params: schema.TerminalOutputRequest,
773787
): Promise<schema.TerminalOutputResponse>;
788+
releaseTerminal(params: schema.ReleaseTerminalRequest): Promise<void>;
774789
}
775790

776791
/**

0 commit comments

Comments
 (0)