diff --git a/Cargo.toml b/Cargo.toml index cd69068..70dd25c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,19 @@ categories = ["development-tools", "api-bindings"] include = ["/src/**/*.rs", "/README.md", "/LICENSE", "/Cargo.toml"] [features] -unstable = ["unstable_session_model", "unstable_session_list", "unstable_session_fork", "unstable_session_resume", "unstable_cancel_request"] +unstable = [ + "unstable_cancel_request", + "unstable_session_fork", + "unstable_session_info_update", + "unstable_session_list", + "unstable_session_model", + "unstable_session_resume", +] unstable_cancel_request = [] -unstable_session_model = [] -unstable_session_list = [] unstable_session_fork = [] +unstable_session_info_update = [] +unstable_session_list = [] +unstable_session_model = [] unstable_session_resume = [] [[bin]] diff --git a/docs/protocol/draft/schema.mdx b/docs/protocol/draft/schema.mdx index de6839d..b3d4195 100644 --- a/docs/protocol/draft/schema.mdx +++ b/docs/protocol/draft/schema.mdx @@ -2926,6 +2926,32 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte ISO 8601 timestamp of last activity +## SessionInfoUpdate + +Update to session metadata. All fields are optional to support partial updates. + +Agents send this notification to update session information like title or custom metadata. +This allows clients to display dynamic session names and track session state changes. + +**Type:** Object + +**Properties:** + + + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Implementations MUST NOT make assumptions about values at +these keys. + +See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + + + + Human-readable title for the session. Set to null to clear. + + + ISO 8601 timestamp of last activity. Set to null to clear. + + ## SessionListCapabilities Capabilities for the `session/list` method. @@ -3288,6 +3314,31 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte + +Session metadata has been updated (title, timestamps, custom metadata) + + + + + The _meta property is reserved by ACP to allow clients and agents to attach additional +metadata to their interactions. Implementations MUST NOT make assumptions about values at +these keys. + +See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + + + + + + Human-readable title for the session. Set to null to clear. + + + ISO 8601 timestamp of last activity. Set to null to clear. + + + + + ## StopReason Reasons why an agent stops processing a prompt turn. diff --git a/schema/schema.unstable.json b/schema/schema.unstable.json index 0c97d63..3acf713 100644 --- a/schema/schema.unstable.json +++ b/schema/schema.unstable.json @@ -2460,6 +2460,25 @@ "required": ["sessionId", "cwd"], "type": "object" }, + "SessionInfoUpdate": { + "description": "Update to session metadata. All fields are optional to support partial updates.\n\nAgents send this notification to update session information like title or custom metadata.\nThis allows clients to display dynamic session names and track session state changes.", + "properties": { + "_meta": { + "additionalProperties": true, + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", + "type": ["object", "null"] + }, + "title": { + "description": "Human-readable title for the session. Set to null to clear.", + "type": ["string", "null"] + }, + "updatedAt": { + "description": "ISO 8601 timestamp of last activity. Set to null to clear.", + "type": ["string", "null"] + } + }, + "type": "object" + }, "SessionListCapabilities": { "description": "Capabilities for the `session/list` method.\n\nBy supplying `{}` it means that the agent supports listing of sessions.\n\nFurther capabilities can be added in the future for other means of filtering or searching the list.", "properties": { @@ -2724,6 +2743,22 @@ }, "required": ["sessionUpdate"], "type": "object" + }, + { + "allOf": [ + { + "$ref": "#/$defs/SessionInfoUpdate" + } + ], + "description": "Session metadata has been updated (title, timestamps, custom metadata)", + "properties": { + "sessionUpdate": { + "const": "session_info_update", + "type": "string" + } + }, + "required": ["sessionUpdate"], + "type": "object" } ] }, diff --git a/src/client.rs b/src/client.rs index bcfdd03..274c990 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,6 +13,8 @@ use crate::{ ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, Plan, SessionId, SessionModeId, ToolCall, ToolCallUpdate, }; +#[cfg(feature = "unstable_session_info_update")] +use crate::{IntoMaybeUndefined, MaybeUndefined}; // Session updates @@ -90,6 +92,9 @@ pub enum SessionUpdate { /// /// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) CurrentModeUpdate(CurrentModeUpdate), + #[cfg(feature = "unstable_session_info_update")] + /// Session metadata has been updated (title, timestamps, custom metadata) + SessionInfoUpdate(SessionInfoUpdate), } /// The current mode of the session has changed @@ -131,6 +136,63 @@ impl CurrentModeUpdate { } } +/// Update to session metadata. All fields are optional to support partial updates. +/// +/// Agents send this notification to update session information like title or custom metadata. +/// This allows clients to display dynamic session names and track session state changes. +#[cfg(feature = "unstable_session_info_update")] +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct SessionInfoUpdate { + /// Human-readable title for the session. Set to null to clear. + #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] + pub title: MaybeUndefined, + /// ISO 8601 timestamp of last activity. Set to null to clear. + #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")] + pub updated_at: MaybeUndefined, + /// The _meta property is reserved by ACP to allow clients and agents to attach additional + /// metadata to their interactions. Implementations MUST NOT make assumptions about values at + /// these keys. + /// + /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + +#[cfg(feature = "unstable_session_info_update")] +impl SessionInfoUpdate { + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Human-readable title for the session. Set to null to clear. + #[must_use] + pub fn title(mut self, title: impl IntoMaybeUndefined) -> Self { + self.title = title.into_maybe_undefined(); + self + } + + /// ISO 8601 timestamp of last activity. Set to null to clear. + #[must_use] + pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined) -> Self { + self.updated_at = updated_at.into_maybe_undefined(); + self + } + + /// The _meta property is reserved by ACP to allow clients and agents to attach additional + /// metadata to their interactions. Implementations MUST NOT make assumptions about values at + /// these keys. + /// + /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility) + #[must_use] + pub fn meta(mut self, meta: impl IntoOption) -> Self { + self.meta = meta.into_option(); + self + } +} + /// A streamed item of content #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] @@ -1587,3 +1649,65 @@ impl AgentNotification { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "unstable_session_info_update")] + #[test] + fn test_serialization_behavior() { + use serde_json::json; + + assert_eq!( + serde_json::from_value::(json!({})).unwrap(), + SessionInfoUpdate { + title: MaybeUndefined::Undefined, + updated_at: MaybeUndefined::Undefined, + meta: None + } + ); + assert_eq!( + serde_json::from_value::(json!({"title": null, "updatedAt": null})) + .unwrap(), + SessionInfoUpdate { + title: MaybeUndefined::Null, + updated_at: MaybeUndefined::Null, + meta: None + } + ); + assert_eq!( + serde_json::from_value::( + json!({"title": "title", "updatedAt": "timestamp"}) + ) + .unwrap(), + SessionInfoUpdate { + title: MaybeUndefined::Value("title".to_string()), + updated_at: MaybeUndefined::Value("timestamp".to_string()), + meta: None + } + ); + + assert_eq!( + serde_json::to_value(SessionInfoUpdate::new()).unwrap(), + json!({}) + ); + assert_eq!( + serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(), + json!({"title": "title"}) + ); + assert_eq!( + serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(), + json!({"title": null}) + ); + assert_eq!( + serde_json::to_value( + SessionInfoUpdate::new() + .title("title") + .title(MaybeUndefined::Undefined) + ) + .unwrap(), + json!({}) + ); + } +}