From 043cdbdb9710242531a38f00766196cc16563cba Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Fri, 28 Nov 2025 09:57:51 +0100 Subject: [PATCH] add session/fork --- Cargo.toml | 3 +- rust/agent.rs | 187 +++++++++++++++++++++++++++++++++++++++++++ rust/bin/generate.rs | 1 + rust/rpc.rs | 4 + 4 files changed, 194 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9df20da..16d2ba0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,10 @@ categories = ["development-tools", "api-bindings"] include = ["/rust/**/*.rs", "/README.md", "/LICENSE", "/Cargo.toml"] [features] -unstable = ["unstable_session_model", "unstable_session_list"] +unstable = ["unstable_session_model", "unstable_session_list", "unstable_session_fork"] unstable_session_model = [] unstable_session_list = [] +unstable_session_fork = [] [lib] path = "rust/acp.rs" diff --git a/rust/agent.rs b/rust/agent.rs index 529d95d..77d3456 100644 --- a/rust/agent.rs +++ b/rust/agent.rs @@ -528,6 +528,121 @@ impl LoadSessionResponse { } } +// Fork session + +/// **UNSTABLE** +/// +/// This capability is not part of the spec yet, and may be removed or changed at any point. +/// +/// Request parameters for forking an existing session. +/// +/// Creates a new session based on the context of an existing one, allowing +/// operations like generating summaries without affecting the original session's history. +/// +/// Only available if the Agent supports the `session.fork` capability. +#[cfg(feature = "unstable_session_fork")] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ForkSessionRequest { + /// The ID of the session to fork. + pub session_id: SessionId, + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + +#[cfg(feature = "unstable_session_fork")] +impl ForkSessionRequest { + pub fn new(session_id: impl Into) -> Self { + Self { + session_id: session_id.into(), + meta: None, + } + } + + /// Extension point for implementations + #[must_use] + pub fn meta(mut self, meta: serde_json::Value) -> Self { + self.meta = Some(meta); + self + } +} + +/// **UNSTABLE** +/// +/// This capability is not part of the spec yet, and may be removed or changed at any point. +/// +/// Response from forking an existing session. +#[cfg(feature = "unstable_session_fork")] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_FORK_METHOD_NAME))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ForkSessionResponse { + /// Unique identifier for the newly created forked session. + pub session_id: SessionId, + /// Initial mode state if supported by the Agent + /// + /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) + #[serde(skip_serializing_if = "Option::is_none")] + pub modes: Option, + /// **UNSTABLE** + /// + /// This capability is not part of the spec yet, and may be removed or changed at any point. + /// + /// Initial model state if supported by the Agent + #[cfg(feature = "unstable_session_model")] + #[serde(skip_serializing_if = "Option::is_none")] + pub models: Option, + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + +#[cfg(feature = "unstable_session_fork")] +impl ForkSessionResponse { + #[must_use] + pub fn new(session_id: impl Into) -> Self { + Self { + session_id: session_id.into(), + modes: None, + #[cfg(feature = "unstable_session_model")] + models: None, + meta: None, + } + } + + /// Initial mode state if supported by the Agent + /// + /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) + #[must_use] + pub fn modes(mut self, modes: SessionModeState) -> Self { + self.modes = Some(modes); + self + } + + /// **UNSTABLE** + /// + /// This capability is not part of the spec yet, and may be removed or changed at any point. + /// + /// Initial model state if supported by the Agent + #[cfg(feature = "unstable_session_model")] + #[must_use] + pub fn models(mut self, models: SessionModelState) -> Self { + self.models = Some(models); + self + } + + /// Extension point for implementations + #[must_use] + pub fn meta(mut self, meta: serde_json::Value) -> Self { + self.meta = Some(meta); + self + } +} + // List sessions /// **UNSTABLE** @@ -1427,6 +1542,14 @@ pub struct SessionCapabilities { #[cfg(feature = "unstable_session_list")] #[serde(skip_serializing_if = "Option::is_none")] pub list: Option, + /// **UNSTABLE** + /// + /// This capability is not part of the spec yet, and may be removed or changed at any point. + /// + /// Whether the agent supports `session/fork`. + #[cfg(feature = "unstable_session_fork")] + #[serde(skip_serializing_if = "Option::is_none")] + pub fork: Option, /// Extension point for implementations #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] pub meta: Option, @@ -1446,6 +1569,14 @@ impl SessionCapabilities { self } + #[cfg(feature = "unstable_session_fork")] + /// Whether the agent supports `session/fork`. + #[must_use] + pub fn fork(mut self, fork: SessionForkCapabilities) -> Self { + self.fork = Some(fork); + self + } + /// Extension point for implementations #[must_use] pub fn meta(mut self, meta: serde_json::Value) -> Self { @@ -1482,6 +1613,37 @@ impl SessionListCapabilities { } } +/// **UNSTABLE** +/// +/// This capability is not part of the spec yet, and may be removed or changed at any point. +/// +/// Capabilities for the `session/fork` method. +/// +/// By supplying `{}` it means that the agent supports forking of sessions. +#[cfg(feature = "unstable_session_fork")] +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[non_exhaustive] +pub struct SessionForkCapabilities { + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + +#[cfg(feature = "unstable_session_fork")] +impl SessionForkCapabilities { + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Extension point for implementations + #[must_use] + pub fn meta(mut self, meta: serde_json::Value) -> Self { + self.meta = Some(meta); + self + } +} + /// Prompt capabilities supported by the agent in `session/prompt` requests. /// /// Baseline agent functionality requires support for [`ContentBlock::Text`] @@ -1625,6 +1787,9 @@ pub struct AgentMethodNames { /// Method for listing existing sessions. #[cfg(feature = "unstable_session_list")] pub session_list: &'static str, + /// Method for forking an existing session. + #[cfg(feature = "unstable_session_fork")] + pub session_fork: &'static str, } /// Constant containing all agent method names. @@ -1640,6 +1805,8 @@ pub const AGENT_METHOD_NAMES: AgentMethodNames = AgentMethodNames { session_set_model: SESSION_SET_MODEL_METHOD_NAME, #[cfg(feature = "unstable_session_list")] session_list: SESSION_LIST_METHOD_NAME, + #[cfg(feature = "unstable_session_fork")] + session_fork: SESSION_FORK_METHOD_NAME, }; /// Method name for the initialize request. @@ -1662,6 +1829,9 @@ pub(crate) const SESSION_SET_MODEL_METHOD_NAME: &str = "session/set_model"; /// Method name for listing existing sessions. #[cfg(feature = "unstable_session_list")] pub(crate) const SESSION_LIST_METHOD_NAME: &str = "session/list"; +/// Method name for forking an existing session. +#[cfg(feature = "unstable_session_fork")] +pub(crate) const SESSION_FORK_METHOD_NAME: &str = "session/fork"; /// All possible requests that a client can send to an agent. /// @@ -1730,6 +1900,19 @@ pub enum ClientRequest { /// /// The agent should return metadata about sessions with optional filtering and pagination support. ListSessionsRequest(ListSessionsRequest), + #[cfg(feature = "unstable_session_fork")] + /// **UNSTABLE** + /// + /// This capability is not part of the spec yet, and may be removed or changed at any point. + /// + /// Forks an existing session to create a new independent session. + /// + /// This method is only available if the agent advertises the `session.fork` capability. + /// + /// The agent should create a new session with the same conversation context as the + /// original, allowing operations like generating summaries without affecting the + /// original session's history. + ForkSessionRequest(ForkSessionRequest), /// Sets the current mode for a session. /// /// Allows switching between different agent modes (e.g., "ask", "architect", "code") @@ -1783,6 +1966,8 @@ impl ClientRequest { Self::LoadSessionRequest(_) => AGENT_METHOD_NAMES.session_load, #[cfg(feature = "unstable_session_list")] Self::ListSessionsRequest(_) => AGENT_METHOD_NAMES.session_list, + #[cfg(feature = "unstable_session_fork")] + Self::ForkSessionRequest(_) => AGENT_METHOD_NAMES.session_fork, Self::SetSessionModeRequest(_) => AGENT_METHOD_NAMES.session_set_mode, Self::PromptRequest(_) => AGENT_METHOD_NAMES.session_prompt, #[cfg(feature = "unstable_session_model")] @@ -1809,6 +1994,8 @@ pub enum AgentResponse { LoadSessionResponse(#[serde(default)] LoadSessionResponse), #[cfg(feature = "unstable_session_list")] ListSessionsResponse(ListSessionsResponse), + #[cfg(feature = "unstable_session_fork")] + ForkSessionResponse(ForkSessionResponse), SetSessionModeResponse(#[serde(default)] SetSessionModeResponse), PromptResponse(PromptResponse), #[cfg(feature = "unstable_session_model")] diff --git a/rust/bin/generate.rs b/rust/bin/generate.rs index 24340f0..f885ec7 100644 --- a/rust/bin/generate.rs +++ b/rust/bin/generate.rs @@ -787,6 +787,7 @@ and control access to resources." "session/new" => self.agent_methods.get("NewSessionRequest").unwrap(), "session/load" => self.agent_methods.get("LoadSessionRequest").unwrap(), "session/list" => self.agent_methods.get("ListSessionsRequest").unwrap(), + "session/fork" => self.agent_methods.get("ForkSessionRequest").unwrap(), "session/set_mode" => self.agent_methods.get("SetSessionModeRequest").unwrap(), "session/prompt" => self.agent_methods.get("PromptRequest").unwrap(), "session/cancel" => self.agent_methods.get("CancelNotification").unwrap(), diff --git a/rust/rpc.rs b/rust/rpc.rs index d5fa971..736425e 100644 --- a/rust/rpc.rs +++ b/rust/rpc.rs @@ -253,6 +253,10 @@ impl Side for AgentSide { m if m == AGENT_METHOD_NAMES.session_list => serde_json::from_str(params.get()) .map(ClientRequest::ListSessionsRequest) .map_err(Into::into), + #[cfg(feature = "unstable_session_fork")] + m if m == AGENT_METHOD_NAMES.session_fork => serde_json::from_str(params.get()) + .map(ClientRequest::ForkSessionRequest) + .map_err(Into::into), m if m == AGENT_METHOD_NAMES.session_set_mode => serde_json::from_str(params.get()) .map(ClientRequest::SetSessionModeRequest) .map_err(Into::into),