From 174d22a53ba2dea5eb9dd8716aab204d5ef2feb5 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 21 Jul 2026 09:47:18 -0700 Subject: [PATCH 01/11] ToolResultTerminalOutputContent for terminal output streaming --- clients/go/ahptypes/state.generated.go | 18 +++++++++ .../generated/State.generated.kt | 14 +++++++ clients/rust/crates/ahp-types/src/state.rs | 15 ++++++++ .../Generated/State.generated.swift | 19 ++++++++++ docs/.changes/20260717-terminal-output.json | 5 +++ schema/actions.schema.json | 22 ++++++++++- schema/commands.schema.json | 22 ++++++++++- schema/errors.schema.json | 22 ++++++++++- schema/notifications.schema.json | 22 ++++++++++- schema/state.schema.json | 22 ++++++++++- scripts/generate-go.ts | 2 + scripts/generate-kotlin.ts | 6 ++- scripts/generate-rust.ts | 2 + scripts/generate-swift.ts | 7 +++- types/channels-chat/state.ts | 17 +++++++++ types/index.ts | 1 + .../030-terminal-output-content.json | 38 +++++++++++++++++++ 17 files changed, 247 insertions(+), 7 deletions(-) create mode 100644 docs/.changes/20260717-terminal-output.json create mode 100644 types/test-cases/round-trips/030-terminal-output-content.json diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 8520b664..728ac327 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -291,6 +291,7 @@ const ( ToolResultContentTypeResource ToolResultContentType = "resource" ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" ToolResultContentTypeTerminal ToolResultContentType = "terminal" + ToolResultContentTypeTerminalOutput ToolResultContentType = "terminalOutput" ToolResultContentTypeTerminalComplete ToolResultContentType = "terminalComplete" ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) @@ -2159,6 +2160,16 @@ type ToolResultTerminalContent struct { Title string `json:"title"` } +// An inline output snapshot from a running terminal-style tool call. +// +// This content may accompany a {@link ToolResultTerminalContent} block but +// does not require a terminal resource. +type ToolResultTerminalOutputContent struct { + Type ToolResultContentType `json:"type"` + // Output in this snapshot + Output string `json:"output"` +} + // Record of a command executed by a terminal-style tool (e.g. a shell tool), // appended to the tool result when the command exits. // @@ -4010,6 +4021,7 @@ func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} func (*ToolResultResourceContent) isToolResultContent() {} func (*ToolResultFileEditContent) isToolResultContent() {} func (*ToolResultTerminalContent) isToolResultContent() {} +func (*ToolResultTerminalOutputContent) isToolResultContent() {} func (*ToolResultTerminalCompleteContent) isToolResultContent() {} func (*ToolResultSubagentContent) isToolResultContent() {} @@ -4057,6 +4069,12 @@ func (u *ToolResultContent) UnmarshalJSON(data []byte) error { return err } u.Value = &value + case "terminalOutput": + var value ToolResultTerminalOutputContent + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value case "terminalComplete": var value ToolResultTerminalCompleteContent if err := json.Unmarshal(data, &value); err != nil { 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 a84af21f..a8226a6e 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 @@ -512,6 +512,8 @@ enum class ToolResultContentType { FILE_EDIT, @SerialName("terminal") TERMINAL, + @SerialName("terminalOutput") + TERMINAL_OUTPUT, @SerialName("terminalComplete") TERMINAL_COMPLETE, @SerialName("subagent") @@ -3022,6 +3024,15 @@ data class ToolResultTerminalContent( val title: String ) +@Serializable +data class ToolResultTerminalOutputContent( + val type: ToolResultContentType, + /** + * Output in this snapshot + */ + val output: String +) + @Serializable data class ToolResultTerminalCompleteContent( val type: ToolResultContentType, @@ -5465,6 +5476,7 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent + @JvmInline value class TerminalOutput(val value: ToolResultTerminalOutputContent) : ToolResultContent @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent @@ -5495,6 +5507,7 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) + "terminalOutput" -> ToolResultContent.TerminalOutput(input.json.decodeFromJsonElement(ToolResultTerminalOutputContent.serializer(), element)) "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) @@ -5510,6 +5523,7 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) + is ToolResultContent.TerminalOutput -> output.json.encodeToJsonElement(ToolResultTerminalOutputContent.serializer(), value.value) is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 1bc1f663..710b29de 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -407,6 +407,8 @@ pub enum ToolResultContentType { FileEdit, #[serde(rename = "terminal")] Terminal, + #[serde(rename = "terminalOutput")] + TerminalOutput, #[serde(rename = "terminalComplete")] TerminalComplete, #[serde(rename = "subagent")] @@ -2676,6 +2678,17 @@ pub struct ToolResultTerminalContent { pub title: String, } +/// An inline output snapshot from a running terminal-style tool call. +/// +/// This content may accompany a {@link ToolResultTerminalContent} block but +/// does not require a terminal resource. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ToolResultTerminalOutputContent { + /// Output in this snapshot + pub output: String, +} + /// Record of a command executed by a terminal-style tool (e.g. a shell tool), /// appended to the tool result when the command exits. /// @@ -4286,6 +4299,8 @@ pub enum ToolResultContent { FileEdit(ToolResultFileEditContent), #[serde(rename = "terminal")] Terminal(ToolResultTerminalContent), + #[serde(rename = "terminalOutput")] + TerminalOutput(ToolResultTerminalOutputContent), #[serde(rename = "terminalComplete")] TerminalComplete(ToolResultTerminalCompleteContent), #[serde(rename = "subagent")] diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index b1f7d168..4fe0ecd6 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -267,6 +267,7 @@ public enum ToolResultContentType: String, Codable, Sendable { case resource = "resource" case fileEdit = "fileEdit" case terminal = "terminal" + case terminalOutput = "terminalOutput" case terminalComplete = "terminalComplete" case subagent = "subagent" } @@ -3232,6 +3233,20 @@ public struct ToolResultTerminalContent: Codable, Sendable { } } +public struct ToolResultTerminalOutputContent: Codable, Sendable { + public var type: ToolResultContentType + /// Output in this snapshot + public var output: String + + public init( + type: ToolResultContentType, + output: String + ) { + self.type = type + self.output = output + } +} + public struct ToolResultTerminalCompleteContent: Codable, Sendable { public var type: ToolResultContentType /// URI of the `ahp-terminal:` channel that carried live output for this @@ -5766,6 +5781,7 @@ public enum ToolResultContent: Codable, Sendable { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) + case terminalOutput(ToolResultTerminalOutputContent) case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved @@ -5790,6 +5806,8 @@ public enum ToolResultContent: Codable, Sendable { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) + case "terminalOutput": + self = .terminalOutput(try ToolResultTerminalOutputContent(from: decoder)) case "terminalComplete": self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": @@ -5812,6 +5830,7 @@ public enum ToolResultContent: Codable, Sendable { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) + case .terminalOutput(let v): try v.encode(to: encoder) case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) diff --git a/docs/.changes/20260717-terminal-output.json b/docs/.changes/20260717-terminal-output.json new file mode 100644 index 00000000..15ef2c85 --- /dev/null +++ b/docs/.changes/20260717-terminal-output.json @@ -0,0 +1,5 @@ +{ + "type": "added", + "message": "`ToolResultTerminalOutputContent` for cumulative live output snapshots from terminal-style tool calls.", + "issues": [323] +} diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 795cc5b9..fab5019b 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6305,6 +6305,23 @@ "title" ] }, + "ToolResultTerminalOutputContent": { + "type": "object", + "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", + "properties": { + "type": { + "const": "terminalOutput" + }, + "output": { + "type": "string", + "description": "Output in this snapshot" + } + }, + "required": [ + "type", + "output" + ] + }, "ToolResultTerminalCompleteContent": { "type": "object", "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", @@ -7256,6 +7273,9 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, + { + "$ref": "#/$defs/ToolResultTerminalOutputContent" + }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -7263,7 +7283,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 0335c18e..5f511494 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5511,6 +5511,23 @@ "title" ] }, + "ToolResultTerminalOutputContent": { + "type": "object", + "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", + "properties": { + "type": { + "const": "terminalOutput" + }, + "output": { + "type": "string", + "description": "Output in this snapshot" + } + }, + "required": [ + "type", + "output" + ] + }, "ToolResultTerminalCompleteContent": { "type": "object", "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", @@ -8887,6 +8904,9 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, + { + "$ref": "#/$defs/ToolResultTerminalOutputContent" + }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -8894,7 +8914,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 468b7625..b2592af8 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4295,6 +4295,23 @@ "title" ] }, + "ToolResultTerminalOutputContent": { + "type": "object", + "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", + "properties": { + "type": { + "const": "terminalOutput" + }, + "output": { + "type": "string", + "description": "Output in this snapshot" + } + }, + "required": [ + "type", + "output" + ] + }, "ToolResultTerminalCompleteContent": { "type": "object", "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", @@ -6636,6 +6653,9 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, + { + "$ref": "#/$defs/ToolResultTerminalOutputContent" + }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -6643,7 +6663,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 96351393..4d0d8b9c 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4455,6 +4455,23 @@ "title" ] }, + "ToolResultTerminalOutputContent": { + "type": "object", + "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", + "properties": { + "type": { + "const": "terminalOutput" + }, + "output": { + "type": "string", + "description": "Output in this snapshot" + } + }, + "required": [ + "type", + "output" + ] + }, "ToolResultTerminalCompleteContent": { "type": "object", "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", @@ -5499,6 +5516,9 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, + { + "$ref": "#/$defs/ToolResultTerminalOutputContent" + }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -5506,7 +5526,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 7ff9dcb3..512dffa6 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4206,6 +4206,23 @@ "title" ] }, + "ToolResultTerminalOutputContent": { + "type": "object", + "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", + "properties": { + "type": { + "const": "terminalOutput" + }, + "output": { + "type": "string", + "description": "Output in this snapshot" + } + }, + "required": [ + "type", + "output" + ] + }, "ToolResultTerminalCompleteContent": { "type": "object", "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", @@ -5157,6 +5174,9 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, + { + "$ref": "#/$defs/ToolResultTerminalOutputContent" + }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -5164,7 +5184,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 02fa0334..51af3ca8 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -731,6 +731,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ToolResultResourceContent' }, { name: 'ToolResultFileEditContent' }, { name: 'ToolResultTerminalContent' }, + { name: 'ToolResultTerminalOutputContent' }, { name: 'ToolResultTerminalCompleteContent' }, { name: 'ToolResultSubagentContent' }, { name: 'CustomizationLoadingState' }, @@ -896,6 +897,7 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, + { variantName: 'TerminalOutput', innerType: 'ToolResultTerminalOutputContent', wireValue: 'terminalOutput' }, { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 49468b01..418748a7 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -730,6 +730,7 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent + @JvmInline value class TerminalOutput(val value: ToolResultTerminalOutputContent) : ToolResultContent @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent @@ -760,6 +761,7 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) + "terminalOutput" -> ToolResultContent.TerminalOutput(input.json.decodeFromJsonElement(ToolResultTerminalOutputContent.serializer(), element)) "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) @@ -775,6 +777,7 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) + is ToolResultContent.TerminalOutput -> output.json.encodeToJsonElement(ToolResultTerminalOutputContent.serializer(), value.value) is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw @@ -833,7 +836,8 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultTerminalOutputContent', + 'ToolResultTerminalCompleteContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 4773ddb5..886b8788 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -762,6 +762,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ToolResultResourceContent', omitDiscriminants: true }, { name: 'ToolResultFileEditContent', omitDiscriminants: true }, { name: 'ToolResultTerminalContent', omitDiscriminants: true }, + { name: 'ToolResultTerminalOutputContent', omitDiscriminants: true }, { name: 'ToolResultTerminalCompleteContent', omitDiscriminants: true }, { name: 'ToolResultSubagentContent', omitDiscriminants: true }, { name: 'CustomizationLoadingState', omitDiscriminants: true }, @@ -927,6 +928,7 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, + { variantName: 'TerminalOutput', innerType: 'ToolResultTerminalOutputContent', wireValue: 'terminalOutput' }, { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index f86be48c..fe1a854e 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -583,7 +583,8 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultTerminalOutputContent', + 'ToolResultTerminalCompleteContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', @@ -821,6 +822,7 @@ function generateToolResultContentUnion(): string { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) + case terminalOutput(ToolResultTerminalOutputContent) case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved @@ -845,6 +847,8 @@ function generateToolResultContentUnion(): string { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) + case "terminalOutput": + self = .terminalOutput(try ToolResultTerminalOutputContent(from: decoder)) case "terminalComplete": self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": @@ -867,6 +871,7 @@ function generateToolResultContentUnion(): string { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) + case .terminalOutput(let v): try v.encode(to: encoder) case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 19877253..ab501c1c 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1331,6 +1331,7 @@ export const enum ToolResultContentType { Resource = 'resource', FileEdit = 'fileEdit', Terminal = 'terminal', + TerminalOutput = 'terminalOutput', TerminalComplete = 'terminalComplete', Subagent = 'subagent', } @@ -1399,6 +1400,20 @@ export interface ToolResultTerminalContent { title: string; } +/** + * An inline output snapshot from a running terminal-style tool call. + * + * This content may accompany a {@link ToolResultTerminalContent} block but + * does not require a terminal resource. + * + * @category Tool Result Content + */ +export interface ToolResultTerminalOutputContent { + type: ToolResultContentType.TerminalOutput; + /** Output in this snapshot */ + output: string; +} + /** * Record of a command executed by a terminal-style tool (e.g. a shell tool), * appended to the tool result when the command exits. @@ -1459,6 +1474,7 @@ export interface ToolResultSubagentContent { * `ToolResultResourceContent` for lazy-loading large results, * `ToolResultFileEditContent` for file edit diffs, * `ToolResultTerminalContent` for live terminal output, + * `ToolResultTerminalOutputContent` for inline live command output, * `ToolResultTerminalCompleteContent` for terminal-style completion metadata, and * `ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions). * @@ -1470,5 +1486,6 @@ export type ToolResultContent = | ToolResultResourceContent | ToolResultFileEditContent | ToolResultTerminalContent + | ToolResultTerminalOutputContent | ToolResultTerminalCompleteContent | ToolResultSubagentContent; diff --git a/types/index.ts b/types/index.ts index fd5d5421..4bda8f80 100644 --- a/types/index.ts +++ b/types/index.ts @@ -68,6 +68,7 @@ export type { FileEdit, ToolResultFileEditContent, ToolResultTerminalContent, + ToolResultTerminalOutputContent, ToolResultTerminalCompleteContent, ToolResultSubagentContent, SessionActiveClient, diff --git a/types/test-cases/round-trips/030-terminal-output-content.json b/types/test-cases/round-trips/030-terminal-output-content.json new file mode 100644 index 00000000..483c6d43 --- /dev/null +++ b/types/test-cases/round-trips/030-terminal-output-content.json @@ -0,0 +1,38 @@ +{ + "name": "terminal-output-content", + "group": "A", + "description": "ActionEnvelope carries an inline terminal output snapshot.", + "type": "ActionEnvelope", + "input": { + "channel": "ahp-chat:/s1/c1", + "action": { + "type": "chat/toolCallContentChanged", + "turnId": "turn-1", + "toolCallId": "tool-1", + "content": [ + { + "type": "terminalOutput", + "output": "line 2\nline 3\n" + } + ] + }, + "serverSeq": 8 + }, + "acceptableOutputs": [ + { + "action": { + "content": [ + { + "output": "line 2\nline 3\n", + "type": "terminalOutput" + } + ], + "toolCallId": "tool-1", + "turnId": "turn-1", + "type": "chat/toolCallContentChanged" + }, + "channel": "ahp-chat:/s1/c1", + "serverSeq": 8 + } + ] +} From 6e4bcd3d84438e2c0048fada92cb30c7feb715c0 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 10:41:32 -0700 Subject: [PATCH 02/11] fold inline terminal output into ToolResultTerminalContent --- clients/go/ahptypes/state.generated.go | 42 ++++++++--------- .../generated/State.generated.kt | 30 ++++++------ clients/rust/crates/ahp-types/src/state.rs | 46 +++++++++++-------- clients/rust/crates/ahp/src/reducers.rs | 1 + .../Generated/State.generated.swift | 41 ++++++++--------- docs/.changes/20260717-terminal-output.json | 5 -- docs/.changes/20260722-terminal-ispty.json | 5 ++ schema/actions.schema.json | 34 +++++--------- schema/commands.schema.json | 34 +++++--------- schema/errors.schema.json | 34 +++++--------- schema/notifications.schema.json | 34 +++++--------- schema/state.schema.json | 34 +++++--------- scripts/generate-go.ts | 2 - scripts/generate-kotlin.ts | 6 +-- scripts/generate-rust.ts | 2 - scripts/generate-swift.ts | 7 +-- types/channels-chat/state.ts | 41 ++++++++--------- types/channels-terminal/state.ts | 2 + types/index.ts | 1 - ...t.json => 030-terminal-inline-output.json} | 8 ++-- .../031-terminal-content-ispty.json | 42 +++++++++++++++++ 21 files changed, 215 insertions(+), 236 deletions(-) delete mode 100644 docs/.changes/20260717-terminal-output.json create mode 100644 docs/.changes/20260722-terminal-ispty.json rename types/test-cases/round-trips/{030-terminal-output-content.json => 030-terminal-inline-output.json} (69%) create mode 100644 types/test-cases/round-trips/031-terminal-content-ispty.json diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 728ac327..b5e51d33 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -291,7 +291,6 @@ const ( ToolResultContentTypeResource ToolResultContentType = "resource" ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeTerminalOutput ToolResultContentType = "terminalOutput" ToolResultContentTypeTerminalComplete ToolResultContentType = "terminalComplete" ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) @@ -2148,26 +2147,30 @@ type ToolResultFileEditContent struct { Type ToolResultContentType `json:"type"` } -// A reference to a terminal whose output is relevant to this tool result. +// A reference to a terminal whose output is relevant to this tool result, +// or an inline terminal-style output snapshot. At least one of +// {@link resource} or {@link output} SHOULD be present. // // Clients can subscribe to the terminal's URI to stream its output in real -// time, providing live feedback while a tool is executing. +// time, providing live feedback while a tool is executing. When both +// `resource` and `output` are present the resource is authoritative — +// `output` offers a bounded snapshot for clients that do not subscribe. +// +// A {@link ToolResultTerminalCompleteContent} block can retain completion +// metadata for a command associated with this terminal and reference the same +// resource. type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) - Resource URI `json:"resource"` + Resource *URI `json:"resource,omitempty"` // Display title for the terminal content - Title string `json:"title"` -} - -// An inline output snapshot from a running terminal-style tool call. -// -// This content may accompany a {@link ToolResultTerminalContent} block but -// does not require a terminal resource. -type ToolResultTerminalOutputContent struct { - Type ToolResultContentType `json:"type"` - // Output in this snapshot - Output string `json:"output"` + Title *string `json:"title,omitempty"` + // Inline snapshot of output produced so far. A replacement snapshot, not a + // delta: each `chat/toolCallContentChanged` action supersedes the previous + // snapshot. + Output *string `json:"output,omitempty"` + // Whether this terminal-style resource is backed by a pseudoterminal. + IsPty *bool `json:"isPty,omitempty"` } // Record of a command executed by a terminal-style tool (e.g. a shell tool), @@ -3016,6 +3019,8 @@ type TerminalState struct { // Do NOT use the presence of a `command` part as a feature flag — parts // are absent in the normal idle state. SupportsCommandDetection *bool `json:"supportsCommandDetection,omitempty"` + // Whether this terminal-style resource is backed by a pseudoterminal. + IsPty *bool `json:"isPty,omitempty"` } // Unstructured terminal output — content before, between, or after commands, @@ -4021,7 +4026,6 @@ func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} func (*ToolResultResourceContent) isToolResultContent() {} func (*ToolResultFileEditContent) isToolResultContent() {} func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultTerminalOutputContent) isToolResultContent() {} func (*ToolResultTerminalCompleteContent) isToolResultContent() {} func (*ToolResultSubagentContent) isToolResultContent() {} @@ -4069,12 +4073,6 @@ func (u *ToolResultContent) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "terminalOutput": - var value ToolResultTerminalOutputContent - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value case "terminalComplete": var value ToolResultTerminalCompleteContent if err := json.Unmarshal(data, &value); err != nil { 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 a8226a6e..caa24907 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 @@ -512,8 +512,6 @@ enum class ToolResultContentType { FILE_EDIT, @SerialName("terminal") TERMINAL, - @SerialName("terminalOutput") - TERMINAL_OUTPUT, @SerialName("terminalComplete") TERMINAL_COMPLETE, @SerialName("subagent") @@ -3017,20 +3015,21 @@ data class ToolResultTerminalContent( /** * Terminal URI (subscribable for full terminal state) */ - val resource: String, + val resource: String? = null, /** * Display title for the terminal content */ - val title: String -) - -@Serializable -data class ToolResultTerminalOutputContent( - val type: ToolResultContentType, + val title: String? = null, + /** + * Inline snapshot of output produced so far. A replacement snapshot, not a + * delta: each `chat/toolCallContentChanged` action supersedes the previous + * snapshot. + */ + val output: String? = null, /** - * Output in this snapshot + * Whether this terminal-style resource is backed by a pseudoterminal. */ - val output: String + val isPty: Boolean? = null ) @Serializable @@ -4046,7 +4045,11 @@ data class TerminalState( * Do NOT use the presence of a `command` part as a feature flag — parts * are absent in the normal idle state. */ - val supportsCommandDetection: Boolean? = null + val supportsCommandDetection: Boolean? = null, + /** + * Whether this terminal-style resource is backed by a pseudoterminal. + */ + val isPty: Boolean? = null ) @Serializable @@ -5476,7 +5479,6 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent - @JvmInline value class TerminalOutput(val value: ToolResultTerminalOutputContent) : ToolResultContent @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent @@ -5507,7 +5509,6 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) - "terminalOutput" -> ToolResultContent.TerminalOutput(input.json.decodeFromJsonElement(ToolResultTerminalOutputContent.serializer(), element)) "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) @@ -5523,7 +5524,6 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) - is ToolResultContent.TerminalOutput -> output.json.encodeToJsonElement(ToolResultTerminalOutputContent.serializer(), value.value) is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 710b29de..bb967bec 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -407,8 +407,6 @@ pub enum ToolResultContentType { FileEdit, #[serde(rename = "terminal")] Terminal, - #[serde(rename = "terminalOutput")] - TerminalOutput, #[serde(rename = "terminalComplete")] TerminalComplete, #[serde(rename = "subagent")] @@ -2665,28 +2663,35 @@ pub struct ToolResultFileEditContent { pub diff: Option, } -/// A reference to a terminal whose output is relevant to this tool result. +/// A reference to a terminal whose output is relevant to this tool result, +/// or an inline terminal-style output snapshot. At least one of +/// {@link resource} or {@link output} SHOULD be present. /// /// Clients can subscribe to the terminal's URI to stream its output in real -/// time, providing live feedback while a tool is executing. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// time, providing live feedback while a tool is executing. When both +/// `resource` and `output` are present the resource is authoritative — +/// `output` offers a bounded snapshot for clients that do not subscribe. +/// +/// A {@link ToolResultTerminalCompleteContent} block can retain completion +/// metadata for a command associated with this terminal and reference the same +/// resource. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { /// Terminal URI (subscribable for full terminal state) - pub resource: Uri, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource: Option, /// Display title for the terminal content - pub title: String, -} - -/// An inline output snapshot from a running terminal-style tool call. -/// -/// This content may accompany a {@link ToolResultTerminalContent} block but -/// does not require a terminal resource. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ToolResultTerminalOutputContent { - /// Output in this snapshot - pub output: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Inline snapshot of output produced so far. A replacement snapshot, not a + /// delta: each `chat/toolCallContentChanged` action supersedes the previous + /// snapshot. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub output: Option, + /// Whether this terminal-style resource is backed by a pseudoterminal. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_pty: Option, } /// Record of a command executed by a terminal-style tool (e.g. a shell tool), @@ -3653,6 +3658,9 @@ pub struct TerminalState { /// are absent in the normal idle state. #[serde(default, skip_serializing_if = "Option::is_none")] pub supports_command_detection: Option, + /// Whether this terminal-style resource is backed by a pseudoterminal. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_pty: Option, } /// Unstructured terminal output — content before, between, or after commands, @@ -4299,8 +4307,6 @@ pub enum ToolResultContent { FileEdit(ToolResultFileEditContent), #[serde(rename = "terminal")] Terminal(ToolResultTerminalContent), - #[serde(rename = "terminalOutput")] - TerminalOutput(ToolResultTerminalOutputContent), #[serde(rename = "terminalComplete")] TerminalComplete(ToolResultTerminalCompleteContent), #[serde(rename = "subagent")] diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 2a655345..187e4ff0 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -2117,6 +2117,7 @@ mod tests { }, ), supports_command_detection: None, + is_pty: None, }; let a = StateAction::TerminalData(ahp_types::actions::TerminalDataAction { data: "hello".into(), diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 4fe0ecd6..f87dca09 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -267,7 +267,6 @@ public enum ToolResultContentType: String, Codable, Sendable { case resource = "resource" case fileEdit = "fileEdit" case terminal = "terminal" - case terminalOutput = "terminalOutput" case terminalComplete = "terminalComplete" case subagent = "subagent" } @@ -3218,32 +3217,28 @@ public struct ToolResultFileEditContent: Codable, Sendable { public struct ToolResultTerminalContent: Codable, Sendable { public var type: ToolResultContentType /// Terminal URI (subscribable for full terminal state) - public var resource: String + public var resource: String? /// Display title for the terminal content - public var title: String + public var title: String? + /// Inline snapshot of output produced so far. A replacement snapshot, not a + /// delta: each `chat/toolCallContentChanged` action supersedes the previous + /// snapshot. + public var output: String? + /// Whether this terminal-style resource is backed by a pseudoterminal. + public var isPty: Bool? public init( type: ToolResultContentType, - resource: String, - title: String + resource: String? = nil, + title: String? = nil, + output: String? = nil, + isPty: Bool? = nil ) { self.type = type self.resource = resource self.title = title - } -} - -public struct ToolResultTerminalOutputContent: Codable, Sendable { - public var type: ToolResultContentType - /// Output in this snapshot - public var output: String - - public init( - type: ToolResultContentType, - output: String - ) { - self.type = type self.output = output + self.isPty = isPty } } @@ -4497,6 +4492,8 @@ public struct TerminalState: Codable, Sendable { /// Do NOT use the presence of a `command` part as a feature flag — parts /// are absent in the normal idle state. public var supportsCommandDetection: Bool? + /// Whether this terminal-style resource is backed by a pseudoterminal. + public var isPty: Bool? public init( title: String, @@ -4506,7 +4503,8 @@ public struct TerminalState: Codable, Sendable { content: [TerminalContentPart], exitCode: Int? = nil, claim: TerminalClaim, - supportsCommandDetection: Bool? = nil + supportsCommandDetection: Bool? = nil, + isPty: Bool? = nil ) { self.title = title self.cwd = cwd @@ -4516,6 +4514,7 @@ public struct TerminalState: Codable, Sendable { self.exitCode = exitCode self.claim = claim self.supportsCommandDetection = supportsCommandDetection + self.isPty = isPty } } @@ -5781,7 +5780,6 @@ public enum ToolResultContent: Codable, Sendable { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) - case terminalOutput(ToolResultTerminalOutputContent) case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved @@ -5806,8 +5804,6 @@ public enum ToolResultContent: Codable, Sendable { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) - case "terminalOutput": - self = .terminalOutput(try ToolResultTerminalOutputContent(from: decoder)) case "terminalComplete": self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": @@ -5830,7 +5826,6 @@ public enum ToolResultContent: Codable, Sendable { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) - case .terminalOutput(let v): try v.encode(to: encoder) case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) diff --git a/docs/.changes/20260717-terminal-output.json b/docs/.changes/20260717-terminal-output.json deleted file mode 100644 index 15ef2c85..00000000 --- a/docs/.changes/20260717-terminal-output.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "added", - "message": "`ToolResultTerminalOutputContent` for cumulative live output snapshots from terminal-style tool calls.", - "issues": [323] -} diff --git a/docs/.changes/20260722-terminal-ispty.json b/docs/.changes/20260722-terminal-ispty.json new file mode 100644 index 00000000..dc5715b6 --- /dev/null +++ b/docs/.changes/20260722-terminal-ispty.json @@ -0,0 +1,5 @@ +{ + "type": "changed", + "message": "`ToolResultTerminalContent` now carries optional inline `output` snapshots and optional `isPty` metadata, with `resource` and `title` now optional; `TerminalState` gains matching `isPty` metadata.", + "issues": [322, 323] +} diff --git a/schema/actions.schema.json b/schema/actions.schema.json index fab5019b..f7d8701d 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", "properties": { "type": { "const": "terminal" @@ -6297,29 +6297,18 @@ "title": { "type": "string", "description": "Display title for the terminal content" - } - }, - "required": [ - "type", - "resource", - "title" - ] - }, - "ToolResultTerminalOutputContent": { - "type": "object", - "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", - "properties": { - "type": { - "const": "terminalOutput" }, "output": { "type": "string", - "description": "Output in this snapshot" + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ - "type", - "output" + "type" ] }, "ToolResultTerminalCompleteContent": { @@ -6493,6 +6482,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ @@ -7273,9 +7266,6 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalOutputContent" - }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -7283,7 +7273,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 5f511494..deb61247 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", "properties": { "type": { "const": "terminal" @@ -5503,29 +5503,18 @@ "title": { "type": "string", "description": "Display title for the terminal content" - } - }, - "required": [ - "type", - "resource", - "title" - ] - }, - "ToolResultTerminalOutputContent": { - "type": "object", - "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", - "properties": { - "type": { - "const": "terminalOutput" }, "output": { "type": "string", - "description": "Output in this snapshot" + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ - "type", - "output" + "type" ] }, "ToolResultTerminalCompleteContent": { @@ -5699,6 +5688,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ @@ -8904,9 +8897,6 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalOutputContent" - }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -8914,7 +8904,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index b2592af8..83d1dfe4 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", "properties": { "type": { "const": "terminal" @@ -4287,29 +4287,18 @@ "title": { "type": "string", "description": "Display title for the terminal content" - } - }, - "required": [ - "type", - "resource", - "title" - ] - }, - "ToolResultTerminalOutputContent": { - "type": "object", - "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", - "properties": { - "type": { - "const": "terminalOutput" }, "output": { "type": "string", - "description": "Output in this snapshot" + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ - "type", - "output" + "type" ] }, "ToolResultTerminalCompleteContent": { @@ -4483,6 +4472,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ @@ -6653,9 +6646,6 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalOutputContent" - }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -6663,7 +6653,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 4d0d8b9c..c27b5f34 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", "properties": { "type": { "const": "terminal" @@ -4447,29 +4447,18 @@ "title": { "type": "string", "description": "Display title for the terminal content" - } - }, - "required": [ - "type", - "resource", - "title" - ] - }, - "ToolResultTerminalOutputContent": { - "type": "object", - "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", - "properties": { - "type": { - "const": "terminalOutput" }, "output": { "type": "string", - "description": "Output in this snapshot" + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ - "type", - "output" + "type" ] }, "ToolResultTerminalCompleteContent": { @@ -4643,6 +4632,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ @@ -5516,9 +5509,6 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalOutputContent" - }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -5526,7 +5516,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 512dffa6..02c11436 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", "properties": { "type": { "const": "terminal" @@ -4198,29 +4198,18 @@ "title": { "type": "string", "description": "Display title for the terminal content" - } - }, - "required": [ - "type", - "resource", - "title" - ] - }, - "ToolResultTerminalOutputContent": { - "type": "object", - "description": "An inline output snapshot from a running terminal-style tool call.\n\nThis content may accompany a {@link ToolResultTerminalContent} block but\ndoes not require a terminal resource.", - "properties": { - "type": { - "const": "terminalOutput" }, "output": { "type": "string", - "description": "Output in this snapshot" + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ - "type", - "output" + "type" ] }, "ToolResultTerminalCompleteContent": { @@ -4394,6 +4383,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal." } }, "required": [ @@ -5174,9 +5167,6 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalOutputContent" - }, { "$ref": "#/$defs/ToolResultTerminalCompleteContent" }, @@ -5184,7 +5174,7 @@ "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalOutputContent` for inline live command output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 51af3ca8..02fa0334 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -731,7 +731,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ToolResultResourceContent' }, { name: 'ToolResultFileEditContent' }, { name: 'ToolResultTerminalContent' }, - { name: 'ToolResultTerminalOutputContent' }, { name: 'ToolResultTerminalCompleteContent' }, { name: 'ToolResultSubagentContent' }, { name: 'CustomizationLoadingState' }, @@ -897,7 +896,6 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, - { variantName: 'TerminalOutput', innerType: 'ToolResultTerminalOutputContent', wireValue: 'terminalOutput' }, { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 418748a7..49468b01 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -730,7 +730,6 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent - @JvmInline value class TerminalOutput(val value: ToolResultTerminalOutputContent) : ToolResultContent @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent @@ -761,7 +760,6 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) - "terminalOutput" -> ToolResultContent.TerminalOutput(input.json.decodeFromJsonElement(ToolResultTerminalOutputContent.serializer(), element)) "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) @@ -777,7 +775,6 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) - is ToolResultContent.TerminalOutput -> output.json.encodeToJsonElement(ToolResultTerminalOutputContent.serializer(), value.value) is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw @@ -836,8 +833,7 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalOutputContent', - 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 886b8788..4773ddb5 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -762,7 +762,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ToolResultResourceContent', omitDiscriminants: true }, { name: 'ToolResultFileEditContent', omitDiscriminants: true }, { name: 'ToolResultTerminalContent', omitDiscriminants: true }, - { name: 'ToolResultTerminalOutputContent', omitDiscriminants: true }, { name: 'ToolResultTerminalCompleteContent', omitDiscriminants: true }, { name: 'ToolResultSubagentContent', omitDiscriminants: true }, { name: 'CustomizationLoadingState', omitDiscriminants: true }, @@ -928,7 +927,6 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, - { variantName: 'TerminalOutput', innerType: 'ToolResultTerminalOutputContent', wireValue: 'terminalOutput' }, { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index fe1a854e..f86be48c 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -583,8 +583,7 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalOutputContent', - 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', @@ -822,7 +821,6 @@ function generateToolResultContentUnion(): string { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) - case terminalOutput(ToolResultTerminalOutputContent) case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved @@ -847,8 +845,6 @@ function generateToolResultContentUnion(): string { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) - case "terminalOutput": - self = .terminalOutput(try ToolResultTerminalOutputContent(from: decoder)) case "terminalComplete": self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": @@ -871,7 +867,6 @@ function generateToolResultContentUnion(): string { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) - case .terminalOutput(let v): try v.encode(to: encoder) case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index ab501c1c..90681c18 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1331,7 +1331,6 @@ export const enum ToolResultContentType { Resource = 'resource', FileEdit = 'fileEdit', Terminal = 'terminal', - TerminalOutput = 'terminalOutput', TerminalComplete = 'terminalComplete', Subagent = 'subagent', } @@ -1385,33 +1384,35 @@ export interface ToolResultFileEditContent extends FileEdit { } /** - * A reference to a terminal whose output is relevant to this tool result. + * A reference to a terminal whose output is relevant to this tool result, + * or an inline terminal-style output snapshot. At least one of + * {@link resource} or {@link output} SHOULD be present. * * Clients can subscribe to the terminal's URI to stream its output in real - * time, providing live feedback while a tool is executing. + * time, providing live feedback while a tool is executing. When both + * `resource` and `output` are present the resource is authoritative — + * `output` offers a bounded snapshot for clients that do not subscribe. + * + * A {@link ToolResultTerminalCompleteContent} block can retain completion + * metadata for a command associated with this terminal and reference the same + * resource. * * @category Tool Result Content */ export interface ToolResultTerminalContent { type: ToolResultContentType.Terminal; /** Terminal URI (subscribable for full terminal state) */ - resource: URI; + resource?: URI; /** Display title for the terminal content */ - title: string; -} - -/** - * An inline output snapshot from a running terminal-style tool call. - * - * This content may accompany a {@link ToolResultTerminalContent} block but - * does not require a terminal resource. - * - * @category Tool Result Content - */ -export interface ToolResultTerminalOutputContent { - type: ToolResultContentType.TerminalOutput; - /** Output in this snapshot */ - output: string; + title?: string; + /** + * Inline snapshot of output produced so far. A replacement snapshot, not a + * delta: each `chat/toolCallContentChanged` action supersedes the previous + * snapshot. + */ + output?: string; + /** Whether this terminal-style resource is backed by a pseudoterminal. */ + isPty?: boolean; } /** @@ -1474,7 +1475,6 @@ export interface ToolResultSubagentContent { * `ToolResultResourceContent` for lazy-loading large results, * `ToolResultFileEditContent` for file edit diffs, * `ToolResultTerminalContent` for live terminal output, - * `ToolResultTerminalOutputContent` for inline live command output, * `ToolResultTerminalCompleteContent` for terminal-style completion metadata, and * `ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions). * @@ -1486,6 +1486,5 @@ export type ToolResultContent = | ToolResultResourceContent | ToolResultFileEditContent | ToolResultTerminalContent - | ToolResultTerminalOutputContent | ToolResultTerminalCompleteContent | ToolResultSubagentContent; diff --git a/types/channels-terminal/state.ts b/types/channels-terminal/state.ts index 4484089b..ce0832d4 100644 --- a/types/channels-terminal/state.ts +++ b/types/channels-terminal/state.ts @@ -107,6 +107,8 @@ export interface TerminalState { * are absent in the normal idle state. */ supportsCommandDetection?: boolean; + /** Whether this terminal-style resource is backed by a pseudoterminal. */ + isPty?: boolean; } // ─── Terminal Content Parts ────────────────────────────────────────────────── diff --git a/types/index.ts b/types/index.ts index 4bda8f80..fd5d5421 100644 --- a/types/index.ts +++ b/types/index.ts @@ -68,7 +68,6 @@ export type { FileEdit, ToolResultFileEditContent, ToolResultTerminalContent, - ToolResultTerminalOutputContent, ToolResultTerminalCompleteContent, ToolResultSubagentContent, SessionActiveClient, diff --git a/types/test-cases/round-trips/030-terminal-output-content.json b/types/test-cases/round-trips/030-terminal-inline-output.json similarity index 69% rename from types/test-cases/round-trips/030-terminal-output-content.json rename to types/test-cases/round-trips/030-terminal-inline-output.json index 483c6d43..d6c0ee4c 100644 --- a/types/test-cases/round-trips/030-terminal-output-content.json +++ b/types/test-cases/round-trips/030-terminal-inline-output.json @@ -1,7 +1,7 @@ { - "name": "terminal-output-content", + "name": "terminal-inline-output", "group": "A", - "description": "ActionEnvelope carries an inline terminal output snapshot.", + "description": "ActionEnvelope carries a resource-less terminal block whose inline output snapshot streams a running command for clients that do not subscribe to a terminal channel.", "type": "ActionEnvelope", "input": { "channel": "ahp-chat:/s1/c1", @@ -11,7 +11,7 @@ "toolCallId": "tool-1", "content": [ { - "type": "terminalOutput", + "type": "terminal", "output": "line 2\nline 3\n" } ] @@ -24,7 +24,7 @@ "content": [ { "output": "line 2\nline 3\n", - "type": "terminalOutput" + "type": "terminal" } ], "toolCallId": "tool-1", diff --git a/types/test-cases/round-trips/031-terminal-content-ispty.json b/types/test-cases/round-trips/031-terminal-content-ispty.json new file mode 100644 index 00000000..c3f6cf82 --- /dev/null +++ b/types/test-cases/round-trips/031-terminal-content-ispty.json @@ -0,0 +1,42 @@ +{ + "name": "terminal-content-ispty", + "group": "A", + "description": "ActionEnvelope carries a terminal reference marked as non-PTY (isPty false), allowing clients to identify its backing before subscribing.", + "type": "ActionEnvelope", + "input": { + "channel": "ahp-chat:/s1/c1", + "action": { + "type": "chat/toolCallContentChanged", + "turnId": "turn-1", + "toolCallId": "tool-1", + "content": [ + { + "type": "terminal", + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "isPty": false + } + ] + }, + "serverSeq": 10 + }, + "acceptableOutputs": [ + { + "action": { + "content": [ + { + "isPty": false, + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "type": "terminal" + } + ], + "toolCallId": "tool-1", + "turnId": "turn-1", + "type": "chat/toolCallContentChanged" + }, + "channel": "ahp-chat:/s1/c1", + "serverSeq": 10 + } + ] +} From 33213e7395e418932cc209d9a19d56286375bdfe Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 10:56:02 -0700 Subject: [PATCH 03/11] improve comments --- clients/go/ahptypes/state.generated.go | 10 +++++----- clients/rust/crates/ahp-types/src/state.rs | 10 +++++----- 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-chat/state.ts | 10 +++++----- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index b5e51d33..bb0a4600 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2153,12 +2153,12 @@ type ToolResultFileEditContent struct { // // Clients can subscribe to the terminal's URI to stream its output in real // time, providing live feedback while a tool is executing. When both -// `resource` and `output` are present the resource is authoritative — -// `output` offers a bounded snapshot for clients that do not subscribe. +// `resource` and `output` are present, `output` is a snapshot for clients +// that do not subscribe. // -// A {@link ToolResultTerminalCompleteContent} block can retain completion -// metadata for a command associated with this terminal and reference the same -// resource. +// This block does not signal command completion: check for a +// {@link ToolResultTerminalCompleteContent} block, which retains completion +// metadata such as the exit code. type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index bb967bec..7906d706 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2669,12 +2669,12 @@ pub struct ToolResultFileEditContent { /// /// Clients can subscribe to the terminal's URI to stream its output in real /// time, providing live feedback while a tool is executing. When both -/// `resource` and `output` are present the resource is authoritative — -/// `output` offers a bounded snapshot for clients that do not subscribe. +/// `resource` and `output` are present, `output` is a snapshot for clients +/// that do not subscribe. /// -/// A {@link ToolResultTerminalCompleteContent} block can retain completion -/// metadata for a command associated with this terminal and reference the same -/// resource. +/// This block does not signal command completion: check for a +/// {@link ToolResultTerminalCompleteContent} block, which retains completion +/// metadata such as the exit code. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { diff --git a/schema/actions.schema.json b/schema/actions.schema.json index f7d8701d..86172d17 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" diff --git a/schema/commands.schema.json b/schema/commands.schema.json index deb61247..b26db98e 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 83d1dfe4..af6c202b 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index c27b5f34..3530c831 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" diff --git a/schema/state.schema.json b/schema/state.schema.json index 02c11436..57ad13b5 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present the resource is authoritative —\n`output` offers a bounded snapshot for clients that do not subscribe.\n\nA {@link ToolResultTerminalCompleteContent} block can retain completion\nmetadata for a command associated with this terminal and reference the same\nresource.", + "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 90681c18..a2c3f152 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1390,12 +1390,12 @@ export interface ToolResultFileEditContent extends FileEdit { * * Clients can subscribe to the terminal's URI to stream its output in real * time, providing live feedback while a tool is executing. When both - * `resource` and `output` are present the resource is authoritative — - * `output` offers a bounded snapshot for clients that do not subscribe. + * `resource` and `output` are present, `output` is a snapshot for clients + * that do not subscribe. * - * A {@link ToolResultTerminalCompleteContent} block can retain completion - * metadata for a command associated with this terminal and reference the same - * resource. + * This block does not signal command completion: check for a + * {@link ToolResultTerminalCompleteContent} block, which retains completion + * metadata such as the exit code. * * @category Tool Result Content */ From dd79bd5dedd8d0c08526524577df5cbbf631ef79 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 11:01:19 -0700 Subject: [PATCH 04/11] be more clear about live snapshot vs. completed command --- clients/go/ahptypes/state.generated.go | 3 ++- .../microsoft/agenthostprotocol/generated/State.generated.kt | 3 ++- clients/rust/crates/ahp-types/src/state.rs | 3 ++- .../Sources/AgentHostProtocol/Generated/State.generated.swift | 3 ++- 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-chat/state.ts | 3 ++- 10 files changed, 15 insertions(+), 10 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index bb0a4600..c9831bbd 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2167,7 +2167,8 @@ type ToolResultTerminalContent struct { Title *string `json:"title,omitempty"` // Inline snapshot of output produced so far. A replacement snapshot, not a // delta: each `chat/toolCallContentChanged` action supersedes the previous - // snapshot. + // snapshot. Meant for live updates while the tool call runs; completed + // results retain output via {@link ToolResultTerminalCompleteContent}. Output *string `json:"output,omitempty"` // Whether this terminal-style resource is backed by a pseudoterminal. IsPty *bool `json:"isPty,omitempty"` 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 caa24907..6f48e507 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 @@ -3023,7 +3023,8 @@ data class ToolResultTerminalContent( /** * Inline snapshot of output produced so far. A replacement snapshot, not a * delta: each `chat/toolCallContentChanged` action supersedes the previous - * snapshot. + * snapshot. Meant for live updates while the tool call runs; completed + * results retain output via {@link ToolResultTerminalCompleteContent}. */ val output: String? = null, /** diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 7906d706..67fddede 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2686,7 +2686,8 @@ pub struct ToolResultTerminalContent { pub title: Option, /// Inline snapshot of output produced so far. A replacement snapshot, not a /// delta: each `chat/toolCallContentChanged` action supersedes the previous - /// snapshot. + /// snapshot. Meant for live updates while the tool call runs; completed + /// results retain output via {@link ToolResultTerminalCompleteContent}. #[serde(default, skip_serializing_if = "Option::is_none")] pub output: Option, /// Whether this terminal-style resource is backed by a pseudoterminal. diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index f87dca09..94c96ecf 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3222,7 +3222,8 @@ public struct ToolResultTerminalContent: Codable, Sendable { public var title: String? /// Inline snapshot of output produced so far. A replacement snapshot, not a /// delta: each `chat/toolCallContentChanged` action supersedes the previous - /// snapshot. + /// snapshot. Meant for live updates while the tool call runs; completed + /// results retain output via {@link ToolResultTerminalCompleteContent}. public var output: String? /// Whether this terminal-style resource is backed by a pseudoterminal. public var isPty: Bool? diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 86172d17..2e47e961 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6300,7 +6300,7 @@ }, "output": { "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." }, "isPty": { "type": "boolean", diff --git a/schema/commands.schema.json b/schema/commands.schema.json index b26db98e..fda21ad2 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5506,7 +5506,7 @@ }, "output": { "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." }, "isPty": { "type": "boolean", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index af6c202b..8117a20c 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4290,7 +4290,7 @@ }, "output": { "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." }, "isPty": { "type": "boolean", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 3530c831..eb5c8f4a 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4450,7 +4450,7 @@ }, "output": { "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." }, "isPty": { "type": "boolean", diff --git a/schema/state.schema.json b/schema/state.schema.json index 57ad13b5..9c8671df 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4201,7 +4201,7 @@ }, "output": { "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot." + "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." }, "isPty": { "type": "boolean", diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index a2c3f152..ef211ee6 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1408,7 +1408,8 @@ export interface ToolResultTerminalContent { /** * Inline snapshot of output produced so far. A replacement snapshot, not a * delta: each `chat/toolCallContentChanged` action supersedes the previous - * snapshot. + * snapshot. Meant for live updates while the tool call runs; completed + * results retain output via {@link ToolResultTerminalCompleteContent}. */ output?: string; /** Whether this terminal-style resource is backed by a pseudoterminal. */ From c4f3f7bb20c71b1b40b7d3649bb65b6bf02b5805 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 11:05:51 -0700 Subject: [PATCH 05/11] mention non pty do not have to parse VT sequences --- clients/go/ahptypes/state.generated.go | 4 ++++ .../agenthostprotocol/generated/State.generated.kt | 4 ++++ clients/rust/crates/ahp-types/src/state.rs | 4 ++++ .../AgentHostProtocol/Generated/State.generated.swift | 4 ++++ schema/actions.schema.json | 4 ++-- schema/commands.schema.json | 4 ++-- schema/errors.schema.json | 4 ++-- schema/notifications.schema.json | 4 ++-- schema/state.schema.json | 4 ++-- types/channels-chat/state.ts | 6 +++++- types/channels-terminal/state.ts | 6 +++++- 11 files changed, 36 insertions(+), 12 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index c9831bbd..809ca21a 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2171,6 +2171,8 @@ type ToolResultTerminalContent struct { // results retain output via {@link ToolResultTerminalCompleteContent}. Output *string `json:"output,omitempty"` // Whether this terminal-style resource is backed by a pseudoterminal. + // When `false`, output is plain text and clients do not need to parse + // VT sequences. IsPty *bool `json:"isPty,omitempty"` } @@ -3021,6 +3023,8 @@ type TerminalState struct { // are absent in the normal idle state. SupportsCommandDetection *bool `json:"supportsCommandDetection,omitempty"` // Whether this terminal-style resource is backed by a pseudoterminal. + // When `false`, output is plain text and clients do not need to parse + // VT sequences. IsPty *bool `json:"isPty,omitempty"` } 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 6f48e507..9dad0751 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 @@ -3029,6 +3029,8 @@ data class ToolResultTerminalContent( val output: String? = null, /** * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. */ val isPty: Boolean? = null ) @@ -4049,6 +4051,8 @@ data class TerminalState( val supportsCommandDetection: Boolean? = null, /** * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. */ val isPty: Boolean? = null ) diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 67fddede..04921312 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2691,6 +2691,8 @@ pub struct ToolResultTerminalContent { #[serde(default, skip_serializing_if = "Option::is_none")] pub output: Option, /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. #[serde(default, skip_serializing_if = "Option::is_none")] pub is_pty: Option, } @@ -3660,6 +3662,8 @@ pub struct TerminalState { #[serde(default, skip_serializing_if = "Option::is_none")] pub supports_command_detection: Option, /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. #[serde(default, skip_serializing_if = "Option::is_none")] pub is_pty: Option, } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 94c96ecf..eb13d21e 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3226,6 +3226,8 @@ public struct ToolResultTerminalContent: Codable, Sendable { /// results retain output via {@link ToolResultTerminalCompleteContent}. public var output: String? /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. public var isPty: Bool? public init( @@ -4494,6 +4496,8 @@ public struct TerminalState: Codable, Sendable { /// are absent in the normal idle state. public var supportsCommandDetection: Bool? /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. public var isPty: Bool? public init( diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 2e47e961..181bc587 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6304,7 +6304,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -6485,7 +6485,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index fda21ad2..7e61d5db 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5510,7 +5510,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -5691,7 +5691,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 8117a20c..84b9b06a 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4294,7 +4294,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -4475,7 +4475,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index eb5c8f4a..02ba6a87 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4454,7 +4454,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -4635,7 +4635,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 9c8671df..61919fac 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4205,7 +4205,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -4386,7 +4386,7 @@ }, "isPty": { "type": "boolean", - "description": "Whether this terminal-style resource is backed by a pseudoterminal." + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index ef211ee6..d07a444b 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1412,7 +1412,11 @@ export interface ToolResultTerminalContent { * results retain output via {@link ToolResultTerminalCompleteContent}. */ output?: string; - /** Whether this terminal-style resource is backed by a pseudoterminal. */ + /** + * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. + */ isPty?: boolean; } diff --git a/types/channels-terminal/state.ts b/types/channels-terminal/state.ts index ce0832d4..28cd36e4 100644 --- a/types/channels-terminal/state.ts +++ b/types/channels-terminal/state.ts @@ -107,7 +107,11 @@ export interface TerminalState { * are absent in the normal idle state. */ supportsCommandDetection?: boolean; - /** Whether this terminal-style resource is backed by a pseudoterminal. */ + /** + * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. + */ isPty?: boolean; } From ea3dabb154b91f6d45453a5e331e22e2dd7941ef Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 13:20:57 -0700 Subject: [PATCH 06/11] drop inline snapshot in favor of resourced non-pty terminal --- ...y.json => 030-terminal-content-ispty.json} | 0 .../030-terminal-inline-output.json | 38 ------------------- 2 files changed, 38 deletions(-) rename types/test-cases/round-trips/{031-terminal-content-ispty.json => 030-terminal-content-ispty.json} (100%) delete mode 100644 types/test-cases/round-trips/030-terminal-inline-output.json diff --git a/types/test-cases/round-trips/031-terminal-content-ispty.json b/types/test-cases/round-trips/030-terminal-content-ispty.json similarity index 100% rename from types/test-cases/round-trips/031-terminal-content-ispty.json rename to types/test-cases/round-trips/030-terminal-content-ispty.json diff --git a/types/test-cases/round-trips/030-terminal-inline-output.json b/types/test-cases/round-trips/030-terminal-inline-output.json deleted file mode 100644 index d6c0ee4c..00000000 --- a/types/test-cases/round-trips/030-terminal-inline-output.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "terminal-inline-output", - "group": "A", - "description": "ActionEnvelope carries a resource-less terminal block whose inline output snapshot streams a running command for clients that do not subscribe to a terminal channel.", - "type": "ActionEnvelope", - "input": { - "channel": "ahp-chat:/s1/c1", - "action": { - "type": "chat/toolCallContentChanged", - "turnId": "turn-1", - "toolCallId": "tool-1", - "content": [ - { - "type": "terminal", - "output": "line 2\nline 3\n" - } - ] - }, - "serverSeq": 8 - }, - "acceptableOutputs": [ - { - "action": { - "content": [ - { - "output": "line 2\nline 3\n", - "type": "terminal" - } - ], - "toolCallId": "tool-1", - "turnId": "turn-1", - "type": "chat/toolCallContentChanged" - }, - "channel": "ahp-chat:/s1/c1", - "serverSeq": 8 - } - ] -} From b6b5668ce4c4d0082c7e76da62a0fb31df0d980a Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 13:21:11 -0700 Subject: [PATCH 07/11] drop inline snapshot in favor of resourced non-pty terminal --- clients/go/ahptypes/state.generated.go | 17 ++++---------- .../generated/State.generated.kt | 11 ++-------- clients/rust/crates/ahp-types/src/state.rs | 22 +++++-------------- .../Generated/State.generated.swift | 15 ++++--------- docs/.changes/20260722-terminal-ispty.json | 6 ++--- schema/actions.schema.json | 10 ++++----- schema/commands.schema.json | 10 ++++----- schema/errors.schema.json | 10 ++++----- schema/notifications.schema.json | 10 ++++----- schema/state.schema.json | 10 ++++----- types/channels-chat/state.ts | 19 ++++------------ 11 files changed, 42 insertions(+), 98 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 809ca21a..ce36a220 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2147,14 +2147,10 @@ type ToolResultFileEditContent struct { Type ToolResultContentType `json:"type"` } -// A reference to a terminal whose output is relevant to this tool result, -// or an inline terminal-style output snapshot. At least one of -// {@link resource} or {@link output} SHOULD be present. +// A reference to a terminal whose output is relevant to this tool result. // // Clients can subscribe to the terminal's URI to stream its output in real -// time, providing live feedback while a tool is executing. When both -// `resource` and `output` are present, `output` is a snapshot for clients -// that do not subscribe. +// time, providing live feedback while a tool is executing. // // This block does not signal command completion: check for a // {@link ToolResultTerminalCompleteContent} block, which retains completion @@ -2162,14 +2158,9 @@ type ToolResultFileEditContent struct { type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) - Resource *URI `json:"resource,omitempty"` + Resource URI `json:"resource"` // Display title for the terminal content - Title *string `json:"title,omitempty"` - // Inline snapshot of output produced so far. A replacement snapshot, not a - // delta: each `chat/toolCallContentChanged` action supersedes the previous - // snapshot. Meant for live updates while the tool call runs; completed - // results retain output via {@link ToolResultTerminalCompleteContent}. - Output *string `json:"output,omitempty"` + Title string `json:"title"` // Whether this terminal-style resource is backed by a pseudoterminal. // When `false`, output is plain text and clients do not need to parse // VT sequences. 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 9dad0751..379a42e7 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 @@ -3015,18 +3015,11 @@ data class ToolResultTerminalContent( /** * Terminal URI (subscribable for full terminal state) */ - val resource: String? = null, + val resource: String, /** * Display title for the terminal content */ - val title: String? = null, - /** - * Inline snapshot of output produced so far. A replacement snapshot, not a - * delta: each `chat/toolCallContentChanged` action supersedes the previous - * snapshot. Meant for live updates while the tool call runs; completed - * results retain output via {@link ToolResultTerminalCompleteContent}. - */ - val output: String? = null, + val title: String, /** * Whether this terminal-style resource is backed by a pseudoterminal. * When `false`, output is plain text and clients do not need to parse diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 04921312..bb768b4f 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2663,33 +2663,21 @@ pub struct ToolResultFileEditContent { pub diff: Option, } -/// A reference to a terminal whose output is relevant to this tool result, -/// or an inline terminal-style output snapshot. At least one of -/// {@link resource} or {@link output} SHOULD be present. +/// A reference to a terminal whose output is relevant to this tool result. /// /// Clients can subscribe to the terminal's URI to stream its output in real -/// time, providing live feedback while a tool is executing. When both -/// `resource` and `output` are present, `output` is a snapshot for clients -/// that do not subscribe. +/// time, providing live feedback while a tool is executing. /// /// This block does not signal command completion: check for a /// {@link ToolResultTerminalCompleteContent} block, which retains completion /// metadata such as the exit code. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { /// Terminal URI (subscribable for full terminal state) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub resource: Option, + pub resource: Uri, /// Display title for the terminal content - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Inline snapshot of output produced so far. A replacement snapshot, not a - /// delta: each `chat/toolCallContentChanged` action supersedes the previous - /// snapshot. Meant for live updates while the tool call runs; completed - /// results retain output via {@link ToolResultTerminalCompleteContent}. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub output: Option, + pub title: String, /// Whether this terminal-style resource is backed by a pseudoterminal. /// When `false`, output is plain text and clients do not need to parse /// VT sequences. diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index eb13d21e..a003a5f1 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3217,14 +3217,9 @@ public struct ToolResultFileEditContent: Codable, Sendable { public struct ToolResultTerminalContent: Codable, Sendable { public var type: ToolResultContentType /// Terminal URI (subscribable for full terminal state) - public var resource: String? + public var resource: String /// Display title for the terminal content - public var title: String? - /// Inline snapshot of output produced so far. A replacement snapshot, not a - /// delta: each `chat/toolCallContentChanged` action supersedes the previous - /// snapshot. Meant for live updates while the tool call runs; completed - /// results retain output via {@link ToolResultTerminalCompleteContent}. - public var output: String? + public var title: String /// Whether this terminal-style resource is backed by a pseudoterminal. /// When `false`, output is plain text and clients do not need to parse /// VT sequences. @@ -3232,15 +3227,13 @@ public struct ToolResultTerminalContent: Codable, Sendable { public init( type: ToolResultContentType, - resource: String? = nil, - title: String? = nil, - output: String? = nil, + resource: String, + title: String, isPty: Bool? = nil ) { self.type = type self.resource = resource self.title = title - self.output = output self.isPty = isPty } } diff --git a/docs/.changes/20260722-terminal-ispty.json b/docs/.changes/20260722-terminal-ispty.json index dc5715b6..51956e39 100644 --- a/docs/.changes/20260722-terminal-ispty.json +++ b/docs/.changes/20260722-terminal-ispty.json @@ -1,5 +1,5 @@ { - "type": "changed", - "message": "`ToolResultTerminalContent` now carries optional inline `output` snapshots and optional `isPty` metadata, with `resource` and `title` now optional; `TerminalState` gains matching `isPty` metadata.", - "issues": [322, 323] + "type": "added", + "message": "Optional `isPty` metadata on `ToolResultTerminalContent` and `TerminalState` indicating whether a terminal resource is PTY-backed.", + "issues": [322] } diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 181bc587..f4d3f25f 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" @@ -6298,17 +6298,15 @@ "type": "string", "description": "Display title for the terminal content" }, - "output": { - "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." - }, "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ - "type" + "type", + "resource", + "title" ] }, "ToolResultTerminalCompleteContent": { diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 7e61d5db..1b0f80aa 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" @@ -5504,17 +5504,15 @@ "type": "string", "description": "Display title for the terminal content" }, - "output": { - "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." - }, "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ - "type" + "type", + "resource", + "title" ] }, "ToolResultTerminalCompleteContent": { diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 84b9b06a..4fda9695 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" @@ -4288,17 +4288,15 @@ "type": "string", "description": "Display title for the terminal content" }, - "output": { - "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." - }, "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ - "type" + "type", + "resource", + "title" ] }, "ToolResultTerminalCompleteContent": { diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 02ba6a87..fea88fc6 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" @@ -4448,17 +4448,15 @@ "type": "string", "description": "Display title for the terminal content" }, - "output": { - "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." - }, "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ - "type" + "type", + "resource", + "title" ] }, "ToolResultTerminalCompleteContent": { diff --git a/schema/state.schema.json b/schema/state.schema.json index 61919fac..76b3d66d 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result,\nor an inline terminal-style output snapshot. At least one of\n{@link resource} or {@link output} SHOULD be present.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing. When both\n`resource` and `output` are present, `output` is a snapshot for clients\nthat do not subscribe.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", "properties": { "type": { "const": "terminal" @@ -4199,17 +4199,15 @@ "type": "string", "description": "Display title for the terminal content" }, - "output": { - "type": "string", - "description": "Inline snapshot of output produced so far. A replacement snapshot, not a\ndelta: each `chat/toolCallContentChanged` action supersedes the previous\nsnapshot. Meant for live updates while the tool call runs; completed\nresults retain output via {@link ToolResultTerminalCompleteContent}." - }, "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ - "type" + "type", + "resource", + "title" ] }, "ToolResultTerminalCompleteContent": { diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index d07a444b..b9327dba 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1384,14 +1384,10 @@ export interface ToolResultFileEditContent extends FileEdit { } /** - * A reference to a terminal whose output is relevant to this tool result, - * or an inline terminal-style output snapshot. At least one of - * {@link resource} or {@link output} SHOULD be present. + * A reference to a terminal whose output is relevant to this tool result. * * Clients can subscribe to the terminal's URI to stream its output in real - * time, providing live feedback while a tool is executing. When both - * `resource` and `output` are present, `output` is a snapshot for clients - * that do not subscribe. + * time, providing live feedback while a tool is executing. * * This block does not signal command completion: check for a * {@link ToolResultTerminalCompleteContent} block, which retains completion @@ -1402,16 +1398,9 @@ export interface ToolResultFileEditContent extends FileEdit { export interface ToolResultTerminalContent { type: ToolResultContentType.Terminal; /** Terminal URI (subscribable for full terminal state) */ - resource?: URI; + resource: URI; /** Display title for the terminal content */ - title?: string; - /** - * Inline snapshot of output produced so far. A replacement snapshot, not a - * delta: each `chat/toolCallContentChanged` action supersedes the previous - * snapshot. Meant for live updates while the tool call runs; completed - * results retain output via {@link ToolResultTerminalCompleteContent}. - */ - output?: string; + title: string; /** * Whether this terminal-style resource is backed by a pseudoterminal. * When `false`, output is plain text and clients do not need to parse From be131a8e1b1f499bff7f597a55b69111f9eb44e9 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 13:47:15 -0700 Subject: [PATCH 08/11] absorb TerminalComplete into ToolResultTerminalContent --- clients/go/ahptypes/state.generated.go | 14 +++++++++++--- .../generated/State.generated.kt | 15 ++++++++++++++- clients/rust/crates/ahp-types/src/state.rs | 17 ++++++++++++++--- .../Generated/State.generated.swift | 15 ++++++++++++++- schema/actions.schema.json | 14 +++++++++++++- schema/commands.schema.json | 14 +++++++++++++- schema/errors.schema.json | 14 +++++++++++++- schema/notifications.schema.json | 14 +++++++++++++- schema/state.schema.json | 14 +++++++++++++- types/channels-chat/state.ts | 19 ++++++++++++++++--- 10 files changed, 134 insertions(+), 16 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index ce36a220..89328735 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2152,9 +2152,10 @@ type ToolResultFileEditContent struct { // Clients can subscribe to the terminal's URI to stream its output in real // time, providing live feedback while a tool is executing. // -// This block does not signal command completion: check for a -// {@link ToolResultTerminalCompleteContent} block, which retains completion -// metadata such as the exit code. +// When the command exits, `exitCode`, `preview`, and `truncated` are filled +// in on the completed result, retaining the outcome for clients that did +// not subscribe. This records the command's exit, not the terminal's — the +// terminal may keep running afterwards. type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) @@ -2165,6 +2166,13 @@ type ToolResultTerminalContent struct { // When `false`, output is plain text and clients do not need to parse // VT sequences. IsPty *bool `json:"isPty,omitempty"` + // Exit code from the completed command, if reported by the runtime + ExitCode *int64 `json:"exitCode,omitempty"` + // Preview of the command's output, for clients that are not subscribed + // to the terminal or that arrive after it is disposed + Preview *string `json:"preview,omitempty"` + // Whether `preview` is known to be incomplete or truncated + Truncated *bool `json:"truncated,omitempty"` } // Record of a command executed by a terminal-style tool (e.g. a shell tool), 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 379a42e7..69d0dce0 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 @@ -3025,7 +3025,20 @@ data class ToolResultTerminalContent( * When `false`, output is plain text and clients do not need to parse * VT sequences. */ - val isPty: Boolean? = null + val isPty: Boolean? = null, + /** + * Exit code from the completed command, if reported by the runtime + */ + val exitCode: Long? = null, + /** + * Preview of the command's output, for clients that are not subscribed + * to the terminal or that arrive after it is disposed + */ + val preview: String? = null, + /** + * Whether `preview` is known to be incomplete or truncated + */ + val truncated: Boolean? = null ) @Serializable diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index bb768b4f..aee3dda1 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2668,9 +2668,10 @@ pub struct ToolResultFileEditContent { /// Clients can subscribe to the terminal's URI to stream its output in real /// time, providing live feedback while a tool is executing. /// -/// This block does not signal command completion: check for a -/// {@link ToolResultTerminalCompleteContent} block, which retains completion -/// metadata such as the exit code. +/// When the command exits, `exitCode`, `preview`, and `truncated` are filled +/// in on the completed result, retaining the outcome for clients that did +/// not subscribe. This records the command's exit, not the terminal's — the +/// terminal may keep running afterwards. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { @@ -2683,6 +2684,16 @@ pub struct ToolResultTerminalContent { /// VT sequences. #[serde(default, skip_serializing_if = "Option::is_none")] pub is_pty: Option, + /// Exit code from the completed command, if reported by the runtime + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exit_code: Option, + /// Preview of the command's output, for clients that are not subscribed + /// to the terminal or that arrive after it is disposed + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preview: Option, + /// Whether `preview` is known to be incomplete or truncated + #[serde(default, skip_serializing_if = "Option::is_none")] + pub truncated: Option, } /// Record of a command executed by a terminal-style tool (e.g. a shell tool), diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index a003a5f1..be1a6c21 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3224,17 +3224,30 @@ public struct ToolResultTerminalContent: Codable, Sendable { /// When `false`, output is plain text and clients do not need to parse /// VT sequences. public var isPty: Bool? + /// Exit code from the completed command, if reported by the runtime + public var exitCode: Int? + /// Preview of the command's output, for clients that are not subscribed + /// to the terminal or that arrive after it is disposed + public var preview: String? + /// Whether `preview` is known to be incomplete or truncated + public var truncated: Bool? public init( type: ToolResultContentType, resource: String, title: String, - isPty: Bool? = nil + isPty: Bool? = nil, + exitCode: Int? = nil, + preview: String? = nil, + truncated: Bool? = nil ) { self.type = type self.resource = resource self.title = title self.isPty = isPty + self.exitCode = exitCode + self.preview = preview + self.truncated = truncated } } diff --git a/schema/actions.schema.json b/schema/actions.schema.json index f4d3f25f..94a755a1 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", "properties": { "type": { "const": "terminal" @@ -6301,6 +6301,18 @@ "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "exitCode": { + "type": "number", + "description": "Exit code from the completed command, if reported by the runtime" + }, + "preview": { + "type": "string", + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + }, + "truncated": { + "type": "boolean", + "description": "Whether `preview` is known to be incomplete or truncated" } }, "required": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 1b0f80aa..4d026407 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", "properties": { "type": { "const": "terminal" @@ -5507,6 +5507,18 @@ "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "exitCode": { + "type": "number", + "description": "Exit code from the completed command, if reported by the runtime" + }, + "preview": { + "type": "string", + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + }, + "truncated": { + "type": "boolean", + "description": "Whether `preview` is known to be incomplete or truncated" } }, "required": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 4fda9695..d0ae69ae 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", "properties": { "type": { "const": "terminal" @@ -4291,6 +4291,18 @@ "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "exitCode": { + "type": "number", + "description": "Exit code from the completed command, if reported by the runtime" + }, + "preview": { + "type": "string", + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + }, + "truncated": { + "type": "boolean", + "description": "Whether `preview` is known to be incomplete or truncated" } }, "required": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index fea88fc6..0d75ea20 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", "properties": { "type": { "const": "terminal" @@ -4451,6 +4451,18 @@ "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "exitCode": { + "type": "number", + "description": "Exit code from the completed command, if reported by the runtime" + }, + "preview": { + "type": "string", + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + }, + "truncated": { + "type": "boolean", + "description": "Whether `preview` is known to be incomplete or truncated" } }, "required": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 76b3d66d..8d07695d 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nThis block does not signal command completion: check for a\n{@link ToolResultTerminalCompleteContent} block, which retains completion\nmetadata such as the exit code.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", "properties": { "type": { "const": "terminal" @@ -4202,6 +4202,18 @@ "isPty": { "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "exitCode": { + "type": "number", + "description": "Exit code from the completed command, if reported by the runtime" + }, + "preview": { + "type": "string", + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + }, + "truncated": { + "type": "boolean", + "description": "Whether `preview` is known to be incomplete or truncated" } }, "required": [ diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index b9327dba..8234026d 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1389,9 +1389,10 @@ export interface ToolResultFileEditContent extends FileEdit { * Clients can subscribe to the terminal's URI to stream its output in real * time, providing live feedback while a tool is executing. * - * This block does not signal command completion: check for a - * {@link ToolResultTerminalCompleteContent} block, which retains completion - * metadata such as the exit code. + * When the command exits, `exitCode`, `preview`, and `truncated` are filled + * in on the completed result, retaining the outcome for clients that did + * not subscribe. This records the command's exit, not the terminal's — the + * terminal may keep running afterwards. * * @category Tool Result Content */ @@ -1407,6 +1408,15 @@ export interface ToolResultTerminalContent { * VT sequences. */ isPty?: boolean; + /** Exit code from the completed command, if reported by the runtime */ + exitCode?: number; + /** + * Preview of the command's output, for clients that are not subscribed + * to the terminal or that arrive after it is disposed + */ + preview?: string; + /** Whether `preview` is known to be incomplete or truncated */ + truncated?: boolean; } /** @@ -1421,6 +1431,9 @@ export interface ToolResultTerminalContent { * {@link resource} identifies that channel; otherwise this block stands alone * as the retained command result. * + * @deprecated Completion metadata is now filled in on the + * {@link ToolResultTerminalContent} block in the completed result. + * * @category Tool Result Content */ export interface ToolResultTerminalCompleteContent { From bd849f6ef9c4dd3322bd45b3e4092a57e1221bae Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 13:58:19 -0700 Subject: [PATCH 09/11] Remove: ToolResultTerminalCompleteContent --- clients/go/ahptypes/state.generated.go | 33 ------------- .../generated/State.generated.kt | 31 ------------ clients/rust/crates/ahp-types/src/state.rs | 35 -------------- .../Generated/State.generated.swift | 36 -------------- .../20260722-remove-terminal-complete.json | 5 ++ .../20260722-terminal-completion-data.json | 5 ++ schema/actions.schema.json | 37 +------------- schema/commands.schema.json | 37 +------------- schema/errors.schema.json | 37 +------------- schema/notifications.schema.json | 37 +------------- schema/state.schema.json | 37 +------------- scripts/generate-go.ts | 2 - scripts/generate-kotlin.ts | 5 +- scripts/generate-rust.ts | 2 - scripts/generate-swift.ts | 6 +-- types/channels-chat/state.ts | 40 +--------------- types/index.ts | 1 - .../031-terminal-content-completed.json | 48 +++++++++++++++++++ 18 files changed, 67 insertions(+), 367 deletions(-) create mode 100644 docs/.changes/20260722-remove-terminal-complete.json create mode 100644 docs/.changes/20260722-terminal-completion-data.json create mode 100644 types/test-cases/round-trips/031-terminal-content-completed.json diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 89328735..b2fa4250 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -291,7 +291,6 @@ const ( ToolResultContentTypeResource ToolResultContentType = "resource" ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeTerminalComplete ToolResultContentType = "terminalComplete" ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) @@ -2175,31 +2174,6 @@ type ToolResultTerminalContent struct { Truncated *bool `json:"truncated,omitempty"` } -// Record of a command executed by a terminal-style tool (e.g. a shell tool), -// appended to the tool result when the command exits. -// -// This records the command's exit, not the terminal's — the terminal may -// keep running afterwards. -// -// When live output was exposed through a terminal channel (a -// {@link ToolResultTerminalContent} block in the same tool result), -// {@link resource} identifies that channel; otherwise this block stands alone -// as the retained command result. -type ToolResultTerminalCompleteContent struct { - Type ToolResultContentType `json:"type"` - // URI of the `ahp-terminal:` channel that carried live output for this - // command, if one was exposed. - Resource *URI `json:"resource,omitempty"` - // Exit code from the completed command, if reported by the runtime - ExitCode *int64 `json:"exitCode,omitempty"` - // Working directory where the command was executed - Cwd *URI `json:"cwd,omitempty"` - // Preview of the command's output, if available - Preview *string `json:"preview,omitempty"` - // Whether `preview` is known to be incomplete or truncated - Truncated *bool `json:"truncated,omitempty"` -} - // A reference, embedded in a tool result, to a worker chat spawned by the tool // call (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`). // @@ -4030,7 +4004,6 @@ func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} func (*ToolResultResourceContent) isToolResultContent() {} func (*ToolResultFileEditContent) isToolResultContent() {} func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultTerminalCompleteContent) isToolResultContent() {} func (*ToolResultSubagentContent) isToolResultContent() {} // ToolResultContentUnknown carries an unrecognized ToolResultContent variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -4077,12 +4050,6 @@ func (u *ToolResultContent) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "terminalComplete": - var value ToolResultTerminalCompleteContent - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value case "subagent": var value ToolResultSubagentContent if err := json.Unmarshal(data, &value); err != nil { 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 69d0dce0..7729d432 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 @@ -512,8 +512,6 @@ enum class ToolResultContentType { FILE_EDIT, @SerialName("terminal") TERMINAL, - @SerialName("terminalComplete") - TERMINAL_COMPLETE, @SerialName("subagent") SUBAGENT } @@ -3041,32 +3039,6 @@ data class ToolResultTerminalContent( val truncated: Boolean? = null ) -@Serializable -data class ToolResultTerminalCompleteContent( - val type: ToolResultContentType, - /** - * URI of the `ahp-terminal:` channel that carried live output for this - * command, if one was exposed. - */ - val resource: String? = null, - /** - * Exit code from the completed command, if reported by the runtime - */ - val exitCode: Long? = null, - /** - * Working directory where the command was executed - */ - val cwd: String? = null, - /** - * Preview of the command's output, if available - */ - val preview: String? = null, - /** - * Whether `preview` is known to be incomplete or truncated - */ - val truncated: Boolean? = null -) - @Serializable data class ToolResultSubagentContent( val type: ToolResultContentType, @@ -5490,7 +5462,6 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent - @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent /** @@ -5520,7 +5491,6 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) - "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) } @@ -5535,7 +5505,6 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) - is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw } diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index aee3dda1..c99a7cf0 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -407,8 +407,6 @@ pub enum ToolResultContentType { FileEdit, #[serde(rename = "terminal")] Terminal, - #[serde(rename = "terminalComplete")] - TerminalComplete, #[serde(rename = "subagent")] Subagent, } @@ -2696,37 +2694,6 @@ pub struct ToolResultTerminalContent { pub truncated: Option, } -/// Record of a command executed by a terminal-style tool (e.g. a shell tool), -/// appended to the tool result when the command exits. -/// -/// This records the command's exit, not the terminal's — the terminal may -/// keep running afterwards. -/// -/// When live output was exposed through a terminal channel (a -/// {@link ToolResultTerminalContent} block in the same tool result), -/// {@link resource} identifies that channel; otherwise this block stands alone -/// as the retained command result. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct ToolResultTerminalCompleteContent { - /// URI of the `ahp-terminal:` channel that carried live output for this - /// command, if one was exposed. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub resource: Option, - /// Exit code from the completed command, if reported by the runtime - #[serde(default, skip_serializing_if = "Option::is_none")] - pub exit_code: Option, - /// Working directory where the command was executed - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Preview of the command's output, if available - #[serde(default, skip_serializing_if = "Option::is_none")] - pub preview: Option, - /// Whether `preview` is known to be incomplete or truncated - #[serde(default, skip_serializing_if = "Option::is_none")] - pub truncated: Option, -} - /// A reference, embedded in a tool result, to a worker chat spawned by the tool /// call (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`). /// @@ -4311,8 +4278,6 @@ pub enum ToolResultContent { FileEdit(ToolResultFileEditContent), #[serde(rename = "terminal")] Terminal(ToolResultTerminalContent), - #[serde(rename = "terminalComplete")] - TerminalComplete(ToolResultTerminalCompleteContent), #[serde(rename = "subagent")] Subagent(ToolResultSubagentContent), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index be1a6c21..72680574 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -267,7 +267,6 @@ public enum ToolResultContentType: String, Codable, Sendable { case resource = "resource" case fileEdit = "fileEdit" case terminal = "terminal" - case terminalComplete = "terminalComplete" case subagent = "subagent" } @@ -3251,37 +3250,6 @@ public struct ToolResultTerminalContent: Codable, Sendable { } } -public struct ToolResultTerminalCompleteContent: Codable, Sendable { - public var type: ToolResultContentType - /// URI of the `ahp-terminal:` channel that carried live output for this - /// command, if one was exposed. - public var resource: String? - /// Exit code from the completed command, if reported by the runtime - public var exitCode: Int? - /// Working directory where the command was executed - public var cwd: String? - /// Preview of the command's output, if available - public var preview: String? - /// Whether `preview` is known to be incomplete or truncated - public var truncated: Bool? - - public init( - type: ToolResultContentType, - resource: String? = nil, - exitCode: Int? = nil, - cwd: String? = nil, - preview: String? = nil, - truncated: Bool? = nil - ) { - self.type = type - self.resource = resource - self.exitCode = exitCode - self.cwd = cwd - self.preview = preview - self.truncated = truncated - } -} - public struct ToolResultSubagentContent: Codable, Sendable { public var type: ToolResultContentType /// Worker chat URI (subscribable for full chat state) @@ -5791,7 +5759,6 @@ public enum ToolResultContent: Codable, Sendable { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) - case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved /// and re-encoded verbatim for forward-compatibility. @@ -5815,8 +5782,6 @@ public enum ToolResultContent: Codable, Sendable { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) - case "terminalComplete": - self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": self = .subagent(try ToolResultSubagentContent(from: decoder)) default: @@ -5837,7 +5802,6 @@ public enum ToolResultContent: Codable, Sendable { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) - case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) } diff --git a/docs/.changes/20260722-remove-terminal-complete.json b/docs/.changes/20260722-remove-terminal-complete.json new file mode 100644 index 00000000..89ff1ecf --- /dev/null +++ b/docs/.changes/20260722-remove-terminal-complete.json @@ -0,0 +1,5 @@ +{ + "type": "removed", + "message": "`ToolResultTerminalCompleteContent`; command completion data now lives on `ToolResultTerminalContent`.", + "issues": [322, 323] +} diff --git a/docs/.changes/20260722-terminal-completion-data.json b/docs/.changes/20260722-terminal-completion-data.json new file mode 100644 index 00000000..17a24ed3 --- /dev/null +++ b/docs/.changes/20260722-terminal-completion-data.json @@ -0,0 +1,5 @@ +{ + "type": "added", + "message": "`exitCode`, `preview`, and `truncated` on `ToolResultTerminalContent`, filled in on the completed result when the command exits.", + "issues": [323] +} diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 94a755a1..ab865ede 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6321,38 +6321,6 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { - "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", - "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, - "exitCode": { - "type": "number", - "description": "Exit code from the completed command, if reported by the runtime" - }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, - "preview": { - "type": "string", - "description": "Preview of the command's output, if available" - }, - "truncated": { - "type": "boolean", - "description": "Whether `preview` is known to be incomplete or truncated" - } - }, - "required": [ - "type" - ] - }, "ToolResultSubagentContent": { "type": "object", "description": "A reference, embedded in a tool result, to a worker chat spawned by the tool\ncall (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`).\n\nThis is the spawning tool call's forward view of the worker. The worker chat\nrecords the same edge in reverse via its {@link ChatOrigin} (`kind: 'tool'`),\nwhose `toolCallId` identifies the tool call that emitted this content.", @@ -7276,14 +7244,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 4d026407..69c9f473 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5527,38 +5527,6 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { - "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", - "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, - "exitCode": { - "type": "number", - "description": "Exit code from the completed command, if reported by the runtime" - }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, - "preview": { - "type": "string", - "description": "Preview of the command's output, if available" - }, - "truncated": { - "type": "boolean", - "description": "Whether `preview` is known to be incomplete or truncated" - } - }, - "required": [ - "type" - ] - }, "ToolResultSubagentContent": { "type": "object", "description": "A reference, embedded in a tool result, to a worker chat spawned by the tool\ncall (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`).\n\nThis is the spawning tool call's forward view of the worker. The worker chat\nrecords the same edge in reverse via its {@link ChatOrigin} (`kind: 'tool'`),\nwhose `toolCallId` identifies the tool call that emitted this content.", @@ -8907,14 +8875,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index d0ae69ae..e21c246e 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4311,38 +4311,6 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { - "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", - "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, - "exitCode": { - "type": "number", - "description": "Exit code from the completed command, if reported by the runtime" - }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, - "preview": { - "type": "string", - "description": "Preview of the command's output, if available" - }, - "truncated": { - "type": "boolean", - "description": "Whether `preview` is known to be incomplete or truncated" - } - }, - "required": [ - "type" - ] - }, "ToolResultSubagentContent": { "type": "object", "description": "A reference, embedded in a tool result, to a worker chat spawned by the tool\ncall (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`).\n\nThis is the spawning tool call's forward view of the worker. The worker chat\nrecords the same edge in reverse via its {@link ChatOrigin} (`kind: 'tool'`),\nwhose `toolCallId` identifies the tool call that emitted this content.", @@ -6656,14 +6624,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 0d75ea20..6bb5a400 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4471,38 +4471,6 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { - "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", - "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, - "exitCode": { - "type": "number", - "description": "Exit code from the completed command, if reported by the runtime" - }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, - "preview": { - "type": "string", - "description": "Preview of the command's output, if available" - }, - "truncated": { - "type": "boolean", - "description": "Whether `preview` is known to be incomplete or truncated" - } - }, - "required": [ - "type" - ] - }, "ToolResultSubagentContent": { "type": "object", "description": "A reference, embedded in a tool result, to a worker chat spawned by the tool\ncall (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`).\n\nThis is the spawning tool call's forward view of the worker. The worker chat\nrecords the same edge in reverse via its {@link ChatOrigin} (`kind: 'tool'`),\nwhose `toolCallId` identifies the tool call that emitted this content.", @@ -5519,14 +5487,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 8d07695d..f2785b08 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4222,38 +4222,6 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { - "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", - "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, - "exitCode": { - "type": "number", - "description": "Exit code from the completed command, if reported by the runtime" - }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, - "preview": { - "type": "string", - "description": "Preview of the command's output, if available" - }, - "truncated": { - "type": "boolean", - "description": "Whether `preview` is known to be incomplete or truncated" - } - }, - "required": [ - "type" - ] - }, "ToolResultSubagentContent": { "type": "object", "description": "A reference, embedded in a tool result, to a worker chat spawned by the tool\ncall (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`).\n\nThis is the spawning tool call's forward view of the worker. The worker chat\nrecords the same edge in reverse via its {@link ChatOrigin} (`kind: 'tool'`),\nwhose `toolCallId` identifies the tool call that emitted this content.", @@ -5177,14 +5145,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 02fa0334..f2194a56 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -731,7 +731,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ToolResultResourceContent' }, { name: 'ToolResultFileEditContent' }, { name: 'ToolResultTerminalContent' }, - { name: 'ToolResultTerminalCompleteContent' }, { name: 'ToolResultSubagentContent' }, { name: 'CustomizationLoadingState' }, { name: 'CustomizationLoadedState' }, @@ -896,7 +895,6 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, - { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], unknown: true, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 49468b01..60124b2d 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -730,7 +730,6 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent - @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent /** @@ -760,7 +759,6 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) - "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) } @@ -775,7 +773,6 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) - is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw } @@ -833,7 +830,7 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 4773ddb5..25f16572 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -762,7 +762,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ToolResultResourceContent', omitDiscriminants: true }, { name: 'ToolResultFileEditContent', omitDiscriminants: true }, { name: 'ToolResultTerminalContent', omitDiscriminants: true }, - { name: 'ToolResultTerminalCompleteContent', omitDiscriminants: true }, { name: 'ToolResultSubagentContent', omitDiscriminants: true }, { name: 'CustomizationLoadingState', omitDiscriminants: true }, { name: 'CustomizationLoadedState', omitDiscriminants: true }, @@ -927,7 +926,6 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, - { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], unknown: true, diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index f86be48c..6619a2d7 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -583,7 +583,7 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', @@ -821,7 +821,6 @@ function generateToolResultContentUnion(): string { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) - case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved /// and re-encoded verbatim for forward-compatibility. @@ -845,8 +844,6 @@ function generateToolResultContentUnion(): string { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) - case "terminalComplete": - self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": self = .subagent(try ToolResultSubagentContent(from: decoder)) default: @@ -867,7 +864,6 @@ function generateToolResultContentUnion(): string { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) - case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) } diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 8234026d..463f9422 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1331,7 +1331,6 @@ export const enum ToolResultContentType { Resource = 'resource', FileEdit = 'fileEdit', Terminal = 'terminal', - TerminalComplete = 'terminalComplete', Subagent = 'subagent', } @@ -1419,40 +1418,6 @@ export interface ToolResultTerminalContent { truncated?: boolean; } -/** - * Record of a command executed by a terminal-style tool (e.g. a shell tool), - * appended to the tool result when the command exits. - * - * This records the command's exit, not the terminal's — the terminal may - * keep running afterwards. - * - * When live output was exposed through a terminal channel (a - * {@link ToolResultTerminalContent} block in the same tool result), - * {@link resource} identifies that channel; otherwise this block stands alone - * as the retained command result. - * - * @deprecated Completion metadata is now filled in on the - * {@link ToolResultTerminalContent} block in the completed result. - * - * @category Tool Result Content - */ -export interface ToolResultTerminalCompleteContent { - type: ToolResultContentType.TerminalComplete; - /** - * URI of the `ahp-terminal:` channel that carried live output for this - * command, if one was exposed. - */ - resource?: URI; - /** Exit code from the completed command, if reported by the runtime */ - exitCode?: number; - /** Working directory where the command was executed */ - cwd?: URI; - /** Preview of the command's output, if available */ - preview?: string; - /** Whether `preview` is known to be incomplete or truncated */ - truncated?: boolean; -} - /** * A reference, embedded in a tool result, to a worker chat spawned by the tool * call (a sub-agent delegation), referenced by a chat URI (`ahp-chat:/...`). @@ -1481,8 +1446,8 @@ export interface ToolResultSubagentContent { * Mirrors the content blocks in MCP `CallToolResult.content`, plus * `ToolResultResourceContent` for lazy-loading large results, * `ToolResultFileEditContent` for file edit diffs, - * `ToolResultTerminalContent` for live terminal output, - * `ToolResultTerminalCompleteContent` for terminal-style completion metadata, and + * `ToolResultTerminalContent` for live terminal output and + * command completion metadata, and * `ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions). * * @category Tool Result Content @@ -1493,5 +1458,4 @@ export type ToolResultContent = | ToolResultResourceContent | ToolResultFileEditContent | ToolResultTerminalContent - | ToolResultTerminalCompleteContent | ToolResultSubagentContent; diff --git a/types/index.ts b/types/index.ts index fd5d5421..d483ffc8 100644 --- a/types/index.ts +++ b/types/index.ts @@ -68,7 +68,6 @@ export type { FileEdit, ToolResultFileEditContent, ToolResultTerminalContent, - ToolResultTerminalCompleteContent, ToolResultSubagentContent, SessionActiveClient, SessionInputRequest, diff --git a/types/test-cases/round-trips/031-terminal-content-completed.json b/types/test-cases/round-trips/031-terminal-content-completed.json new file mode 100644 index 00000000..615182dc --- /dev/null +++ b/types/test-cases/round-trips/031-terminal-content-completed.json @@ -0,0 +1,48 @@ +{ + "name": "terminal-content-completed", + "group": "A", + "description": "ActionEnvelope carries a terminal block with completion data filled in after the command exited: exit code, bounded preview, and truncation marker.", + "type": "ActionEnvelope", + "input": { + "channel": "ahp-chat:/s1/c1", + "action": { + "type": "chat/toolCallContentChanged", + "turnId": "turn-1", + "toolCallId": "tool-1", + "content": [ + { + "type": "terminal", + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "isPty": false, + "exitCode": 1, + "preview": "line 2\nline 3\n", + "truncated": true + } + ] + }, + "serverSeq": 11 + }, + "acceptableOutputs": [ + { + "action": { + "content": [ + { + "exitCode": 1, + "isPty": false, + "preview": "line 2\nline 3\n", + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "truncated": true, + "type": "terminal" + } + ], + "toolCallId": "tool-1", + "turnId": "turn-1", + "type": "chat/toolCallContentChanged" + }, + "channel": "ahp-chat:/s1/c1", + "serverSeq": 11 + } + ] +} From 8f27e1ef4e10a645e7af21039289d4b9360389f3 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 14:01:18 -0700 Subject: [PATCH 10/11] last tweak on wording . --- clients/go/ahptypes/state.generated.go | 8 ++++---- clients/rust/crates/ahp-types/src/state.rs | 8 ++++---- 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-chat/state.ts | 8 ++++---- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index b2fa4250..06f2ecad 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2151,10 +2151,10 @@ type ToolResultFileEditContent struct { // Clients can subscribe to the terminal's URI to stream its output in real // time, providing live feedback while a tool is executing. // -// When the command exits, `exitCode`, `preview`, and `truncated` are filled -// in on the completed result, retaining the outcome for clients that did -// not subscribe. This records the command's exit, not the terminal's — the -// terminal may keep running afterwards. +// When the command exits, `exitCode`, `preview`, and `truncated` may be +// filled in on the completed result, retaining the outcome for clients that +// did not subscribe. This records the command's exit, not the terminal's — +// the terminal may keep running afterwards. type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index c99a7cf0..5a2142cc 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2666,10 +2666,10 @@ pub struct ToolResultFileEditContent { /// Clients can subscribe to the terminal's URI to stream its output in real /// time, providing live feedback while a tool is executing. /// -/// When the command exits, `exitCode`, `preview`, and `truncated` are filled -/// in on the completed result, retaining the outcome for clients that did -/// not subscribe. This records the command's exit, not the terminal's — the -/// terminal may keep running afterwards. +/// When the command exits, `exitCode`, `preview`, and `truncated` may be +/// filled in on the completed result, retaining the outcome for clients that +/// did not subscribe. This records the command's exit, not the terminal's — +/// the terminal may keep running afterwards. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { diff --git a/schema/actions.schema.json b/schema/actions.schema.json index ab865ede..20ac8b3c 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", "properties": { "type": { "const": "terminal" diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 69c9f473..8fbdb215 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", "properties": { "type": { "const": "terminal" diff --git a/schema/errors.schema.json b/schema/errors.schema.json index e21c246e..e2d7e65a 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", "properties": { "type": { "const": "terminal" diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 6bb5a400..efb07e05 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", "properties": { "type": { "const": "terminal" diff --git a/schema/state.schema.json b/schema/state.schema.json index f2785b08..9f125632 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` are filled\nin on the completed result, retaining the outcome for clients that did\nnot subscribe. This records the command's exit, not the terminal's — the\nterminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", "properties": { "type": { "const": "terminal" diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 463f9422..76a8dc1c 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1388,10 +1388,10 @@ export interface ToolResultFileEditContent extends FileEdit { * Clients can subscribe to the terminal's URI to stream its output in real * time, providing live feedback while a tool is executing. * - * When the command exits, `exitCode`, `preview`, and `truncated` are filled - * in on the completed result, retaining the outcome for clients that did - * not subscribe. This records the command's exit, not the terminal's — the - * terminal may keep running afterwards. + * When the command exits, `exitCode`, `preview`, and `truncated` may be + * filled in on the completed result, retaining the outcome for clients that + * did not subscribe. This records the command's exit, not the terminal's — + * the terminal may keep running afterwards. * * @category Tool Result Content */ From 4c2e10105f13b1de659d3f253f8ad05fc48036a8 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 22 Jul 2026 14:19:29 -0700 Subject: [PATCH 11/11] create TerminalCommandResult --- clients/go/ahptypes/state.generated.go | 31 +++++++++------ .../generated/State.generated.kt | 32 +++++++++------ clients/rust/crates/ahp-types/src/state.rs | 38 +++++++++++------- .../Generated/State.generated.swift | 39 ++++++++++++------- .../20260722-terminal-completion-data.json | 2 +- schema/actions.schema.json | 26 +++++++++---- schema/commands.schema.json | 26 +++++++++---- schema/errors.schema.json | 26 +++++++++---- schema/notifications.schema.json | 26 +++++++++---- schema/state.schema.json | 26 +++++++++---- scripts/generate-go.ts | 1 + scripts/generate-kotlin.ts | 2 +- scripts/generate-rust.ts | 1 + scripts/generate-swift.ts | 2 +- types/channels-chat/state.ts | 23 ++++++++--- types/index.ts | 1 + .../031-terminal-content-completed.json | 18 +++++---- 17 files changed, 217 insertions(+), 103 deletions(-) diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 06f2ecad..20056d56 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2151,10 +2151,10 @@ type ToolResultFileEditContent struct { // Clients can subscribe to the terminal's URI to stream its output in real // time, providing live feedback while a tool is executing. // -// When the command exits, `exitCode`, `preview`, and `truncated` may be -// filled in on the completed result, retaining the outcome for clients that -// did not subscribe. This records the command's exit, not the terminal's — -// the terminal may keep running afterwards. +// When the command exits, {@link result} is filled in on the completed +// result, retaining the outcome for clients that did not subscribe. This +// records the command's exit, not the terminal's — the terminal may keep +// running afterwards. type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) @@ -2165,13 +2165,8 @@ type ToolResultTerminalContent struct { // When `false`, output is plain text and clients do not need to parse // VT sequences. IsPty *bool `json:"isPty,omitempty"` - // Exit code from the completed command, if reported by the runtime - ExitCode *int64 `json:"exitCode,omitempty"` - // Preview of the command's output, for clients that are not subscribed - // to the terminal or that arrive after it is disposed - Preview *string `json:"preview,omitempty"` - // Whether `preview` is known to be incomplete or truncated - Truncated *bool `json:"truncated,omitempty"` + // Outcome of the command, present once it has exited. + Result *TerminalCommandResult `json:"result,omitempty"` } // A reference, embedded in a tool result, to a worker chat spawned by the tool @@ -2935,6 +2930,20 @@ type FileEdit struct { Diff *json.RawMessage `json:"diff,omitempty"` } +// Outcome of a command run in a terminal-style tool, filled in on +// {@link ToolResultTerminalContent.result} once the command exits. +type TerminalCommandResult struct { + // Exit code from the completed command, if reported by the runtime + ExitCode *int64 `json:"exitCode,omitempty"` + // Preview of the command's output, for clients that are not subscribed + // to the terminal or that arrive after it is disposed. When `isPty` is + // `true` the preview may contain VT sequences; when `false` it is plain + // text. + Preview *string `json:"preview,omitempty"` + // Whether `preview` is known to be incomplete or truncated + Truncated *bool `json:"truncated,omitempty"` +} + // Lightweight terminal metadata exposed on the root state. type TerminalInfo struct { // Terminal URI (subscribable for full terminal state) 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 7729d432..25d1312c 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 @@ -3025,18 +3025,9 @@ data class ToolResultTerminalContent( */ val isPty: Boolean? = null, /** - * Exit code from the completed command, if reported by the runtime + * Outcome of the command, present once it has exited. */ - val exitCode: Long? = null, - /** - * Preview of the command's output, for clients that are not subscribed - * to the terminal or that arrive after it is disposed - */ - val preview: String? = null, - /** - * Whether `preview` is known to be incomplete or truncated - */ - val truncated: Boolean? = null + val result: TerminalCommandResult? = null ) @Serializable @@ -3931,6 +3922,25 @@ data class FileEdit( val diff: JsonElement? = null ) +@Serializable +data class TerminalCommandResult( + /** + * Exit code from the completed command, if reported by the runtime + */ + val exitCode: Long? = null, + /** + * Preview of the command's output, for clients that are not subscribed + * to the terminal or that arrive after it is disposed. When `isPty` is + * `true` the preview may contain VT sequences; when `false` it is plain + * text. + */ + val preview: String? = null, + /** + * Whether `preview` is known to be incomplete or truncated + */ + val truncated: Boolean? = null +) + @Serializable data class TerminalInfo( /** diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 5a2142cc..d1bfef63 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2666,10 +2666,10 @@ pub struct ToolResultFileEditContent { /// Clients can subscribe to the terminal's URI to stream its output in real /// time, providing live feedback while a tool is executing. /// -/// When the command exits, `exitCode`, `preview`, and `truncated` may be -/// filled in on the completed result, retaining the outcome for clients that -/// did not subscribe. This records the command's exit, not the terminal's — -/// the terminal may keep running afterwards. +/// When the command exits, {@link result} is filled in on the completed +/// result, retaining the outcome for clients that did not subscribe. This +/// records the command's exit, not the terminal's — the terminal may keep +/// running afterwards. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { @@ -2682,16 +2682,9 @@ pub struct ToolResultTerminalContent { /// VT sequences. #[serde(default, skip_serializing_if = "Option::is_none")] pub is_pty: Option, - /// Exit code from the completed command, if reported by the runtime - #[serde(default, skip_serializing_if = "Option::is_none")] - pub exit_code: Option, - /// Preview of the command's output, for clients that are not subscribed - /// to the terminal or that arrive after it is disposed + /// Outcome of the command, present once it has exited. #[serde(default, skip_serializing_if = "Option::is_none")] - pub preview: Option, - /// Whether `preview` is known to be incomplete or truncated - #[serde(default, skip_serializing_if = "Option::is_none")] - pub truncated: Option, + pub result: Option, } /// A reference, embedded in a tool result, to a worker chat spawned by the tool @@ -3555,6 +3548,25 @@ pub struct FileEdit { pub diff: Option, } +/// Outcome of a command run in a terminal-style tool, filled in on +/// {@link ToolResultTerminalContent.result} once the command exits. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct TerminalCommandResult { + /// Exit code from the completed command, if reported by the runtime + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exit_code: Option, + /// Preview of the command's output, for clients that are not subscribed + /// to the terminal or that arrive after it is disposed. When `isPty` is + /// `true` the preview may contain VT sequences; when `false` it is plain + /// text. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preview: Option, + /// Whether `preview` is known to be incomplete or truncated + #[serde(default, skip_serializing_if = "Option::is_none")] + pub truncated: Option, +} + /// Lightweight terminal metadata exposed on the root state. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 72680574..62dbc15a 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3223,30 +3223,21 @@ public struct ToolResultTerminalContent: Codable, Sendable { /// When `false`, output is plain text and clients do not need to parse /// VT sequences. public var isPty: Bool? - /// Exit code from the completed command, if reported by the runtime - public var exitCode: Int? - /// Preview of the command's output, for clients that are not subscribed - /// to the terminal or that arrive after it is disposed - public var preview: String? - /// Whether `preview` is known to be incomplete or truncated - public var truncated: Bool? + /// Outcome of the command, present once it has exited. + public var result: TerminalCommandResult? public init( type: ToolResultContentType, resource: String, title: String, isPty: Bool? = nil, - exitCode: Int? = nil, - preview: String? = nil, - truncated: Bool? = nil + result: TerminalCommandResult? = nil ) { self.type = type self.resource = resource self.title = title self.isPty = isPty - self.exitCode = exitCode - self.preview = preview - self.truncated = truncated + self.result = result } } @@ -4381,6 +4372,28 @@ public struct FileEdit: Codable, Sendable { } } +public struct TerminalCommandResult: Codable, Sendable { + /// Exit code from the completed command, if reported by the runtime + public var exitCode: Int? + /// Preview of the command's output, for clients that are not subscribed + /// to the terminal or that arrive after it is disposed. When `isPty` is + /// `true` the preview may contain VT sequences; when `false` it is plain + /// text. + public var preview: String? + /// Whether `preview` is known to be incomplete or truncated + public var truncated: Bool? + + public init( + exitCode: Int? = nil, + preview: String? = nil, + truncated: Bool? = nil + ) { + self.exitCode = exitCode + self.preview = preview + self.truncated = truncated + } +} + public struct TerminalInfo: Codable, Sendable { /// Terminal URI (subscribable for full terminal state) public var resource: String diff --git a/docs/.changes/20260722-terminal-completion-data.json b/docs/.changes/20260722-terminal-completion-data.json index 17a24ed3..7b2da8b8 100644 --- a/docs/.changes/20260722-terminal-completion-data.json +++ b/docs/.changes/20260722-terminal-completion-data.json @@ -1,5 +1,5 @@ { "type": "added", - "message": "`exitCode`, `preview`, and `truncated` on `ToolResultTerminalContent`, filled in on the completed result when the command exits.", + "message": "`result` on `ToolResultTerminalContent` (`exitCode`, `preview`, `truncated`), present once the command exits.", "issues": [323] } diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 20ac8b3c..46224c38 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -6302,24 +6302,34 @@ "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." + } + }, + "required": [ + "type", + "resource", + "title" + ] + }, + "TerminalCommandResult": { + "type": "object", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", + "properties": { "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, "preview": { "type": "string", - "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type", - "resource", - "title" - ] + } }, "ToolResultSubagentContent": { "type": "object", diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 8fbdb215..221b89d6 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -5508,24 +5508,34 @@ "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." + } + }, + "required": [ + "type", + "resource", + "title" + ] + }, + "TerminalCommandResult": { + "type": "object", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", + "properties": { "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, "preview": { "type": "string", - "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type", - "resource", - "title" - ] + } }, "ToolResultSubagentContent": { "type": "object", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index e2d7e65a..c0dea916 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -4292,24 +4292,34 @@ "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." + } + }, + "required": [ + "type", + "resource", + "title" + ] + }, + "TerminalCommandResult": { + "type": "object", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", + "properties": { "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, "preview": { "type": "string", - "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type", - "resource", - "title" - ] + } }, "ToolResultSubagentContent": { "type": "object", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index efb07e05..5e89283e 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -4452,24 +4452,34 @@ "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." + } + }, + "required": [ + "type", + "resource", + "title" + ] + }, + "TerminalCommandResult": { + "type": "object", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", + "properties": { "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, "preview": { "type": "string", - "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type", - "resource", - "title" - ] + } }, "ToolResultSubagentContent": { "type": "object", diff --git a/schema/state.schema.json b/schema/state.schema.json index 9f125632..7e13f096 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, `exitCode`, `preview`, and `truncated` may be\nfilled in on the completed result, retaining the outcome for clients that\ndid not subscribe. This records the command's exit, not the terminal's —\nthe terminal may keep running afterwards.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -4203,24 +4203,34 @@ "type": "boolean", "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." + } + }, + "required": [ + "type", + "resource", + "title" + ] + }, + "TerminalCommandResult": { + "type": "object", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", + "properties": { "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, "preview": { "type": "string", - "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type", - "resource", - "title" - ] + } }, "ToolResultSubagentContent": { "type": "object", diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index f2194a56..0f92fd3c 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -757,6 +757,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ToolCallClientContributor' }, { name: 'ToolCallMcpContributor' }, { name: 'FileEdit' }, + { name: 'TerminalCommandResult' }, { name: 'TerminalInfo' }, { name: 'TerminalClientClaim' }, { name: 'TerminalSessionClaim' }, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 60124b2d..d4514416 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -841,7 +841,7 @@ const STATE_STRUCTS = [ 'McpServerStartingState', 'McpServerReadyState', 'McpServerAuthRequiredState', 'McpServerErrorState', 'McpServerStoppedState', 'McpOAuthClient', 'McpAuthRequirement', 'ToolCallClientContributor', 'ToolCallMcpContributor', - 'FileEdit', 'TerminalInfo', + 'FileEdit', 'TerminalCommandResult', 'TerminalInfo', 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 25f16572..de8b3635 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -788,6 +788,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ToolCallClientContributor', omitDiscriminants: true }, { name: 'ToolCallMcpContributor', omitDiscriminants: true }, { name: 'FileEdit' }, + { name: 'TerminalCommandResult' }, { name: 'TerminalInfo' }, { name: 'TerminalClientClaim', omitDiscriminants: true }, { name: 'TerminalSessionClaim', omitDiscriminants: true }, diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 6619a2d7..3ca5b664 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -594,7 +594,7 @@ const STATE_STRUCTS = [ 'McpServerStartingState', 'McpServerReadyState', 'McpServerAuthRequiredState', 'McpServerErrorState', 'McpServerStoppedState', 'McpOAuthClient', 'McpAuthRequirement', 'ToolCallClientContributor', 'ToolCallMcpContributor', - 'FileEdit', 'TerminalInfo', + 'FileEdit', 'TerminalCommandResult', 'TerminalInfo', 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 76a8dc1c..b7a6df95 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1388,10 +1388,10 @@ export interface ToolResultFileEditContent extends FileEdit { * Clients can subscribe to the terminal's URI to stream its output in real * time, providing live feedback while a tool is executing. * - * When the command exits, `exitCode`, `preview`, and `truncated` may be - * filled in on the completed result, retaining the outcome for clients that - * did not subscribe. This records the command's exit, not the terminal's — - * the terminal may keep running afterwards. + * When the command exits, {@link result} is filled in on the completed + * result, retaining the outcome for clients that did not subscribe. This + * records the command's exit, not the terminal's — the terminal may keep + * running afterwards. * * @category Tool Result Content */ @@ -1407,11 +1407,24 @@ export interface ToolResultTerminalContent { * VT sequences. */ isPty?: boolean; + /** Outcome of the command, present once it has exited. */ + result?: TerminalCommandResult; +} + +/** + * Outcome of a command run in a terminal-style tool, filled in on + * {@link ToolResultTerminalContent.result} once the command exits. + * + * @category Tool Result Content + */ +export interface TerminalCommandResult { /** Exit code from the completed command, if reported by the runtime */ exitCode?: number; /** * Preview of the command's output, for clients that are not subscribed - * to the terminal or that arrive after it is disposed + * to the terminal or that arrive after it is disposed. When `isPty` is + * `true` the preview may contain VT sequences; when `false` it is plain + * text. */ preview?: string; /** Whether `preview` is known to be incomplete or truncated */ diff --git a/types/index.ts b/types/index.ts index d483ffc8..d25e2aae 100644 --- a/types/index.ts +++ b/types/index.ts @@ -68,6 +68,7 @@ export type { FileEdit, ToolResultFileEditContent, ToolResultTerminalContent, + TerminalCommandResult, ToolResultSubagentContent, SessionActiveClient, SessionInputRequest, diff --git a/types/test-cases/round-trips/031-terminal-content-completed.json b/types/test-cases/round-trips/031-terminal-content-completed.json index 615182dc..5400ed87 100644 --- a/types/test-cases/round-trips/031-terminal-content-completed.json +++ b/types/test-cases/round-trips/031-terminal-content-completed.json @@ -1,7 +1,7 @@ { "name": "terminal-content-completed", "group": "A", - "description": "ActionEnvelope carries a terminal block with completion data filled in after the command exited: exit code, bounded preview, and truncation marker.", + "description": "ActionEnvelope carries a terminal block whose result object is present after the command exited, with exit code, bounded preview, and truncation marker.", "type": "ActionEnvelope", "input": { "channel": "ahp-chat:/s1/c1", @@ -15,9 +15,11 @@ "resource": "ahp-terminal:/s1/t1", "title": "npm test", "isPty": false, - "exitCode": 1, - "preview": "line 2\nline 3\n", - "truncated": true + "result": { + "exitCode": 1, + "preview": "line 2\nline 3\n", + "truncated": true + } } ] }, @@ -28,12 +30,14 @@ "action": { "content": [ { - "exitCode": 1, "isPty": false, - "preview": "line 2\nline 3\n", "resource": "ahp-terminal:/s1/t1", + "result": { + "exitCode": 1, + "preview": "line 2\nline 3\n", + "truncated": true + }, "title": "npm test", - "truncated": true, "type": "terminal" } ],