From 8c0eaccffa819077d4c2e7407dfde7ed6872a9e7 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Fri, 5 Sep 2025 10:12:09 -0300 Subject: [PATCH 1/4] Introduce experimental session modes --- docs/protocol/schema.mdx | 83 ++++++++++++++++++++++++++++ rust/acp.rs | 31 ++++++++++- rust/agent.rs | 90 +++++++++++++++++++++++++++++- rust/example_agent.rs | 8 ++- rust/markdown_generator.rs | 1 + rust/rpc_tests.rs | 17 +++++- schema/meta.json | 3 +- schema/schema.json | 94 +++++++++++++++++++++++++++++++- typescript/acp.ts | 45 ++++++++++++++- typescript/examples/agent.ts | 7 +++ typescript/schema.ts | 103 ++++++++++++++++++++++++++++++++--- 11 files changed, 459 insertions(+), 23 deletions(-) diff --git a/docs/protocol/schema.mdx b/docs/protocol/schema.mdx index b6da658..cea5b07 100644 --- a/docs/protocol/schema.mdx +++ b/docs/protocol/schema.mdx @@ -250,6 +250,12 @@ See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol **Properties:** +SessionModeState | null} > + **UNSTABLE** + +This field is not part of the spec, and may be removed or changed at any point. + + SessionId} required> Unique identifier for the created session. @@ -960,6 +966,21 @@ An image provided to or from an LLM. +## LoadSessionResponse + +Response from loading an existing session. + +**Type:** Object + +**Properties:** + +SessionModeState | null} > + **UNSTABLE** + +This field is not part of the spec, and may be removed or changed at any point. + + + ## McpServer Configuration for connecting to an MCP (Model Context Protocol) server. @@ -1317,6 +1338,60 @@ See protocol docs: [Session ID](https://agentclientprotocol.com/protocol/session **Type:** `string` +## SessionMode + +**UNSTABLE** + +This type is not part of the spec, and may be removed or changed at any point. + +**Type:** Object + +**Properties:** + + +SessionModeId} + required +> + + +## SessionModeId + +**UNSTABLE** + +This type is not part of the spec, and may be removed or changed at any point. + +**Type:** `string` + +## SessionModeState + +**UNSTABLE** + +This type is not part of the spec, and may be removed or changed at any point. + +**Type:** Object + +**Properties:** + + + + SessionMode + + [] + + } + required +> +SessionModeId} + required +> + ## SessionUpdate Different types of updates that can be sent during session processing. @@ -1534,6 +1609,14 @@ Available commands are ready or have changed +## SetSessionModeResponse + +**UNSTABLE** + +This type is not part of the spec, and may be removed or changed at any point. + +**Type:** `object` + ## StopReason Reasons why an agent stops processing a prompt turn. diff --git a/rust/acp.rs b/rust/acp.rs index 32c6448..3eb5252 100644 --- a/rust/acp.rs +++ b/rust/acp.rs @@ -192,7 +192,10 @@ impl Agent for ClientSideConnection { .await } - async fn load_session(&self, arguments: LoadSessionRequest) -> Result<(), Error> { + async fn load_session( + &self, + arguments: LoadSessionRequest, + ) -> Result { self.conn .request( SESSION_LOAD_METHOD_NAME, @@ -201,6 +204,19 @@ impl Agent for ClientSideConnection { .await } + #[cfg(feature = "unstable")] + async fn set_session_mode( + &self, + arguments: SetSessionModeRequest, + ) -> Result { + self.conn + .request( + SESSION_SET_MODE_METHOD_NAME, + Some(ClientRequest::SetSessionModeRequest(arguments)), + ) + .await + } + async fn prompt(&self, arguments: PromptRequest) -> Result { self.conn .request( @@ -525,6 +541,10 @@ impl Side for AgentSide { SESSION_LOAD_METHOD_NAME => serde_json::from_str(params.get()) .map(ClientRequest::LoadSessionRequest) .map_err(Into::into), + #[cfg(feature = "unstable")] + SESSION_SET_MODE_METHOD_NAME => serde_json::from_str(params.get()) + .map(ClientRequest::SetSessionModeRequest) + .map_err(Into::into), SESSION_PROMPT_METHOD_NAME => serde_json::from_str(params.get()) .map(ClientRequest::PromptRequest) .map_err(Into::into), @@ -563,13 +583,18 @@ impl MessageHandler for T { Ok(AgentResponse::NewSessionResponse(response)) } ClientRequest::LoadSessionRequest(args) => { - self.load_session(args).await?; - Ok(AgentResponse::LoadSessionResponse) + let response = self.load_session(args).await?; + Ok(AgentResponse::LoadSessionResponse(response)) } ClientRequest::PromptRequest(args) => { let response = self.prompt(args).await?; Ok(AgentResponse::PromptResponse(response)) } + #[cfg(feature = "unstable")] + ClientRequest::SetSessionModeRequest(args) => { + let response = self.set_session_mode(args).await?; + Ok(AgentResponse::SetSessionModeResponse(response)) + } } } diff --git a/rust/agent.rs b/rust/agent.rs index ffeda9a..94e5b2b 100644 --- a/rust/agent.rs +++ b/rust/agent.rs @@ -75,7 +75,16 @@ pub trait Agent { fn load_session( &self, arguments: LoadSessionRequest, - ) -> impl Future>; + ) -> impl Future>; + + /// **UNSTABLE** + /// + /// This method is not part of the spec, and may be removed or changed at any point. + #[cfg(feature = "unstable")] + fn set_session_mode( + &self, + arguments: SetSessionModeRequest, + ) -> impl Future>; /// Processes a user prompt within a session. /// @@ -204,6 +213,11 @@ pub struct NewSessionResponse { /// /// Used in all subsequent requests for this conversation. pub session_id: SessionId, + /// **UNSTABLE** + /// + /// This field is not part of the spec, and may be removed or changed at any point. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub modes: Option, } // Load session @@ -225,6 +239,66 @@ pub struct LoadSessionRequest { pub session_id: SessionId, } +/// Response from loading an existing session. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct LoadSessionResponse { + /// **UNSTABLE** + /// + /// This field is not part of the spec, and may be removed or changed at any point. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub modes: Option, +} + +// Session modes + +/// **UNSTABLE** +/// +/// This type is not part of the spec, and may be removed or changed at any point. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct SessionModeState { + pub current_mode_id: SessionModeId, + pub available_modes: Vec, +} + +/// **UNSTABLE** +/// +/// This type is not part of the spec, and may be removed or changed at any point. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct SessionMode { + pub id: SessionModeId, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +/// **UNSTABLE** +/// +/// This type is not part of the spec, and may be removed or changed at any point. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct SessionModeId(pub Arc); + +/// **UNSTABLE** +/// +/// This type is not part of the spec, and may be removed or changed at any point. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(extend("x-docs-ignore" = true))] +#[serde(rename_all = "camelCase")] +pub struct SetSessionModeRequest { + pub session_id: SessionId, + pub mode_id: SessionModeId, +} + +/// **UNSTABLE** +/// +/// This type is not part of the spec, and may be removed or changed at any point. +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct SetSessionModeResponse {} + // MCP /// Configuration for connecting to an MCP (Model Context Protocol) server. @@ -385,6 +459,9 @@ pub struct AgentMethodNames { pub session_new: &'static str, /// Method for loading an existing session. pub session_load: &'static str, + /// Method for setting the mode for a session. + #[cfg(feature = "unstable")] + pub session_set_mode: &'static str, /// Method for sending a prompt to the agent. pub session_prompt: &'static str, /// Notification for cancelling operations. @@ -397,6 +474,8 @@ pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames { authenticate: AUTHENTICATE_METHOD_NAME, session_new: SESSION_NEW_METHOD_NAME, session_load: SESSION_LOAD_METHOD_NAME, + #[cfg(feature = "unstable")] + session_set_mode: SESSION_SET_MODE_METHOD_NAME, session_prompt: SESSION_PROMPT_METHOD_NAME, session_cancel: SESSION_CANCEL_METHOD_NAME, }; @@ -409,6 +488,9 @@ pub(crate) const AUTHENTICATE_METHOD_NAME: &str = "authenticate"; pub(crate) const SESSION_NEW_METHOD_NAME: &str = "session/new"; /// Method name for loading an existing session. pub(crate) const SESSION_LOAD_METHOD_NAME: &str = "session/load"; +/// Method name for setting the mode for a session. +#[cfg(feature = "unstable")] +pub(crate) const SESSION_SET_MODE_METHOD_NAME: &str = "session/set_mode"; /// Method name for sending a prompt. pub(crate) const SESSION_PROMPT_METHOD_NAME: &str = "session/prompt"; /// Method name for the cancel notification. @@ -428,6 +510,8 @@ pub enum ClientRequest { AuthenticateRequest(AuthenticateRequest), NewSessionRequest(NewSessionRequest), LoadSessionRequest(LoadSessionRequest), + #[cfg(feature = "unstable")] + SetSessionModeRequest(SetSessionModeRequest), PromptRequest(PromptRequest), } @@ -444,7 +528,9 @@ pub enum AgentResponse { InitializeResponse(InitializeResponse), AuthenticateResponse, NewSessionResponse(NewSessionResponse), - LoadSessionResponse, + LoadSessionResponse(LoadSessionResponse), + #[cfg(feature = "unstable")] + SetSessionModeResponse(SetSessionModeResponse), PromptResponse(PromptResponse), } diff --git a/rust/example_agent.rs b/rust/example_agent.rs index f6bde22..39d3625 100644 --- a/rust/example_agent.rs +++ b/rust/example_agent.rs @@ -61,12 +61,16 @@ impl acp::Agent for ExampleAgent { self.next_session_id.set(session_id + 1); Ok(acp::NewSessionResponse { session_id: acp::SessionId(session_id.to_string().into()), + modes: None, }) } - async fn load_session(&self, arguments: acp::LoadSessionRequest) -> Result<(), acp::Error> { + async fn load_session( + &self, + arguments: acp::LoadSessionRequest, + ) -> Result { log::info!("Received load session request {arguments:?}"); - Err(acp::Error::method_not_found()) + Ok(acp::LoadSessionResponse { modes: None }) } async fn prompt( diff --git a/rust/markdown_generator.rs b/rust/markdown_generator.rs index 398733f..e776fd0 100644 --- a/rust/markdown_generator.rs +++ b/rust/markdown_generator.rs @@ -632,6 +632,7 @@ impl SideDocs { "authenticate" => self.agent_methods.get("authenticate").unwrap(), "session/new" => self.agent_methods.get("new_session").unwrap(), "session/load" => self.agent_methods.get("load_session").unwrap(), + "session/set_mode" => self.agent_methods.get("set_session_mode").unwrap(), "session/prompt" => self.agent_methods.get("prompt").unwrap(), "session/cancel" => self.agent_methods.get("cancel").unwrap(), _ => panic!("Introduced a method? Add it here :)"), diff --git a/rust/rpc_tests.rs b/rust/rpc_tests.rs index f677373..29fd715 100644 --- a/rust/rpc_tests.rs +++ b/rust/rpc_tests.rs @@ -136,11 +136,22 @@ impl Agent for TestAgent { ) -> Result { let session_id = SessionId(Arc::from("test-session-123")); self.sessions.lock().unwrap().insert(session_id.clone()); - Ok(NewSessionResponse { session_id }) + Ok(NewSessionResponse { + session_id, + modes: None, + }) } - async fn load_session(&self, _: LoadSessionRequest) -> Result<(), Error> { - Ok(()) + async fn load_session(&self, _: LoadSessionRequest) -> Result { + Ok(LoadSessionResponse { modes: None }) + } + + #[cfg(feature = "unstable")] + async fn set_session_mode( + &self, + _arguments: SetSessionModeRequest, + ) -> Result { + Ok(SetSessionModeResponse {}) } async fn prompt(&self, arguments: PromptRequest) -> Result { diff --git a/schema/meta.json b/schema/meta.json index 741ab3d..ec0ba7d 100644 --- a/schema/meta.json +++ b/schema/meta.json @@ -5,7 +5,8 @@ "session_cancel": "session/cancel", "session_load": "session/load", "session_new": "session/new", - "session_prompt": "session/prompt" + "session_prompt": "session/prompt", + "session_set_mode": "session/set_mode" }, "clientMethods": { "fs_read_text_file": "fs/read_text_file", diff --git a/schema/schema.json b/schema/schema.json index f583f21..173c019 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -83,8 +83,12 @@ "title": "NewSessionResponse" }, { - "title": "LoadSessionResponse", - "type": "null" + "$ref": "#/$defs/LoadSessionResponse", + "title": "LoadSessionResponse" + }, + { + "$ref": "#/$defs/SetSessionModeResponse", + "title": "SetSessionModeResponse" }, { "$ref": "#/$defs/PromptResponse", @@ -290,6 +294,10 @@ "$ref": "#/$defs/LoadSessionRequest", "title": "LoadSessionRequest" }, + { + "$ref": "#/$defs/SetSessionModeRequest", + "title": "SetSessionModeRequest" + }, { "$ref": "#/$defs/PromptRequest", "title": "PromptRequest" @@ -713,6 +721,23 @@ "x-method": "session/load", "x-side": "agent" }, + "LoadSessionResponse": { + "description": "Response from loading an existing session.", + "properties": { + "modes": { + "anyOf": [ + { + "$ref": "#/$defs/SessionModeState" + }, + { + "type": "null" + } + ], + "description": "**UNSTABLE**\n\nThis field is not part of the spec, and may be removed or changed at any point." + } + }, + "type": "object" + }, "McpServer": { "description": "Configuration for connecting to an MCP (Model Context Protocol) server.\n\nMCP servers provide tools and context that the agent can use when\nprocessing prompts.\n\nSee protocol docs: [MCP Servers](https://agentclientprotocol.com/protocol/session-setup#mcp-servers)", "properties": { @@ -765,6 +790,17 @@ "NewSessionResponse": { "description": "Response from creating a new session.\n\nSee protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session)", "properties": { + "modes": { + "anyOf": [ + { + "$ref": "#/$defs/SessionModeState" + }, + { + "type": "null" + } + ], + "description": "**UNSTABLE**\n\nThis field is not part of the spec, and may be removed or changed at any point." + }, "sessionId": { "$ref": "#/$defs/SessionId", "description": "Unique identifier for the created session.\n\nUsed in all subsequent requests for this conversation." @@ -1122,6 +1158,42 @@ "description": "A unique identifier for a conversation session between a client and agent.\n\nSessions maintain their own context, conversation history, and state,\nallowing multiple independent interactions with the same agent.\n\n# Example\n\n```\nuse agent_client_protocol::SessionId;\nuse std::sync::Arc;\n\nlet session_id = SessionId(Arc::from(\"sess_abc123def456\"));\n```\n\nSee protocol docs: [Session ID](https://agentclientprotocol.com/protocol/session-setup#session-id)", "type": "string" }, + "SessionMode": { + "description": "**UNSTABLE**\n\nThis type is not part of the spec, and may be removed or changed at any point.", + "properties": { + "description": { + "type": ["string", "null"] + }, + "id": { + "$ref": "#/$defs/SessionModeId" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"], + "type": "object" + }, + "SessionModeId": { + "description": "**UNSTABLE**\n\nThis type is not part of the spec, and may be removed or changed at any point.", + "type": "string" + }, + "SessionModeState": { + "description": "**UNSTABLE**\n\nThis type is not part of the spec, and may be removed or changed at any point.", + "properties": { + "availableModes": { + "items": { + "$ref": "#/$defs/SessionMode" + }, + "type": "array" + }, + "currentModeId": { + "$ref": "#/$defs/SessionModeId" + } + }, + "required": ["currentModeId", "availableModes"], + "type": "object" + }, "SessionNotification": { "description": "Notification containing a session update from the agent.\n\nUsed to stream real-time progress and results during prompt processing.\n\nSee protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)", "properties": { @@ -1330,6 +1402,24 @@ } ] }, + "SetSessionModeRequest": { + "description": "**UNSTABLE**\n\nThis type is not part of the spec, and may be removed or changed at any point.", + "properties": { + "modeId": { + "$ref": "#/$defs/SessionModeId" + }, + "sessionId": { + "$ref": "#/$defs/SessionId" + } + }, + "required": ["sessionId", "modeId"], + "type": "object", + "x-docs-ignore": true + }, + "SetSessionModeResponse": { + "description": "**UNSTABLE**\n\nThis type is not part of the spec, and may be removed or changed at any point.", + "type": "object" + }, "StopReason": { "description": "Reasons why an agent stops processing a prompt turn.\n\nSee protocol docs: [Stop Reasons](https://agentclientprotocol.com/protocol/prompt-turn#stop-reasons)", "oneOf": [ diff --git a/typescript/acp.ts b/typescript/acp.ts index 1c0ac22..34712e3 100644 --- a/typescript/acp.ts +++ b/typescript/acp.ts @@ -58,6 +58,16 @@ export class AgentSideConnection { validatedParams as schema.LoadSessionRequest, ); } + case schema.AGENT_METHODS.session_set_mode: { + if (!agent.setSessionMode) { + throw RequestError.methodNotFound(method); + } + const validatedParams = + schema.setSessionModeRequestSchema.parse(params); + return agent.setSessionMode( + validatedParams as schema.SetSessionModeRequest, + ); + } case schema.AGENT_METHODS.authenticate: { const validatedParams = schema.authenticateRequestSchema.parse(params); @@ -396,13 +406,32 @@ export class ClientSideConnection implements Agent { * * See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions) */ - async loadSession(params: schema.LoadSessionRequest): Promise { - await this.#connection.sendRequest( + async loadSession( + params: schema.LoadSessionRequest, + ): Promise { + return await this.#connection.sendRequest( schema.AGENT_METHODS.session_load, params, ); } + /** + * Sets the mode for an existing session. + * + * This method allows changing the operational mode of an existing session. + * The available modes are advertised in the agent's capabilities during initialization. + * + * See protocol docs: [Session Mode Management](https://agentclientprotocol.com/protocol/session-management#mode-setting) + */ + async setSessionMode( + params: schema.SetSessionModeRequest, + ): Promise { + return await this.#connection.sendRequest( + schema.AGENT_METHODS.session_set_mode, + params, + ); + } + /** * Authenticates the client using the specified authentication method. * @@ -914,7 +943,17 @@ export interface Agent { * * See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions) */ - loadSession?(params: schema.LoadSessionRequest): Promise; + loadSession?( + params: schema.LoadSessionRequest, + ): Promise; + /** + * @internal **UNSTABLE** + * + * This method is not part of the spec, and may be removed or changed at any point. + */ + setSessionMode?( + params: schema.SetSessionModeRequest, + ): Promise; /** * Authenticates the client using the specified authentication method. * diff --git a/typescript/examples/agent.ts b/typescript/examples/agent.ts index 6814e14..d978620 100644 --- a/typescript/examples/agent.ts +++ b/typescript/examples/agent.ts @@ -47,6 +47,13 @@ class ExampleAgent implements Agent { // No auth needed } + async setSessionMode( + params: schema.SetSessionModeRequest, + ): Promise { + // Session mode changes not implemented in this example + return {}; + } + async prompt(params: schema.PromptRequest): Promise { const session = this.sessions.get(params.sessionId); diff --git a/typescript/schema.ts b/typescript/schema.ts index 2098829..3410527 100644 --- a/typescript/schema.ts +++ b/typescript/schema.ts @@ -5,6 +5,7 @@ export const AGENT_METHODS = { session_load: "session/load", session_new: "session/new", session_prompt: "session/prompt", + session_set_mode: "session/set_mode", }; export const CLIENT_METHODS = { @@ -228,7 +229,14 @@ export type AgentRequest = | AuthenticateRequest | NewSessionRequest | LoadSessionRequest + | SetSessionModeRequest | PromptRequest; +/** + * **UNSTABLE** + * + * This type is not part of the spec, and may be removed or changed at any point. + */ +export type SessionModeId = string; /** * Content blocks represent displayable information in the Agent Client Protocol. * @@ -293,9 +301,9 @@ export type AgentResponse = | AuthenticateResponse | NewSessionResponse | LoadSessionResponse + | SetSessionModeResponse | PromptResponse; export type AuthenticateResponse = null; -export type LoadSessionResponse = null; /** * All possible notifications that an agent can send to a client. * @@ -699,6 +707,15 @@ export interface LoadSessionRequest { */ sessionId: string; } +/** + * **UNSTABLE** + * + * This type is not part of the spec, and may be removed or changed at any point. + */ +export interface SetSessionModeRequest { + modeId: SessionModeId; + sessionId: SessionId; +} /** * Request parameters for sending a user prompt to the agent. * @@ -816,6 +833,12 @@ export interface AuthMethod { * See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session) */ export interface NewSessionResponse { + /** + * **UNSTABLE** + * + * This field is not part of the spec, and may be removed or changed at any point. + */ + modes?: SessionModeState | null; /** * A unique identifier for a conversation session between a client and agent. * @@ -835,6 +858,42 @@ export interface NewSessionResponse { */ sessionId: string; } +/** + * **UNSTABLE** + * + * This type is not part of the spec, and may be removed or changed at any point. + */ +export interface SessionModeState { + availableModes: SessionMode[]; + currentModeId: SessionModeId; +} +/** + * **UNSTABLE** + * + * This type is not part of the spec, and may be removed or changed at any point. + */ +export interface SessionMode { + description?: string | null; + id: SessionModeId; + name: string; +} +/** + * Response from loading an existing session. + */ +export interface LoadSessionResponse { + /** + * **UNSTABLE** + * + * This field is not part of the spec, and may be removed or changed at any point. + */ + modes?: SessionModeState | null; +} +/** + * **UNSTABLE** + * + * This type is not part of the spec, and may be removed or changed at any point. + */ +export interface SetSessionModeResponse {} /** * Response from processing a user prompt. * @@ -1136,6 +1195,9 @@ export const authenticateRequestSchema = z.object({ methodId: z.string(), }); +/** @internal */ +export const sessionModeIdSchema = z.string(); + /** @internal */ export const annotationsSchema = z.object({ audience: z.array(roleSchema).optional().nullable(), @@ -1153,12 +1215,7 @@ export const embeddedResourceResourceSchema = z.union([ export const authenticateResponseSchema = z.null(); /** @internal */ -export const newSessionResponseSchema = z.object({ - sessionId: z.string(), -}); - -/** @internal */ -export const loadSessionResponseSchema = z.null(); +export const setSessionModeResponseSchema = z.object({}); /** @internal */ export const promptResponseSchema = z.object({ @@ -1303,6 +1360,12 @@ export const loadSessionRequestSchema = z.object({ sessionId: z.string(), }); +/** @internal */ +export const setSessionModeRequestSchema = z.object({ + modeId: sessionModeIdSchema, + sessionId: sessionIdSchema, +}); + /** @internal */ export const contentBlockSchema = z.union([ z.object({ @@ -1354,6 +1417,19 @@ export const promptCapabilitiesSchema = z.object({ image: z.boolean().optional(), }); +/** @internal */ +export const sessionModeSchema = z.object({ + description: z.string().optional().nullable(), + id: sessionModeIdSchema, + name: z.string(), +}); + +/** @internal */ +export const sessionModeStateSchema = z.object({ + availableModes: z.array(sessionModeSchema), + currentModeId: sessionModeIdSchema, +}); + /** @internal */ export const planEntrySchema = z.object({ content: z.string(), @@ -1400,6 +1476,17 @@ export const promptRequestSchema = z.object({ sessionId: z.string(), }); +/** @internal */ +export const newSessionResponseSchema = z.object({ + modes: sessionModeStateSchema.optional().nullable(), + sessionId: z.string(), +}); + +/** @internal */ +export const loadSessionResponseSchema = z.object({ + modes: sessionModeStateSchema.optional().nullable(), +}); + /** @internal */ export const toolCallUpdateSchema = z.object({ content: z.array(toolCallContentSchema).optional().nullable(), @@ -1549,6 +1636,7 @@ export const agentRequestSchema = z.union([ authenticateRequestSchema, newSessionRequestSchema, loadSessionRequestSchema, + setSessionModeRequestSchema, promptRequestSchema, ]); @@ -1558,6 +1646,7 @@ export const agentResponseSchema = z.union([ authenticateResponseSchema, newSessionResponseSchema, loadSessionResponseSchema, + setSessionModeResponseSchema, promptResponseSchema, ]); From aed65fa03df32afa120ef61e913441ed5b0cb4e5 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 8 Sep 2025 11:42:24 -0300 Subject: [PATCH 2/4] Add `SessionUpdate::CurrentModeUpdate` Co-authored-by: Bennet Bo Fenner Co-authored-by: Richard Feldman --- docs/protocol/schema.mdx | 15 +++++++++++++++ rust/client.rs | 7 +++++++ schema/schema.json | 15 +++++++++++++++ typescript/schema.ts | 8 ++++++++ 4 files changed, 45 insertions(+) diff --git a/docs/protocol/schema.mdx b/docs/protocol/schema.mdx index cea5b07..41b39c4 100644 --- a/docs/protocol/schema.mdx +++ b/docs/protocol/schema.mdx @@ -1609,6 +1609,21 @@ Available commands are ready or have changed + +The current mode of the session has changed + + + +SessionModeId} + required +> + + + + + ## SetSessionModeResponse **UNSTABLE** diff --git a/rust/client.rs b/rust/client.rs index bc91056..453863e 100644 --- a/rust/client.rs +++ b/rust/client.rs @@ -9,6 +9,8 @@ use anyhow::Result; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[cfg(feature = "unstable")] +use crate::SessionModeId; use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate}; /// Defines the interface that ACP-compliant clients must implement. @@ -165,6 +167,11 @@ pub enum SessionUpdate { AvailableCommandsUpdate { available_commands: Vec, }, + /// The current mode of the session has changed + #[cfg(feature = "unstable")] + #[serde(rename_all = "camelCase")] + #[schemars(extend("x-docs-ignore" = true))] + CurrentModeUpdate { current_mode_id: SessionModeId }, } /// Information about a command. diff --git a/schema/schema.json b/schema/schema.json index 173c019..e5d317b 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -1399,6 +1399,21 @@ "required": ["sessionUpdate", "availableCommands"], "type": "object", "x-docs-ignore": true + }, + { + "description": "The current mode of the session has changed", + "properties": { + "currentModeId": { + "$ref": "#/$defs/SessionModeId" + }, + "sessionUpdate": { + "const": "current_mode_update", + "type": "string" + } + }, + "required": ["sessionUpdate", "currentModeId"], + "type": "object", + "x-docs-ignore": true } ] }, diff --git a/typescript/schema.ts b/typescript/schema.ts index 3410527..3983687 100644 --- a/typescript/schema.ts +++ b/typescript/schema.ts @@ -1040,6 +1040,10 @@ export interface SessionNotification { | { availableCommands: AvailableCommand[]; sessionUpdate: "available_commands_update"; + } + | { + currentModeId: SessionModeId; + sessionUpdate: "current_mode_update"; }; } /** @@ -1615,6 +1619,10 @@ export const sessionNotificationSchema = z.object({ availableCommands: z.array(availableCommandSchema), sessionUpdate: z.literal("available_commands_update"), }), + z.object({ + currentModeId: sessionModeIdSchema, + sessionUpdate: z.literal("current_mode_update"), + }), ]), }); From d26c32f620f532345b93a75b58e79085a26a5599 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 8 Sep 2025 19:12:27 -0300 Subject: [PATCH 3/4] Add switch mode kind --- docs/protocol/schema.mdx | 7 +++++++ rust/tool_call.rs | 8 +++++++- schema/schema.json | 5 +++++ typescript/schema.ts | 4 ++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/protocol/schema.mdx b/docs/protocol/schema.mdx index 74a7b31..d68a9b9 100644 --- a/docs/protocol/schema.mdx +++ b/docs/protocol/schema.mdx @@ -2082,4 +2082,11 @@ See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-call Retrieving external data. + +**UNSTABLE** + +This tool kind is not part of the spec and may be removed at any point. + + + Other tool types (default). diff --git a/rust/tool_call.rs b/rust/tool_call.rs index 60ca03f..576263e 100644 --- a/rust/tool_call.rs +++ b/rust/tool_call.rs @@ -198,7 +198,7 @@ pub struct ToolCallId(pub Arc); /// display tool execution progress. /// /// See protocol docs: [Creating](https://agentclientprotocol.com/protocol/tool-calls#creating) -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ToolKind { /// Reading files or data. @@ -217,8 +217,14 @@ pub enum ToolKind { Think, /// Retrieving external data. Fetch, + /// **UNSTABLE** + /// + /// This tool kind is not part of the spec and may be removed at any point. + #[cfg(feature = "unstable")] + SwitchMode, /// Other tool types (default). #[default] + #[serde(other)] Other, } diff --git a/schema/schema.json b/schema/schema.json index 481604a..a8b9f9b 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -1897,6 +1897,11 @@ "description": "Retrieving external data.", "type": "string" }, + { + "const": "switch_mode", + "description": "**UNSTABLE**\n\nThis tool kind is not part of the spec and may be removed at any point.", + "type": "string" + }, { "const": "other", "description": "Other tool types (default).", diff --git a/typescript/schema.ts b/typescript/schema.ts index 78c2896..e791e70 100644 --- a/typescript/schema.ts +++ b/typescript/schema.ts @@ -157,6 +157,7 @@ export type ToolKind = | "execute" | "think" | "fetch" + | "switch_mode" | "other"; /** * Execution status of a tool call. @@ -1020,6 +1021,7 @@ export interface SessionNotification { | "execute" | "think" | "fetch" + | "switch_mode" | "other"; /** * File locations affected by this tool call. @@ -1201,6 +1203,7 @@ export const toolKindSchema = z.union([ z.literal("execute"), z.literal("think"), z.literal("fetch"), + z.literal("switch_mode"), z.literal("other"), ]); @@ -1676,6 +1679,7 @@ export const sessionNotificationSchema = z.object({ z.literal("execute"), z.literal("think"), z.literal("fetch"), + z.literal("switch_mode"), z.literal("other"), ]) .optional(), From dcb3b7e1efbdfd9bc557526368735eba07a9ec4a Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 8 Sep 2025 23:01:48 -0300 Subject: [PATCH 4/4] SessionModeId display impl --- rust/agent.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/agent.rs b/rust/agent.rs index bdb181d..25655c8 100644 --- a/rust/agent.rs +++ b/rust/agent.rs @@ -281,6 +281,12 @@ pub struct SessionMode { #[serde(rename_all = "camelCase")] pub struct SessionModeId(pub Arc); +impl std::fmt::Display for SessionModeId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + /// **UNSTABLE** /// /// This type is not part of the spec, and may be removed or changed at any point.