Skip to content

Commit 6225be9

Browse files
agu-zbennetbortfeldman
authored
Introduce experimental session modes (agentclientprotocol#67)
* Introduce experimental session modes * Add `SessionUpdate::CurrentModeUpdate` Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Richard Feldman <oss@rtfeldman.com> * Add switch mode kind * SessionModeId display impl --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Richard Feldman <oss@rtfeldman.com>
1 parent 74aa838 commit 6225be9

13 files changed

Lines changed: 533 additions & 24 deletions

File tree

docs/protocol/schema.mdx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol
250250

251251
**Properties:**
252252

253+
<ResponseField name="modes" type={<><span><a href="#sessionmodestate">SessionModeState</a></span><span> | null</span></>} >
254+
**UNSTABLE**
255+
256+
This field is not part of the spec, and may be removed or changed at any point.
257+
258+
</ResponseField>
253259
<ResponseField name="sessionId" type={<a href="#sessionid">SessionId</a>} required>
254260
Unique identifier for the created session.
255261

@@ -981,6 +987,21 @@ An image provided to or from an LLM.
981987
<ResponseField name="mimeType" type={"string"} required></ResponseField>
982988
<ResponseField name="uri" type={"string | null"}></ResponseField>
983989

990+
## <span class="font-mono">LoadSessionResponse</span>
991+
992+
Response from loading an existing session.
993+
994+
**Type:** Object
995+
996+
**Properties:**
997+
998+
<ResponseField name="modes" type={<><span><a href="#sessionmodestate">SessionModeState</a></span><span> | null</span></>} >
999+
**UNSTABLE**
1000+
1001+
This field is not part of the spec, and may be removed or changed at any point.
1002+
1003+
</ResponseField>
1004+
9841005
## <span class="font-mono">McpCapabilities</span>
9851006

9861007
MCP capabilities supported by the agent
@@ -1431,6 +1452,60 @@ See protocol docs: [Session ID](https://agentclientprotocol.com/protocol/session
14311452

14321453
**Type:** `string`
14331454

1455+
## <span class="font-mono">SessionMode</span>
1456+
1457+
**UNSTABLE**
1458+
1459+
This type is not part of the spec, and may be removed or changed at any point.
1460+
1461+
**Type:** Object
1462+
1463+
**Properties:**
1464+
1465+
<ResponseField name="description" type={"string | null"}></ResponseField>
1466+
<ResponseField
1467+
name="id"
1468+
type={<a href="#sessionmodeid">SessionModeId</a>}
1469+
required
1470+
></ResponseField>
1471+
<ResponseField name="name" type={"string"} required></ResponseField>
1472+
1473+
## <span class="font-mono">SessionModeId</span>
1474+
1475+
**UNSTABLE**
1476+
1477+
This type is not part of the spec, and may be removed or changed at any point.
1478+
1479+
**Type:** `string`
1480+
1481+
## <span class="font-mono">SessionModeState</span>
1482+
1483+
**UNSTABLE**
1484+
1485+
This type is not part of the spec, and may be removed or changed at any point.
1486+
1487+
**Type:** Object
1488+
1489+
**Properties:**
1490+
1491+
<ResponseField
1492+
name="availableModes"
1493+
type={
1494+
<>
1495+
<span>
1496+
<a href="#sessionmode">SessionMode</a>
1497+
</span>
1498+
<span>[]</span>
1499+
</>
1500+
}
1501+
required
1502+
></ResponseField>
1503+
<ResponseField
1504+
name="currentModeId"
1505+
type={<a href="#sessionmodeid">SessionModeId</a>}
1506+
required
1507+
></ResponseField>
1508+
14341509
## <span class="font-mono">SessionUpdate</span>
14351510

14361511
Different types of updates that can be sent during session processing.
@@ -1648,6 +1723,29 @@ Available commands are ready or have changed
16481723
</Expandable>
16491724
</ResponseField>
16501725

1726+
<ResponseField name="current_mode_update">
1727+
The current mode of the session has changed
1728+
1729+
<Expandable title="Properties">
1730+
1731+
<ResponseField
1732+
name="currentModeId"
1733+
type={<a href="#sessionmodeid">SessionModeId</a>}
1734+
required
1735+
></ResponseField>
1736+
<ResponseField name="sessionUpdate" type={"string"} required></ResponseField>
1737+
1738+
</Expandable>
1739+
</ResponseField>
1740+
1741+
## <span class="font-mono">SetSessionModeResponse</span>
1742+
1743+
**UNSTABLE**
1744+
1745+
This type is not part of the spec, and may be removed or changed at any point.
1746+
1747+
**Type:** `object`
1748+
16511749
## <span class="font-mono">StopReason</span>
16521750

16531751
Reasons why an agent stops processing a prompt turn.
@@ -1984,4 +2082,11 @@ See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-call
19842082

19852083
<ResponseField name="fetch">Retrieving external data.</ResponseField>
19862084

2085+
<ResponseField name="switch_mode">
2086+
**UNSTABLE**
2087+
2088+
This tool kind is not part of the spec and may be removed at any point.
2089+
2090+
</ResponseField>
2091+
19872092
<ResponseField name="other">Other tool types (default).</ResponseField>

rust/acp.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ impl Agent for ClientSideConnection {
192192
.await
193193
}
194194

195-
async fn load_session(&self, arguments: LoadSessionRequest) -> Result<(), Error> {
195+
async fn load_session(
196+
&self,
197+
arguments: LoadSessionRequest,
198+
) -> Result<LoadSessionResponse, Error> {
196199
self.conn
197200
.request(
198201
SESSION_LOAD_METHOD_NAME,
@@ -201,6 +204,19 @@ impl Agent for ClientSideConnection {
201204
.await
202205
}
203206

207+
#[cfg(feature = "unstable")]
208+
async fn set_session_mode(
209+
&self,
210+
arguments: SetSessionModeRequest,
211+
) -> Result<SetSessionModeResponse, Error> {
212+
self.conn
213+
.request(
214+
SESSION_SET_MODE_METHOD_NAME,
215+
Some(ClientRequest::SetSessionModeRequest(arguments)),
216+
)
217+
.await
218+
}
219+
204220
async fn prompt(&self, arguments: PromptRequest) -> Result<PromptResponse, Error> {
205221
self.conn
206222
.request(
@@ -525,6 +541,10 @@ impl Side for AgentSide {
525541
SESSION_LOAD_METHOD_NAME => serde_json::from_str(params.get())
526542
.map(ClientRequest::LoadSessionRequest)
527543
.map_err(Into::into),
544+
#[cfg(feature = "unstable")]
545+
SESSION_SET_MODE_METHOD_NAME => serde_json::from_str(params.get())
546+
.map(ClientRequest::SetSessionModeRequest)
547+
.map_err(Into::into),
528548
SESSION_PROMPT_METHOD_NAME => serde_json::from_str(params.get())
529549
.map(ClientRequest::PromptRequest)
530550
.map_err(Into::into),
@@ -563,13 +583,18 @@ impl<T: Agent> MessageHandler<AgentSide> for T {
563583
Ok(AgentResponse::NewSessionResponse(response))
564584
}
565585
ClientRequest::LoadSessionRequest(args) => {
566-
self.load_session(args).await?;
567-
Ok(AgentResponse::LoadSessionResponse)
586+
let response = self.load_session(args).await?;
587+
Ok(AgentResponse::LoadSessionResponse(response))
568588
}
569589
ClientRequest::PromptRequest(args) => {
570590
let response = self.prompt(args).await?;
571591
Ok(AgentResponse::PromptResponse(response))
572592
}
593+
#[cfg(feature = "unstable")]
594+
ClientRequest::SetSessionModeRequest(args) => {
595+
let response = self.set_session_mode(args).await?;
596+
Ok(AgentResponse::SetSessionModeResponse(response))
597+
}
573598
}
574599
}
575600

rust/agent.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,16 @@ pub trait Agent {
7575
fn load_session(
7676
&self,
7777
arguments: LoadSessionRequest,
78-
) -> impl Future<Output = Result<(), Error>>;
78+
) -> impl Future<Output = Result<LoadSessionResponse, Error>>;
79+
80+
/// **UNSTABLE**
81+
///
82+
/// This method is not part of the spec, and may be removed or changed at any point.
83+
#[cfg(feature = "unstable")]
84+
fn set_session_mode(
85+
&self,
86+
arguments: SetSessionModeRequest,
87+
) -> impl Future<Output = Result<SetSessionModeResponse, Error>>;
7988

8089
/// Processes a user prompt within a session.
8190
///
@@ -204,6 +213,11 @@ pub struct NewSessionResponse {
204213
///
205214
/// Used in all subsequent requests for this conversation.
206215
pub session_id: SessionId,
216+
/// **UNSTABLE**
217+
///
218+
/// This field is not part of the spec, and may be removed or changed at any point.
219+
#[serde(default, skip_serializing_if = "Option::is_none")]
220+
pub modes: Option<SessionModeState>,
207221
}
208222

209223
// Load session
@@ -225,6 +239,72 @@ pub struct LoadSessionRequest {
225239
pub session_id: SessionId,
226240
}
227241

242+
/// Response from loading an existing session.
243+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
244+
#[serde(rename_all = "camelCase")]
245+
pub struct LoadSessionResponse {
246+
/// **UNSTABLE**
247+
///
248+
/// This field is not part of the spec, and may be removed or changed at any point.
249+
#[serde(default, skip_serializing_if = "Option::is_none")]
250+
pub modes: Option<SessionModeState>,
251+
}
252+
253+
// Session modes
254+
255+
/// **UNSTABLE**
256+
///
257+
/// This type is not part of the spec, and may be removed or changed at any point.
258+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
259+
#[serde(rename_all = "camelCase")]
260+
pub struct SessionModeState {
261+
pub current_mode_id: SessionModeId,
262+
pub available_modes: Vec<SessionMode>,
263+
}
264+
265+
/// **UNSTABLE**
266+
///
267+
/// This type is not part of the spec, and may be removed or changed at any point.
268+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
269+
#[serde(rename_all = "camelCase")]
270+
pub struct SessionMode {
271+
pub id: SessionModeId,
272+
pub name: String,
273+
#[serde(default, skip_serializing_if = "Option::is_none")]
274+
pub description: Option<String>,
275+
}
276+
277+
/// **UNSTABLE**
278+
///
279+
/// This type is not part of the spec, and may be removed or changed at any point.
280+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
281+
#[serde(rename_all = "camelCase")]
282+
pub struct SessionModeId(pub Arc<str>);
283+
284+
impl std::fmt::Display for SessionModeId {
285+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286+
write!(f, "{}", self.0)
287+
}
288+
}
289+
290+
/// **UNSTABLE**
291+
///
292+
/// This type is not part of the spec, and may be removed or changed at any point.
293+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
294+
#[schemars(extend("x-docs-ignore" = true))]
295+
#[serde(rename_all = "camelCase")]
296+
pub struct SetSessionModeRequest {
297+
pub session_id: SessionId,
298+
pub mode_id: SessionModeId,
299+
}
300+
301+
/// **UNSTABLE**
302+
///
303+
/// This type is not part of the spec, and may be removed or changed at any point.
304+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
305+
#[serde(rename_all = "camelCase")]
306+
pub struct SetSessionModeResponse {}
307+
228308
// MCP
229309

230310
/// Configuration for connecting to an MCP (Model Context Protocol) server.
@@ -440,6 +520,9 @@ pub struct AgentMethodNames {
440520
pub session_new: &'static str,
441521
/// Method for loading an existing session.
442522
pub session_load: &'static str,
523+
/// Method for setting the mode for a session.
524+
#[cfg(feature = "unstable")]
525+
pub session_set_mode: &'static str,
443526
/// Method for sending a prompt to the agent.
444527
pub session_prompt: &'static str,
445528
/// Notification for cancelling operations.
@@ -452,6 +535,8 @@ pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames {
452535
authenticate: AUTHENTICATE_METHOD_NAME,
453536
session_new: SESSION_NEW_METHOD_NAME,
454537
session_load: SESSION_LOAD_METHOD_NAME,
538+
#[cfg(feature = "unstable")]
539+
session_set_mode: SESSION_SET_MODE_METHOD_NAME,
455540
session_prompt: SESSION_PROMPT_METHOD_NAME,
456541
session_cancel: SESSION_CANCEL_METHOD_NAME,
457542
};
@@ -464,6 +549,9 @@ pub(crate) const AUTHENTICATE_METHOD_NAME: &str = "authenticate";
464549
pub(crate) const SESSION_NEW_METHOD_NAME: &str = "session/new";
465550
/// Method name for loading an existing session.
466551
pub(crate) const SESSION_LOAD_METHOD_NAME: &str = "session/load";
552+
/// Method name for setting the mode for a session.
553+
#[cfg(feature = "unstable")]
554+
pub(crate) const SESSION_SET_MODE_METHOD_NAME: &str = "session/set_mode";
467555
/// Method name for sending a prompt.
468556
pub(crate) const SESSION_PROMPT_METHOD_NAME: &str = "session/prompt";
469557
/// Method name for the cancel notification.
@@ -483,6 +571,8 @@ pub enum ClientRequest {
483571
AuthenticateRequest(AuthenticateRequest),
484572
NewSessionRequest(NewSessionRequest),
485573
LoadSessionRequest(LoadSessionRequest),
574+
#[cfg(feature = "unstable")]
575+
SetSessionModeRequest(SetSessionModeRequest),
486576
PromptRequest(PromptRequest),
487577
}
488578

@@ -499,7 +589,9 @@ pub enum AgentResponse {
499589
InitializeResponse(InitializeResponse),
500590
AuthenticateResponse,
501591
NewSessionResponse(NewSessionResponse),
502-
LoadSessionResponse,
592+
LoadSessionResponse(LoadSessionResponse),
593+
#[cfg(feature = "unstable")]
594+
SetSessionModeResponse(SetSessionModeResponse),
503595
PromptResponse(PromptResponse),
504596
}
505597

rust/client.rs

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

12+
#[cfg(feature = "unstable")]
13+
use crate::SessionModeId;
1214
use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
1315

1416
/// Defines the interface that ACP-compliant clients must implement.
@@ -165,6 +167,11 @@ pub enum SessionUpdate {
165167
AvailableCommandsUpdate {
166168
available_commands: Vec<AvailableCommand>,
167169
},
170+
/// The current mode of the session has changed
171+
#[cfg(feature = "unstable")]
172+
#[serde(rename_all = "camelCase")]
173+
#[schemars(extend("x-docs-ignore" = true))]
174+
CurrentModeUpdate { current_mode_id: SessionModeId },
168175
}
169176

170177
/// Information about a command.

rust/example_agent.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,16 @@ impl acp::Agent for ExampleAgent {
6161
self.next_session_id.set(session_id + 1);
6262
Ok(acp::NewSessionResponse {
6363
session_id: acp::SessionId(session_id.to_string().into()),
64+
modes: None,
6465
})
6566
}
6667

67-
async fn load_session(&self, arguments: acp::LoadSessionRequest) -> Result<(), acp::Error> {
68+
async fn load_session(
69+
&self,
70+
arguments: acp::LoadSessionRequest,
71+
) -> Result<acp::LoadSessionResponse, acp::Error> {
6872
log::info!("Received load session request {arguments:?}");
69-
Err(acp::Error::method_not_found())
73+
Ok(acp::LoadSessionResponse { modes: None })
7074
}
7175

7276
async fn prompt(

0 commit comments

Comments
 (0)