From eb3b2badad3bfd5d5c641438c93d4d40968679bb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Jul 2026 20:04:19 -0700 Subject: [PATCH 1/6] types: pre-1.0 surface corrections (typo, narrowing, optional chat title) Three small, independent corrections to the protocol surface. Each is source- or wire-breaking and so has to land before the 1.0 freeze, but none involves a design choice -- they align the declared types with what the docs already promise and what both reference implementations already do. 1. Rename `ResourceReponsePart` -> `ResourceResponsePart` The interface name misspelled "Response". The Rust and Go generators already carried per-language rename entries to correct it on output, while the TypeScript, Kotlin and Swift clients shipped the typo verbatim -- so a single protocol type had two different names across the five clients. Fix the canonical spelling and delete the two now redundant generator renames. 2. Narrow `SessionToolClientExecutionRequest.toolCall` Declared as the full `ToolCallState` union while its own doc comment said the host "only ever populates this with a ToolCallRunningState". Its two sibling variants are already narrowed (`SessionToolConfirmationRequest` -> `ToolCallConfirmationState`, `SessionToolAuthenticationRequest` -> `ToolCallAuthRequiredState`), so this was the odd one out. Narrow it to `ToolCallRunningState` and state the invariant in the type rather than in prose. 3. Make `ChatState.title` / `ChatSummary.title` optional Both were required `string`, but a session's default chat generally has no identity separate from its session. VS Code was papering over this by writing an empty string as an internal sentinel meaning "show the session title instead" -- a convention the protocol never defined. Make the field optional, specify that absence means "inherit the session's title", and explicitly forbid the empty-string sentinel so a deliberately blank title stays distinguishable from an absent one. No behavioural or reducer changes; `npm run generate` output is limited to these three shapes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- clients/go/ahptypes/state.generated.go | 26 ++++--- .../generated/Actions.generated.kt | 4 +- .../generated/State.generated.kt | 34 ++++++---- clients/rust/crates/ahp-types/src/actions.rs | 4 +- clients/rust/crates/ahp-types/src/state.rs | 28 +++++--- .../Generated/Actions.generated.swift | 4 +- .../Generated/State.generated.swift | 38 +++++++---- .../20260730-chat-title-optional.json | 4 ++ ...0260730-resource-response-part-rename.json | 4 ++ ...ession-tool-client-execution-narrowed.json | 4 ++ docs/specification/chat-channel.md | 6 ++ schema/actions.schema.json | 16 ++--- schema/commands.schema.json | 68 +++++++++---------- schema/errors.schema.json | 68 +++++++++---------- schema/notifications.schema.json | 66 +++++++++--------- schema/state.schema.json | 14 ++-- scripts/generate-go.ts | 2 +- scripts/generate-kotlin.ts | 4 +- scripts/generate-rust.ts | 2 +- scripts/generate-swift.ts | 4 +- types/channels-chat/state.ts | 24 +++++-- types/channels-session/state.ts | 11 +-- 22 files changed, 253 insertions(+), 182 deletions(-) create mode 100644 docs/.changes/20260730-chat-title-optional.json create mode 100644 docs/.changes/20260730-resource-response-part-rename.json create mode 100644 docs/.changes/20260730-session-tool-client-execution-narrowed.json diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index e0618669..622ac81e 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -937,10 +937,12 @@ type SessionToolClientExecutionRequest struct { // The `clientId` expected to execute the tool. Matches the `clientId` of the // tool call's client {@link ToolCallContributor}. ClientId string `json:"clientId"` - // The running tool call the session wants the owning client to execute. The - // host only ever populates this with a {@link ToolCallRunningState} (i.e. a - // {@link ToolCallState} in `running` status). - ToolCall ToolCallState `json:"toolCall"` + // The running tool call the session wants the owning client to execute. + // Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in + // `running` status), matching the narrowed `toolCall` on the sibling + // {@link SessionToolConfirmationRequest} and + // {@link SessionToolAuthenticationRequest} variants. + ToolCall ToolCallRunningState `json:"toolCall"` } // A tool call blocked on MCP authentication mid-execution, surfaced at the @@ -1076,8 +1078,14 @@ type ChangesSummary struct { type ChatState struct { // Chat URI Resource URI `json:"resource"` - // Chat title - Title string `json:"title"` + // Chat title. + // + // Absent means the chat has no title of its own and consumers SHOULD fall + // back to the owning {@link SessionState.title | session's title}. This is + // the normal case for a session's default chat, which typically has no + // identity separate from the session itself. Producers MUST NOT use an empty + // string to mean "inherit" — omit the field instead. + Title *string `json:"title,omitempty"` // Current chat status (reuses SessionStatus shape) Status SessionStatus `json:"status"` // Human-readable description of what the chat is currently doing @@ -1141,8 +1149,10 @@ type ChatState struct { type ChatSummary struct { // Chat URI Resource URI `json:"resource"` - // Chat title - Title string `json:"title"` + // Chat title. Absent means the chat has no title of its own and consumers + // SHOULD fall back to the owning session's title — see + // {@link ChatState.title} for the full semantics. + Title *string `json:"title,omitempty"` // Current chat status (reuses SessionStatus shape) Status SessionStatus `json:"status"` // Human-readable description of what the chat is currently doing diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt index f696130b..8cd60744 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt @@ -1489,7 +1489,9 @@ data class PartialChatSummary( */ val resource: String? = null, /** - * Chat title + * Chat title. Absent means the chat has no title of its own and consumers + * SHOULD fall back to the owning session's title — see + * {@link ChatState.title} for the full semantics. */ val title: String? = null, /** diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index 840fe331..8671a3ce 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -1165,9 +1165,15 @@ data class ChatState( */ val resource: String, /** - * Chat title + * Chat title. + * + * Absent means the chat has no title of its own and consumers SHOULD fall + * back to the owning {@link SessionState.title | session's title}. This is + * the normal case for a session's default chat, which typically has no + * identity separate from the session itself. Producers MUST NOT use an empty + * string to mean "inherit" — omit the field instead. */ - val title: String, + val title: String? = null, /** * Current chat status (reuses SessionStatus shape) */ @@ -1259,9 +1265,11 @@ data class ChatSummary( */ val resource: String, /** - * Chat title + * Chat title. Absent means the chat has no title of its own and consumers + * SHOULD fall back to the owning session's title — see + * {@link ChatState.title} for the full semantics. */ - val title: String, + val title: String? = null, /** * Current chat status (reuses SessionStatus shape) */ @@ -1551,11 +1559,13 @@ data class SessionToolClientExecutionRequest( */ val clientId: String, /** - * The running tool call the session wants the owning client to execute. The - * host only ever populates this with a {@link ToolCallRunningState} (i.e. a - * {@link ToolCallState} in `running` status). + * The running tool call the session wants the owning client to execute. + * Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in + * `running` status), matching the narrowed `toolCall` on the sibling + * {@link SessionToolConfirmationRequest} and + * {@link SessionToolAuthenticationRequest} variants. */ - val toolCall: ToolCallState + val toolCall: ToolCallRunningState ) @Serializable @@ -2416,7 +2426,7 @@ data class ContentRef( ) @Serializable -data class ResourceReponsePart( +data class ResourceResponsePart( /** * Content URI */ @@ -4702,7 +4712,7 @@ sealed interface ResponsePart @JvmInline value class ResponsePartMarkdown(val value: MarkdownResponsePart) : ResponsePart @JvmInline -value class ResponsePartContentRef(val value: ResourceReponsePart) : ResponsePart +value class ResponsePartContentRef(val value: ResourceResponsePart) : ResponsePart @JvmInline value class ResponsePartToolCall(val value: ToolCallResponsePart) : ResponsePart @JvmInline @@ -4736,7 +4746,7 @@ internal object ResponsePartSerializer : KSerializer { ?: return ResponsePartUnknown(obj) return when (discriminant) { "markdown" -> ResponsePartMarkdown(input.json.decodeFromJsonElement(MarkdownResponsePart.serializer(), element)) - "contentRef" -> ResponsePartContentRef(input.json.decodeFromJsonElement(ResourceReponsePart.serializer(), element)) + "contentRef" -> ResponsePartContentRef(input.json.decodeFromJsonElement(ResourceResponsePart.serializer(), element)) "toolCall" -> ResponsePartToolCall(input.json.decodeFromJsonElement(ToolCallResponsePart.serializer(), element)) "reasoning" -> ResponsePartReasoning(input.json.decodeFromJsonElement(ReasoningResponsePart.serializer(), element)) "systemNotification" -> ResponsePartSystemNotification(input.json.decodeFromJsonElement(SystemNotificationResponsePart.serializer(), element)) @@ -4750,7 +4760,7 @@ internal object ResponsePartSerializer : KSerializer { ?: error("ResponsePart can only be serialized to JSON") val element: JsonElement = when (value) { is ResponsePartMarkdown -> output.json.encodeToJsonElement(MarkdownResponsePart.serializer(), value.value) - is ResponsePartContentRef -> output.json.encodeToJsonElement(ResourceReponsePart.serializer(), value.value) + is ResponsePartContentRef -> output.json.encodeToJsonElement(ResourceResponsePart.serializer(), value.value) is ResponsePartToolCall -> output.json.encodeToJsonElement(ToolCallResponsePart.serializer(), value.value) is ResponsePartReasoning -> output.json.encodeToJsonElement(ReasoningResponsePart.serializer(), value.value) is ResponsePartSystemNotification -> output.json.encodeToJsonElement(SystemNotificationResponsePart.serializer(), value.value) diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index 8cd2c8f4..0d4ec42c 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -1744,7 +1744,9 @@ pub struct PartialChatSummary { /// Chat URI #[serde(default, skip_serializing_if = "Option::is_none")] pub resource: Option, - /// Chat title + /// Chat title. Absent means the chat has no title of its own and consumers + /// SHOULD fall back to the owning session's title — see + /// {@link ChatState.title} for the full semantics. #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, /// Current chat status (reuses SessionStatus shape) diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index bd978751..48cae0d1 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -1009,8 +1009,15 @@ pub struct PendingMessage { pub struct ChatState { /// Chat URI pub resource: Uri, - /// Chat title - pub title: String, + /// Chat title. + /// + /// Absent means the chat has no title of its own and consumers SHOULD fall + /// back to the owning {@link SessionState.title | session's title}. This is + /// the normal case for a session's default chat, which typically has no + /// identity separate from the session itself. Producers MUST NOT use an empty + /// string to mean "inherit" — omit the field instead. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, /// Current chat status (reuses SessionStatus shape) pub status: u32, /// Human-readable description of what the chat is currently doing @@ -1086,8 +1093,11 @@ pub struct ChatState { pub struct ChatSummary { /// Chat URI pub resource: Uri, - /// Chat title - pub title: String, + /// Chat title. Absent means the chat has no title of its own and consumers + /// SHOULD fall back to the owning session's title — see + /// {@link ChatState.title} for the full semantics. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, /// Current chat status (reuses SessionStatus shape) pub status: u32, /// Human-readable description of what the chat is currently doing @@ -1357,10 +1367,12 @@ pub struct SessionToolClientExecutionRequest { /// The `clientId` expected to execute the tool. Matches the `clientId` of the /// tool call's client {@link ToolCallContributor}. pub client_id: String, - /// The running tool call the session wants the owning client to execute. The - /// host only ever populates this with a {@link ToolCallRunningState} (i.e. a - /// {@link ToolCallState} in `running` status). - pub tool_call: ToolCallState, + /// The running tool call the session wants the owning client to execute. + /// Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in + /// `running` status), matching the narrowed `toolCall` on the sibling + /// {@link SessionToolConfirmationRequest} and + /// {@link SessionToolAuthenticationRequest} variants. + pub tool_call: ToolCallRunningState, } /// A tool call blocked on MCP authentication mid-execution, surfaced at the diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 809dd13b..8f07cf19 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -1972,7 +1972,9 @@ public struct ResourceWatchChangedAction: Codable, Sendable { public struct PartialChatSummary: Codable, Sendable { /// Chat URI public var resource: String? - /// Chat title + /// Chat title. Absent means the chat has no title of its own and consumers + /// SHOULD fall back to the owning session's title — see + /// {@link ChatState.title} for the full semantics. public var title: String? /// Current chat status (reuses SessionStatus shape) public var status: SessionStatus? diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 515f9c4a..75fdcd33 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -892,8 +892,14 @@ public struct PendingMessage: Codable, Sendable { public struct ChatState: Codable, Sendable { /// Chat URI public var resource: String - /// Chat title - public var title: String + /// Chat title. + /// + /// Absent means the chat has no title of its own and consumers SHOULD fall + /// back to the owning {@link SessionState.title | session's title}. This is + /// the normal case for a session's default chat, which typically has no + /// identity separate from the session itself. Producers MUST NOT use an empty + /// string to mean "inherit" — omit the field instead. + public var title: String? /// Current chat status (reuses SessionStatus shape) public var status: SessionStatus /// Human-readable description of what the chat is currently doing @@ -970,7 +976,7 @@ public struct ChatState: Codable, Sendable { public init( resource: String, - title: String, + title: String? = nil, status: SessionStatus, activity: String? = nil, modifiedAt: String, @@ -1006,8 +1012,10 @@ public struct ChatState: Codable, Sendable { public struct ChatSummary: Codable, Sendable { /// Chat URI public var resource: String - /// Chat title - public var title: String + /// Chat title. Absent means the chat has no title of its own and consumers + /// SHOULD fall back to the owning session's title — see + /// {@link ChatState.title} for the full semantics. + public var title: String? /// Current chat status (reuses SessionStatus shape) public var status: SessionStatus /// Human-readable description of what the chat is currently doing @@ -1028,7 +1036,7 @@ public struct ChatSummary: Codable, Sendable { public init( resource: String, - title: String, + title: String? = nil, status: SessionStatus, activity: String? = nil, modifiedAt: String, @@ -1334,10 +1342,12 @@ public struct SessionToolClientExecutionRequest: Codable, Sendable { /// The `clientId` expected to execute the tool. Matches the `clientId` of the /// tool call's client {@link ToolCallContributor}. public var clientId: String - /// The running tool call the session wants the owning client to execute. The - /// host only ever populates this with a {@link ToolCallRunningState} (i.e. a - /// {@link ToolCallState} in `running` status). - public var toolCall: ToolCallState + /// The running tool call the session wants the owning client to execute. + /// Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in + /// `running` status), matching the narrowed `toolCall` on the sibling + /// {@link SessionToolConfirmationRequest} and + /// {@link SessionToolAuthenticationRequest} variants. + public var toolCall: ToolCallRunningState public init( id: String, @@ -1345,7 +1355,7 @@ public struct SessionToolClientExecutionRequest: Codable, Sendable { kind: SessionInputRequestKind, turnId: String, clientId: String, - toolCall: ToolCallState + toolCall: ToolCallRunningState ) { self.id = id self.chat = chat @@ -2414,7 +2424,7 @@ public struct ContentRef: Codable, Sendable { } } -public struct ResourceReponsePart: Codable, Sendable { +public struct ResourceResponsePart: Codable, Sendable { /// Content URI public var uri: String /// Approximate size in bytes @@ -5275,7 +5285,7 @@ public enum ChatOrigin: Codable, Sendable { public enum ResponsePart: Codable, Sendable { case markdown(MarkdownResponsePart) - case contentRef(ResourceReponsePart) + case contentRef(ResourceResponsePart) case toolCall(ToolCallResponsePart) case reasoning(ReasoningResponsePart) case systemNotification(SystemNotificationResponsePart) @@ -5295,7 +5305,7 @@ public enum ResponsePart: Codable, Sendable { case "markdown": self = .markdown(try MarkdownResponsePart(from: decoder)) case "contentRef": - self = .contentRef(try ResourceReponsePart(from: decoder)) + self = .contentRef(try ResourceResponsePart(from: decoder)) case "toolCall": self = .toolCall(try ToolCallResponsePart(from: decoder)) case "reasoning": diff --git a/docs/.changes/20260730-chat-title-optional.json b/docs/.changes/20260730-chat-title-optional.json new file mode 100644 index 00000000..c1f2d400 --- /dev/null +++ b/docs/.changes/20260730-chat-title-optional.json @@ -0,0 +1,4 @@ +{ + "type": "changed", + "message": "`ChatState.title` and `ChatSummary.title` are now optional. An absent title means the chat has no title of its own and consumers should fall back to the owning session's title — the normal case for a session's default chat. Producers must omit the field rather than sending an empty string to mean \"inherit\"." +} diff --git a/docs/.changes/20260730-resource-response-part-rename.json b/docs/.changes/20260730-resource-response-part-rename.json new file mode 100644 index 00000000..9d561559 --- /dev/null +++ b/docs/.changes/20260730-resource-response-part-rename.json @@ -0,0 +1,4 @@ +{ + "type": "changed", + "message": "Renamed `ResourceReponsePart` to `ResourceResponsePart`, correcting a misspelling. The Rust and Go clients already exposed the corrected name via generator-level renames; the TypeScript, Kotlin and Swift clients previously carried the typo, so the type now has one consistent name across all five clients." +} diff --git a/docs/.changes/20260730-session-tool-client-execution-narrowed.json b/docs/.changes/20260730-session-tool-client-execution-narrowed.json new file mode 100644 index 00000000..6e8465bc --- /dev/null +++ b/docs/.changes/20260730-session-tool-client-execution-narrowed.json @@ -0,0 +1,4 @@ +{ + "type": "changed", + "message": "`SessionToolClientExecutionRequest.toolCall` is now typed `ToolCallRunningState` instead of the full `ToolCallState` union, matching the already-narrowed `toolCall` on the sibling `SessionToolConfirmationRequest` and `SessionToolAuthenticationRequest` variants. The documented contract is unchanged — hosts only ever populated it with a running tool call." +} diff --git a/docs/specification/chat-channel.md b/docs/specification/chat-channel.md index a2c8d020..aef0c63d 100644 --- a/docs/specification/chat-channel.md +++ b/docs/specification/chat-channel.md @@ -37,6 +37,12 @@ older turn). Clients MAY periodically sync their local input state into the draft by dispatching [`chat/draftChanged`](/reference/chat#actions). Eager syncing is not required — clients SHOULD debounce and MAY sync only at convenient points (for example, on blur). When presenting input UI for an existing chat, clients SHOULD use any `draft` to initialize their input state. Dispatch `chat/draftChanged` with no `draft` to clear it once the message is sent. +### Chat title + +`ChatState.title` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat has no title of its own and consumers SHOULD fall back to the owning session's [`title`](/reference/session#sessionsummary). This is the normal case for a session's default chat, which usually has no identity separate from the session itself; hosts typically set a title only on additional chats (forks, side chats, tool-spawned workers) where distinguishing them in a list is useful. + +Producers MUST NOT use an empty string to mean "inherit" — omit the field instead, so that a deliberately blank title and an absent one remain distinguishable. + ### Per-chat working directory `ChatState.workingDirectory` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat inherits the session's [`workingDirectory`](/reference/session#sessionsummary). Hosts MAY set a per-chat working directory to give individual chats their own filesystem context — for example, allocating a separate git worktree per chat so multiple chats in the same session can make independent edits that the orchestrating chat later merges back. The session-level `workingDirectory` is then the default/primary location for chats that do not override it. diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 9a046769..b07f1615 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -211,7 +211,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -3118,8 +3118,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." + "$ref": "#/$defs/ToolCallRunningState", + "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." } }, "required": [ @@ -4487,7 +4487,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -4554,7 +4554,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt", "turns" @@ -4570,7 +4569,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -4602,7 +4601,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt" ] @@ -5483,7 +5481,7 @@ "content" ] }, - "ResourceReponsePart": { + "ResourceResponsePart": { "type": "object", "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { @@ -7384,7 +7382,7 @@ "$ref": "#/$defs/MarkdownResponsePart" }, { - "$ref": "#/$defs/ResourceReponsePart" + "$ref": "#/$defs/ResourceResponsePart" }, { "$ref": "#/$defs/ToolCallResponsePart" diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 775ee128..2b57ab5b 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -2281,8 +2281,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." + "$ref": "#/$defs/ToolCallRunningState", + "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." } }, "required": [ @@ -3650,7 +3650,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -3717,7 +3717,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt", "turns" @@ -3733,7 +3732,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -3765,7 +3764,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt" ] @@ -4646,7 +4644,7 @@ "content" ] }, - "ResourceReponsePart": { + "ResourceResponsePart": { "type": "object", "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { @@ -6451,7 +6449,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -8833,32 +8831,6 @@ ], "description": "The two tool-call states that block on a client confirmation: parameter\nconfirmation before execution ({@link ToolCallPendingConfirmationState}) and\nresult confirmation after execution\n({@link ToolCallPendingResultConfirmationState}).\n\n{@link ToolCallAuthRequiredState} is intentionally **not** part of this\nunion: it doesn't block on a `chat/toolCallConfirmed`-style client\ndecision, it blocks on the client completing an OAuth flow and calling\n`authenticate`. See {@link SessionToolAuthenticationRequest} for its\nsession-level surfacing.\n\nSurfaced at the session level by {@link SessionToolConfirmationRequest}." }, - "ToolCallState": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallStreamingState" - }, - { - "$ref": "#/$defs/ToolCallPendingConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallRunningState" - }, - { - "$ref": "#/$defs/ToolCallAuthRequiredState" - }, - { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallCompletedState" - }, - { - "$ref": "#/$defs/ToolCallCancelledState" - } - ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." - }, "CustomizationLoadState": { "oneOf": [ { @@ -9092,7 +9064,7 @@ "$ref": "#/$defs/MarkdownResponsePart" }, { - "$ref": "#/$defs/ResourceReponsePart" + "$ref": "#/$defs/ResourceResponsePart" }, { "$ref": "#/$defs/ToolCallResponsePart" @@ -9127,6 +9099,32 @@ "type": "string", "description": "Discriminant for {@link MessageOrigin} — identifies who produced a message." }, + "ToolCallState": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallRunningState" + }, + { + "$ref": "#/$defs/ToolCallAuthRequiredState" + }, + { + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallCompletedState" + }, + { + "$ref": "#/$defs/ToolCallCancelledState" + } + ], + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + }, "ChatInputResponseKind": { "enum": [ "accept", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index c770002f..ce5689ca 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -1017,8 +1017,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." + "$ref": "#/$defs/ToolCallRunningState", + "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." } }, "required": [ @@ -2386,7 +2386,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2453,7 +2453,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt", "turns" @@ -2469,7 +2468,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2501,7 +2500,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt" ] @@ -3382,7 +3380,7 @@ "content" ] }, - "ResourceReponsePart": { + "ResourceResponsePart": { "type": "object", "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { @@ -6427,32 +6425,6 @@ ], "description": "The two tool-call states that block on a client confirmation: parameter\nconfirmation before execution ({@link ToolCallPendingConfirmationState}) and\nresult confirmation after execution\n({@link ToolCallPendingResultConfirmationState}).\n\n{@link ToolCallAuthRequiredState} is intentionally **not** part of this\nunion: it doesn't block on a `chat/toolCallConfirmed`-style client\ndecision, it blocks on the client completing an OAuth flow and calling\n`authenticate`. See {@link SessionToolAuthenticationRequest} for its\nsession-level surfacing.\n\nSurfaced at the session level by {@link SessionToolConfirmationRequest}." }, - "ToolCallState": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallStreamingState" - }, - { - "$ref": "#/$defs/ToolCallPendingConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallRunningState" - }, - { - "$ref": "#/$defs/ToolCallAuthRequiredState" - }, - { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallCompletedState" - }, - { - "$ref": "#/$defs/ToolCallCancelledState" - } - ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." - }, "CustomizationLoadState": { "oneOf": [ { @@ -6686,7 +6658,7 @@ "$ref": "#/$defs/MarkdownResponsePart" }, { - "$ref": "#/$defs/ResourceReponsePart" + "$ref": "#/$defs/ResourceResponsePart" }, { "$ref": "#/$defs/ToolCallResponsePart" @@ -6741,6 +6713,32 @@ ], "description": "An attachment associated with a {@link Message}." }, + "ToolCallState": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallRunningState" + }, + { + "$ref": "#/$defs/ToolCallAuthRequiredState" + }, + { + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallCompletedState" + }, + { + "$ref": "#/$defs/ToolCallCancelledState" + } + ], + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + }, "ChatInputResponseKind": { "enum": [ "accept", @@ -7467,7 +7465,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index a11260fd..4e6d91b7 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -1180,8 +1180,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." + "$ref": "#/$defs/ToolCallRunningState", + "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." } }, "required": [ @@ -2549,7 +2549,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2616,7 +2616,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt", "turns" @@ -2632,7 +2631,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2664,7 +2663,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt" ] @@ -3545,7 +3543,7 @@ "content" ] }, - "ResourceReponsePart": { + "ResourceResponsePart": { "type": "object", "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { @@ -5245,32 +5243,6 @@ ], "description": "The two tool-call states that block on a client confirmation: parameter\nconfirmation before execution ({@link ToolCallPendingConfirmationState}) and\nresult confirmation after execution\n({@link ToolCallPendingResultConfirmationState}).\n\n{@link ToolCallAuthRequiredState} is intentionally **not** part of this\nunion: it doesn't block on a `chat/toolCallConfirmed`-style client\ndecision, it blocks on the client completing an OAuth flow and calling\n`authenticate`. See {@link SessionToolAuthenticationRequest} for its\nsession-level surfacing.\n\nSurfaced at the session level by {@link SessionToolConfirmationRequest}." }, - "ToolCallState": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallStreamingState" - }, - { - "$ref": "#/$defs/ToolCallPendingConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallRunningState" - }, - { - "$ref": "#/$defs/ToolCallAuthRequiredState" - }, - { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallCompletedState" - }, - { - "$ref": "#/$defs/ToolCallCancelledState" - } - ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." - }, "CustomizationLoadState": { "oneOf": [ { @@ -5504,7 +5476,7 @@ "$ref": "#/$defs/MarkdownResponsePart" }, { - "$ref": "#/$defs/ResourceReponsePart" + "$ref": "#/$defs/ResourceResponsePart" }, { "$ref": "#/$defs/ToolCallResponsePart" @@ -5559,6 +5531,32 @@ ], "description": "An attachment associated with a {@link Message}." }, + "ToolCallState": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallRunningState" + }, + { + "$ref": "#/$defs/ToolCallAuthRequiredState" + }, + { + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallCompletedState" + }, + { + "$ref": "#/$defs/ToolCallCancelledState" + } + ], + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + }, "ChatInputResponseKind": { "enum": [ "accept", diff --git a/schema/state.schema.json b/schema/state.schema.json index 0d347e2e..c031455a 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -928,8 +928,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." + "$ref": "#/$defs/ToolCallRunningState", + "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." } }, "required": [ @@ -2297,7 +2297,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2364,7 +2364,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt", "turns" @@ -2380,7 +2379,7 @@ }, "title": { "type": "string", - "description": "Chat title" + "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2412,7 +2411,6 @@ }, "required": [ "resource", - "title", "status", "modifiedAt" ] @@ -3293,7 +3291,7 @@ "content" ] }, - "ResourceReponsePart": { + "ResourceResponsePart": { "type": "object", "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { @@ -5194,7 +5192,7 @@ "$ref": "#/$defs/MarkdownResponsePart" }, { - "$ref": "#/$defs/ResourceReponsePart" + "$ref": "#/$defs/ResourceResponsePart" }, { "$ref": "#/$defs/ToolCallResponsePart" diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 33d7009c..bd334b6b 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -757,7 +757,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'MessageChatAttachment' }, { name: 'MarkdownResponsePart' }, { name: 'ContentRef' }, - { name: 'ResourceReponsePart', goName: 'ResourceResponsePart' }, + { name: 'ResourceResponsePart' }, { name: 'ToolCallResponsePart' }, { name: 'ReasoningResponsePart' }, { name: 'SystemNotificationResponsePart' }, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 4fad6f13..6c88d2d5 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -887,7 +887,7 @@ const STATE_STRUCTS = [ 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', 'MessageAnnotationsAttachment', 'MessageChatAttachment', 'MarkdownResponsePart', 'ContentRef', - 'ResourceReponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', + 'ResourceResponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', 'SystemNotificationResponsePart', 'InputRequestResponsePart', 'ToolCallResult', 'ToolCallStreamingState', 'ToolCallPendingConfirmationState', 'ToolCallRunningState', 'ToolCallAuthRequiredState', @@ -923,7 +923,7 @@ const RESPONSE_PART_UNION: UnionConfig = { discriminantField: 'kind', variants: [ { caseName: 'Markdown', structName: 'MarkdownResponsePart', discriminantValue: 'markdown' }, - { caseName: 'ContentRef', structName: 'ResourceReponsePart', discriminantValue: 'contentRef' }, + { caseName: 'ContentRef', structName: 'ResourceResponsePart', discriminantValue: 'contentRef' }, { caseName: 'ToolCall', structName: 'ToolCallResponsePart', discriminantValue: 'toolCall' }, { caseName: 'Reasoning', structName: 'ReasoningResponsePart', discriminantValue: 'reasoning' }, { caseName: 'SystemNotification', structName: 'SystemNotificationResponsePart', discriminantValue: 'systemNotification' }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index eaba7592..fbe7e3e9 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -740,7 +740,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'MessageChatAttachment', omitDiscriminants: true }, { name: 'MarkdownResponsePart', omitDiscriminants: true }, { name: 'ContentRef' }, - { name: 'ResourceReponsePart', omitDiscriminants: true, rustName: 'ResourceResponsePart' }, + { name: 'ResourceResponsePart', omitDiscriminants: true }, { name: 'ToolCallResponsePart', omitDiscriminants: true }, { name: 'ReasoningResponsePart', omitDiscriminants: true }, { name: 'SystemNotificationResponsePart', omitDiscriminants: true }, diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 88d307d6..01c889c9 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -637,7 +637,7 @@ const STATE_STRUCTS = [ 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', 'MessageAnnotationsAttachment', 'MessageChatAttachment', 'MarkdownResponsePart', 'ContentRef', - 'ResourceReponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', + 'ResourceResponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', 'SystemNotificationResponsePart', 'InputRequestResponsePart', 'ToolCallResult', 'ToolCallStreamingState', 'ToolCallPendingConfirmationState', 'ToolCallRunningState', 'ToolCallAuthRequiredState', @@ -678,7 +678,7 @@ const RESPONSE_PART_UNION: UnionConfig = { allowUnknown: true, variants: [ { caseName: 'markdown', structName: 'MarkdownResponsePart', discriminantValue: 'markdown' }, - { caseName: 'contentRef', structName: 'ResourceReponsePart', discriminantValue: 'contentRef' }, + { caseName: 'contentRef', structName: 'ResourceResponsePart', discriminantValue: 'contentRef' }, { caseName: 'toolCall', structName: 'ToolCallResponsePart', discriminantValue: 'toolCall' }, { caseName: 'reasoning', structName: 'ReasoningResponsePart', discriminantValue: 'reasoning' }, { caseName: 'systemNotification', structName: 'SystemNotificationResponsePart', discriminantValue: 'systemNotification' }, diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index c2644995..32af67da 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -39,8 +39,16 @@ export interface ChatState { // ── Summary fields (denormalized from ChatSummary) ───────────────── /** Chat URI */ resource: URI; - /** Chat title */ - title: string; + /** + * Chat title. + * + * Absent means the chat has no title of its own and consumers SHOULD fall + * back to the owning {@link SessionState.title | session's title}. This is + * the normal case for a session's default chat, which typically has no + * identity separate from the session itself. Producers MUST NOT use an empty + * string to mean "inherit" — omit the field instead. + */ + title?: string; /** Current chat status (reuses SessionStatus shape) */ status: SessionStatus; /** Human-readable description of what the chat is currently doing */ @@ -120,8 +128,12 @@ export interface ChatState { export interface ChatSummary { /** Chat URI */ resource: URI; - /** Chat title */ - title: string; + /** + * Chat title. Absent means the chat has no title of its own and consumers + * SHOULD fall back to the owning session's title — see + * {@link ChatState.title} for the full semantics. + */ + title?: string; /** Current chat status (reuses SessionStatus shape) */ status: SessionStatus; /** Human-readable description of what the chat is currently doing */ @@ -881,7 +893,7 @@ export interface MarkdownResponsePart { * * @category Response Parts */ -export interface ResourceReponsePart extends ContentRef { +export interface ResourceResponsePart extends ContentRef { /** Discriminant */ kind: ResponsePartKind.ContentRef; } @@ -921,7 +933,7 @@ export interface ReasoningResponsePart { */ export type ResponsePart = | MarkdownResponsePart - | ResourceReponsePart + | ResourceResponsePart | ToolCallResponsePart | ReasoningResponsePart | SystemNotificationResponsePart diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 6a1832a9..99e5034f 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -11,6 +11,7 @@ import type { ChatInputRequest, ToolCallConfirmationState, ToolCallState, + ToolCallRunningState, ToolCallAuthRequiredState, } from '../channels-chat/state.js'; import type { @@ -335,11 +336,13 @@ export interface SessionToolClientExecutionRequest extends SessionInputRequestBa */ clientId: string; /** - * The running tool call the session wants the owning client to execute. The - * host only ever populates this with a {@link ToolCallRunningState} (i.e. a - * {@link ToolCallState} in `running` status). + * The running tool call the session wants the owning client to execute. + * Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in + * `running` status), matching the narrowed `toolCall` on the sibling + * {@link SessionToolConfirmationRequest} and + * {@link SessionToolAuthenticationRequest} variants. */ - toolCall: ToolCallState; + toolCall: ToolCallRunningState; } /** From a9402cf53cf1ad8165211323737868b4353a3737 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 30 Jul 2026 20:19:39 -0700 Subject: [PATCH 2/6] chat: allow naming a chat at creation; drop the toolCall narrowing Follow-up to the optional-title change, plus a revert. Add `CreateChatParams.title`. Previously `createChat` had no way to pass a title, so *every* chat necessarily had a titleless window between creation and the host assigning one -- not just a session's default chat. A client that already knows the name (a fork, a side chat, a tool-spawned worker) can now supply it up front instead of creating the chat untitled and immediately following up with `session/chatUpdated`. Chats whose title is derived from the conversation still start untitled and inherit the session's title until one is assigned. Revert the `SessionToolClientExecutionRequest.toolCall` narrowing from the previous commit. It looked like a free precision win, but `ToolCallRunningState` is a *variant* of the `ToolCallState` tagged union and the per-language generators emit variant structs with `omitDiscriminants`, because the `status` tag is written by the enum wrapper. Referencing the variant directly therefore dropped `"status": "running"` from the wire in Rust/Kotlin/Swift/Go while TypeScript -- whose variant interface still declares `status` -- would have kept emitting it. That is both a wire change and a cross-language inconsistency; the round-trip fixtures caught it. The field keeps its documented invariant in prose, with a note explaining why it is not narrowed. Doing this properly needs generator support for retaining discriminants on directly-referenced variants. Also fix the Go reducers example, which set `ChatState.Title` as a bare string. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- clients/go/ahp/reducers.go | 2 +- clients/go/ahptypes/commands.generated.go | 8 +++ clients/go/ahptypes/state.generated.go | 17 ++++-- clients/go/examples/reducers_demo/main.go | 3 +- .../generated/Commands.generated.kt | 10 ++++ .../generated/State.generated.kt | 17 ++++-- clients/rust/crates/ahp-types/src/commands.rs | 9 +++ clients/rust/crates/ahp-types/src/state.rs | 17 ++++-- clients/rust/crates/ahp/src/reducers.rs | 8 +-- .../Generated/Commands.generated.swift | 10 ++++ .../Generated/State.generated.swift | 19 +++--- .../20260730-chat-title-optional.json | 2 +- docs/.changes/20260730-create-chat-title.json | 4 ++ ...ession-tool-client-execution-narrowed.json | 4 -- docs/specification/chat-channel.md | 9 ++- schema/actions.schema.json | 4 +- schema/commands.schema.json | 60 ++++++++++--------- schema/errors.schema.json | 60 ++++++++++--------- schema/notifications.schema.json | 56 ++++++++--------- schema/state.schema.json | 4 +- types/channels-chat/commands.ts | 10 ++++ types/channels-session/state.ts | 18 +++--- 22 files changed, 218 insertions(+), 133 deletions(-) create mode 100644 docs/.changes/20260730-create-chat-title.json delete mode 100644 docs/.changes/20260730-session-tool-client-execution-narrowed.json diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index 978608c9..601885d4 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -753,7 +753,7 @@ func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) R func mergeChatSummaryPartial(summary *ahptypes.ChatSummary, changes ahptypes.PartialChatSummary) { if changes.Title != nil { - summary.Title = *changes.Title + summary.Title = changes.Title } if changes.Status != nil { summary.Status = *changes.Status diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index aa70d3ad..a077adaf 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -409,6 +409,14 @@ type CreateChatParams struct { Chat URI `json:"chat"` // Optional initial message for the new chat. InitialMessage *Message `json:"initialMessage,omitempty"` + // Optional title for the new chat. + // + // Lets a client name a chat it already has a name for — a fork, a side chat, + // or a tool-spawned worker — instead of creating it untitled and following up + // with a `session/chatUpdated`. When omitted the chat starts with no title of + // its own and consumers fall back to the session's title until the host + // assigns one (see {@link ChatState.title}). + Title *string `json:"title,omitempty"` // Optional source chat and source turn. // // The source chat MUST belong to this session. Clients MUST only request diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 622ac81e..6fc891b9 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -937,12 +937,17 @@ type SessionToolClientExecutionRequest struct { // The `clientId` expected to execute the tool. Matches the `clientId` of the // tool call's client {@link ToolCallContributor}. ClientId string `json:"clientId"` - // The running tool call the session wants the owning client to execute. - // Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in - // `running` status), matching the narrowed `toolCall` on the sibling - // {@link SessionToolConfirmationRequest} and - // {@link SessionToolAuthenticationRequest} variants. - ToolCall ToolCallRunningState `json:"toolCall"` + // The running tool call the session wants the owning client to execute. The + // host only ever populates this with a {@link ToolCallRunningState} (i.e. a + // {@link ToolCallState} in `running` status). + // + // Declared as the full union rather than narrowed to + // `ToolCallRunningState` because the per-language generators emit + // tagged-union *variants* without their discriminant (the `status` tag is + // written by the enum wrapper). Referencing the variant directly would drop + // `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still + // emitted it. Narrowing this needs generator support first. + ToolCall ToolCallState `json:"toolCall"` } // A tool call blocked on MCP authentication mid-execution, surfaced at the diff --git a/clients/go/examples/reducers_demo/main.go b/clients/go/examples/reducers_demo/main.go index 139d8c63..f8331ea2 100644 --- a/clients/go/examples/reducers_demo/main.go +++ b/clients/go/examples/reducers_demo/main.go @@ -11,9 +11,10 @@ import ( ) func main() { + title := "Demo" state := ahptypes.ChatState{ Resource: "ahp-chat:/demo", - Title: "Demo", + Title: &title, Status: ahptypes.SessionStatusIdle, ModifiedAt: "1970-01-01T00:00:00.001Z", } diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index f259cb67..06fd0ee0 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -575,6 +575,16 @@ data class CreateChatParams( * Optional initial message for the new chat. */ val initialMessage: Message? = null, + /** + * Optional title for the new chat. + * + * Lets a client name a chat it already has a name for — a fork, a side chat, + * or a tool-spawned worker — instead of creating it untitled and following up + * with a `session/chatUpdated`. When omitted the chat starts with no title of + * its own and consumers fall back to the session's title until the host + * assigns one (see {@link ChatState.title}). + */ + val title: String? = null, /** * Optional source chat and source turn. * diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index 8671a3ce..072afa90 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -1559,13 +1559,18 @@ data class SessionToolClientExecutionRequest( */ val clientId: String, /** - * The running tool call the session wants the owning client to execute. - * Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in - * `running` status), matching the narrowed `toolCall` on the sibling - * {@link SessionToolConfirmationRequest} and - * {@link SessionToolAuthenticationRequest} variants. + * The running tool call the session wants the owning client to execute. The + * host only ever populates this with a {@link ToolCallRunningState} (i.e. a + * {@link ToolCallState} in `running` status). + * + * Declared as the full union rather than narrowed to + * `ToolCallRunningState` because the per-language generators emit + * tagged-union *variants* without their discriminant (the `status` tag is + * written by the enum wrapper). Referencing the variant directly would drop + * `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still + * emitted it. Narrowing this needs generator support first. */ - val toolCall: ToolCallRunningState + val toolCall: ToolCallState ) @Serializable diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 565ea531..a0e05c43 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -507,6 +507,15 @@ pub struct CreateChatParams { /// Optional initial message for the new chat. #[serde(default, skip_serializing_if = "Option::is_none")] pub initial_message: Option, + /// Optional title for the new chat. + /// + /// Lets a client name a chat it already has a name for — a fork, a side chat, + /// or a tool-spawned worker — instead of creating it untitled and following up + /// with a `session/chatUpdated`. When omitted the chat starts with no title of + /// its own and consumers fall back to the session's title until the host + /// assigns one (see {@link ChatState.title}). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, /// Optional source chat and source turn. /// /// The source chat MUST belong to this session. Clients MUST only request diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 48cae0d1..99268944 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -1367,12 +1367,17 @@ pub struct SessionToolClientExecutionRequest { /// The `clientId` expected to execute the tool. Matches the `clientId` of the /// tool call's client {@link ToolCallContributor}. pub client_id: String, - /// The running tool call the session wants the owning client to execute. - /// Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in - /// `running` status), matching the narrowed `toolCall` on the sibling - /// {@link SessionToolConfirmationRequest} and - /// {@link SessionToolAuthenticationRequest} variants. - pub tool_call: ToolCallRunningState, + /// The running tool call the session wants the owning client to execute. The + /// host only ever populates this with a {@link ToolCallRunningState} (i.e. a + /// {@link ToolCallState} in `running` status). + /// + /// Declared as the full union rather than narrowed to + /// `ToolCallRunningState` because the per-language generators emit + /// tagged-union *variants* without their discriminant (the `status` tag is + /// written by the enum wrapper). Referencing the variant directly would drop + /// `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still + /// emitted it. Narrowing this needs generator support first. + pub tool_call: ToolCallState, } /// A tool call blocked on MCP authentication mid-execution, surfaced at the diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 59c62518..0fe4ea64 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -674,7 +674,7 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - return ReduceOutcome::NoOp; }; if let Some(title) = &a.changes.title { - chat.title = title.clone(); + chat.title = Some(title.clone()); } if let Some(status) = a.changes.status { chat.status = status; @@ -1990,7 +1990,7 @@ mod tests { fn empty_chat(resource: &str) -> ChatState { ChatState { resource: resource.to_string(), - title: String::new(), + title: None, status: SessionStatus::Idle.bits(), activity: None, modified_at: "1970-01-01T00:00:00.000Z".into(), @@ -2086,7 +2086,7 @@ mod tests { let chat = ChatSummary { resource: "copilot:/s1/chat/1".into(), - title: "c1".into(), + title: Some("c1".into()), status: SessionStatus::Idle.bits(), activity: None, modified_at: "1970-01-01T00:00:00.000Z".into(), @@ -2116,7 +2116,7 @@ mod tests { apply_action_to_session(&mut s, &updated), ReduceOutcome::Applied ); - assert_eq!(s.chats[0].title, "renamed"); + assert_eq!(s.chats[0].title.as_deref(), Some("renamed")); assert_eq!(s.chats[0].modified_at, "1970-01-01T00:00:09.999Z"); s.default_chat = Some(chat.resource.clone()); diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 4abe4305..279850c8 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -540,6 +540,14 @@ public struct CreateChatParams: Codable, Sendable { public var chat: String /// Optional initial message for the new chat. public var initialMessage: Message? + /// Optional title for the new chat. + /// + /// Lets a client name a chat it already has a name for — a fork, a side chat, + /// or a tool-spawned worker — instead of creating it untitled and following up + /// with a `session/chatUpdated`. When omitted the chat starts with no title of + /// its own and consumers fall back to the session's title until the host + /// assigns one (see {@link ChatState.title}). + public var title: String? /// Optional source chat and source turn. /// /// The source chat MUST belong to this session. Clients MUST only request @@ -569,12 +577,14 @@ public struct CreateChatParams: Codable, Sendable { channel: String, chat: String, initialMessage: Message? = nil, + title: String? = nil, source: ChatSource? = nil, workingDirectories: [String]? = nil ) { self.channel = channel self.chat = chat self.initialMessage = initialMessage + self.title = title self.source = source self.workingDirectories = workingDirectories } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 75fdcd33..5a0266bd 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -1342,12 +1342,17 @@ public struct SessionToolClientExecutionRequest: Codable, Sendable { /// The `clientId` expected to execute the tool. Matches the `clientId` of the /// tool call's client {@link ToolCallContributor}. public var clientId: String - /// The running tool call the session wants the owning client to execute. - /// Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in - /// `running` status), matching the narrowed `toolCall` on the sibling - /// {@link SessionToolConfirmationRequest} and - /// {@link SessionToolAuthenticationRequest} variants. - public var toolCall: ToolCallRunningState + /// The running tool call the session wants the owning client to execute. The + /// host only ever populates this with a {@link ToolCallRunningState} (i.e. a + /// {@link ToolCallState} in `running` status). + /// + /// Declared as the full union rather than narrowed to + /// `ToolCallRunningState` because the per-language generators emit + /// tagged-union *variants* without their discriminant (the `status` tag is + /// written by the enum wrapper). Referencing the variant directly would drop + /// `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still + /// emitted it. Narrowing this needs generator support first. + public var toolCall: ToolCallState public init( id: String, @@ -1355,7 +1360,7 @@ public struct SessionToolClientExecutionRequest: Codable, Sendable { kind: SessionInputRequestKind, turnId: String, clientId: String, - toolCall: ToolCallRunningState + toolCall: ToolCallState ) { self.id = id self.chat = chat diff --git a/docs/.changes/20260730-chat-title-optional.json b/docs/.changes/20260730-chat-title-optional.json index c1f2d400..d5c50250 100644 --- a/docs/.changes/20260730-chat-title-optional.json +++ b/docs/.changes/20260730-chat-title-optional.json @@ -1,4 +1,4 @@ { "type": "changed", - "message": "`ChatState.title` and `ChatSummary.title` are now optional. An absent title means the chat has no title of its own and consumers should fall back to the owning session's title — the normal case for a session's default chat. Producers must omit the field rather than sending an empty string to mean \"inherit\"." + "message": "`ChatState.title` and `ChatSummary.title` are now optional. An absent title means the chat has no title of its own and consumers should fall back to the owning session's title — the case for a session's default chat, and for any chat between creation and the host assigning it a title. Producers must omit the field rather than sending an empty string to mean \"inherit\"." } diff --git a/docs/.changes/20260730-create-chat-title.json b/docs/.changes/20260730-create-chat-title.json new file mode 100644 index 00000000..2c0d836d --- /dev/null +++ b/docs/.changes/20260730-create-chat-title.json @@ -0,0 +1,4 @@ +{ + "type": "added", + "message": "`CreateChatParams.title`, so a client can name a chat at creation instead of creating it untitled and following up with a `session/chatUpdated`." +} diff --git a/docs/.changes/20260730-session-tool-client-execution-narrowed.json b/docs/.changes/20260730-session-tool-client-execution-narrowed.json deleted file mode 100644 index 6e8465bc..00000000 --- a/docs/.changes/20260730-session-tool-client-execution-narrowed.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "changed", - "message": "`SessionToolClientExecutionRequest.toolCall` is now typed `ToolCallRunningState` instead of the full `ToolCallState` union, matching the already-narrowed `toolCall` on the sibling `SessionToolConfirmationRequest` and `SessionToolAuthenticationRequest` variants. The documented contract is unchanged — hosts only ever populated it with a running tool call." -} diff --git a/docs/specification/chat-channel.md b/docs/specification/chat-channel.md index aef0c63d..0f94f13b 100644 --- a/docs/specification/chat-channel.md +++ b/docs/specification/chat-channel.md @@ -39,9 +39,14 @@ Clients MAY periodically sync their local input state into the draft by dispatch ### Chat title -`ChatState.title` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat has no title of its own and consumers SHOULD fall back to the owning session's [`title`](/reference/session#sessionsummary). This is the normal case for a session's default chat, which usually has no identity separate from the session itself; hosts typically set a title only on additional chats (forks, side chats, tool-spawned workers) where distinguishing them in a list is useful. +`ChatState.title` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat has no title of its own and consumers SHOULD fall back to the owning session's [`title`](/reference/session#sessionsummary). -Producers MUST NOT use an empty string to mean "inherit" — omit the field instead, so that a deliberately blank title and an absent one remain distinguishable. +Two situations produce an untitled chat: + +- A session's **default chat**, which usually has no identity separate from the session itself. Hosts commonly leave it untitled for the life of the session, and give it a title only once the session holds more than one chat and the default chat needs its own label in a list. +- **Any newly created chat**, between `createChat` and the point where a title is known. Titles are typically derived from the conversation, so they are not available at creation. A client that already knows the name — for a fork, a side chat, or a tool-spawned worker — MAY supply [`CreateChatParams.title`](/reference/chat#createchat) to skip the untitled window; otherwise the host assigns one later with [`session/chatUpdated`](/reference/session#actions). + +Producers MUST NOT use an empty string to mean "inherit" — omit the field instead, so that a deliberately blank title and an absent one remain distinguishable, and so a host can tell which chats are still tracking the session's title. ### Per-chat working directory diff --git a/schema/actions.schema.json b/schema/actions.schema.json index b07f1615..94220f25 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -3118,8 +3118,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallRunningState", - "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." + "$ref": "#/$defs/ToolCallState", + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." } }, "required": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 2b57ab5b..3006db52 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1162,6 +1162,10 @@ "$ref": "#/$defs/Message", "description": "Optional initial message for the new chat." }, + "title": { + "type": "string", + "description": "Optional title for the new chat.\n\nLets a client name a chat it already has a name for — a fork, a side chat,\nor a tool-spawned worker — instead of creating it untitled and following up\nwith a `session/chatUpdated`. When omitted the chat starts with no title of\nits own and consumers fall back to the session's title until the host\nassigns one (see {@link ChatState.title})." + }, "source": { "$ref": "#/$defs/ChatSource", "description": "Optional source chat and source turn.\n\nThe source chat MUST belong to this session. Clients MUST only request\n`kind: \"fork\"` when the selected agent advertises\n`capabilities.multipleChats.fork`, and `kind: \"sideChat\"` when the\nselected agent advertises `capabilities.multipleChats.sideChat`. Both\nsource forms carry a stable top-level `turnId`. Forks target completed\nturns. Side chats also carry a stable `turnId`, which the host resolves\nagainst the source chat's current active turn or retained history. If it\nresolves to the active turn, the host snapshots the currently available\npartial response when accepting `createChat`. When\n`source.kind === \"sideChat\"` and `source.selection` is present, the host\nalso snapshots and preserves that exact selected text in the created chat's\norigin; any `responsePartId` there is provenance only, not a live range." @@ -2281,8 +2285,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallRunningState", - "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." + "$ref": "#/$defs/ToolCallState", + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." } }, "required": [ @@ -8831,6 +8835,32 @@ ], "description": "The two tool-call states that block on a client confirmation: parameter\nconfirmation before execution ({@link ToolCallPendingConfirmationState}) and\nresult confirmation after execution\n({@link ToolCallPendingResultConfirmationState}).\n\n{@link ToolCallAuthRequiredState} is intentionally **not** part of this\nunion: it doesn't block on a `chat/toolCallConfirmed`-style client\ndecision, it blocks on the client completing an OAuth flow and calling\n`authenticate`. See {@link SessionToolAuthenticationRequest} for its\nsession-level surfacing.\n\nSurfaced at the session level by {@link SessionToolConfirmationRequest}." }, + "ToolCallState": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallRunningState" + }, + { + "$ref": "#/$defs/ToolCallAuthRequiredState" + }, + { + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallCompletedState" + }, + { + "$ref": "#/$defs/ToolCallCancelledState" + } + ], + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + }, "CustomizationLoadState": { "oneOf": [ { @@ -9099,32 +9129,6 @@ "type": "string", "description": "Discriminant for {@link MessageOrigin} — identifies who produced a message." }, - "ToolCallState": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallStreamingState" - }, - { - "$ref": "#/$defs/ToolCallPendingConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallRunningState" - }, - { - "$ref": "#/$defs/ToolCallAuthRequiredState" - }, - { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallCompletedState" - }, - { - "$ref": "#/$defs/ToolCallCancelledState" - } - ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." - }, "ChatInputResponseKind": { "enum": [ "accept", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index ce5689ca..68f032c2 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -1017,8 +1017,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallRunningState", - "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." + "$ref": "#/$defs/ToolCallState", + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." } }, "required": [ @@ -6136,6 +6136,10 @@ "$ref": "#/$defs/Message", "description": "Optional initial message for the new chat." }, + "title": { + "type": "string", + "description": "Optional title for the new chat.\n\nLets a client name a chat it already has a name for — a fork, a side chat,\nor a tool-spawned worker — instead of creating it untitled and following up\nwith a `session/chatUpdated`. When omitted the chat starts with no title of\nits own and consumers fall back to the session's title until the host\nassigns one (see {@link ChatState.title})." + }, "source": { "$ref": "#/$defs/ChatSource", "description": "Optional source chat and source turn.\n\nThe source chat MUST belong to this session. Clients MUST only request\n`kind: \"fork\"` when the selected agent advertises\n`capabilities.multipleChats.fork`, and `kind: \"sideChat\"` when the\nselected agent advertises `capabilities.multipleChats.sideChat`. Both\nsource forms carry a stable top-level `turnId`. Forks target completed\nturns. Side chats also carry a stable `turnId`, which the host resolves\nagainst the source chat's current active turn or retained history. If it\nresolves to the active turn, the host snapshots the currently available\npartial response when accepting `createChat`. When\n`source.kind === \"sideChat\"` and `source.selection` is present, the host\nalso snapshots and preserves that exact selected text in the created chat's\norigin; any `responsePartId` there is provenance only, not a live range." @@ -6425,6 +6429,32 @@ ], "description": "The two tool-call states that block on a client confirmation: parameter\nconfirmation before execution ({@link ToolCallPendingConfirmationState}) and\nresult confirmation after execution\n({@link ToolCallPendingResultConfirmationState}).\n\n{@link ToolCallAuthRequiredState} is intentionally **not** part of this\nunion: it doesn't block on a `chat/toolCallConfirmed`-style client\ndecision, it blocks on the client completing an OAuth flow and calling\n`authenticate`. See {@link SessionToolAuthenticationRequest} for its\nsession-level surfacing.\n\nSurfaced at the session level by {@link SessionToolConfirmationRequest}." }, + "ToolCallState": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallRunningState" + }, + { + "$ref": "#/$defs/ToolCallAuthRequiredState" + }, + { + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallCompletedState" + }, + { + "$ref": "#/$defs/ToolCallCancelledState" + } + ], + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + }, "CustomizationLoadState": { "oneOf": [ { @@ -6713,32 +6743,6 @@ ], "description": "An attachment associated with a {@link Message}." }, - "ToolCallState": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallStreamingState" - }, - { - "$ref": "#/$defs/ToolCallPendingConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallRunningState" - }, - { - "$ref": "#/$defs/ToolCallAuthRequiredState" - }, - { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallCompletedState" - }, - { - "$ref": "#/$defs/ToolCallCancelledState" - } - ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." - }, "ChatInputResponseKind": { "enum": [ "accept", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 4e6d91b7..553e7fbe 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -1180,8 +1180,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallRunningState", - "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." + "$ref": "#/$defs/ToolCallState", + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." } }, "required": [ @@ -5243,6 +5243,32 @@ ], "description": "The two tool-call states that block on a client confirmation: parameter\nconfirmation before execution ({@link ToolCallPendingConfirmationState}) and\nresult confirmation after execution\n({@link ToolCallPendingResultConfirmationState}).\n\n{@link ToolCallAuthRequiredState} is intentionally **not** part of this\nunion: it doesn't block on a `chat/toolCallConfirmed`-style client\ndecision, it blocks on the client completing an OAuth flow and calling\n`authenticate`. See {@link SessionToolAuthenticationRequest} for its\nsession-level surfacing.\n\nSurfaced at the session level by {@link SessionToolConfirmationRequest}." }, + "ToolCallState": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallRunningState" + }, + { + "$ref": "#/$defs/ToolCallAuthRequiredState" + }, + { + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + }, + { + "$ref": "#/$defs/ToolCallCompletedState" + }, + { + "$ref": "#/$defs/ToolCallCancelledState" + } + ], + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + }, "CustomizationLoadState": { "oneOf": [ { @@ -5531,32 +5557,6 @@ ], "description": "An attachment associated with a {@link Message}." }, - "ToolCallState": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallStreamingState" - }, - { - "$ref": "#/$defs/ToolCallPendingConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallRunningState" - }, - { - "$ref": "#/$defs/ToolCallAuthRequiredState" - }, - { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" - }, - { - "$ref": "#/$defs/ToolCallCompletedState" - }, - { - "$ref": "#/$defs/ToolCallCancelledState" - } - ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." - }, "ChatInputResponseKind": { "enum": [ "accept", diff --git a/schema/state.schema.json b/schema/state.schema.json index c031455a..a9f01ec5 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -928,8 +928,8 @@ "description": "The `clientId` expected to execute the tool. Matches the `clientId` of the\ntool call's client {@link ToolCallContributor}." }, "toolCall": { - "$ref": "#/$defs/ToolCallRunningState", - "description": "The running tool call the session wants the owning client to execute.\nAlways a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in\n`running` status), matching the narrowed `toolCall` on the sibling\n{@link SessionToolConfirmationRequest} and\n{@link SessionToolAuthenticationRequest} variants." + "$ref": "#/$defs/ToolCallState", + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." } }, "required": [ diff --git a/types/channels-chat/commands.ts b/types/channels-chat/commands.ts index 419e3c24..d7ce6a80 100644 --- a/types/channels-chat/commands.ts +++ b/types/channels-chat/commands.ts @@ -89,6 +89,16 @@ export interface CreateChatParams extends BaseParams { chat: URI; /** Optional initial message for the new chat. */ initialMessage?: Message; + /** + * Optional title for the new chat. + * + * Lets a client name a chat it already has a name for — a fork, a side chat, + * or a tool-spawned worker — instead of creating it untitled and following up + * with a `session/chatUpdated`. When omitted the chat starts with no title of + * its own and consumers fall back to the session's title until the host + * assigns one (see {@link ChatState.title}). + */ + title?: string; /** * Optional source chat and source turn. * diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 99e5034f..1ebc3be7 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -11,7 +11,6 @@ import type { ChatInputRequest, ToolCallConfirmationState, ToolCallState, - ToolCallRunningState, ToolCallAuthRequiredState, } from '../channels-chat/state.js'; import type { @@ -336,13 +335,18 @@ export interface SessionToolClientExecutionRequest extends SessionInputRequestBa */ clientId: string; /** - * The running tool call the session wants the owning client to execute. - * Always a {@link ToolCallRunningState} (i.e. a {@link ToolCallState} in - * `running` status), matching the narrowed `toolCall` on the sibling - * {@link SessionToolConfirmationRequest} and - * {@link SessionToolAuthenticationRequest} variants. + * The running tool call the session wants the owning client to execute. The + * host only ever populates this with a {@link ToolCallRunningState} (i.e. a + * {@link ToolCallState} in `running` status). + * + * Declared as the full union rather than narrowed to + * `ToolCallRunningState` because the per-language generators emit + * tagged-union *variants* without their discriminant (the `status` tag is + * written by the enum wrapper). Referencing the variant directly would drop + * `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still + * emitted it. Narrowing this needs generator support first. */ - toolCall: ToolCallRunningState; + toolCall: ToolCallState; } /** From 407a7f7f37f4bb255888ebd0f5efeef93a66986a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 31 Jul 2026 14:59:01 -0700 Subject: [PATCH 3/6] revert chat title changes --- clients/go/ahp/reducers.go | 91 ++++++++++--------- clients/go/examples/reducers_demo/main.go | 3 +- clients/rust/crates/ahp/src/reducers.rs | 70 +++++++------- .../20260730-chat-title-optional.json | 4 - docs/.changes/20260730-create-chat-title.json | 4 - docs/specification/chat-channel.md | 11 --- types/channels-chat/commands.ts | 10 -- types/channels-chat/state.ts | 20 +--- 8 files changed, 90 insertions(+), 123 deletions(-) delete mode 100644 docs/.changes/20260730-chat-title-optional.json delete mode 100644 docs/.changes/20260730-create-chat-title.json diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index 601885d4..f912928e 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -91,6 +91,7 @@ type toolCallCommon struct { name string displayName string intention *string + toolInput *ahptypes.ToolInput contributor *ahptypes.ToolCallContributor meta ahptypes.JSONObject } @@ -98,44 +99,43 @@ type toolCallCommon struct { func toolCallMeta(tc ahptypes.ToolCallState) toolCallCommon { switch v := tc.Value.(type) { case *ahptypes.ToolCallStreamingState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, nil, v.Contributor, v.Meta} case *ahptypes.ToolCallPendingConfirmationState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta} case *ahptypes.ToolCallRunningState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta} case *ahptypes.ToolCallAuthRequiredState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta} case *ahptypes.ToolCallPendingResultConfirmationState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta} case *ahptypes.ToolCallCompletedState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta} case *ahptypes.ToolCallCancelledState: - return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.Contributor, v.Meta} + return toolCallCommon{v.ToolCallId, v.ToolName, v.DisplayName, v.Intention, v.ToolInput, v.Contributor, v.Meta} } return toolCallCommon{} } -// toolCallInvocationAndInput pulls the optional invocationMessage and -// toolInput fields out of whichever non-terminal variant tc points to. -// Returns (StringOrMarkdown{}, nil) for variants that don't carry them. -func toolCallInvocationAndInput(tc ahptypes.ToolCallState) (ahptypes.StringOrMarkdown, *string) { +// toolCallInvocationMessage pulls the invocation message from a non-terminal +// tool-call variant. +func toolCallInvocationMessage(tc ahptypes.ToolCallState) ahptypes.StringOrMarkdown { switch v := tc.Value.(type) { case *ahptypes.ToolCallStreamingState: var im ahptypes.StringOrMarkdown if v.InvocationMessage != nil { im = *v.InvocationMessage } - return im, nil + return im case *ahptypes.ToolCallPendingConfirmationState: - return v.InvocationMessage, v.ToolInput + return v.InvocationMessage case *ahptypes.ToolCallRunningState: - return v.InvocationMessage, v.ToolInput + return v.InvocationMessage case *ahptypes.ToolCallAuthRequiredState: - return v.InvocationMessage, v.ToolInput + return v.InvocationMessage case *ahptypes.ToolCallPendingResultConfirmationState: - return v.InvocationMessage, v.ToolInput + return v.InvocationMessage } - return ahptypes.StringOrMarkdown{}, nil + return ahptypes.StringOrMarkdown{} } func toolCallID(tc ahptypes.ToolCallState) string { @@ -221,17 +221,17 @@ func endTurn(state *ahptypes.ChatState, turnID string, duration int64, turnState continue } common := toolCallMeta(tc.ToolCall) - invocation, toolInput := toolCallInvocationAndInput(tc.ToolCall) + invocation := toolCallInvocationMessage(tc.ToolCall) cancelled := &ahptypes.ToolCallCancelledState{ Status: ahptypes.ToolCallStatusCancelled, ToolCallId: common.id, ToolName: common.name, DisplayName: common.displayName, Intention: common.intention, + ToolInput: common.toolInput, Contributor: common.contributor, Meta: common.meta, InvocationMessage: invocation, - ToolInput: toolInput, Reason: ahptypes.ToolCallCancellationReasonSkipped, } parts = append(parts, ahptypes.ResponsePart{Value: &ahptypes.ToolCallResponsePart{ @@ -753,7 +753,7 @@ func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) R func mergeChatSummaryPartial(summary *ahptypes.ChatSummary, changes ahptypes.PartialChatSummary) { if changes.Title != nil { - summary.Title = changes.Title + summary.Title = *changes.Title } if changes.Status != nil { summary.Status = *changes.Status @@ -1026,18 +1026,20 @@ func applyToolCallDelta(state *ahptypes.ChatState, a *ahptypes.ChatToolCallDelta if !ok { return tc } - current := "" - if s.PartialInput != nil { - current = *s.PartialInput - } - joined := current + a.Content - s.PartialInput = &joined if a.Meta != nil { s.Meta = a.Meta } + if a.Content != nil { + partial := "" + if s.PartialInput != nil { + partial = *s.PartialInput + } + partial += *a.Content + s.PartialInput = &partial + } if a.InvocationMessage != nil { - im := *a.InvocationMessage - s.InvocationMessage = &im + invocationMessage := *a.InvocationMessage + s.InvocationMessage = &invocationMessage } return tc }) @@ -1067,6 +1069,9 @@ func applyToolCallReady(state *ahptypes.ChatState, a *ahptypes.ChatToolCallReady if a.Intention != nil { common.intention = a.Intention } + if a.ToolInput != nil { + common.toolInput = a.ToolInput + } common.contributor = refineToolCallContributor(common.contributor, a.Contributor) if a.Meta != nil { common.meta = a.Meta @@ -1086,10 +1091,10 @@ func applyToolCallReady(state *ahptypes.ChatState, a *ahptypes.ChatToolCallReady ToolName: common.name, DisplayName: common.displayName, Intention: common.intention, + ToolInput: common.toolInput, Contributor: common.contributor, Meta: common.meta, InvocationMessage: a.InvocationMessage, - ToolInput: a.ToolInput, Confirmed: *a.Confirmed, }} } @@ -1099,10 +1104,10 @@ func applyToolCallReady(state *ahptypes.ChatState, a *ahptypes.ChatToolCallReady ToolName: common.name, DisplayName: common.displayName, Intention: common.intention, + ToolInput: common.toolInput, Contributor: common.contributor, Meta: common.meta, InvocationMessage: a.InvocationMessage, - ToolInput: a.ToolInput, ConfirmationTitle: a.ConfirmationTitle, RiskAssessment: a.RiskAssessment, Edits: a.Edits, @@ -1110,9 +1115,6 @@ func applyToolCallReady(state *ahptypes.ChatState, a *ahptypes.ChatToolCallReady Options: a.Options, } if pending != nil { - if next.ToolInput == nil { - next.ToolInput = pending.ToolInput - } if next.ConfirmationTitle == nil { next.ConfirmationTitle = pending.ConfirmationTitle } @@ -1157,8 +1159,9 @@ func applyToolCallConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallC selected := resolveSelectedOption(s.Options, a.SelectedOptionId) if a.Approved { toolInput := s.ToolInput - if a.EditedToolInput != nil { - toolInput = a.EditedToolInput + if a.EditedToolInput != nil && toolInput != nil && toolInput.Inline != nil { + inline := *a.EditedToolInput + toolInput = &ahptypes.ToolInput{Inline: &inline} } meta := s.Meta if a.Meta != nil { @@ -1174,10 +1177,10 @@ func applyToolCallConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallC ToolName: s.ToolName, DisplayName: s.DisplayName, Intention: s.Intention, + ToolInput: toolInput, Contributor: s.Contributor, Meta: meta, InvocationMessage: s.InvocationMessage, - ToolInput: toolInput, Confirmed: confirmed, SelectedOption: selected, }} @@ -1196,10 +1199,10 @@ func applyToolCallConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallC ToolName: s.ToolName, DisplayName: s.DisplayName, Intention: s.Intention, + ToolInput: s.ToolInput, Contributor: s.Contributor, Meta: meta, InvocationMessage: s.InvocationMessage, - ToolInput: s.ToolInput, Reason: reason, ReasonMessage: a.ReasonMessage, UserSuggestion: a.UserSuggestion, @@ -1216,7 +1219,7 @@ func applyToolCallComplete(state *ahptypes.ChatState, a *ahptypes.ChatToolCallCo } var ( invocation ahptypes.StringOrMarkdown - toolInput *string + toolInput *ahptypes.ToolInput confirmed = ahptypes.ToolCallConfirmationReasonNotNeeded selectedOption *ahptypes.ConfirmationOption preAuthContent []ahptypes.ToolResultContent @@ -1266,10 +1269,10 @@ func applyToolCallComplete(state *ahptypes.ChatState, a *ahptypes.ChatToolCallCo ToolName: common.name, DisplayName: common.displayName, Intention: common.intention, + ToolInput: toolInput, Contributor: common.contributor, Meta: common.meta, InvocationMessage: invocation, - ToolInput: toolInput, Success: a.Result.Success, PastTenseMessage: a.Result.PastTenseMessage, Content: append([]ahptypes.ToolResultContent(nil), content...), @@ -1285,10 +1288,10 @@ func applyToolCallComplete(state *ahptypes.ChatState, a *ahptypes.ChatToolCallCo ToolName: common.name, DisplayName: common.displayName, Intention: common.intention, + ToolInput: toolInput, Contributor: common.contributor, Meta: common.meta, InvocationMessage: invocation, - ToolInput: toolInput, Success: a.Result.Success, PastTenseMessage: a.Result.PastTenseMessage, Content: append([]ahptypes.ToolResultContent(nil), content...), @@ -1317,10 +1320,10 @@ func applyToolCallResultConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToo ToolName: s.ToolName, DisplayName: s.DisplayName, Intention: s.Intention, + ToolInput: s.ToolInput, Contributor: s.Contributor, Meta: meta, InvocationMessage: s.InvocationMessage, - ToolInput: s.ToolInput, Success: s.Success, PastTenseMessage: s.PastTenseMessage, Content: s.Content, @@ -1340,10 +1343,10 @@ func applyToolCallResultConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToo ToolName: s.ToolName, DisplayName: s.DisplayName, Intention: s.Intention, + ToolInput: s.ToolInput, Contributor: s.Contributor, Meta: meta, InvocationMessage: s.InvocationMessage, - ToolInput: s.ToolInput, Reason: ahptypes.ToolCallCancellationReasonResultDenied, SelectedOption: s.SelectedOption, }} @@ -1372,10 +1375,10 @@ func applyToolCallAuthRequired(state *ahptypes.ChatState, a *ahptypes.ChatToolCa ToolName: s.ToolName, DisplayName: s.DisplayName, Intention: s.Intention, + ToolInput: s.ToolInput, Contributor: s.Contributor, Meta: meta, InvocationMessage: s.InvocationMessage, - ToolInput: s.ToolInput, Confirmed: s.Confirmed, SelectedOption: s.SelectedOption, Auth: a.Auth, @@ -1400,10 +1403,10 @@ func applyToolCallAuthResolved(state *ahptypes.ChatState, a *ahptypes.ChatToolCa ToolName: s.ToolName, DisplayName: s.DisplayName, Intention: s.Intention, + ToolInput: s.ToolInput, Contributor: s.Contributor, Meta: meta, InvocationMessage: s.InvocationMessage, - ToolInput: s.ToolInput, Confirmed: s.Confirmed, SelectedOption: s.SelectedOption, Content: append([]ahptypes.ToolResultContent(nil), s.Content...), diff --git a/clients/go/examples/reducers_demo/main.go b/clients/go/examples/reducers_demo/main.go index f8331ea2..139d8c63 100644 --- a/clients/go/examples/reducers_demo/main.go +++ b/clients/go/examples/reducers_demo/main.go @@ -11,10 +11,9 @@ import ( ) func main() { - title := "Demo" state := ahptypes.ChatState{ Resource: "ahp-chat:/demo", - Title: &title, + Title: "Demo", Status: ahptypes.SessionStatusIdle, ModifiedAt: "1970-01-01T00:00:00.001Z", } diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 0fe4ea64..a2cf5109 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -67,7 +67,7 @@ use ahp_types::state::{ ToolCallCancellationReason, ToolCallCancelledState, ToolCallCompletedState, ToolCallConfirmationReason, ToolCallContributor, ToolCallPendingConfirmationState, ToolCallPendingResultConfirmationState, ToolCallResponsePart, ToolCallRunningState, - ToolCallState, ToolCallStatus, ToolCallStreamingState, Turn, TurnState, + ToolCallState, ToolCallStatus, ToolCallStreamingState, ToolInput, Turn, TurnState, }; /// What happened when an action was applied. @@ -133,6 +133,7 @@ struct ToolCallBase { tool_name: String, display_name: String, intention: Option, + tool_input: Option, contributor: Option, meta: Option>, } @@ -165,6 +166,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: None, contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -173,6 +175,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: s.tool_input.clone(), contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -181,6 +184,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: s.tool_input.clone(), contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -189,6 +193,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: s.tool_input.clone(), contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -197,6 +202,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: s.tool_input.clone(), contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -205,6 +211,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: s.tool_input.clone(), contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -213,6 +220,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: s.tool_name.clone(), display_name: s.display_name.clone(), intention: s.intention.clone(), + tool_input: s.tool_input.clone(), contributor: s.contributor.clone(), meta: s.meta.clone(), }, @@ -221,6 +229,7 @@ fn tool_call_meta(tc: &ToolCallState) -> ToolCallBase { tool_name: String::new(), display_name: String::new(), intention: None, + tool_input: None, contributor: None, meta: None, }, @@ -354,23 +363,15 @@ fn end_turn( } _ => Default::default(), }; - let tool_input = match &tc { - ToolCallState::Streaming(_) => None, - ToolCallState::PendingConfirmation(s) => s.tool_input.clone(), - ToolCallState::Running(s) => s.tool_input.clone(), - ToolCallState::AuthRequired(s) => s.tool_input.clone(), - ToolCallState::PendingResultConfirmation(s) => s.tool_input.clone(), - _ => None, - }; let cancelled = ToolCallCancelledState { tool_call_id: base.tool_call_id, tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input: base.tool_input, contributor: base.contributor, meta: base.meta, invocation_message, - tool_input, reason: ToolCallCancellationReason::Skipped, reason_message: None, user_suggestion: None, @@ -546,10 +547,10 @@ where tool_name: String::new(), display_name: String::new(), intention: None, + tool_input: None, contributor: None, meta: None, invocation_message: Default::default(), - tool_input: None, reason: ToolCallCancellationReason::Skipped, reason_message: None, user_suggestion: None, @@ -674,7 +675,7 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - return ReduceOutcome::NoOp; }; if let Some(title) = &a.changes.title { - chat.title = Some(title.clone()); + chat.title = title.clone(); } if let Some(status) = a.changes.status { chat.status = status; @@ -1239,13 +1240,16 @@ fn apply_turn_started(state: &mut ChatState, a: &ChatTurnStartedAction) -> Reduc fn apply_tool_call_delta(state: &mut ChatState, a: &ChatToolCallDeltaAction) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| match tc { ToolCallState::Streaming(mut s) => { - let current = s.partial_input.unwrap_or_default(); - s.partial_input = Some(current + &a.content); + if let Some(content) = &a.content { + let mut partial = s.partial_input.unwrap_or_default(); + partial.push_str(content); + s.partial_input = Some(partial); + } if let Some(meta) = &a.meta { s.meta = Some(meta.clone()); } - if let Some(im) = &a.invocation_message { - s.invocation_message = Some(im.clone()); + if let Some(invocation_message) = &a.invocation_message { + s.invocation_message = Some(invocation_message.clone()); } ToolCallState::Streaming(s) } @@ -1257,6 +1261,7 @@ fn apply_tool_call_ready(state: &mut ChatState, a: &ChatToolCallReadyAction) -> update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| { let mut base = tool_call_meta(&tc); base.intention = a.intention.clone().or(base.intention); + base.tool_input = a.tool_input.clone().or(base.tool_input); base.contributor = refine_tool_call_contributor(base.contributor, a.contributor.clone()); let meta = a.meta.clone().or(base.meta); let pending = match &tc { @@ -1273,10 +1278,10 @@ fn apply_tool_call_ready(state: &mut ChatState, a: &ChatToolCallReadyAction) -> tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input: base.tool_input, contributor: base.contributor, meta, invocation_message: a.invocation_message.clone(), - tool_input: a.tool_input.clone(), confirmed, selected_option: None, content: None, @@ -1287,13 +1292,10 @@ fn apply_tool_call_ready(state: &mut ChatState, a: &ChatToolCallReadyAction) -> tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input: base.tool_input, contributor: base.contributor, meta, invocation_message: a.invocation_message.clone(), - tool_input: a - .tool_input - .clone() - .or_else(|| pending.as_ref().and_then(|p| p.tool_input.clone())), confirmation_title: a.confirmation_title.clone().or_else(|| { pending.as_ref().and_then(|p| p.confirmation_title.clone()) }), @@ -1348,15 +1350,19 @@ fn apply_tool_call_confirmed( let invocation_message = s.invocation_message; let tool_input = s.tool_input; if a.approved { + let tool_input = match (a.edited_tool_input.clone(), tool_input) { + (Some(edited), Some(ToolInput::Inline(_))) => Some(ToolInput::Inline(edited)), + (_, tool_input) => tool_input, + }; ToolCallState::Running(ToolCallRunningState { tool_call_id, tool_name, display_name, intention, + tool_input, contributor, meta, invocation_message, - tool_input: a.edited_tool_input.clone().or(tool_input), confirmed: a.confirmed.unwrap_or(ToolCallConfirmationReason::NotNeeded), selected_option, content: None, @@ -1367,10 +1373,10 @@ fn apply_tool_call_confirmed( tool_name, display_name, intention, + tool_input, contributor, meta, invocation_message, - tool_input, reason: a.reason.unwrap_or(ToolCallCancellationReason::Denied), reason_message: a.reason_message.clone(), user_suggestion: a.user_suggestion.clone(), @@ -1443,10 +1449,10 @@ fn apply_tool_call_complete( tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input, contributor: base.contributor, meta, invocation_message, - tool_input, success: a.result.success, past_tense_message: a.result.past_tense_message.clone(), content, @@ -1461,10 +1467,10 @@ fn apply_tool_call_complete( tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input, contributor: base.contributor, meta, invocation_message, - tool_input, success: a.result.success, past_tense_message: a.result.past_tense_message.clone(), content, @@ -1491,10 +1497,10 @@ fn apply_tool_call_result_confirmed( tool_name: s.tool_name, display_name: s.display_name, intention: s.intention, + tool_input: s.tool_input, contributor: s.contributor, meta: a.meta.clone().or(s.meta), invocation_message: s.invocation_message, - tool_input: s.tool_input, success: s.success, past_tense_message: s.past_tense_message, content: s.content, @@ -1509,10 +1515,10 @@ fn apply_tool_call_result_confirmed( tool_name: s.tool_name, display_name: s.display_name, intention: s.intention, + tool_input: s.tool_input, contributor: s.contributor, meta: a.meta.clone().or(s.meta), invocation_message: s.invocation_message, - tool_input: s.tool_input, reason: ToolCallCancellationReason::ResultDenied, reason_message: None, user_suggestion: None, @@ -1555,10 +1561,10 @@ fn apply_tool_call_auth_required( tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input: s.tool_input, contributor: s.contributor, meta, invocation_message: s.invocation_message, - tool_input: s.tool_input, confirmed: s.confirmed, selected_option: s.selected_option, status: ToolCallStatus::AuthRequired, @@ -1584,10 +1590,10 @@ fn apply_tool_call_auth_resolved( tool_name: base.tool_name, display_name: base.display_name, intention: base.intention, + tool_input: s.tool_input, contributor: s.contributor, meta, invocation_message: s.invocation_message, - tool_input: s.tool_input, confirmed: s.confirmed, selected_option: s.selected_option, content: s.content, @@ -1990,7 +1996,7 @@ mod tests { fn empty_chat(resource: &str) -> ChatState { ChatState { resource: resource.to_string(), - title: None, + title: String::new(), status: SessionStatus::Idle.bits(), activity: None, modified_at: "1970-01-01T00:00:00.000Z".into(), @@ -2086,7 +2092,7 @@ mod tests { let chat = ChatSummary { resource: "copilot:/s1/chat/1".into(), - title: Some("c1".into()), + title: "c1".into(), status: SessionStatus::Idle.bits(), activity: None, modified_at: "1970-01-01T00:00:00.000Z".into(), @@ -2116,7 +2122,7 @@ mod tests { apply_action_to_session(&mut s, &updated), ReduceOutcome::Applied ); - assert_eq!(s.chats[0].title.as_deref(), Some("renamed")); + assert_eq!(s.chats[0].title, "renamed"); assert_eq!(s.chats[0].modified_at, "1970-01-01T00:00:09.999Z"); s.default_chat = Some(chat.resource.clone()); diff --git a/docs/.changes/20260730-chat-title-optional.json b/docs/.changes/20260730-chat-title-optional.json deleted file mode 100644 index d5c50250..00000000 --- a/docs/.changes/20260730-chat-title-optional.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "changed", - "message": "`ChatState.title` and `ChatSummary.title` are now optional. An absent title means the chat has no title of its own and consumers should fall back to the owning session's title — the case for a session's default chat, and for any chat between creation and the host assigning it a title. Producers must omit the field rather than sending an empty string to mean \"inherit\"." -} diff --git a/docs/.changes/20260730-create-chat-title.json b/docs/.changes/20260730-create-chat-title.json deleted file mode 100644 index 2c0d836d..00000000 --- a/docs/.changes/20260730-create-chat-title.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "added", - "message": "`CreateChatParams.title`, so a client can name a chat at creation instead of creating it untitled and following up with a `session/chatUpdated`." -} diff --git a/docs/specification/chat-channel.md b/docs/specification/chat-channel.md index 0f94f13b..a2c8d020 100644 --- a/docs/specification/chat-channel.md +++ b/docs/specification/chat-channel.md @@ -37,17 +37,6 @@ older turn). Clients MAY periodically sync their local input state into the draft by dispatching [`chat/draftChanged`](/reference/chat#actions). Eager syncing is not required — clients SHOULD debounce and MAY sync only at convenient points (for example, on blur). When presenting input UI for an existing chat, clients SHOULD use any `draft` to initialize their input state. Dispatch `chat/draftChanged` with no `draft` to clear it once the message is sent. -### Chat title - -`ChatState.title` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat has no title of its own and consumers SHOULD fall back to the owning session's [`title`](/reference/session#sessionsummary). - -Two situations produce an untitled chat: - -- A session's **default chat**, which usually has no identity separate from the session itself. Hosts commonly leave it untitled for the life of the session, and give it a title only once the session holds more than one chat and the default chat needs its own label in a list. -- **Any newly created chat**, between `createChat` and the point where a title is known. Titles are typically derived from the conversation, so they are not available at creation. A client that already knows the name — for a fork, a side chat, or a tool-spawned worker — MAY supply [`CreateChatParams.title`](/reference/chat#createchat) to skip the untitled window; otherwise the host assigns one later with [`session/chatUpdated`](/reference/session#actions). - -Producers MUST NOT use an empty string to mean "inherit" — omit the field instead, so that a deliberately blank title and an absent one remain distinguishable, and so a host can tell which chats are still tracking the session's title. - ### Per-chat working directory `ChatState.workingDirectory` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat inherits the session's [`workingDirectory`](/reference/session#sessionsummary). Hosts MAY set a per-chat working directory to give individual chats their own filesystem context — for example, allocating a separate git worktree per chat so multiple chats in the same session can make independent edits that the orchestrating chat later merges back. The session-level `workingDirectory` is then the default/primary location for chats that do not override it. diff --git a/types/channels-chat/commands.ts b/types/channels-chat/commands.ts index d7ce6a80..419e3c24 100644 --- a/types/channels-chat/commands.ts +++ b/types/channels-chat/commands.ts @@ -89,16 +89,6 @@ export interface CreateChatParams extends BaseParams { chat: URI; /** Optional initial message for the new chat. */ initialMessage?: Message; - /** - * Optional title for the new chat. - * - * Lets a client name a chat it already has a name for — a fork, a side chat, - * or a tool-spawned worker — instead of creating it untitled and following up - * with a `session/chatUpdated`. When omitted the chat starts with no title of - * its own and consumers fall back to the session's title until the host - * assigns one (see {@link ChatState.title}). - */ - title?: string; /** * Optional source chat and source turn. * diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 32af67da..0c6bcb7a 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -39,16 +39,8 @@ export interface ChatState { // ── Summary fields (denormalized from ChatSummary) ───────────────── /** Chat URI */ resource: URI; - /** - * Chat title. - * - * Absent means the chat has no title of its own and consumers SHOULD fall - * back to the owning {@link SessionState.title | session's title}. This is - * the normal case for a session's default chat, which typically has no - * identity separate from the session itself. Producers MUST NOT use an empty - * string to mean "inherit" — omit the field instead. - */ - title?: string; + /** Chat title */ + title: string; /** Current chat status (reuses SessionStatus shape) */ status: SessionStatus; /** Human-readable description of what the chat is currently doing */ @@ -128,12 +120,8 @@ export interface ChatState { export interface ChatSummary { /** Chat URI */ resource: URI; - /** - * Chat title. Absent means the chat has no title of its own and consumers - * SHOULD fall back to the owning session's title — see - * {@link ChatState.title} for the full semantics. - */ - title?: string; + /** Chat title */ + title: string; /** Current chat status (reuses SessionStatus shape) */ status: SessionStatus; /** Human-readable description of what the chat is currently doing */ From 6f51aa42b5d9985c0e4f56592ccbdb398e836d21 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 31 Jul 2026 14:59:27 -0700 Subject: [PATCH 4/6] regenerate after merge --- clients/go/ahptypes/commands.generated.go | 8 -------- clients/go/ahptypes/state.generated.go | 16 ++++----------- .../generated/Actions.generated.kt | 4 +--- .../generated/Commands.generated.kt | 10 ---------- .../generated/State.generated.kt | 16 ++++----------- clients/rust/crates/ahp-types/src/actions.rs | 4 +--- clients/rust/crates/ahp-types/src/commands.rs | 9 --------- clients/rust/crates/ahp-types/src/state.rs | 18 ++++------------- .../Generated/Actions.generated.swift | 4 +--- .../Generated/Commands.generated.swift | 10 ---------- .../Generated/State.generated.swift | 20 ++++++------------- schema/actions.schema.json | 8 +++++--- schema/commands.schema.json | 12 +++++------ schema/errors.schema.json | 12 +++++------ schema/notifications.schema.json | 6 ++++-- schema/state.schema.json | 6 ++++-- 16 files changed, 44 insertions(+), 119 deletions(-) diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index da218a09..136d4add 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -409,14 +409,6 @@ type CreateChatParams struct { Chat URI `json:"chat"` // Optional initial message for the new chat. InitialMessage *Message `json:"initialMessage,omitempty"` - // Optional title for the new chat. - // - // Lets a client name a chat it already has a name for — a fork, a side chat, - // or a tool-spawned worker — instead of creating it untitled and following up - // with a `session/chatUpdated`. When omitted the chat starts with no title of - // its own and consumers fall back to the session's title until the host - // assigns one (see {@link ChatState.title}). - Title *string `json:"title,omitempty"` // Optional source chat and source turn. // // The source chat MUST belong to this session. Clients MUST only request diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 24bee6d9..c7ce63c1 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -1083,14 +1083,8 @@ type ChangesSummary struct { type ChatState struct { // Chat URI Resource URI `json:"resource"` - // Chat title. - // - // Absent means the chat has no title of its own and consumers SHOULD fall - // back to the owning {@link SessionState.title | session's title}. This is - // the normal case for a session's default chat, which typically has no - // identity separate from the session itself. Producers MUST NOT use an empty - // string to mean "inherit" — omit the field instead. - Title *string `json:"title,omitempty"` + // Chat title + Title string `json:"title"` // Current chat status (reuses SessionStatus shape) Status SessionStatus `json:"status"` // Human-readable description of what the chat is currently doing @@ -1154,10 +1148,8 @@ type ChatState struct { type ChatSummary struct { // Chat URI Resource URI `json:"resource"` - // Chat title. Absent means the chat has no title of its own and consumers - // SHOULD fall back to the owning session's title — see - // {@link ChatState.title} for the full semantics. - Title *string `json:"title,omitempty"` + // Chat title + Title string `json:"title"` // Current chat status (reuses SessionStatus shape) Status SessionStatus `json:"status"` // Human-readable description of what the chat is currently doing diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt index f5e65abb..cde9f9ed 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt @@ -1489,9 +1489,7 @@ data class PartialChatSummary( */ val resource: String? = null, /** - * Chat title. Absent means the chat has no title of its own and consumers - * SHOULD fall back to the owning session's title — see - * {@link ChatState.title} for the full semantics. + * Chat title */ val title: String? = null, /** diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index 27f4cb06..acd08cd7 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -575,16 +575,6 @@ data class CreateChatParams( * Optional initial message for the new chat. */ val initialMessage: Message? = null, - /** - * Optional title for the new chat. - * - * Lets a client name a chat it already has a name for — a fork, a side chat, - * or a tool-spawned worker — instead of creating it untitled and following up - * with a `session/chatUpdated`. When omitted the chat starts with no title of - * its own and consumers fall back to the session's title until the host - * assigns one (see {@link ChatState.title}). - */ - val title: String? = null, /** * Optional source chat and source turn. * diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index bfbfe7b6..bd11ce9c 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -1165,15 +1165,9 @@ data class ChatState( */ val resource: String, /** - * Chat title. - * - * Absent means the chat has no title of its own and consumers SHOULD fall - * back to the owning {@link SessionState.title | session's title}. This is - * the normal case for a session's default chat, which typically has no - * identity separate from the session itself. Producers MUST NOT use an empty - * string to mean "inherit" — omit the field instead. + * Chat title */ - val title: String? = null, + val title: String, /** * Current chat status (reuses SessionStatus shape) */ @@ -1265,11 +1259,9 @@ data class ChatSummary( */ val resource: String, /** - * Chat title. Absent means the chat has no title of its own and consumers - * SHOULD fall back to the owning session's title — see - * {@link ChatState.title} for the full semantics. + * Chat title */ - val title: String? = null, + val title: String, /** * Current chat status (reuses SessionStatus shape) */ diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index 4d48da97..849a339c 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -1745,9 +1745,7 @@ pub struct PartialChatSummary { /// Chat URI #[serde(default, skip_serializing_if = "Option::is_none")] pub resource: Option, - /// Chat title. Absent means the chat has no title of its own and consumers - /// SHOULD fall back to the owning session's title — see - /// {@link ChatState.title} for the full semantics. + /// Chat title #[serde(default, skip_serializing_if = "Option::is_none")] pub title: Option, /// Current chat status (reuses SessionStatus shape) diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 485cc04f..57206385 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -507,15 +507,6 @@ pub struct CreateChatParams { /// Optional initial message for the new chat. #[serde(default, skip_serializing_if = "Option::is_none")] pub initial_message: Option, - /// Optional title for the new chat. - /// - /// Lets a client name a chat it already has a name for — a fork, a side chat, - /// or a tool-spawned worker — instead of creating it untitled and following up - /// with a `session/chatUpdated`. When omitted the chat starts with no title of - /// its own and consumers fall back to the session's title until the host - /// assigns one (see {@link ChatState.title}). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, /// Optional source chat and source turn. /// /// The source chat MUST belong to this session. Clients MUST only request diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 840cf067..27ccff52 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -1009,15 +1009,8 @@ pub struct PendingMessage { pub struct ChatState { /// Chat URI pub resource: Uri, - /// Chat title. - /// - /// Absent means the chat has no title of its own and consumers SHOULD fall - /// back to the owning {@link SessionState.title | session's title}. This is - /// the normal case for a session's default chat, which typically has no - /// identity separate from the session itself. Producers MUST NOT use an empty - /// string to mean "inherit" — omit the field instead. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, + /// Chat title + pub title: String, /// Current chat status (reuses SessionStatus shape) pub status: u32, /// Human-readable description of what the chat is currently doing @@ -1093,11 +1086,8 @@ pub struct ChatState { pub struct ChatSummary { /// Chat URI pub resource: Uri, - /// Chat title. Absent means the chat has no title of its own and consumers - /// SHOULD fall back to the owning session's title — see - /// {@link ChatState.title} for the full semantics. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, + /// Chat title + pub title: String, /// Current chat status (reuses SessionStatus shape) pub status: u32, /// Human-readable description of what the chat is currently doing diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 3e98b9bd..da101cfd 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -1972,9 +1972,7 @@ public struct ResourceWatchChangedAction: Codable, Sendable { public struct PartialChatSummary: Codable, Sendable { /// Chat URI public var resource: String? - /// Chat title. Absent means the chat has no title of its own and consumers - /// SHOULD fall back to the owning session's title — see - /// {@link ChatState.title} for the full semantics. + /// Chat title public var title: String? /// Current chat status (reuses SessionStatus shape) public var status: SessionStatus? diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 3092961c..68adcab1 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -540,14 +540,6 @@ public struct CreateChatParams: Codable, Sendable { public var chat: String /// Optional initial message for the new chat. public var initialMessage: Message? - /// Optional title for the new chat. - /// - /// Lets a client name a chat it already has a name for — a fork, a side chat, - /// or a tool-spawned worker — instead of creating it untitled and following up - /// with a `session/chatUpdated`. When omitted the chat starts with no title of - /// its own and consumers fall back to the session's title until the host - /// assigns one (see {@link ChatState.title}). - public var title: String? /// Optional source chat and source turn. /// /// The source chat MUST belong to this session. Clients MUST only request @@ -577,14 +569,12 @@ public struct CreateChatParams: Codable, Sendable { channel: String, chat: String, initialMessage: Message? = nil, - title: String? = nil, source: ChatSource? = nil, workingDirectories: [String]? = nil ) { self.channel = channel self.chat = chat self.initialMessage = initialMessage - self.title = title self.source = source self.workingDirectories = workingDirectories } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 022a5b67..3579c608 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -892,14 +892,8 @@ public struct PendingMessage: Codable, Sendable { public struct ChatState: Codable, Sendable { /// Chat URI public var resource: String - /// Chat title. - /// - /// Absent means the chat has no title of its own and consumers SHOULD fall - /// back to the owning {@link SessionState.title | session's title}. This is - /// the normal case for a session's default chat, which typically has no - /// identity separate from the session itself. Producers MUST NOT use an empty - /// string to mean "inherit" — omit the field instead. - public var title: String? + /// Chat title + public var title: String /// Current chat status (reuses SessionStatus shape) public var status: SessionStatus /// Human-readable description of what the chat is currently doing @@ -976,7 +970,7 @@ public struct ChatState: Codable, Sendable { public init( resource: String, - title: String? = nil, + title: String, status: SessionStatus, activity: String? = nil, modifiedAt: String, @@ -1012,10 +1006,8 @@ public struct ChatState: Codable, Sendable { public struct ChatSummary: Codable, Sendable { /// Chat URI public var resource: String - /// Chat title. Absent means the chat has no title of its own and consumers - /// SHOULD fall back to the owning session's title — see - /// {@link ChatState.title} for the full semantics. - public var title: String? + /// Chat title + public var title: String /// Current chat status (reuses SessionStatus shape) public var status: SessionStatus /// Human-readable description of what the chat is currently doing @@ -1036,7 +1028,7 @@ public struct ChatSummary: Codable, Sendable { public init( resource: String, - title: String? = nil, + title: String, status: SessionStatus, activity: String? = nil, modifiedAt: String, diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 698ea978..ba045f66 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -210,7 +210,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -4481,7 +4481,7 @@ }, "title": { "type": "string", - "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -4548,6 +4548,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt", "turns" @@ -4563,7 +4564,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -4595,6 +4596,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt" ] diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 4a27b2d8..3f1c0373 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1162,10 +1162,6 @@ "$ref": "#/$defs/Message", "description": "Optional initial message for the new chat." }, - "title": { - "type": "string", - "description": "Optional title for the new chat.\n\nLets a client name a chat it already has a name for — a fork, a side chat,\nor a tool-spawned worker — instead of creating it untitled and following up\nwith a `session/chatUpdated`. When omitted the chat starts with no title of\nits own and consumers fall back to the session's title until the host\nassigns one (see {@link ChatState.title})." - }, "source": { "$ref": "#/$defs/ChatSource", "description": "Optional source chat and source turn.\n\nThe source chat MUST belong to this session. Clients MUST only request\n`kind: \"fork\"` when the selected agent advertises\n`capabilities.multipleChats.fork`, and `kind: \"sideChat\"` when the\nselected agent advertises `capabilities.multipleChats.sideChat`. Both\nsource forms carry a stable top-level `turnId`. Forks target completed\nturns. Side chats also carry a stable `turnId`, which the host resolves\nagainst the source chat's current active turn or retained history. If it\nresolves to the active turn, the host snapshots the currently available\npartial response when accepting `createChat`. When\n`source.kind === \"sideChat\"` and `source.selection` is present, the host\nalso snapshots and preserves that exact selected text in the created chat's\norigin; any `responsePartId` there is provenance only, not a live range." @@ -3654,7 +3650,7 @@ }, "title": { "type": "string", - "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -3721,6 +3717,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt", "turns" @@ -3736,7 +3733,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -3768,6 +3765,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt" ] @@ -6450,7 +6448,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index f9417c39..07219176 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -2386,7 +2386,7 @@ }, "title": { "type": "string", - "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2453,6 +2453,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt", "turns" @@ -2468,7 +2469,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2500,6 +2501,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt" ] @@ -6134,10 +6136,6 @@ "$ref": "#/$defs/Message", "description": "Optional initial message for the new chat." }, - "title": { - "type": "string", - "description": "Optional title for the new chat.\n\nLets a client name a chat it already has a name for — a fork, a side chat,\nor a tool-spawned worker — instead of creating it untitled and following up\nwith a `session/chatUpdated`. When omitted the chat starts with no title of\nits own and consumers fall back to the session's title until the host\nassigns one (see {@link ChatState.title})." - }, "source": { "$ref": "#/$defs/ChatSource", "description": "Optional source chat and source turn.\n\nThe source chat MUST belong to this session. Clients MUST only request\n`kind: \"fork\"` when the selected agent advertises\n`capabilities.multipleChats.fork`, and `kind: \"sideChat\"` when the\nselected agent advertises `capabilities.multipleChats.sideChat`. Both\nsource forms carry a stable top-level `turnId`. Forks target completed\nturns. Side chats also carry a stable `turnId`, which the host resolves\nagainst the source chat's current active turn or retained history. If it\nresolves to the active turn, the host snapshots the currently available\npartial response when accepting `createChat`. When\n`source.kind === \"sideChat\"` and `source.selection` is present, the host\nalso snapshots and preserves that exact selected text in the created chat's\norigin; any `responsePartId` there is provenance only, not a live range." @@ -7477,7 +7475,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 207889d0..6d35ab1d 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -2549,7 +2549,7 @@ }, "title": { "type": "string", - "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2616,6 +2616,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt", "turns" @@ -2631,7 +2632,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2663,6 +2664,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt" ] diff --git a/schema/state.schema.json b/schema/state.schema.json index 2b2378db..2c8a21a4 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -2297,7 +2297,7 @@ }, "title": { "type": "string", - "description": "Chat title.\n\nAbsent means the chat has no title of its own and consumers SHOULD fall\nback to the owning {@link SessionState.title | session's title}. This is\nthe normal case for a session's default chat, which typically has no\nidentity separate from the session itself. Producers MUST NOT use an empty\nstring to mean \"inherit\" — omit the field instead." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2364,6 +2364,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt", "turns" @@ -2379,7 +2380,7 @@ }, "title": { "type": "string", - "description": "Chat title. Absent means the chat has no title of its own and consumers\nSHOULD fall back to the owning session's title — see\n{@link ChatState.title} for the full semantics." + "description": "Chat title" }, "status": { "$ref": "#/$defs/SessionStatus", @@ -2411,6 +2412,7 @@ }, "required": [ "resource", + "title", "status", "modifiedAt" ] From 34cf2689157f44fd0639ddc18c5efc5638cc608f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 31 Jul 2026 15:23:34 -0700 Subject: [PATCH 5/6] revert the toolCall doc comment; out of scope for this PR --- clients/go/ahptypes/state.generated.go | 7 ------- .../agenthostprotocol/generated/State.generated.kt | 7 ------- clients/rust/crates/ahp-types/src/state.rs | 7 ------- .../AgentHostProtocol/Generated/State.generated.swift | 7 ------- schema/actions.schema.json | 2 +- schema/commands.schema.json | 2 +- schema/errors.schema.json | 2 +- schema/notifications.schema.json | 2 +- schema/state.schema.json | 2 +- types/channels-session/state.ts | 7 ------- 10 files changed, 5 insertions(+), 40 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index c7ce63c1..78c43dd2 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -940,13 +940,6 @@ type SessionToolClientExecutionRequest struct { // The running tool call the session wants the owning client to execute. The // host only ever populates this with a {@link ToolCallRunningState} (i.e. a // {@link ToolCallState} in `running` status). - // - // Declared as the full union rather than narrowed to - // `ToolCallRunningState` because the per-language generators emit - // tagged-union *variants* without their discriminant (the `status` tag is - // written by the enum wrapper). Referencing the variant directly would drop - // `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still - // emitted it. Narrowing this needs generator support first. ToolCall ToolCallState `json:"toolCall"` } diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index bd11ce9c..840af62b 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -1554,13 +1554,6 @@ data class SessionToolClientExecutionRequest( * The running tool call the session wants the owning client to execute. The * host only ever populates this with a {@link ToolCallRunningState} (i.e. a * {@link ToolCallState} in `running` status). - * - * Declared as the full union rather than narrowed to - * `ToolCallRunningState` because the per-language generators emit - * tagged-union *variants* without their discriminant (the `status` tag is - * written by the enum wrapper). Referencing the variant directly would drop - * `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still - * emitted it. Narrowing this needs generator support first. */ val toolCall: ToolCallState ) diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 27ccff52..b02d4257 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -1360,13 +1360,6 @@ pub struct SessionToolClientExecutionRequest { /// The running tool call the session wants the owning client to execute. The /// host only ever populates this with a {@link ToolCallRunningState} (i.e. a /// {@link ToolCallState} in `running` status). - /// - /// Declared as the full union rather than narrowed to - /// `ToolCallRunningState` because the per-language generators emit - /// tagged-union *variants* without their discriminant (the `status` tag is - /// written by the enum wrapper). Referencing the variant directly would drop - /// `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still - /// emitted it. Narrowing this needs generator support first. pub tool_call: ToolCallState, } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 3579c608..4c3adb9b 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -1337,13 +1337,6 @@ public struct SessionToolClientExecutionRequest: Codable, Sendable { /// The running tool call the session wants the owning client to execute. The /// host only ever populates this with a {@link ToolCallRunningState} (i.e. a /// {@link ToolCallState} in `running` status). - /// - /// Declared as the full union rather than narrowed to - /// `ToolCallRunningState` because the per-language generators emit - /// tagged-union *variants* without their discriminant (the `status` tag is - /// written by the enum wrapper). Referencing the variant directly would drop - /// `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still - /// emitted it. Narrowing this needs generator support first. public var toolCall: ToolCallState public init( diff --git a/schema/actions.schema.json b/schema/actions.schema.json index ba045f66..f5d965f1 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -3113,7 +3113,7 @@ }, "toolCall": { "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." } }, "required": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 3f1c0373..9ab17160 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -2282,7 +2282,7 @@ }, "toolCall": { "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." } }, "required": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 07219176..77945879 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -1018,7 +1018,7 @@ }, "toolCall": { "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." } }, "required": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 6d35ab1d..e95f38c8 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -1181,7 +1181,7 @@ }, "toolCall": { "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." } }, "required": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 2c8a21a4..07be2112 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -929,7 +929,7 @@ }, "toolCall": { "$ref": "#/$defs/ToolCallState", - "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status).\n\nDeclared as the full union rather than narrowed to\n`ToolCallRunningState` because the per-language generators emit\ntagged-union *variants* without their discriminant (the `status` tag is\nwritten by the enum wrapper). Referencing the variant directly would drop\n`status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still\nemitted it. Narrowing this needs generator support first." + "description": "The running tool call the session wants the owning client to execute. The\nhost only ever populates this with a {@link ToolCallRunningState} (i.e. a\n{@link ToolCallState} in `running` status)." } }, "required": [ diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 71303fed..f3d3f51e 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -338,13 +338,6 @@ export interface SessionToolClientExecutionRequest extends SessionInputRequestBa * The running tool call the session wants the owning client to execute. The * host only ever populates this with a {@link ToolCallRunningState} (i.e. a * {@link ToolCallState} in `running` status). - * - * Declared as the full union rather than narrowed to - * `ToolCallRunningState` because the per-language generators emit - * tagged-union *variants* without their discriminant (the `status` tag is - * written by the enum wrapper). Referencing the variant directly would drop - * `status` from the wire in Rust/Kotlin/Swift/Go while TypeScript still - * emitted it. Narrowing this needs generator support first. */ toolCall: ToolCallState; } From a3c127031d229e2770a83893b3b5864b1a65f3ef Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 31 Jul 2026 15:26:10 -0700 Subject: [PATCH 6/6] swift/docs: complete the ResourceResponsePart rename Two hand-written references the generators don't reach: the AHPApp sample app (which imports AgentHostProtocol and so would no longer compile against the renamed type) and the state-model guide. Neither is covered by CI, which is why the swift job stayed green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- clients/swift/AHPApp/AHPApp/Views/ResponsePartView.swift | 4 ++-- docs/guide/state-model.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/swift/AHPApp/AHPApp/Views/ResponsePartView.swift b/clients/swift/AHPApp/AHPApp/Views/ResponsePartView.swift index fd59358d..21938d07 100644 --- a/clients/swift/AHPApp/AHPApp/Views/ResponsePartView.swift +++ b/clients/swift/AHPApp/AHPApp/Views/ResponsePartView.swift @@ -733,7 +733,7 @@ struct TerminalToolResultView: View { // MARK: - ContentRefView struct ContentRefView: View { - let ref: ResourceReponsePart + let ref: ResourceResponsePart var body: some View { HStack { @@ -892,7 +892,7 @@ struct ContentRefView: View { // Content Ref Text("Content Ref").font(.caption.bold()).foregroundStyle(.secondary) - ContentRefView(ref: ResourceReponsePart( + ContentRefView(ref: ResourceResponsePart( uri: "file:///Users/me/project/README.md", contentType: "text/markdown", kind: .contentRef diff --git a/docs/guide/state-model.md b/docs/guide/state-model.md index 642952e7..2f55c06c 100644 --- a/docs/guide/state-model.md +++ b/docs/guide/state-model.md @@ -323,7 +323,7 @@ ToolCallResponsePart { } // Response-part wrapper around a large content reference -ResourceReponsePart { +ResourceResponsePart { kind: 'contentRef' uri: string sizeHint?: number