Skip to content

Commit 59b7d1e

Browse files
ignatovSergey Ignatovbenbrandt
authored
feat(unstable): add SessionInfoUpdate to SessionUpdate enum (#334)
* feat: add SessionInfoUpdate to SessionUpdate enum Add a new session_info_update variant to the SessionUpdate discriminated union, allowing agents to notify clients about session metadata changes (title, timestamps, custom metadata) in real-time. * Put behind feature flag * Use maybeundefined --------- Co-authored-by: Sergey Ignatov <[email protected]> Co-authored-by: Ben Brandt <[email protected]>
1 parent bf47576 commit 59b7d1e

File tree

4 files changed

+221
-3
lines changed

4 files changed

+221
-3
lines changed

Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ categories = ["development-tools", "api-bindings"]
1414
include = ["/src/**/*.rs", "/README.md", "/LICENSE", "/Cargo.toml"]
1515

1616
[features]
17-
unstable = ["unstable_session_model", "unstable_session_list", "unstable_session_fork", "unstable_session_resume", "unstable_cancel_request"]
17+
unstable = [
18+
"unstable_cancel_request",
19+
"unstable_session_fork",
20+
"unstable_session_info_update",
21+
"unstable_session_list",
22+
"unstable_session_model",
23+
"unstable_session_resume",
24+
]
1825
unstable_cancel_request = []
19-
unstable_session_model = []
20-
unstable_session_list = []
2126
unstable_session_fork = []
27+
unstable_session_info_update = []
28+
unstable_session_list = []
29+
unstable_session_model = []
2230
unstable_session_resume = []
2331

2432
[[bin]]

docs/protocol/draft/schema.mdx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2926,6 +2926,32 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte
29262926
ISO 8601 timestamp of last activity
29272927
</ResponseField>
29282928

2929+
## <span class="font-mono">SessionInfoUpdate</span>
2930+
2931+
Update to session metadata. All fields are optional to support partial updates.
2932+
2933+
Agents send this notification to update session information like title or custom metadata.
2934+
This allows clients to display dynamic session names and track session state changes.
2935+
2936+
**Type:** Object
2937+
2938+
**Properties:**
2939+
2940+
<ResponseField name="_meta" type={"object | null"} >
2941+
The _meta property is reserved by ACP to allow clients and agents to attach additional
2942+
metadata to their interactions. Implementations MUST NOT make assumptions about values at
2943+
these keys.
2944+
2945+
See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
2946+
2947+
</ResponseField>
2948+
<ResponseField name="title" type={"string | null"} >
2949+
Human-readable title for the session. Set to null to clear.
2950+
</ResponseField>
2951+
<ResponseField name="updatedAt" type={"string | null"} >
2952+
ISO 8601 timestamp of last activity. Set to null to clear.
2953+
</ResponseField>
2954+
29292955
## <span class="font-mono">SessionListCapabilities</span>
29302956

29312957
Capabilities for the `session/list` method.
@@ -3288,6 +3314,31 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte
32883314
</Expandable>
32893315
</ResponseField>
32903316

3317+
<ResponseField name="session_info_update" type="object">
3318+
Session metadata has been updated (title, timestamps, custom metadata)
3319+
3320+
<Expandable title="Properties">
3321+
3322+
<ResponseField name="_meta" type={"object | null"} >
3323+
The _meta property is reserved by ACP to allow clients and agents to attach additional
3324+
metadata to their interactions. Implementations MUST NOT make assumptions about values at
3325+
these keys.
3326+
3327+
See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
3328+
3329+
</ResponseField>
3330+
<ResponseField name="sessionUpdate" type={"string"} required>
3331+
</ResponseField>
3332+
<ResponseField name="title" type={"string | null"} >
3333+
Human-readable title for the session. Set to null to clear.
3334+
</ResponseField>
3335+
<ResponseField name="updatedAt" type={"string | null"} >
3336+
ISO 8601 timestamp of last activity. Set to null to clear.
3337+
</ResponseField>
3338+
3339+
</Expandable>
3340+
</ResponseField>
3341+
32913342
## <span class="font-mono">StopReason</span>
32923343

32933344
Reasons why an agent stops processing a prompt turn.

schema/schema.unstable.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2460,6 +2460,25 @@
24602460
"required": ["sessionId", "cwd"],
24612461
"type": "object"
24622462
},
2463+
"SessionInfoUpdate": {
2464+
"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.",
2465+
"properties": {
2466+
"_meta": {
2467+
"additionalProperties": true,
2468+
"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)",
2469+
"type": ["object", "null"]
2470+
},
2471+
"title": {
2472+
"description": "Human-readable title for the session. Set to null to clear.",
2473+
"type": ["string", "null"]
2474+
},
2475+
"updatedAt": {
2476+
"description": "ISO 8601 timestamp of last activity. Set to null to clear.",
2477+
"type": ["string", "null"]
2478+
}
2479+
},
2480+
"type": "object"
2481+
},
24632482
"SessionListCapabilities": {
24642483
"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.",
24652484
"properties": {
@@ -2724,6 +2743,22 @@
27242743
},
27252744
"required": ["sessionUpdate"],
27262745
"type": "object"
2746+
},
2747+
{
2748+
"allOf": [
2749+
{
2750+
"$ref": "#/$defs/SessionInfoUpdate"
2751+
}
2752+
],
2753+
"description": "Session metadata has been updated (title, timestamps, custom metadata)",
2754+
"properties": {
2755+
"sessionUpdate": {
2756+
"const": "session_info_update",
2757+
"type": "string"
2758+
}
2759+
},
2760+
"required": ["sessionUpdate"],
2761+
"type": "object"
27272762
}
27282763
]
27292764
},

src/client.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use crate::{
1313
ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, Plan, SessionId,
1414
SessionModeId, ToolCall, ToolCallUpdate,
1515
};
16+
#[cfg(feature = "unstable_session_info_update")]
17+
use crate::{IntoMaybeUndefined, MaybeUndefined};
1618

1719
// Session updates
1820

@@ -90,6 +92,9 @@ pub enum SessionUpdate {
9092
///
9193
/// See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)
9294
CurrentModeUpdate(CurrentModeUpdate),
95+
#[cfg(feature = "unstable_session_info_update")]
96+
/// Session metadata has been updated (title, timestamps, custom metadata)
97+
SessionInfoUpdate(SessionInfoUpdate),
9398
}
9499

95100
/// The current mode of the session has changed
@@ -131,6 +136,63 @@ impl CurrentModeUpdate {
131136
}
132137
}
133138

139+
/// Update to session metadata. All fields are optional to support partial updates.
140+
///
141+
/// Agents send this notification to update session information like title or custom metadata.
142+
/// This allows clients to display dynamic session names and track session state changes.
143+
#[cfg(feature = "unstable_session_info_update")]
144+
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
145+
#[serde(rename_all = "camelCase")]
146+
#[non_exhaustive]
147+
pub struct SessionInfoUpdate {
148+
/// Human-readable title for the session. Set to null to clear.
149+
#[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
150+
pub title: MaybeUndefined<String>,
151+
/// ISO 8601 timestamp of last activity. Set to null to clear.
152+
#[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
153+
pub updated_at: MaybeUndefined<String>,
154+
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
155+
/// metadata to their interactions. Implementations MUST NOT make assumptions about values at
156+
/// these keys.
157+
///
158+
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
159+
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
160+
pub meta: Option<Meta>,
161+
}
162+
163+
#[cfg(feature = "unstable_session_info_update")]
164+
impl SessionInfoUpdate {
165+
#[must_use]
166+
pub fn new() -> Self {
167+
Self::default()
168+
}
169+
170+
/// Human-readable title for the session. Set to null to clear.
171+
#[must_use]
172+
pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
173+
self.title = title.into_maybe_undefined();
174+
self
175+
}
176+
177+
/// ISO 8601 timestamp of last activity. Set to null to clear.
178+
#[must_use]
179+
pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
180+
self.updated_at = updated_at.into_maybe_undefined();
181+
self
182+
}
183+
184+
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
185+
/// metadata to their interactions. Implementations MUST NOT make assumptions about values at
186+
/// these keys.
187+
///
188+
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
189+
#[must_use]
190+
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
191+
self.meta = meta.into_option();
192+
self
193+
}
194+
}
195+
134196
/// A streamed item of content
135197
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
136198
#[serde(rename_all = "camelCase")]
@@ -1587,3 +1649,65 @@ impl AgentNotification {
15871649
}
15881650
}
15891651
}
1652+
1653+
#[cfg(test)]
1654+
mod tests {
1655+
use super::*;
1656+
1657+
#[cfg(feature = "unstable_session_info_update")]
1658+
#[test]
1659+
fn test_serialization_behavior() {
1660+
use serde_json::json;
1661+
1662+
assert_eq!(
1663+
serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
1664+
SessionInfoUpdate {
1665+
title: MaybeUndefined::Undefined,
1666+
updated_at: MaybeUndefined::Undefined,
1667+
meta: None
1668+
}
1669+
);
1670+
assert_eq!(
1671+
serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
1672+
.unwrap(),
1673+
SessionInfoUpdate {
1674+
title: MaybeUndefined::Null,
1675+
updated_at: MaybeUndefined::Null,
1676+
meta: None
1677+
}
1678+
);
1679+
assert_eq!(
1680+
serde_json::from_value::<SessionInfoUpdate>(
1681+
json!({"title": "title", "updatedAt": "timestamp"})
1682+
)
1683+
.unwrap(),
1684+
SessionInfoUpdate {
1685+
title: MaybeUndefined::Value("title".to_string()),
1686+
updated_at: MaybeUndefined::Value("timestamp".to_string()),
1687+
meta: None
1688+
}
1689+
);
1690+
1691+
assert_eq!(
1692+
serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
1693+
json!({})
1694+
);
1695+
assert_eq!(
1696+
serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
1697+
json!({"title": "title"})
1698+
);
1699+
assert_eq!(
1700+
serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
1701+
json!({"title": null})
1702+
);
1703+
assert_eq!(
1704+
serde_json::to_value(
1705+
SessionInfoUpdate::new()
1706+
.title("title")
1707+
.title(MaybeUndefined::Undefined)
1708+
)
1709+
.unwrap(),
1710+
json!({})
1711+
);
1712+
}
1713+
}

0 commit comments

Comments
 (0)