Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,10 @@ public sealed record CreateChatParams
/// {@link AgentCapabilities.multipleWorkingDirectories}.</summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? WorkingDirectories { get; init; }

/// <summary>Client-selected initial display title for the new chat.</summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Title { get; init; }
}

/// <summary>Disposes a chat and cleans up server-side resources.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ private static (object decoded, string reencoded) DecodeAndReencode(string type,
return Wrap(Ser.Deserialize<Implementation>(inputJson));
case "InitializeResult":
return Wrap(Ser.Deserialize<InitializeResult>(inputJson));
case "CreateChatParams":
return Wrap(Ser.Deserialize<CreateChatParams>(inputJson));
case "Snapshot":
return Wrap(Ser.Deserialize<Snapshot>(inputJson));
case "SubscribeParams":
Expand Down
2 changes: 2 additions & 0 deletions clients/go/ahptypes/commands.generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ type CreateChatParams struct {
// A client MUST NOT supply this field unless the agent advertises
// {@link AgentCapabilities.multipleWorkingDirectories}.
WorkingDirectories []URI `json:"workingDirectories,omitempty"`
// Client-selected initial display title for the new chat.
Title *string `json:"title,omitempty"`
}

// Disposes a chat and cleans up server-side resources.
Expand Down
4 changes: 4 additions & 0 deletions clients/go/ahptypes/roundtrip_fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ func decodeAndReencode(t *testing.T, name, typ, inputJSON string) string {
var v InitializeResult
dec(&v)
return enc(&v)
case "CreateChatParams":
var v CreateChatParams
dec(&v)
return enc(&v)
case "ChatSource":
var v ChatSource
dec(&v)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,11 @@ data class CreateChatParams(
* A client MUST NOT supply this field unless the agent advertises
* {@link AgentCapabilities.multipleWorkingDirectories}.
*/
val workingDirectories: List<String>? = null
val workingDirectories: List<String>? = null,
/**
* Client-selected initial display title for the new chat.
*/
val title: String? = null
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.microsoft.agenthostprotocol

import com.microsoft.agenthostprotocol.generated.CreateChatParams
import com.microsoft.agenthostprotocol.generated.Message
import com.microsoft.agenthostprotocol.generated.MessageKind
import com.microsoft.agenthostprotocol.generated.MessageOrigin
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class ConstructorCompatibilityTest {
@Test
fun `old full positional CreateChatParams constructor retains its source semantics`() {
val message = Message("Hello", MessageOrigin(MessageKind.USER))
val createChat =
CreateChatParams(
"ahp-session:/session",
null,
"ahp-chat:/chat",
message,
null,
listOf("file:///workspace"),
)

assertEquals(message, createChat.initialMessage)
assertEquals(listOf("file:///workspace"), createChat.workingDirectories)
assertNull(createChat.title)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package com.microsoft.agenthostprotocol
import com.microsoft.agenthostprotocol.generated.ActionEnvelope
import com.microsoft.agenthostprotocol.generated.ChangesetOperationTarget
import com.microsoft.agenthostprotocol.generated.ChatSource
import com.microsoft.agenthostprotocol.generated.CreateChatParams
import com.microsoft.agenthostprotocol.generated.Customization
import com.microsoft.agenthostprotocol.generated.Implementation
import com.microsoft.agenthostprotocol.generated.InitializeResult
Expand Down Expand Up @@ -263,6 +264,7 @@ class RoundTripCorpusTest {
"PartialSessionSummary" -> rt(PartialSessionSummary.serializer())
"Implementation" -> rt(Implementation.serializer())
"InitializeResult" -> rt(InitializeResult.serializer())
"CreateChatParams" -> rt(CreateChatParams.serializer())
"ChatSource" -> rt(ChatSource.serializer())
"Snapshot" -> rt(Snapshot.serializer())
"SubscribeParams" -> rt(SubscribeParams.serializer())
Expand Down
3 changes: 3 additions & 0 deletions clients/rust/crates/ahp-types/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,9 @@ pub struct CreateChatParams {
/// {@link AgentCapabilities.multipleWorkingDirectories}.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub working_directories: Option<Vec<Uri>>,
/// Client-selected initial display title for the new chat.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}

/// Disposes a chat and cleans up server-side resources.
Expand Down
6 changes: 4 additions & 2 deletions clients/rust/crates/ahp-types/tests/roundtrip_corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
use ahp_types::{
actions::{ActionEnvelope, StateAction},
commands::{
ChangesetOperationTarget, ChatSource, CreateTcpConnectionParams, Implementation,
InitializeResult, ReconnectParams, ReconnectResult, SubscribeParams, SubscribeResult,
ChangesetOperationTarget, ChatSource, CreateChatParams, CreateTcpConnectionParams,
Implementation, InitializeResult, ReconnectParams, ReconnectResult, SubscribeParams,
SubscribeResult,
},
common::StringOrMarkdown,
messages::JsonRpcMessage,
Expand Down Expand Up @@ -229,6 +230,7 @@ fn decode_and_reencode(file: &str, type_name: &str, input_json: &str) -> Result<
"PartialSessionSummary" => round_trip!(PartialSessionSummary),
"Implementation" => round_trip!(Implementation),
"InitializeResult" => round_trip!(InitializeResult),
"CreateChatParams" => round_trip!(CreateChatParams),
"ChatSource" => round_trip!(ChatSource),
"Snapshot" => round_trip!(Snapshot),
"SubscribeParams" => round_trip!(SubscribeParams),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,8 @@ public struct CreateChatParams: Codable, Sendable {
/// A client MUST NOT supply this field unless the agent advertises
/// {@link AgentCapabilities.multipleWorkingDirectories}.
public var workingDirectories: [String]?
/// Client-selected initial display title for the new chat.
public var title: String?

enum CodingKeys: String, CodingKey {
case channel
Expand All @@ -981,6 +983,7 @@ public struct CreateChatParams: Codable, Sendable {
case initialMessage
case source
case workingDirectories
case title
}

public init(
Expand All @@ -989,14 +992,16 @@ public struct CreateChatParams: Codable, Sendable {
chat: String,
initialMessage: Message? = nil,
source: ChatSource? = nil,
workingDirectories: [String]? = nil
workingDirectories: [String]? = nil,
title: String? = nil
) {
self.channel = channel
self.meta = meta
self.chat = chat
self.initialMessage = initialMessage
self.source = source
self.workingDirectories = workingDirectories
self.title = title
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ final class TypesRoundTripFixtureTests: XCTestCase {
return try reencode(dec.decode(Implementation.self, from: inputData))
case "InitializeResult":
return try reencode(dec.decode(InitializeResult.self, from: inputData))
case "CreateChatParams":
return try reencode(dec.decode(CreateChatParams.self, from: inputData))
case "ChatSource":
return try reencode(dec.decode(ChatSource.self, from: inputData))
case "Snapshot":
Expand Down
3 changes: 2 additions & 1 deletion clients/typescript/test/types-round-trip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import type {
import type { ChannelFrameParams, ChannelCreditParams } from '../src/types/common/notifications.js';
import type { TcpDataParams, TcpEofParams, TcpResetParams } from '../src/types/channels-tcp/notifications.js';
import type { CreateTcpConnectionParams } from '../src/types/channels-tcp/commands.js';
import type { ChatSource } from '../src/types/channels-chat/commands.js';
import type { ChatSource, CreateChatParams } from '../src/types/channels-chat/commands.js';

// ─── Fixture directory ───────────────────────────────────────────────────────

Expand Down Expand Up @@ -248,6 +248,7 @@ function bindToType(file: string, type: string, parsed: unknown): void {
case 'PartialSessionSummary': void (parsed as Partial<SessionSummary>); break;
case 'Implementation': void (parsed as Implementation); break;
case 'InitializeResult': void (parsed as InitializeResult); break;
case 'CreateChatParams': void (parsed as CreateChatParams); break;
case 'ChatSource': void (parsed as ChatSource); break;
case 'Snapshot': void (parsed as Snapshot); break;
case 'SubscribeParams': void (parsed as SubscribeParams); break;
Expand Down
4 changes: 4 additions & 0 deletions docs/.changes/20261005-initial-chat-title.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "added",
"message": "Optional `CreateChatParams.title` for a client-selected initial display title."
}
1 change: 1 addition & 0 deletions docs/specification/chat-channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ omits the property. Every other property is changed on the session with

[`createChat`](/reference/chat#createchat) is a JSON-RPC request. Callers identify the owning session via the request's `channel` parameter (`ahp-session:/<sid>`) and MAY supply:

- a `title` containing the client-selected initial display title for the target chat,
- an `initialMessage` to start the first turn immediately — carrying its own [`model`](/reference/chat#message) / [`agent`](/reference/chat#message) selection — and
- a `source` of type [`ChatSource`](/reference/chat#chatsource), either
`{ kind: "fork", chat, turnId }` or `{ kind: "sideChat", chat, turnId }`,
Expand Down
4 changes: 4 additions & 0 deletions schema/commands.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,10 @@
"$ref": "#/$defs/URI"
},
"description": "Initial working-directory subset for this chat. Every entry MUST be\npresent in the owning session's `workingDirectories`; the server MUST\nreject any entry that is not. When absent, the chat inherits the full\nsession set. Forked chats (those whose `source.kind` is `\"fork\"`) inherit\nthe source chat's `workingDirectories`; this field is ignored for forks.\n\nA client MUST NOT supply this field unless the agent advertises\n{@link AgentCapabilities.multipleWorkingDirectories}."
},
"title": {
"type": "string",
"description": "Client-selected initial display title for the new chat."
}
},
"required": [
Expand Down
4 changes: 4 additions & 0 deletions schema/errors.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7508,6 +7508,10 @@
"$ref": "#/$defs/URI"
},
"description": "Initial working-directory subset for this chat. Every entry MUST be\npresent in the owning session's `workingDirectories`; the server MUST\nreject any entry that is not. When absent, the chat inherits the full\nsession set. Forked chats (those whose `source.kind` is `\"fork\"`) inherit\nthe source chat's `workingDirectories`; this field is ignored for forks.\n\nA client MUST NOT supply this field unless the agent advertises\n{@link AgentCapabilities.multipleWorkingDirectories}."
},
"title": {
"type": "string",
"description": "Client-selected initial display title for the new chat."
}
},
"required": [
Expand Down
2 changes: 2 additions & 0 deletions types/channels-chat/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export interface CreateChatParams extends BaseParams {
* {@link AgentCapabilities.multipleWorkingDirectories}.
*/
workingDirectories?: URI[];
/** Client-selected initial display title for the new chat. */
title?: string;
}

// ─── moveChat ────────────────────────────────────────────────────────────────
Expand Down
18 changes: 18 additions & 0 deletions types/test-cases/round-trips/055-create-chat-initial-title.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "create-chat-initial-title",
"group": "A",
"description": "CreateChatParams preserves a client-selected initial display title through every generated client.",
"type": "CreateChatParams",
"input": {
"channel": "ahp-session:/s1",
"chat": "ahp-chat:/c2",
"title": "Investigate startup performance"
},
"acceptableOutputs": [
{
"channel": "ahp-session:/s1",
"chat": "ahp-chat:/c2",
"title": "Investigate startup performance"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "create-chat-fork-initial-message",
"group": "A",
"description": "CreateChatParams preserves an initial user message, including its model and agent selections, when it is combined with a fork source.",
"type": "CreateChatParams",
"input": {
"channel": "ahp-session:/s1",
"chat": "ahp-chat:/fork",
"source": {
"kind": "fork",
"chat": "ahp-chat:/source",
"turnId": "turn-12"
},
"initialMessage": {
"text": "Continue by extracting the parser.",
"origin": {
"kind": "user"
},
"model": {
"id": "model-2"
},
"agent": {
"uri": "ahp-agent:/agent-2"
}
}
},
"acceptableOutputs": [
{
"channel": "ahp-session:/s1",
"chat": "ahp-chat:/fork",
"source": {
"kind": "fork",
"chat": "ahp-chat:/source",
"turnId": "turn-12"
},
"initialMessage": {
"text": "Continue by extracting the parser.",
"origin": {
"kind": "user"
},
"model": {
"id": "model-2"
},
"agent": {
"uri": "ahp-agent:/agent-2"
}
}
}
]
}
Loading