diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index 978608c9..0092eaf0 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -681,6 +681,8 @@ func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) R list = append(list, entry) } state.QueuedMessages = list + default: + return ReduceOutcomeNoOp } return ReduceOutcomeApplied case *ahptypes.ChatPendingMessageRemovedAction: diff --git a/clients/go/ahptypes/roundtrip_fixture_test.go b/clients/go/ahptypes/roundtrip_fixture_test.go index 834d30ee..c3e7ee0a 100644 --- a/clients/go/ahptypes/roundtrip_fixture_test.go +++ b/clients/go/ahptypes/roundtrip_fixture_test.go @@ -246,6 +246,14 @@ func decodeAndReencode(t *testing.T, name, typ, inputJSON string) string { var v ChatSource dec(&v) return enc(&v) + case "AuthRequiredParams": + var v AuthRequiredParams + dec(&v) + return enc(&v) + case "Turn": + var v Turn + dec(&v) + return enc(&v) default: t.Fatalf("%s: round-trip fixture: unknown wire type %q. Add a decode entry to decodeAndReencode.", name, typ) return "" diff --git a/clients/kotlin/AGENTS.md b/clients/kotlin/AGENTS.md index c862f886..e1fbd49c 100644 --- a/clients/kotlin/AGENTS.md +++ b/clients/kotlin/AGENTS.md @@ -43,8 +43,8 @@ CI verifies the committed generated files match the output of `npm run generate: | `T[]` / `Array` | `List` | | `Record` | `Map` | | `Partial` | `PartialT` data class with all fields nullable | -| `enum E { A = "a" }` | `@Serializable enum class E { @SerialName("a") A }` | -| Bitset enum (JSDoc "Bitset") | `@JvmInline value class` over `Int` w/ companion-object constants | +| `enum E { A = "a" }` | string-backed `@JvmInline value class E` with known constants | +| Bitset enum (JSDoc "Bitset") | `@JvmInline value class` over `UInt` w/ companion-object constants | | Interface struct | `@Serializable data class` | | Discriminated union | sealed interface + custom `KSerializer` (mirrors Swift) | | `URI` | `typealias URI = String` | @@ -78,13 +78,20 @@ The TS source models `ChangesetOperationTarget` as a discriminated union over tw ### Bitset enums -`SessionStatus` is currently the only bitset enum in the protocol. It's emitted as a `@JvmInline value class` over `Int` so that **unknown future flags survive a decode/encode round-trip** without being silently dropped. Use `or`/`and`/`in` for combinator/containment ops: +`SessionStatus` is currently the only bitset enum in the protocol. It's emitted as a `@JvmInline value class` over `UInt` so that **unknown future flags survive a decode/encode round-trip** without being silently dropped. Use `or`/`and`/`in` for combinator/containment ops: ```kotlin val combined = SessionStatus.IDLE or SessionStatus.IS_READ SessionStatus.IDLE in combined // true ``` +### String enums + +String enums are emitted as `@JvmInline value class` wrappers with a string +`rawValue` and companion-object constants for known values. This keeps familiar +references such as `TurnState.COMPLETE` while allowing unknown future values to +decode and round-trip without failing the containing message. + ## Distribution Artifacts are published to Maven Central (Sonatype Central Portal) via Microsoft's ESRP-backed `vscode-engineering` `maven-package` pipeline template on `kotlin/v*` git tags. The [`gradle-maven-publish-plugin`](https://github.com/vanniktech/gradle-maven-publish-plugin) (v0.36+) is used to stage and GPG-sign the artifacts into a local Maven repository layout that ESRP then uploads. diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt index b62d7a61..46e7ceb3 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -1386,7 +1386,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when val entry = PendingMessage(id = a.id, message = a.message) if (a.kind == PendingMessageKind.STEERING) { state.copy(steeringMessage = entry) - } else { + } else if (a.kind == PendingMessageKind.QUEUED) { val existing = state.queuedMessages ?: emptyList() val idx = existing.indexOfFirst { it.id == a.id } val updated = if (idx >= 0) { @@ -1395,6 +1395,8 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when existing + entry } state.copy(queuedMessages = updated) + } else { + state } } @@ -1407,7 +1409,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } else { state.copy(steeringMessage = null) } - } else { + } else if (a.kind == PendingMessageKind.QUEUED) { val existing = state.queuedMessages ?: return@chatReducer state val filtered = existing.filter { it.id != a.id } if (filtered.size == existing.size) { @@ -1415,6 +1417,8 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } else { state.copy(queuedMessages = filtered.ifEmpty { null }) } + } else { + state } } diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt index f696130b..bee8a93d 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt @@ -24,178 +24,106 @@ import kotlinx.serialization.json.contentOrNull /** * Discriminant values for all state actions. */ -@Serializable -enum class ActionType { - @SerialName("root/agentsChanged") - ROOT_AGENTS_CHANGED, - @SerialName("root/activeSessionsChanged") - ROOT_ACTIVE_SESSIONS_CHANGED, - @SerialName("session/ready") - SESSION_READY, - @SerialName("session/creationFailed") - SESSION_CREATION_FAILED, - @SerialName("session/chatAdded") - SESSION_CHAT_ADDED, - @SerialName("session/chatRemoved") - SESSION_CHAT_REMOVED, - @SerialName("session/chatUpdated") - SESSION_CHAT_UPDATED, - @SerialName("session/defaultChatChanged") - SESSION_DEFAULT_CHAT_CHANGED, - @SerialName("chat/turnStarted") - CHAT_TURN_STARTED, - @SerialName("chat/delta") - CHAT_DELTA, - @SerialName("chat/responsePart") - CHAT_RESPONSE_PART, - @SerialName("chat/toolCallStart") - CHAT_TOOL_CALL_START, - @SerialName("chat/toolCallDelta") - CHAT_TOOL_CALL_DELTA, - @SerialName("chat/toolCallReady") - CHAT_TOOL_CALL_READY, - @SerialName("chat/toolCallConfirmed") - CHAT_TOOL_CALL_CONFIRMED, - @SerialName("chat/toolCallComplete") - CHAT_TOOL_CALL_COMPLETE, - @SerialName("chat/toolCallResultConfirmed") - CHAT_TOOL_CALL_RESULT_CONFIRMED, - @SerialName("chat/toolCallContentChanged") - CHAT_TOOL_CALL_CONTENT_CHANGED, - @SerialName("chat/toolCallAuthRequired") - CHAT_TOOL_CALL_AUTH_REQUIRED, - @SerialName("chat/toolCallAuthResolved") - CHAT_TOOL_CALL_AUTH_RESOLVED, - @SerialName("chat/turnComplete") - CHAT_TURN_COMPLETE, - @SerialName("chat/turnCancelled") - CHAT_TURN_CANCELLED, - @SerialName("chat/error") - CHAT_ERROR, - @SerialName("chat/activityChanged") - CHAT_ACTIVITY_CHANGED, - @SerialName("chat/workingDirectorySet") - CHAT_WORKING_DIRECTORY_SET, - @SerialName("chat/workingDirectoryRemoved") - CHAT_WORKING_DIRECTORY_REMOVED, - @SerialName("session/titleChanged") - SESSION_TITLE_CHANGED, - @SerialName("chat/usage") - CHAT_USAGE, - @SerialName("chat/reasoning") - CHAT_REASONING, - @SerialName("session/serverToolsChanged") - SESSION_SERVER_TOOLS_CHANGED, - @SerialName("session/activeClientSet") - SESSION_ACTIVE_CLIENT_SET, - @SerialName("session/activeClientRemoved") - SESSION_ACTIVE_CLIENT_REMOVED, - @SerialName("session/workingDirectorySet") - SESSION_WORKING_DIRECTORY_SET, - @SerialName("session/workingDirectoryRemoved") - SESSION_WORKING_DIRECTORY_REMOVED, - @SerialName("session/inputNeededSet") - SESSION_INPUT_NEEDED_SET, - @SerialName("session/inputNeededRemoved") - SESSION_INPUT_NEEDED_REMOVED, - @SerialName("chat/pendingMessageSet") - CHAT_PENDING_MESSAGE_SET, - @SerialName("chat/pendingMessageRemoved") - CHAT_PENDING_MESSAGE_REMOVED, - @SerialName("chat/queuedMessagesReordered") - CHAT_QUEUED_MESSAGES_REORDERED, - @SerialName("chat/draftChanged") - CHAT_DRAFT_CHANGED, - @SerialName("chat/inputRequested") - CHAT_INPUT_REQUESTED, - @SerialName("chat/inputAnswerChanged") - CHAT_INPUT_ANSWER_CHANGED, - @SerialName("chat/inputCompleted") - CHAT_INPUT_COMPLETED, - @SerialName("session/customizationsChanged") - SESSION_CUSTOMIZATIONS_CHANGED, - @SerialName("session/customizationToggled") - SESSION_CUSTOMIZATION_TOGGLED, - @SerialName("session/customizationUpdated") - SESSION_CUSTOMIZATION_UPDATED, - @SerialName("session/customizationRemoved") - SESSION_CUSTOMIZATION_REMOVED, - @SerialName("session/mcpServerStateChanged") - SESSION_MCP_SERVER_STATE_CHANGED, - @SerialName("session/mcpServerStartRequested") - SESSION_MCP_SERVER_START_REQUESTED, - @SerialName("session/mcpServerStopRequested") - SESSION_MCP_SERVER_STOP_REQUESTED, - @SerialName("chat/truncated") - CHAT_TRUNCATED, - @SerialName("chat/turnsLoaded") - CHAT_TURNS_LOADED, - @SerialName("session/isReadChanged") - SESSION_IS_READ_CHANGED, - @SerialName("session/isArchivedChanged") - SESSION_IS_ARCHIVED_CHANGED, - @SerialName("session/activityChanged") - SESSION_ACTIVITY_CHANGED, - @SerialName("session/changesetsChanged") - SESSION_CHANGESETS_CHANGED, - @SerialName("session/configChanged") - SESSION_CONFIG_CHANGED, - @SerialName("session/metaChanged") - SESSION_META_CHANGED, - @SerialName("changeset/statusChanged") - CHANGESET_STATUS_CHANGED, - @SerialName("changeset/fileSet") - CHANGESET_FILE_SET, - @SerialName("changeset/fileRemoved") - CHANGESET_FILE_REMOVED, - @SerialName("changeset/filesReviewChanged") - CHANGESET_FILES_REVIEW_CHANGED, - @SerialName("changeset/contentChanged") - CHANGESET_CONTENT_CHANGED, - @SerialName("changeset/operationsChanged") - CHANGESET_OPERATIONS_CHANGED, - @SerialName("changeset/operationStatusChanged") - CHANGESET_OPERATION_STATUS_CHANGED, - @SerialName("changeset/cleared") - CHANGESET_CLEARED, - @SerialName("annotations/set") - ANNOTATIONS_SET, - @SerialName("annotations/updated") - ANNOTATIONS_UPDATED, - @SerialName("annotations/removed") - ANNOTATIONS_REMOVED, - @SerialName("annotations/entrySet") - ANNOTATIONS_ENTRY_SET, - @SerialName("annotations/entryRemoved") - ANNOTATIONS_ENTRY_REMOVED, - @SerialName("root/terminalsChanged") - ROOT_TERMINALS_CHANGED, - @SerialName("root/configChanged") - ROOT_CONFIG_CHANGED, - @SerialName("terminal/data") - TERMINAL_DATA, - @SerialName("terminal/input") - TERMINAL_INPUT, - @SerialName("terminal/resized") - TERMINAL_RESIZED, - @SerialName("terminal/claimed") - TERMINAL_CLAIMED, - @SerialName("terminal/titleChanged") - TERMINAL_TITLE_CHANGED, - @SerialName("terminal/cwdChanged") - TERMINAL_CWD_CHANGED, - @SerialName("terminal/exited") - TERMINAL_EXITED, - @SerialName("terminal/cleared") - TERMINAL_CLEARED, - @SerialName("terminal/commandDetectionAvailable") - TERMINAL_COMMAND_DETECTION_AVAILABLE, - @SerialName("terminal/commandExecuted") - TERMINAL_COMMAND_EXECUTED, - @SerialName("terminal/commandFinished") - TERMINAL_COMMAND_FINISHED, - @SerialName("resourceWatch/changed") - RESOURCE_WATCH_CHANGED +@Serializable(with = ActionTypeSerializer::class) +@JvmInline +value class ActionType(val rawValue: String) { + companion object { + val ROOT_AGENTS_CHANGED: ActionType = ActionType("root/agentsChanged") + val ROOT_ACTIVE_SESSIONS_CHANGED: ActionType = ActionType("root/activeSessionsChanged") + val SESSION_READY: ActionType = ActionType("session/ready") + val SESSION_CREATION_FAILED: ActionType = ActionType("session/creationFailed") + val SESSION_CHAT_ADDED: ActionType = ActionType("session/chatAdded") + val SESSION_CHAT_REMOVED: ActionType = ActionType("session/chatRemoved") + val SESSION_CHAT_UPDATED: ActionType = ActionType("session/chatUpdated") + val SESSION_DEFAULT_CHAT_CHANGED: ActionType = ActionType("session/defaultChatChanged") + val CHAT_TURN_STARTED: ActionType = ActionType("chat/turnStarted") + val CHAT_DELTA: ActionType = ActionType("chat/delta") + val CHAT_RESPONSE_PART: ActionType = ActionType("chat/responsePart") + val CHAT_TOOL_CALL_START: ActionType = ActionType("chat/toolCallStart") + val CHAT_TOOL_CALL_DELTA: ActionType = ActionType("chat/toolCallDelta") + val CHAT_TOOL_CALL_READY: ActionType = ActionType("chat/toolCallReady") + val CHAT_TOOL_CALL_CONFIRMED: ActionType = ActionType("chat/toolCallConfirmed") + val CHAT_TOOL_CALL_COMPLETE: ActionType = ActionType("chat/toolCallComplete") + val CHAT_TOOL_CALL_RESULT_CONFIRMED: ActionType = ActionType("chat/toolCallResultConfirmed") + val CHAT_TOOL_CALL_CONTENT_CHANGED: ActionType = ActionType("chat/toolCallContentChanged") + val CHAT_TOOL_CALL_AUTH_REQUIRED: ActionType = ActionType("chat/toolCallAuthRequired") + val CHAT_TOOL_CALL_AUTH_RESOLVED: ActionType = ActionType("chat/toolCallAuthResolved") + val CHAT_TURN_COMPLETE: ActionType = ActionType("chat/turnComplete") + val CHAT_TURN_CANCELLED: ActionType = ActionType("chat/turnCancelled") + val CHAT_ERROR: ActionType = ActionType("chat/error") + val CHAT_ACTIVITY_CHANGED: ActionType = ActionType("chat/activityChanged") + val CHAT_WORKING_DIRECTORY_SET: ActionType = ActionType("chat/workingDirectorySet") + val CHAT_WORKING_DIRECTORY_REMOVED: ActionType = ActionType("chat/workingDirectoryRemoved") + val SESSION_TITLE_CHANGED: ActionType = ActionType("session/titleChanged") + val CHAT_USAGE: ActionType = ActionType("chat/usage") + val CHAT_REASONING: ActionType = ActionType("chat/reasoning") + val SESSION_SERVER_TOOLS_CHANGED: ActionType = ActionType("session/serverToolsChanged") + val SESSION_ACTIVE_CLIENT_SET: ActionType = ActionType("session/activeClientSet") + val SESSION_ACTIVE_CLIENT_REMOVED: ActionType = ActionType("session/activeClientRemoved") + val SESSION_WORKING_DIRECTORY_SET: ActionType = ActionType("session/workingDirectorySet") + val SESSION_WORKING_DIRECTORY_REMOVED: ActionType = ActionType("session/workingDirectoryRemoved") + val SESSION_INPUT_NEEDED_SET: ActionType = ActionType("session/inputNeededSet") + val SESSION_INPUT_NEEDED_REMOVED: ActionType = ActionType("session/inputNeededRemoved") + val CHAT_PENDING_MESSAGE_SET: ActionType = ActionType("chat/pendingMessageSet") + val CHAT_PENDING_MESSAGE_REMOVED: ActionType = ActionType("chat/pendingMessageRemoved") + val CHAT_QUEUED_MESSAGES_REORDERED: ActionType = ActionType("chat/queuedMessagesReordered") + val CHAT_DRAFT_CHANGED: ActionType = ActionType("chat/draftChanged") + val CHAT_INPUT_REQUESTED: ActionType = ActionType("chat/inputRequested") + val CHAT_INPUT_ANSWER_CHANGED: ActionType = ActionType("chat/inputAnswerChanged") + val CHAT_INPUT_COMPLETED: ActionType = ActionType("chat/inputCompleted") + val SESSION_CUSTOMIZATIONS_CHANGED: ActionType = ActionType("session/customizationsChanged") + val SESSION_CUSTOMIZATION_TOGGLED: ActionType = ActionType("session/customizationToggled") + val SESSION_CUSTOMIZATION_UPDATED: ActionType = ActionType("session/customizationUpdated") + val SESSION_CUSTOMIZATION_REMOVED: ActionType = ActionType("session/customizationRemoved") + val SESSION_MCP_SERVER_STATE_CHANGED: ActionType = ActionType("session/mcpServerStateChanged") + val SESSION_MCP_SERVER_START_REQUESTED: ActionType = ActionType("session/mcpServerStartRequested") + val SESSION_MCP_SERVER_STOP_REQUESTED: ActionType = ActionType("session/mcpServerStopRequested") + val CHAT_TRUNCATED: ActionType = ActionType("chat/truncated") + val CHAT_TURNS_LOADED: ActionType = ActionType("chat/turnsLoaded") + val SESSION_IS_READ_CHANGED: ActionType = ActionType("session/isReadChanged") + val SESSION_IS_ARCHIVED_CHANGED: ActionType = ActionType("session/isArchivedChanged") + val SESSION_ACTIVITY_CHANGED: ActionType = ActionType("session/activityChanged") + val SESSION_CHANGESETS_CHANGED: ActionType = ActionType("session/changesetsChanged") + val SESSION_CONFIG_CHANGED: ActionType = ActionType("session/configChanged") + val SESSION_META_CHANGED: ActionType = ActionType("session/metaChanged") + val CHANGESET_STATUS_CHANGED: ActionType = ActionType("changeset/statusChanged") + val CHANGESET_FILE_SET: ActionType = ActionType("changeset/fileSet") + val CHANGESET_FILE_REMOVED: ActionType = ActionType("changeset/fileRemoved") + val CHANGESET_FILES_REVIEW_CHANGED: ActionType = ActionType("changeset/filesReviewChanged") + val CHANGESET_CONTENT_CHANGED: ActionType = ActionType("changeset/contentChanged") + val CHANGESET_OPERATIONS_CHANGED: ActionType = ActionType("changeset/operationsChanged") + val CHANGESET_OPERATION_STATUS_CHANGED: ActionType = ActionType("changeset/operationStatusChanged") + val CHANGESET_CLEARED: ActionType = ActionType("changeset/cleared") + val ANNOTATIONS_SET: ActionType = ActionType("annotations/set") + val ANNOTATIONS_UPDATED: ActionType = ActionType("annotations/updated") + val ANNOTATIONS_REMOVED: ActionType = ActionType("annotations/removed") + val ANNOTATIONS_ENTRY_SET: ActionType = ActionType("annotations/entrySet") + val ANNOTATIONS_ENTRY_REMOVED: ActionType = ActionType("annotations/entryRemoved") + val ROOT_TERMINALS_CHANGED: ActionType = ActionType("root/terminalsChanged") + val ROOT_CONFIG_CHANGED: ActionType = ActionType("root/configChanged") + val TERMINAL_DATA: ActionType = ActionType("terminal/data") + val TERMINAL_INPUT: ActionType = ActionType("terminal/input") + val TERMINAL_RESIZED: ActionType = ActionType("terminal/resized") + val TERMINAL_CLAIMED: ActionType = ActionType("terminal/claimed") + val TERMINAL_TITLE_CHANGED: ActionType = ActionType("terminal/titleChanged") + val TERMINAL_CWD_CHANGED: ActionType = ActionType("terminal/cwdChanged") + val TERMINAL_EXITED: ActionType = ActionType("terminal/exited") + val TERMINAL_CLEARED: ActionType = ActionType("terminal/cleared") + val TERMINAL_COMMAND_DETECTION_AVAILABLE: ActionType = ActionType("terminal/commandDetectionAvailable") + val TERMINAL_COMMAND_EXECUTED: ActionType = ActionType("terminal/commandExecuted") + val TERMINAL_COMMAND_FINISHED: ActionType = ActionType("terminal/commandFinished") + val RESOURCE_WATCH_CHANGED: ActionType = ActionType("resourceWatch/changed") + } +} + +internal object ActionTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ActionType", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ActionType) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ActionType = + ActionType(decoder.decodeString()) } // ─── Action Infrastructure ────────────────────────────────────────────────── diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index f259cb67..337d3ae4 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -24,67 +24,122 @@ import kotlinx.serialization.json.contentOrNull /** * Discriminant for reconnect result types. */ -@Serializable -enum class ReconnectResultType { - @SerialName("replay") - REPLAY, - @SerialName("snapshot") - SNAPSHOT +@Serializable(with = ReconnectResultTypeSerializer::class) +@JvmInline +value class ReconnectResultType(val rawValue: String) { + companion object { + val REPLAY: ReconnectResultType = ReconnectResultType("replay") + val SNAPSHOT: ReconnectResultType = ReconnectResultType("snapshot") + } +} + +internal object ReconnectResultTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ReconnectResultType", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ReconnectResultType) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ReconnectResultType = + ReconnectResultType(decoder.decodeString()) } /** * How a new chat uses its source chat and turn. */ -@Serializable -enum class ChatSourceKind { - /** - * Copy source history through the referenced turn into the new chat. - */ - @SerialName("fork") - FORK, - /** - * Supply source context without copying it into the new chat's visible history. - */ - @SerialName("sideChat") - SIDE_CHAT +@Serializable(with = ChatSourceKindSerializer::class) +@JvmInline +value class ChatSourceKind(val rawValue: String) { + companion object { + /** + * Copy source history through the referenced turn into the new chat. + */ + val FORK: ChatSourceKind = ChatSourceKind("fork") + /** + * Supply source context without copying it into the new chat's visible history. + */ + val SIDE_CHAT: ChatSourceKind = ChatSourceKind("sideChat") + } +} + +internal object ChatSourceKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatSourceKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatSourceKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatSourceKind = + ChatSourceKind(decoder.decodeString()) } /** * Encoding of fetched content data. */ -@Serializable -enum class ContentEncoding { - @SerialName("base64") - BASE64, - @SerialName("utf-8") - UTF8 +@Serializable(with = ContentEncodingSerializer::class) +@JvmInline +value class ContentEncoding(val rawValue: String) { + companion object { + val BASE64: ContentEncoding = ContentEncoding("base64") + val UTF8: ContentEncoding = ContentEncoding("utf-8") + } +} + +internal object ContentEncodingSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ContentEncoding", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ContentEncoding) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ContentEncoding = + ContentEncoding(decoder.decodeString()) } /** * The kind of completion items being requested. */ -@Serializable -enum class CompletionItemKind { - /** - * Completions for the text of a {@link Message} the user is composing. - * Each returned item carries an attachment that gets associated with the - * message when accepted. - */ - @SerialName("userMessage") - USER_MESSAGE +@Serializable(with = CompletionItemKindSerializer::class) +@JvmInline +value class CompletionItemKind(val rawValue: String) { + companion object { + /** + * Completions for the text of a {@link Message} the user is composing. + * Each returned item carries an attachment that gets associated with the + * message when accepted. + */ + val USER_MESSAGE: CompletionItemKind = CompletionItemKind("userMessage") + } +} + +internal object CompletionItemKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("CompletionItemKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: CompletionItemKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): CompletionItemKind = + CompletionItemKind(decoder.decodeString()) } /** * Discriminant for {@link ResourceResolveResult.type}. */ -@Serializable -enum class ResourceType { - @SerialName("file") - FILE, - @SerialName("directory") - DIRECTORY, - @SerialName("symlink") - SYMLINK +@Serializable(with = ResourceTypeSerializer::class) +@JvmInline +value class ResourceType(val rawValue: String) { + companion object { + val FILE: ResourceType = ResourceType("file") + val DIRECTORY: ResourceType = ResourceType("directory") + val SYMLINK: ResourceType = ResourceType("symlink") + } +} + +internal object ResourceTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ResourceType", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ResourceType) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ResourceType = + ResourceType(decoder.decodeString()) } /** @@ -108,14 +163,24 @@ enum class ResourceType { * `position` are shifted right by `data.length`. `insert` always grows * the file — use `truncate` to overwrite bytes in place. */ -@Serializable -enum class ResourceWriteMode { - @SerialName("truncate") - TRUNCATE, - @SerialName("append") - APPEND, - @SerialName("insert") - INSERT +@Serializable(with = ResourceWriteModeSerializer::class) +@JvmInline +value class ResourceWriteMode(val rawValue: String) { + companion object { + val TRUNCATE: ResourceWriteMode = ResourceWriteMode("truncate") + val APPEND: ResourceWriteMode = ResourceWriteMode("append") + val INSERT: ResourceWriteMode = ResourceWriteMode("insert") + } +} + +internal object ResourceWriteModeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ResourceWriteMode", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ResourceWriteMode) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ResourceWriteMode = + ResourceWriteMode(decoder.decodeString()) } // ─── Command Types ────────────────────────────────────────────────────────── diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt index 64e93a15..d191886a 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt @@ -24,18 +24,29 @@ import kotlinx.serialization.json.contentOrNull /** * Reason why authentication is required. */ -@Serializable -enum class AuthRequiredReason { - /** - * The client has not yet authenticated for the resource - */ - @SerialName("required") - REQUIRED, - /** - * A previously valid token has expired or been revoked - */ - @SerialName("expired") - EXPIRED +@Serializable(with = AuthRequiredReasonSerializer::class) +@JvmInline +value class AuthRequiredReason(val rawValue: String) { + companion object { + /** + * The client has not yet authenticated for the resource + */ + val REQUIRED: AuthRequiredReason = AuthRequiredReason("required") + /** + * A previously valid token has expired or been revoked + */ + val EXPIRED: AuthRequiredReason = AuthRequiredReason("expired") + } +} + +internal object AuthRequiredReasonSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("AuthRequiredReason", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: AuthRequiredReason) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): AuthRequiredReason = + AuthRequiredReason(decoder.decodeString()) } // ─── Notification Types ───────────────────────────────────────────────────── 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 3b912915..513f05e4 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 @@ -74,44 +74,75 @@ internal object StringOrMarkdownSerializer : KSerializer { /** * Policy configuration state for a model. */ -@Serializable -enum class PolicyState { - @SerialName("enabled") - ENABLED, - @SerialName("disabled") - DISABLED, - @SerialName("unconfigured") - UNCONFIGURED +@Serializable(with = PolicyStateSerializer::class) +@JvmInline +value class PolicyState(val rawValue: String) { + companion object { + val ENABLED: PolicyState = PolicyState("enabled") + val DISABLED: PolicyState = PolicyState("disabled") + val UNCONFIGURED: PolicyState = PolicyState("unconfigured") + } +} + +internal object PolicyStateSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("PolicyState", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: PolicyState) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): PolicyState = + PolicyState(decoder.decodeString()) } /** * Discriminant for pending message kinds. */ -@Serializable -enum class PendingMessageKind { - /** - * Injected into the current turn at a convenient point - */ - @SerialName("steering") - STEERING, - /** - * Sent automatically as a new turn after the current turn finishes - */ - @SerialName("queued") - QUEUED +@Serializable(with = PendingMessageKindSerializer::class) +@JvmInline +value class PendingMessageKind(val rawValue: String) { + companion object { + /** + * Injected into the current turn at a convenient point + */ + val STEERING: PendingMessageKind = PendingMessageKind("steering") + /** + * Sent automatically as a new turn after the current turn finishes + */ + val QUEUED: PendingMessageKind = PendingMessageKind("queued") + } +} + +internal object PendingMessageKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("PendingMessageKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: PendingMessageKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): PendingMessageKind = + PendingMessageKind(decoder.decodeString()) } /** * Session initialization state. */ -@Serializable -enum class SessionLifecycle { - @SerialName("creating") - CREATING, - @SerialName("ready") - READY, - @SerialName("creationFailed") - CREATION_FAILED +@Serializable(with = SessionLifecycleSerializer::class) +@JvmInline +value class SessionLifecycle(val rawValue: String) { + companion object { + val CREATING: SessionLifecycle = SessionLifecycle("creating") + val READY: SessionLifecycle = SessionLifecycle("ready") + val CREATION_FAILED: SessionLifecycle = SessionLifecycle("creationFailed") + } +} + +internal object SessionLifecycleSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("SessionLifecycle", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: SessionLifecycle) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): SessionLifecycle = + SessionLifecycle(decoder.decodeString()) } /** @@ -171,28 +202,37 @@ internal object SessionStatusSerializer : KSerializer { /** * Discriminant for {@link ChatOrigin} — how a chat came into existence. */ -@Serializable -enum class ChatOriginKind { - /** - * User created the chat explicitly (e.g. via the host UI). - */ - @SerialName("user") - USER, - /** - * Forked from an existing chat at a specific turn. - */ - @SerialName("fork") - FORK, - /** - * Created as an independent side conversation from a specific turn. - */ - @SerialName("sideChat") - SIDE_CHAT, - /** - * Spawned by a tool call running in another chat (e.g. a sub-agent delegation). - */ - @SerialName("tool") - TOOL +@Serializable(with = ChatOriginKindSerializer::class) +@JvmInline +value class ChatOriginKind(val rawValue: String) { + companion object { + /** + * User created the chat explicitly (e.g. via the host UI). + */ + val USER: ChatOriginKind = ChatOriginKind("user") + /** + * Forked from an existing chat at a specific turn. + */ + val FORK: ChatOriginKind = ChatOriginKind("fork") + /** + * Created as an independent side conversation from a specific turn. + */ + val SIDE_CHAT: ChatOriginKind = ChatOriginKind("sideChat") + /** + * Spawned by a tool call running in another chat (e.g. a sub-agent delegation). + */ + val TOOL: ChatOriginKind = ChatOriginKind("tool") + } +} + +internal object ChatOriginKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatOriginKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatOriginKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatOriginKind = + ChatOriginKind(decoder.decodeString()) } /** @@ -207,85 +247,130 @@ enum class ChatOriginKind { * implementation detail). The harness sets this based on the chat's role; * the UI uses it to show appropriate controls. */ -@Serializable -enum class ChatInteractivity { - /** - * User can send messages and watch (default when absent) - */ - @SerialName("full") - FULL, - /** - * User can watch but not send messages - */ - @SerialName("read-only") - READ_ONLY, - /** - * Internal worker not shown in UI at all - */ - @SerialName("hidden") - HIDDEN +@Serializable(with = ChatInteractivitySerializer::class) +@JvmInline +value class ChatInteractivity(val rawValue: String) { + companion object { + /** + * User can send messages and watch (default when absent) + */ + val FULL: ChatInteractivity = ChatInteractivity("full") + /** + * User can watch but not send messages + */ + val READ_ONLY: ChatInteractivity = ChatInteractivity("read-only") + /** + * Internal worker not shown in UI at all + */ + val HIDDEN: ChatInteractivity = ChatInteractivity("hidden") + } +} + +internal object ChatInteractivitySerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatInteractivity", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatInteractivity) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatInteractivity = + ChatInteractivity(decoder.decodeString()) } /** * Answer lifecycle state. */ -@Serializable -enum class ChatInputAnswerState { - @SerialName("draft") - DRAFT, - @SerialName("submitted") - SUBMITTED, - @SerialName("skipped") - SKIPPED +@Serializable(with = ChatInputAnswerStateSerializer::class) +@JvmInline +value class ChatInputAnswerState(val rawValue: String) { + companion object { + val DRAFT: ChatInputAnswerState = ChatInputAnswerState("draft") + val SUBMITTED: ChatInputAnswerState = ChatInputAnswerState("submitted") + val SKIPPED: ChatInputAnswerState = ChatInputAnswerState("skipped") + } +} + +internal object ChatInputAnswerStateSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatInputAnswerState", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatInputAnswerState) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatInputAnswerState = + ChatInputAnswerState(decoder.decodeString()) } /** * Answer value kind. */ -@Serializable -enum class ChatInputAnswerValueKind { - @SerialName("text") - TEXT, - @SerialName("number") - NUMBER, - @SerialName("boolean") - BOOLEAN, - @SerialName("selected") - SELECTED, - @SerialName("selected-many") - SELECTED_MANY +@Serializable(with = ChatInputAnswerValueKindSerializer::class) +@JvmInline +value class ChatInputAnswerValueKind(val rawValue: String) { + companion object { + val TEXT: ChatInputAnswerValueKind = ChatInputAnswerValueKind("text") + val NUMBER: ChatInputAnswerValueKind = ChatInputAnswerValueKind("number") + val BOOLEAN: ChatInputAnswerValueKind = ChatInputAnswerValueKind("boolean") + val SELECTED: ChatInputAnswerValueKind = ChatInputAnswerValueKind("selected") + val SELECTED_MANY: ChatInputAnswerValueKind = ChatInputAnswerValueKind("selected-many") + } +} + +internal object ChatInputAnswerValueKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatInputAnswerValueKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatInputAnswerValueKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatInputAnswerValueKind = + ChatInputAnswerValueKind(decoder.decodeString()) } /** * Question/input control kind. */ -@Serializable -enum class ChatInputQuestionKind { - @SerialName("text") - TEXT, - @SerialName("number") - NUMBER, - @SerialName("integer") - INTEGER, - @SerialName("boolean") - BOOLEAN, - @SerialName("single-select") - SINGLE_SELECT, - @SerialName("multi-select") - MULTI_SELECT +@Serializable(with = ChatInputQuestionKindSerializer::class) +@JvmInline +value class ChatInputQuestionKind(val rawValue: String) { + companion object { + val TEXT: ChatInputQuestionKind = ChatInputQuestionKind("text") + val NUMBER: ChatInputQuestionKind = ChatInputQuestionKind("number") + val INTEGER: ChatInputQuestionKind = ChatInputQuestionKind("integer") + val BOOLEAN: ChatInputQuestionKind = ChatInputQuestionKind("boolean") + val SINGLE_SELECT: ChatInputQuestionKind = ChatInputQuestionKind("single-select") + val MULTI_SELECT: ChatInputQuestionKind = ChatInputQuestionKind("multi-select") + } +} + +internal object ChatInputQuestionKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatInputQuestionKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatInputQuestionKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatInputQuestionKind = + ChatInputQuestionKind(decoder.decodeString()) } /** * How a client completed an input request. */ -@Serializable -enum class ChatInputResponseKind { - @SerialName("accept") - ACCEPT, - @SerialName("decline") - DECLINE, - @SerialName("cancel") - CANCEL +@Serializable(with = ChatInputResponseKindSerializer::class) +@JvmInline +value class ChatInputResponseKind(val rawValue: String) { + companion object { + val ACCEPT: ChatInputResponseKind = ChatInputResponseKind("accept") + val DECLINE: ChatInputResponseKind = ChatInputResponseKind("decline") + val CANCEL: ChatInputResponseKind = ChatInputResponseKind("cancel") + } +} + +internal object ChatInputResponseKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChatInputResponseKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChatInputResponseKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChatInputResponseKind = + ChatInputResponseKind(decoder.decodeString()) } /** @@ -295,147 +380,196 @@ enum class ChatInputResponseKind { * This is a general/typological union (not a lifecycle), so the discriminant is * a `*Kind`. */ -@Serializable -enum class SessionInputRequestKind { - /** - * A user-facing elicitation mirrored from an unresolved chat response part. - */ - @SerialName("chatInput") - CHAT_INPUT, - /** - * A tool call awaiting parameter- or result-confirmation. - */ - @SerialName("toolConfirmation") - TOOL_CONFIRMATION, - /** - * A running tool the session wants an active client to execute. - */ - @SerialName("toolClientExecution") - TOOL_CLIENT_EXECUTION, - /** - * A tool call blocked on MCP authentication mid-execution. - */ - @SerialName("toolAuthentication") - TOOL_AUTHENTICATION +@Serializable(with = SessionInputRequestKindSerializer::class) +@JvmInline +value class SessionInputRequestKind(val rawValue: String) { + companion object { + /** + * A user-facing elicitation mirrored from an unresolved chat response part. + */ + val CHAT_INPUT: SessionInputRequestKind = SessionInputRequestKind("chatInput") + /** + * A tool call awaiting parameter- or result-confirmation. + */ + val TOOL_CONFIRMATION: SessionInputRequestKind = SessionInputRequestKind("toolConfirmation") + /** + * A running tool the session wants an active client to execute. + */ + val TOOL_CLIENT_EXECUTION: SessionInputRequestKind = SessionInputRequestKind("toolClientExecution") + /** + * A tool call blocked on MCP authentication mid-execution. + */ + val TOOL_AUTHENTICATION: SessionInputRequestKind = SessionInputRequestKind("toolAuthentication") + } +} + +internal object SessionInputRequestKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("SessionInputRequestKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: SessionInputRequestKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): SessionInputRequestKind = + SessionInputRequestKind(decoder.decodeString()) } /** * How a turn ended. */ -@Serializable -enum class TurnState { - @SerialName("complete") - COMPLETE, - @SerialName("cancelled") - CANCELLED, - @SerialName("error") - ERROR +@Serializable(with = TurnStateSerializer::class) +@JvmInline +value class TurnState(val rawValue: String) { + companion object { + val COMPLETE: TurnState = TurnState("complete") + val CANCELLED: TurnState = TurnState("cancelled") + val ERROR: TurnState = TurnState("error") + } +} + +internal object TurnStateSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("TurnState", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: TurnState) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): TurnState = + TurnState(decoder.decodeString()) } /** * Discriminant for {@link MessageOrigin} — identifies who produced a message. */ -@Serializable -enum class MessageKind { - /** - * Sent directly by the user. - */ - @SerialName("user") - USER, - /** - * Produced by the agent itself rather than the user — for example, an agent - * that seeds the first message of a chat it spawned. - */ - @SerialName("agent") - AGENT, - /** - * Produced by a tool rather than the user — for example, a tool that spawns a - * worker chat whose first message carries a seed prompt. - */ - @SerialName("tool") - TOOL, - /** - * A system-generated notification rather than a direct user message. - */ - @SerialName("systemNotification") - SYSTEM_NOTIFICATION +@Serializable(with = MessageKindSerializer::class) +@JvmInline +value class MessageKind(val rawValue: String) { + companion object { + /** + * Sent directly by the user. + */ + val USER: MessageKind = MessageKind("user") + /** + * Produced by the agent itself rather than the user — for example, an agent + * that seeds the first message of a chat it spawned. + */ + val AGENT: MessageKind = MessageKind("agent") + /** + * Produced by a tool rather than the user — for example, a tool that spawns a + * worker chat whose first message carries a seed prompt. + */ + val TOOL: MessageKind = MessageKind("tool") + /** + * A system-generated notification rather than a direct user message. + */ + val SYSTEM_NOTIFICATION: MessageKind = MessageKind("systemNotification") + } +} + +internal object MessageKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("MessageKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: MessageKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): MessageKind = + MessageKind(decoder.decodeString()) } /** * Discriminant for {@link MessageAttachment} variants. */ -@Serializable -enum class MessageAttachmentKind { - /** - * A simple, opaque attachment whose representation is described by the producer. - */ - @SerialName("simple") - SIMPLE, - /** - * An attachment whose data is embedded inline as a base64 string. - */ - @SerialName("embeddedResource") - EMBEDDED_RESOURCE, - /** - * An attachment that references a resource by URI. - */ - @SerialName("resource") - RESOURCE, - /** - * An attachment that references annotations on an annotations channel. - */ - @SerialName("annotations") - ANNOTATIONS, - /** - * An attachment that references a bounded transcript from another chat. - */ - @SerialName("chat") - CHAT +@Serializable(with = MessageAttachmentKindSerializer::class) +@JvmInline +value class MessageAttachmentKind(val rawValue: String) { + companion object { + /** + * A simple, opaque attachment whose representation is described by the producer. + */ + val SIMPLE: MessageAttachmentKind = MessageAttachmentKind("simple") + /** + * An attachment whose data is embedded inline as a base64 string. + */ + val EMBEDDED_RESOURCE: MessageAttachmentKind = MessageAttachmentKind("embeddedResource") + /** + * An attachment that references a resource by URI. + */ + val RESOURCE: MessageAttachmentKind = MessageAttachmentKind("resource") + /** + * An attachment that references annotations on an annotations channel. + */ + val ANNOTATIONS: MessageAttachmentKind = MessageAttachmentKind("annotations") + /** + * An attachment that references a bounded transcript from another chat. + */ + val CHAT: MessageAttachmentKind = MessageAttachmentKind("chat") + } +} + +internal object MessageAttachmentKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("MessageAttachmentKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: MessageAttachmentKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): MessageAttachmentKind = + MessageAttachmentKind(decoder.decodeString()) } /** * Discriminant for response part types. */ -@Serializable -enum class ResponsePartKind { - @SerialName("markdown") - MARKDOWN, - @SerialName("contentRef") - CONTENT_REF, - @SerialName("toolCall") - TOOL_CALL, - @SerialName("reasoning") - REASONING, - @SerialName("systemNotification") - SYSTEM_NOTIFICATION, - @SerialName("inputRequest") - INPUT_REQUEST +@Serializable(with = ResponsePartKindSerializer::class) +@JvmInline +value class ResponsePartKind(val rawValue: String) { + companion object { + val MARKDOWN: ResponsePartKind = ResponsePartKind("markdown") + val CONTENT_REF: ResponsePartKind = ResponsePartKind("contentRef") + val TOOL_CALL: ResponsePartKind = ResponsePartKind("toolCall") + val REASONING: ResponsePartKind = ResponsePartKind("reasoning") + val SYSTEM_NOTIFICATION: ResponsePartKind = ResponsePartKind("systemNotification") + val INPUT_REQUEST: ResponsePartKind = ResponsePartKind("inputRequest") + } +} + +internal object ResponsePartKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ResponsePartKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ResponsePartKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ResponsePartKind = + ResponsePartKind(decoder.decodeString()) } /** * Status of a tool call in the lifecycle state machine. */ -@Serializable -enum class ToolCallStatus { - @SerialName("streaming") - STREAMING, - @SerialName("pending-confirmation") - PENDING_CONFIRMATION, - @SerialName("running") - RUNNING, - /** - * Running paused because the MCP server backing this call needs - * authentication (typically step-up auth for insufficient scope, - * surfacing mid-execution). See {@link ToolCallAuthRequiredState}. - */ - @SerialName("auth-required") - AUTH_REQUIRED, - @SerialName("pending-result-confirmation") - PENDING_RESULT_CONFIRMATION, - @SerialName("completed") - COMPLETED, - @SerialName("cancelled") - CANCELLED +@Serializable(with = ToolCallStatusSerializer::class) +@JvmInline +value class ToolCallStatus(val rawValue: String) { + companion object { + val STREAMING: ToolCallStatus = ToolCallStatus("streaming") + val PENDING_CONFIRMATION: ToolCallStatus = ToolCallStatus("pending-confirmation") + val RUNNING: ToolCallStatus = ToolCallStatus("running") + /** + * Running paused because the MCP server backing this call needs + * authentication (typically step-up auth for insufficient scope, + * surfacing mid-execution). See {@link ToolCallAuthRequiredState}. + */ + val AUTH_REQUIRED: ToolCallStatus = ToolCallStatus("auth-required") + val PENDING_RESULT_CONFIRMATION: ToolCallStatus = ToolCallStatus("pending-result-confirmation") + val COMPLETED: ToolCallStatus = ToolCallStatus("completed") + val CANCELLED: ToolCallStatus = ToolCallStatus("cancelled") + } +} + +internal object ToolCallStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolCallStatus", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolCallStatus) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolCallStatus = + ToolCallStatus(decoder.decodeString()) } /** @@ -445,85 +579,157 @@ enum class ToolCallStatus { * - `UserAction` — User explicitly approved * - `Setting` — Approved by a persistent user setting */ -@Serializable -enum class ToolCallConfirmationReason { - @SerialName("not-needed") - NOT_NEEDED, - @SerialName("user-action") - USER_ACTION, - @SerialName("setting") - SETTING +@Serializable(with = ToolCallConfirmationReasonSerializer::class) +@JvmInline +value class ToolCallConfirmationReason(val rawValue: String) { + companion object { + val NOT_NEEDED: ToolCallConfirmationReason = ToolCallConfirmationReason("not-needed") + val USER_ACTION: ToolCallConfirmationReason = ToolCallConfirmationReason("user-action") + val SETTING: ToolCallConfirmationReason = ToolCallConfirmationReason("setting") + } +} + +internal object ToolCallConfirmationReasonSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolCallConfirmationReason", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolCallConfirmationReason) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolCallConfirmationReason = + ToolCallConfirmationReason(decoder.decodeString()) } /** * Identifies a model judge as the source of a confirmation requirement. */ -@Serializable -enum class ToolCallRiskAssessmentKind { - @SerialName("judge") - JUDGE +@Serializable(with = ToolCallRiskAssessmentKindSerializer::class) +@JvmInline +value class ToolCallRiskAssessmentKind(val rawValue: String) { + companion object { + val JUDGE: ToolCallRiskAssessmentKind = ToolCallRiskAssessmentKind("judge") + } +} + +internal object ToolCallRiskAssessmentKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolCallRiskAssessmentKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolCallRiskAssessmentKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolCallRiskAssessmentKind = + ToolCallRiskAssessmentKind(decoder.decodeString()) } /** * Lifecycle status of an asynchronous model-judge confirmation decision. */ -@Serializable -enum class ToolCallRiskAssessmentStatus { - @SerialName("loading") - LOADING, - @SerialName("complete") - COMPLETE +@Serializable(with = ToolCallRiskAssessmentStatusSerializer::class) +@JvmInline +value class ToolCallRiskAssessmentStatus(val rawValue: String) { + companion object { + val LOADING: ToolCallRiskAssessmentStatus = ToolCallRiskAssessmentStatus("loading") + val COMPLETE: ToolCallRiskAssessmentStatus = ToolCallRiskAssessmentStatus("complete") + } +} + +internal object ToolCallRiskAssessmentStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolCallRiskAssessmentStatus", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolCallRiskAssessmentStatus) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolCallRiskAssessmentStatus = + ToolCallRiskAssessmentStatus(decoder.decodeString()) } /** * Why a tool call was cancelled. */ -@Serializable -enum class ToolCallCancellationReason { - @SerialName("denied") - DENIED, - @SerialName("skipped") - SKIPPED, - @SerialName("result-denied") - RESULT_DENIED +@Serializable(with = ToolCallCancellationReasonSerializer::class) +@JvmInline +value class ToolCallCancellationReason(val rawValue: String) { + companion object { + val DENIED: ToolCallCancellationReason = ToolCallCancellationReason("denied") + val SKIPPED: ToolCallCancellationReason = ToolCallCancellationReason("skipped") + val RESULT_DENIED: ToolCallCancellationReason = ToolCallCancellationReason("result-denied") + } +} + +internal object ToolCallCancellationReasonSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolCallCancellationReason", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolCallCancellationReason) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolCallCancellationReason = + ToolCallCancellationReason(decoder.decodeString()) } /** * Whether a confirmation option represents an approval or denial action. */ -@Serializable -enum class ConfirmationOptionKind { - @SerialName("approve") - APPROVE, - @SerialName("deny") - DENY +@Serializable(with = ConfirmationOptionKindSerializer::class) +@JvmInline +value class ConfirmationOptionKind(val rawValue: String) { + companion object { + val APPROVE: ConfirmationOptionKind = ConfirmationOptionKind("approve") + val DENY: ConfirmationOptionKind = ConfirmationOptionKind("deny") + } } -@Serializable -enum class ToolCallContributorKind { - @SerialName("client") - CLIENT, - @SerialName("mcp") - MCP +internal object ConfirmationOptionKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ConfirmationOptionKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ConfirmationOptionKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ConfirmationOptionKind = + ConfirmationOptionKind(decoder.decodeString()) +} + +@Serializable(with = ToolCallContributorKindSerializer::class) +@JvmInline +value class ToolCallContributorKind(val rawValue: String) { + companion object { + val CLIENT: ToolCallContributorKind = ToolCallContributorKind("client") + val MCP: ToolCallContributorKind = ToolCallContributorKind("mcp") + } +} + +internal object ToolCallContributorKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolCallContributorKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolCallContributorKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolCallContributorKind = + ToolCallContributorKind(decoder.decodeString()) } /** * Discriminant for tool result content types. */ -@Serializable -enum class ToolResultContentType { - @SerialName("text") - TEXT, - @SerialName("embeddedResource") - EMBEDDED_RESOURCE, - @SerialName("resource") - RESOURCE, - @SerialName("fileEdit") - FILE_EDIT, - @SerialName("terminal") - TERMINAL, - @SerialName("subagent") - SUBAGENT +@Serializable(with = ToolResultContentTypeSerializer::class) +@JvmInline +value class ToolResultContentType(val rawValue: String) { + companion object { + val TEXT: ToolResultContentType = ToolResultContentType("text") + val EMBEDDED_RESOURCE: ToolResultContentType = ToolResultContentType("embeddedResource") + val RESOURCE: ToolResultContentType = ToolResultContentType("resource") + val FILE_EDIT: ToolResultContentType = ToolResultContentType("fileEdit") + val TERMINAL: ToolResultContentType = ToolResultContentType("terminal") + val SUBAGENT: ToolResultContentType = ToolResultContentType("subagent") + } +} + +internal object ToolResultContentTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ToolResultContentType", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ToolResultContentType) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ToolResultContentType = + ToolResultContentType(decoder.decodeString()) } /** @@ -537,86 +743,119 @@ enum class ToolResultContentType { * directly by the host. The remaining types appear only as children of * a container. */ -@Serializable -enum class CustomizationType { - @SerialName("plugin") - PLUGIN, - @SerialName("directory") - DIRECTORY, - @SerialName("agent") - AGENT, - @SerialName("skill") - SKILL, - @SerialName("prompt") - PROMPT, - @SerialName("rule") - RULE, - @SerialName("hook") - HOOK, - @SerialName("mcpServer") - MCP_SERVER +@Serializable(with = CustomizationTypeSerializer::class) +@JvmInline +value class CustomizationType(val rawValue: String) { + companion object { + val PLUGIN: CustomizationType = CustomizationType("plugin") + val DIRECTORY: CustomizationType = CustomizationType("directory") + val AGENT: CustomizationType = CustomizationType("agent") + val SKILL: CustomizationType = CustomizationType("skill") + val PROMPT: CustomizationType = CustomizationType("prompt") + val RULE: CustomizationType = CustomizationType("rule") + val HOOK: CustomizationType = CustomizationType("hook") + val MCP_SERVER: CustomizationType = CustomizationType("mcpServer") + } +} + +internal object CustomizationTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("CustomizationType", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: CustomizationType) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): CustomizationType = + CustomizationType(decoder.decodeString()) } /** * Discriminant values for {@link CustomizationLoadState}. */ -@Serializable -enum class CustomizationLoadStatus { - @SerialName("loading") - LOADING, - @SerialName("loaded") - LOADED, - @SerialName("degraded") - DEGRADED, - @SerialName("error") - ERROR +@Serializable(with = CustomizationLoadStatusSerializer::class) +@JvmInline +value class CustomizationLoadStatus(val rawValue: String) { + companion object { + val LOADING: CustomizationLoadStatus = CustomizationLoadStatus("loading") + val LOADED: CustomizationLoadStatus = CustomizationLoadStatus("loaded") + val DEGRADED: CustomizationLoadStatus = CustomizationLoadStatus("degraded") + val ERROR: CustomizationLoadStatus = CustomizationLoadStatus("error") + } +} + +internal object CustomizationLoadStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("CustomizationLoadStatus", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: CustomizationLoadStatus) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): CustomizationLoadStatus = + CustomizationLoadStatus(decoder.decodeString()) } /** * Discriminant for terminal claim kinds. */ -@Serializable -enum class TerminalClaimKind { - @SerialName("client") - CLIENT, - @SerialName("session") - SESSION +@Serializable(with = TerminalClaimKindSerializer::class) +@JvmInline +value class TerminalClaimKind(val rawValue: String) { + companion object { + val CLIENT: TerminalClaimKind = TerminalClaimKind("client") + val SESSION: TerminalClaimKind = TerminalClaimKind("session") + } +} + +internal object TerminalClaimKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("TerminalClaimKind", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: TerminalClaimKind) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): TerminalClaimKind = + TerminalClaimKind(decoder.decodeString()) } /** * Discriminant for the {@link McpServerState} union. */ -@Serializable -enum class McpServerStatus { - /** - * Server has been registered but is not yet running. - */ - @SerialName("starting") - STARTING, - /** - * Server is running and serving requests. - */ - @SerialName("ready") - READY, - /** - * Server is reachable but requires additional authentication before it - * can start, or before it can serve a particular request. Carries the - * RFC 9728 Protected Resource Metadata the client needs to obtain a - * token; the client then pushes the token via the existing - * `authenticate` command. - */ - @SerialName("authRequired") - AUTH_REQUIRED, - /** - * Server failed to start, crashed, or otherwise transitioned to a fatal error. - */ - @SerialName("error") - ERROR, - /** - * Server has been shut down. - */ - @SerialName("stopped") - STOPPED +@Serializable(with = McpServerStatusSerializer::class) +@JvmInline +value class McpServerStatus(val rawValue: String) { + companion object { + /** + * Server has been registered but is not yet running. + */ + val STARTING: McpServerStatus = McpServerStatus("starting") + /** + * Server is running and serving requests. + */ + val READY: McpServerStatus = McpServerStatus("ready") + /** + * Server is reachable but requires additional authentication before it + * can start, or before it can serve a particular request. Carries the + * RFC 9728 Protected Resource Metadata the client needs to obtain a + * token; the client then pushes the token via the existing + * `authenticate` command. + */ + val AUTH_REQUIRED: McpServerStatus = McpServerStatus("authRequired") + /** + * Server failed to start, crashed, or otherwise transitioned to a fatal error. + */ + val ERROR: McpServerStatus = McpServerStatus("error") + /** + * Server has been shut down. + */ + val STOPPED: McpServerStatus = McpServerStatus("stopped") + } +} + +internal object McpServerStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("McpServerStatus", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: McpServerStatus) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): McpServerStatus = + McpServerStatus(decoder.decodeString()) } /** @@ -624,61 +863,81 @@ enum class McpServerStatus { * state. Mirrors the three failure modes defined by the * [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization.md). */ -@Serializable -enum class McpAuthRequiredReason { - /** - * No token has been provided yet (HTTP 401, no prior token). - */ - @SerialName("required") - REQUIRED, - /** - * A previously valid token expired or was revoked (HTTP 401). - */ - @SerialName("expired") - EXPIRED, - /** - * Step-up auth: a token is present but its scopes are insufficient for - * the requested operation (HTTP 403 with - * `WWW-Authenticate: Bearer error="insufficient_scope"`). - * - * Unlike {@link Required} and {@link Expired} — which typically surface - * before any tool work is in flight — `InsufficientScope` is almost - * always triggered by an MCP request issued mid-turn (a `tools/call`, - * `resources/read`, etc.). The host SHOULD pair the - * {@link McpServerAuthRequiredState} transition with - * {@link SessionStatus.InputNeeded} on - * {@link SessionSummary.status | the session} so the activity becomes - * visible at the session-summary level, and clients SHOULD watch for - * this kind on any - * {@link McpServerCustomization | MCP server} backing a running tool - * call so they can present an explicit "grant more access" affordance - * tied to the blocked tool call. - */ - @SerialName("insufficientScope") - INSUFFICIENT_SCOPE +@Serializable(with = McpAuthRequiredReasonSerializer::class) +@JvmInline +value class McpAuthRequiredReason(val rawValue: String) { + companion object { + /** + * No token has been provided yet (HTTP 401, no prior token). + */ + val REQUIRED: McpAuthRequiredReason = McpAuthRequiredReason("required") + /** + * A previously valid token expired or was revoked (HTTP 401). + */ + val EXPIRED: McpAuthRequiredReason = McpAuthRequiredReason("expired") + /** + * Step-up auth: a token is present but its scopes are insufficient for + * the requested operation (HTTP 403 with + * `WWW-Authenticate: Bearer error="insufficient_scope"`). + * + * Unlike {@link Required} and {@link Expired} — which typically surface + * before any tool work is in flight — `InsufficientScope` is almost + * always triggered by an MCP request issued mid-turn (a `tools/call`, + * `resources/read`, etc.). The host SHOULD pair the + * {@link McpServerAuthRequiredState} transition with + * {@link SessionStatus.InputNeeded} on + * {@link SessionSummary.status | the session} so the activity becomes + * visible at the session-summary level, and clients SHOULD watch for + * this kind on any + * {@link McpServerCustomization | MCP server} backing a running tool + * call so they can present an explicit "grant more access" affordance + * tied to the blocked tool call. + */ + val INSUFFICIENT_SCOPE: McpAuthRequiredReason = McpAuthRequiredReason("insufficientScope") + } +} + +internal object McpAuthRequiredReasonSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("McpAuthRequiredReason", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: McpAuthRequiredReason) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): McpAuthRequiredReason = + McpAuthRequiredReason(decoder.decodeString()) } /** * Computation lifecycle of a {@link ChangesetState}. */ -@Serializable -enum class ChangesetStatus { - /** - * The server is still computing the contents of this changeset. - */ - @SerialName("computing") - COMPUTING, - /** - * The changeset has been fully computed and is up-to-date. - */ - @SerialName("ready") - READY, - /** - * Computation failed. The cause is described by - * {@link ChangesetState.error}. - */ - @SerialName("error") - ERROR +@Serializable(with = ChangesetStatusSerializer::class) +@JvmInline +value class ChangesetStatus(val rawValue: String) { + companion object { + /** + * The server is still computing the contents of this changeset. + */ + val COMPUTING: ChangesetStatus = ChangesetStatus("computing") + /** + * The changeset has been fully computed and is up-to-date. + */ + val READY: ChangesetStatus = ChangesetStatus("ready") + /** + * Computation failed. The cause is described by + * {@link ChangesetState.error}. + */ + val ERROR: ChangesetStatus = ChangesetStatus("error") + } +} + +internal object ChangesetStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChangesetStatus", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChangesetStatus) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChangesetStatus = + ChangesetStatus(decoder.decodeString()) } /** @@ -689,65 +948,94 @@ enum class ChangesetStatus { * every subscriber observes a consistent view (e.g. a spinner on a "Create * Pull Request" button, or an inline error after a failed "revert"). */ -@Serializable -enum class ChangesetOperationStatus { - /** - * The operation is ready to be invoked. This is the default when - * {@link ChangesetOperation.status} is omitted. - */ - @SerialName("idle") - IDLE, - /** - * An invocation of this operation is currently in flight. - */ - @SerialName("running") - RUNNING, - /** - * The most recent invocation failed. The cause is described by - * {@link ChangesetOperation.error}. - */ - @SerialName("error") - ERROR, - /** - * The operation is currently disabled and cannot be invoked. - */ - @SerialName("disabled") - DISABLED +@Serializable(with = ChangesetOperationStatusSerializer::class) +@JvmInline +value class ChangesetOperationStatus(val rawValue: String) { + companion object { + /** + * The operation is ready to be invoked. This is the default when + * {@link ChangesetOperation.status} is omitted. + */ + val IDLE: ChangesetOperationStatus = ChangesetOperationStatus("idle") + /** + * An invocation of this operation is currently in flight. + */ + val RUNNING: ChangesetOperationStatus = ChangesetOperationStatus("running") + /** + * The most recent invocation failed. The cause is described by + * {@link ChangesetOperation.error}. + */ + val ERROR: ChangesetOperationStatus = ChangesetOperationStatus("error") + /** + * The operation is currently disabled and cannot be invoked. + */ + val DISABLED: ChangesetOperationStatus = ChangesetOperationStatus("disabled") + } +} + +internal object ChangesetOperationStatusSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChangesetOperationStatus", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChangesetOperationStatus) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChangesetOperationStatus = + ChangesetOperationStatus(decoder.decodeString()) } /** * Where a {@link ChangesetOperation} can be invoked. */ -@Serializable -enum class ChangesetOperationScope { - /** - * Applies to the whole changeset. - */ - @SerialName("changeset") - CHANGESET, - /** - * Applies to a single file within the changeset. - */ - @SerialName("resource") - RESOURCE, - /** - * Applies to a line range within a single file. - */ - @SerialName("range") - RANGE +@Serializable(with = ChangesetOperationScopeSerializer::class) +@JvmInline +value class ChangesetOperationScope(val rawValue: String) { + companion object { + /** + * Applies to the whole changeset. + */ + val CHANGESET: ChangesetOperationScope = ChangesetOperationScope("changeset") + /** + * Applies to a single file within the changeset. + */ + val RESOURCE: ChangesetOperationScope = ChangesetOperationScope("resource") + /** + * Applies to a line range within a single file. + */ + val RANGE: ChangesetOperationScope = ChangesetOperationScope("range") + } +} + +internal object ChangesetOperationScopeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ChangesetOperationScope", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ChangesetOperationScope) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ChangesetOperationScope = + ChangesetOperationScope(decoder.decodeString()) } /** * Discriminant for {@link ResourceChange.type}. */ -@Serializable -enum class ResourceChangeType { - @SerialName("added") - ADDED, - @SerialName("updated") - UPDATED, - @SerialName("deleted") - DELETED +@Serializable(with = ResourceChangeTypeSerializer::class) +@JvmInline +value class ResourceChangeType(val rawValue: String) { + companion object { + val ADDED: ResourceChangeType = ResourceChangeType("added") + val UPDATED: ResourceChangeType = ResourceChangeType("updated") + val DELETED: ResourceChangeType = ResourceChangeType("deleted") + } +} + +internal object ResourceChangeTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ResourceChangeType", PrimitiveKind.STRING) + override fun serialize(encoder: Encoder, value: ResourceChangeType) { + encoder.encodeString(value.rawValue) + } + override fun deserialize(decoder: Decoder): ResourceChangeType = + ResourceChangeType(decoder.decodeString()) } // ─── State Types ──────────────────────────────────────────────────────────── diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/RoundTripCorpusTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/RoundTripCorpusTest.kt index d11abc8a..a74f2415 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/RoundTripCorpusTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/RoundTripCorpusTest.kt @@ -29,6 +29,7 @@ package com.microsoft.agenthostprotocol // generated types and re-encodes with Ahp.json. import com.microsoft.agenthostprotocol.generated.ActionEnvelope +import com.microsoft.agenthostprotocol.generated.AuthRequiredParams import com.microsoft.agenthostprotocol.generated.ChangesetOperationTarget import com.microsoft.agenthostprotocol.generated.ChatSource import com.microsoft.agenthostprotocol.generated.Customization @@ -45,6 +46,7 @@ import com.microsoft.agenthostprotocol.generated.SessionStatus import com.microsoft.agenthostprotocol.generated.SessionSummary import com.microsoft.agenthostprotocol.generated.StateAction import com.microsoft.agenthostprotocol.generated.StringOrMarkdown +import com.microsoft.agenthostprotocol.generated.Turn import java.io.File import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json @@ -253,6 +255,8 @@ class RoundTripCorpusTest { "Implementation" -> rt(Implementation.serializer()) "InitializeResult" -> rt(InitializeResult.serializer()) "ChatSource" -> rt(ChatSource.serializer()) + "AuthRequiredParams" -> rt(AuthRequiredParams.serializer()) + "Turn" -> rt(Turn.serializer()) else -> fail( "$file: unknown wire type \"$typeName\". " + "Add a decode entry to decodeAndReencode.", diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index 8cd2c8f4..9c2aac54 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -26,178 +26,298 @@ use crate::state::{ // ─── ActionType ────────────────────────────────────────────────────── /// Discriminant values for all state actions. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ActionType { - #[serde(rename = "root/agentsChanged")] RootAgentsChanged, - #[serde(rename = "root/activeSessionsChanged")] RootActiveSessionsChanged, - #[serde(rename = "session/ready")] SessionReady, - #[serde(rename = "session/creationFailed")] SessionCreationFailed, - #[serde(rename = "session/chatAdded")] SessionChatAdded, - #[serde(rename = "session/chatRemoved")] SessionChatRemoved, - #[serde(rename = "session/chatUpdated")] SessionChatUpdated, - #[serde(rename = "session/defaultChatChanged")] SessionDefaultChatChanged, - #[serde(rename = "chat/turnStarted")] ChatTurnStarted, - #[serde(rename = "chat/delta")] ChatDelta, - #[serde(rename = "chat/responsePart")] ChatResponsePart, - #[serde(rename = "chat/toolCallStart")] ChatToolCallStart, - #[serde(rename = "chat/toolCallDelta")] ChatToolCallDelta, - #[serde(rename = "chat/toolCallReady")] ChatToolCallReady, - #[serde(rename = "chat/toolCallConfirmed")] ChatToolCallConfirmed, - #[serde(rename = "chat/toolCallComplete")] ChatToolCallComplete, - #[serde(rename = "chat/toolCallResultConfirmed")] ChatToolCallResultConfirmed, - #[serde(rename = "chat/toolCallContentChanged")] ChatToolCallContentChanged, - #[serde(rename = "chat/toolCallAuthRequired")] ChatToolCallAuthRequired, - #[serde(rename = "chat/toolCallAuthResolved")] ChatToolCallAuthResolved, - #[serde(rename = "chat/turnComplete")] ChatTurnComplete, - #[serde(rename = "chat/turnCancelled")] ChatTurnCancelled, - #[serde(rename = "chat/error")] ChatError, - #[serde(rename = "chat/activityChanged")] ChatActivityChanged, - #[serde(rename = "chat/workingDirectorySet")] ChatWorkingDirectorySet, - #[serde(rename = "chat/workingDirectoryRemoved")] ChatWorkingDirectoryRemoved, - #[serde(rename = "session/titleChanged")] SessionTitleChanged, - #[serde(rename = "chat/usage")] ChatUsage, - #[serde(rename = "chat/reasoning")] ChatReasoning, - #[serde(rename = "session/serverToolsChanged")] SessionServerToolsChanged, - #[serde(rename = "session/activeClientSet")] SessionActiveClientSet, - #[serde(rename = "session/activeClientRemoved")] SessionActiveClientRemoved, - #[serde(rename = "session/workingDirectorySet")] SessionWorkingDirectorySet, - #[serde(rename = "session/workingDirectoryRemoved")] SessionWorkingDirectoryRemoved, - #[serde(rename = "session/inputNeededSet")] SessionInputNeededSet, - #[serde(rename = "session/inputNeededRemoved")] SessionInputNeededRemoved, - #[serde(rename = "chat/pendingMessageSet")] ChatPendingMessageSet, - #[serde(rename = "chat/pendingMessageRemoved")] ChatPendingMessageRemoved, - #[serde(rename = "chat/queuedMessagesReordered")] ChatQueuedMessagesReordered, - #[serde(rename = "chat/draftChanged")] ChatDraftChanged, - #[serde(rename = "chat/inputRequested")] ChatInputRequested, - #[serde(rename = "chat/inputAnswerChanged")] ChatInputAnswerChanged, - #[serde(rename = "chat/inputCompleted")] ChatInputCompleted, - #[serde(rename = "session/customizationsChanged")] SessionCustomizationsChanged, - #[serde(rename = "session/customizationToggled")] SessionCustomizationToggled, - #[serde(rename = "session/customizationUpdated")] SessionCustomizationUpdated, - #[serde(rename = "session/customizationRemoved")] SessionCustomizationRemoved, - #[serde(rename = "session/mcpServerStateChanged")] SessionMcpServerStateChanged, - #[serde(rename = "session/mcpServerStartRequested")] SessionMcpServerStartRequested, - #[serde(rename = "session/mcpServerStopRequested")] SessionMcpServerStopRequested, - #[serde(rename = "chat/truncated")] ChatTruncated, - #[serde(rename = "chat/turnsLoaded")] ChatTurnsLoaded, - #[serde(rename = "session/isReadChanged")] SessionIsReadChanged, - #[serde(rename = "session/isArchivedChanged")] SessionIsArchivedChanged, - #[serde(rename = "session/activityChanged")] SessionActivityChanged, - #[serde(rename = "session/changesetsChanged")] SessionChangesetsChanged, - #[serde(rename = "session/configChanged")] SessionConfigChanged, - #[serde(rename = "session/metaChanged")] SessionMetaChanged, - #[serde(rename = "changeset/statusChanged")] ChangesetStatusChanged, - #[serde(rename = "changeset/fileSet")] ChangesetFileSet, - #[serde(rename = "changeset/fileRemoved")] ChangesetFileRemoved, - #[serde(rename = "changeset/filesReviewChanged")] ChangesetFilesReviewChanged, - #[serde(rename = "changeset/contentChanged")] ChangesetContentChanged, - #[serde(rename = "changeset/operationsChanged")] ChangesetOperationsChanged, - #[serde(rename = "changeset/operationStatusChanged")] ChangesetOperationStatusChanged, - #[serde(rename = "changeset/cleared")] ChangesetCleared, - #[serde(rename = "annotations/set")] AnnotationsSet, - #[serde(rename = "annotations/updated")] AnnotationsUpdated, - #[serde(rename = "annotations/removed")] AnnotationsRemoved, - #[serde(rename = "annotations/entrySet")] AnnotationsEntrySet, - #[serde(rename = "annotations/entryRemoved")] AnnotationsEntryRemoved, - #[serde(rename = "root/terminalsChanged")] RootTerminalsChanged, - #[serde(rename = "root/configChanged")] RootConfigChanged, - #[serde(rename = "terminal/data")] TerminalData, - #[serde(rename = "terminal/input")] TerminalInput, - #[serde(rename = "terminal/resized")] TerminalResized, - #[serde(rename = "terminal/claimed")] TerminalClaimed, - #[serde(rename = "terminal/titleChanged")] TerminalTitleChanged, - #[serde(rename = "terminal/cwdChanged")] TerminalCwdChanged, - #[serde(rename = "terminal/exited")] TerminalExited, - #[serde(rename = "terminal/cleared")] TerminalCleared, - #[serde(rename = "terminal/commandDetectionAvailable")] TerminalCommandDetectionAvailable, - #[serde(rename = "terminal/commandExecuted")] TerminalCommandExecuted, - #[serde(rename = "terminal/commandFinished")] TerminalCommandFinished, - #[serde(rename = "resourceWatch/changed")] ResourceWatchChanged, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ActionType { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::RootAgentsChanged => "root/agentsChanged", + Self::RootActiveSessionsChanged => "root/activeSessionsChanged", + Self::SessionReady => "session/ready", + Self::SessionCreationFailed => "session/creationFailed", + Self::SessionChatAdded => "session/chatAdded", + Self::SessionChatRemoved => "session/chatRemoved", + Self::SessionChatUpdated => "session/chatUpdated", + Self::SessionDefaultChatChanged => "session/defaultChatChanged", + Self::ChatTurnStarted => "chat/turnStarted", + Self::ChatDelta => "chat/delta", + Self::ChatResponsePart => "chat/responsePart", + Self::ChatToolCallStart => "chat/toolCallStart", + Self::ChatToolCallDelta => "chat/toolCallDelta", + Self::ChatToolCallReady => "chat/toolCallReady", + Self::ChatToolCallConfirmed => "chat/toolCallConfirmed", + Self::ChatToolCallComplete => "chat/toolCallComplete", + Self::ChatToolCallResultConfirmed => "chat/toolCallResultConfirmed", + Self::ChatToolCallContentChanged => "chat/toolCallContentChanged", + Self::ChatToolCallAuthRequired => "chat/toolCallAuthRequired", + Self::ChatToolCallAuthResolved => "chat/toolCallAuthResolved", + Self::ChatTurnComplete => "chat/turnComplete", + Self::ChatTurnCancelled => "chat/turnCancelled", + Self::ChatError => "chat/error", + Self::ChatActivityChanged => "chat/activityChanged", + Self::ChatWorkingDirectorySet => "chat/workingDirectorySet", + Self::ChatWorkingDirectoryRemoved => "chat/workingDirectoryRemoved", + Self::SessionTitleChanged => "session/titleChanged", + Self::ChatUsage => "chat/usage", + Self::ChatReasoning => "chat/reasoning", + Self::SessionServerToolsChanged => "session/serverToolsChanged", + Self::SessionActiveClientSet => "session/activeClientSet", + Self::SessionActiveClientRemoved => "session/activeClientRemoved", + Self::SessionWorkingDirectorySet => "session/workingDirectorySet", + Self::SessionWorkingDirectoryRemoved => "session/workingDirectoryRemoved", + Self::SessionInputNeededSet => "session/inputNeededSet", + Self::SessionInputNeededRemoved => "session/inputNeededRemoved", + Self::ChatPendingMessageSet => "chat/pendingMessageSet", + Self::ChatPendingMessageRemoved => "chat/pendingMessageRemoved", + Self::ChatQueuedMessagesReordered => "chat/queuedMessagesReordered", + Self::ChatDraftChanged => "chat/draftChanged", + Self::ChatInputRequested => "chat/inputRequested", + Self::ChatInputAnswerChanged => "chat/inputAnswerChanged", + Self::ChatInputCompleted => "chat/inputCompleted", + Self::SessionCustomizationsChanged => "session/customizationsChanged", + Self::SessionCustomizationToggled => "session/customizationToggled", + Self::SessionCustomizationUpdated => "session/customizationUpdated", + Self::SessionCustomizationRemoved => "session/customizationRemoved", + Self::SessionMcpServerStateChanged => "session/mcpServerStateChanged", + Self::SessionMcpServerStartRequested => "session/mcpServerStartRequested", + Self::SessionMcpServerStopRequested => "session/mcpServerStopRequested", + Self::ChatTruncated => "chat/truncated", + Self::ChatTurnsLoaded => "chat/turnsLoaded", + Self::SessionIsReadChanged => "session/isReadChanged", + Self::SessionIsArchivedChanged => "session/isArchivedChanged", + Self::SessionActivityChanged => "session/activityChanged", + Self::SessionChangesetsChanged => "session/changesetsChanged", + Self::SessionConfigChanged => "session/configChanged", + Self::SessionMetaChanged => "session/metaChanged", + Self::ChangesetStatusChanged => "changeset/statusChanged", + Self::ChangesetFileSet => "changeset/fileSet", + Self::ChangesetFileRemoved => "changeset/fileRemoved", + Self::ChangesetFilesReviewChanged => "changeset/filesReviewChanged", + Self::ChangesetContentChanged => "changeset/contentChanged", + Self::ChangesetOperationsChanged => "changeset/operationsChanged", + Self::ChangesetOperationStatusChanged => "changeset/operationStatusChanged", + Self::ChangesetCleared => "changeset/cleared", + Self::AnnotationsSet => "annotations/set", + Self::AnnotationsUpdated => "annotations/updated", + Self::AnnotationsRemoved => "annotations/removed", + Self::AnnotationsEntrySet => "annotations/entrySet", + Self::AnnotationsEntryRemoved => "annotations/entryRemoved", + Self::RootTerminalsChanged => "root/terminalsChanged", + Self::RootConfigChanged => "root/configChanged", + Self::TerminalData => "terminal/data", + Self::TerminalInput => "terminal/input", + Self::TerminalResized => "terminal/resized", + Self::TerminalClaimed => "terminal/claimed", + Self::TerminalTitleChanged => "terminal/titleChanged", + Self::TerminalCwdChanged => "terminal/cwdChanged", + Self::TerminalExited => "terminal/exited", + Self::TerminalCleared => "terminal/cleared", + Self::TerminalCommandDetectionAvailable => "terminal/commandDetectionAvailable", + Self::TerminalCommandExecuted => "terminal/commandExecuted", + Self::TerminalCommandFinished => "terminal/commandFinished", + Self::ResourceWatchChanged => "resourceWatch/changed", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ActionType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ActionType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "root/agentsChanged" => Self::RootAgentsChanged, + "root/activeSessionsChanged" => Self::RootActiveSessionsChanged, + "session/ready" => Self::SessionReady, + "session/creationFailed" => Self::SessionCreationFailed, + "session/chatAdded" => Self::SessionChatAdded, + "session/chatRemoved" => Self::SessionChatRemoved, + "session/chatUpdated" => Self::SessionChatUpdated, + "session/defaultChatChanged" => Self::SessionDefaultChatChanged, + "chat/turnStarted" => Self::ChatTurnStarted, + "chat/delta" => Self::ChatDelta, + "chat/responsePart" => Self::ChatResponsePart, + "chat/toolCallStart" => Self::ChatToolCallStart, + "chat/toolCallDelta" => Self::ChatToolCallDelta, + "chat/toolCallReady" => Self::ChatToolCallReady, + "chat/toolCallConfirmed" => Self::ChatToolCallConfirmed, + "chat/toolCallComplete" => Self::ChatToolCallComplete, + "chat/toolCallResultConfirmed" => Self::ChatToolCallResultConfirmed, + "chat/toolCallContentChanged" => Self::ChatToolCallContentChanged, + "chat/toolCallAuthRequired" => Self::ChatToolCallAuthRequired, + "chat/toolCallAuthResolved" => Self::ChatToolCallAuthResolved, + "chat/turnComplete" => Self::ChatTurnComplete, + "chat/turnCancelled" => Self::ChatTurnCancelled, + "chat/error" => Self::ChatError, + "chat/activityChanged" => Self::ChatActivityChanged, + "chat/workingDirectorySet" => Self::ChatWorkingDirectorySet, + "chat/workingDirectoryRemoved" => Self::ChatWorkingDirectoryRemoved, + "session/titleChanged" => Self::SessionTitleChanged, + "chat/usage" => Self::ChatUsage, + "chat/reasoning" => Self::ChatReasoning, + "session/serverToolsChanged" => Self::SessionServerToolsChanged, + "session/activeClientSet" => Self::SessionActiveClientSet, + "session/activeClientRemoved" => Self::SessionActiveClientRemoved, + "session/workingDirectorySet" => Self::SessionWorkingDirectorySet, + "session/workingDirectoryRemoved" => Self::SessionWorkingDirectoryRemoved, + "session/inputNeededSet" => Self::SessionInputNeededSet, + "session/inputNeededRemoved" => Self::SessionInputNeededRemoved, + "chat/pendingMessageSet" => Self::ChatPendingMessageSet, + "chat/pendingMessageRemoved" => Self::ChatPendingMessageRemoved, + "chat/queuedMessagesReordered" => Self::ChatQueuedMessagesReordered, + "chat/draftChanged" => Self::ChatDraftChanged, + "chat/inputRequested" => Self::ChatInputRequested, + "chat/inputAnswerChanged" => Self::ChatInputAnswerChanged, + "chat/inputCompleted" => Self::ChatInputCompleted, + "session/customizationsChanged" => Self::SessionCustomizationsChanged, + "session/customizationToggled" => Self::SessionCustomizationToggled, + "session/customizationUpdated" => Self::SessionCustomizationUpdated, + "session/customizationRemoved" => Self::SessionCustomizationRemoved, + "session/mcpServerStateChanged" => Self::SessionMcpServerStateChanged, + "session/mcpServerStartRequested" => Self::SessionMcpServerStartRequested, + "session/mcpServerStopRequested" => Self::SessionMcpServerStopRequested, + "chat/truncated" => Self::ChatTruncated, + "chat/turnsLoaded" => Self::ChatTurnsLoaded, + "session/isReadChanged" => Self::SessionIsReadChanged, + "session/isArchivedChanged" => Self::SessionIsArchivedChanged, + "session/activityChanged" => Self::SessionActivityChanged, + "session/changesetsChanged" => Self::SessionChangesetsChanged, + "session/configChanged" => Self::SessionConfigChanged, + "session/metaChanged" => Self::SessionMetaChanged, + "changeset/statusChanged" => Self::ChangesetStatusChanged, + "changeset/fileSet" => Self::ChangesetFileSet, + "changeset/fileRemoved" => Self::ChangesetFileRemoved, + "changeset/filesReviewChanged" => Self::ChangesetFilesReviewChanged, + "changeset/contentChanged" => Self::ChangesetContentChanged, + "changeset/operationsChanged" => Self::ChangesetOperationsChanged, + "changeset/operationStatusChanged" => Self::ChangesetOperationStatusChanged, + "changeset/cleared" => Self::ChangesetCleared, + "annotations/set" => Self::AnnotationsSet, + "annotations/updated" => Self::AnnotationsUpdated, + "annotations/removed" => Self::AnnotationsRemoved, + "annotations/entrySet" => Self::AnnotationsEntrySet, + "annotations/entryRemoved" => Self::AnnotationsEntryRemoved, + "root/terminalsChanged" => Self::RootTerminalsChanged, + "root/configChanged" => Self::RootConfigChanged, + "terminal/data" => Self::TerminalData, + "terminal/input" => Self::TerminalInput, + "terminal/resized" => Self::TerminalResized, + "terminal/claimed" => Self::TerminalClaimed, + "terminal/titleChanged" => Self::TerminalTitleChanged, + "terminal/cwdChanged" => Self::TerminalCwdChanged, + "terminal/exited" => Self::TerminalExited, + "terminal/cleared" => Self::TerminalCleared, + "terminal/commandDetectionAvailable" => Self::TerminalCommandDetectionAvailable, + "terminal/commandExecuted" => Self::TerminalCommandExecuted, + "terminal/commandFinished" => Self::TerminalCommandFinished, + "resourceWatch/changed" => Self::ResourceWatchChanged, + _ => Self::Unknown(value), + }) + } } // ─── Action Envelope ───────────────────────────────────────────────── diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 565ea531..411b5b44 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -23,53 +23,238 @@ use crate::state::{ // ─── Enums ──────────────────────────────────────────────────────────── /// Discriminant for reconnect result types. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ReconnectResultType { - #[serde(rename = "replay")] Replay, - #[serde(rename = "snapshot")] Snapshot, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ReconnectResultType { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Replay => "replay", + Self::Snapshot => "snapshot", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ReconnectResultType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ReconnectResultType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "replay" => Self::Replay, + "snapshot" => Self::Snapshot, + _ => Self::Unknown(value), + }) + } } /// How a new chat uses its source chat and turn. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatSourceKind { /// Copy source history through the referenced turn into the new chat. - #[serde(rename = "fork")] Fork, /// Supply source context without copying it into the new chat's visible history. - #[serde(rename = "sideChat")] SideChat, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatSourceKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Fork => "fork", + Self::SideChat => "sideChat", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatSourceKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatSourceKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "fork" => Self::Fork, + "sideChat" => Self::SideChat, + _ => Self::Unknown(value), + }) + } } /// Encoding of fetched content data. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ContentEncoding { - #[serde(rename = "base64")] Base64, - #[serde(rename = "utf-8")] Utf8, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ContentEncoding { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Base64 => "base64", + Self::Utf8 => "utf-8", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ContentEncoding { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ContentEncoding { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "base64" => Self::Base64, + "utf-8" => Self::Utf8, + _ => Self::Unknown(value), + }) + } } /// The kind of completion items being requested. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CompletionItemKind { /// Completions for the text of a {@link Message} the user is composing. /// Each returned item carries an attachment that gets associated with the /// message when accepted. - #[serde(rename = "userMessage")] UserMessage, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl CompletionItemKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::UserMessage => "userMessage", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for CompletionItemKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for CompletionItemKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "userMessage" => Self::UserMessage, + _ => Self::Unknown(value), + }) + } } /// Discriminant for {@link ResourceResolveResult.type}. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ResourceType { - #[serde(rename = "file")] File, - #[serde(rename = "directory")] Directory, - #[serde(rename = "symlink")] Symlink, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ResourceType { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::File => "file", + Self::Directory => "directory", + Self::Symlink => "symlink", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ResourceType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ResourceType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "file" => Self::File, + "directory" => Self::Directory, + "symlink" => Self::Symlink, + _ => Self::Unknown(value), + }) + } } /// How {@link ResourceWriteParams.data} is placed within the target file. @@ -91,14 +276,52 @@ pub enum ResourceType { /// is the byte offset at which `data` is spliced in; bytes at or after /// `position` are shifted right by `data.length`. `insert` always grows /// the file — use `truncate` to overwrite bytes in place. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ResourceWriteMode { - #[serde(rename = "truncate")] Truncate, - #[serde(rename = "append")] Append, - #[serde(rename = "insert")] Insert, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ResourceWriteMode { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Truncate => "truncate", + Self::Append => "append", + Self::Insert => "insert", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ResourceWriteMode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ResourceWriteMode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "truncate" => Self::Truncate, + "append" => Self::Append, + "insert" => Self::Insert, + _ => Self::Unknown(value), + }) + } } // ─── Command Payloads ───────────────────────────────────────────────── diff --git a/clients/rust/crates/ahp-types/src/notifications.rs b/clients/rust/crates/ahp-types/src/notifications.rs index e54e9376..20f13c52 100644 --- a/clients/rust/crates/ahp-types/src/notifications.rs +++ b/clients/rust/crates/ahp-types/src/notifications.rs @@ -20,14 +20,51 @@ use crate::state::{ // ─── Enums ──────────────────────────────────────────────────────────── /// Reason why authentication is required. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AuthRequiredReason { /// The client has not yet authenticated for the resource - #[serde(rename = "required")] Required, /// A previously valid token has expired or been revoked - #[serde(rename = "expired")] Expired, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl AuthRequiredReason { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Required => "required", + Self::Expired => "expired", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for AuthRequiredReason { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for AuthRequiredReason { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "required" => Self::Required, + "expired" => Self::Expired, + _ => Self::Unknown(value), + }) + } } // ─── Notification Payloads ──────────────────────────────────────────── diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index e5519ca5..a994751f 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -14,36 +14,149 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; // ─── Enums ──────────────────────────────────────────────────────────── /// Policy configuration state for a model. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum PolicyState { - #[serde(rename = "enabled")] Enabled, - #[serde(rename = "disabled")] Disabled, - #[serde(rename = "unconfigured")] Unconfigured, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl PolicyState { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Enabled => "enabled", + Self::Disabled => "disabled", + Self::Unconfigured => "unconfigured", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for PolicyState { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for PolicyState { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "enabled" => Self::Enabled, + "disabled" => Self::Disabled, + "unconfigured" => Self::Unconfigured, + _ => Self::Unknown(value), + }) + } } /// Discriminant for pending message kinds. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum PendingMessageKind { /// Injected into the current turn at a convenient point - #[serde(rename = "steering")] Steering, /// Sent automatically as a new turn after the current turn finishes - #[serde(rename = "queued")] Queued, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl PendingMessageKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Steering => "steering", + Self::Queued => "queued", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for PendingMessageKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for PendingMessageKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "steering" => Self::Steering, + "queued" => Self::Queued, + _ => Self::Unknown(value), + }) + } } /// Session initialization state. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SessionLifecycle { - #[serde(rename = "creating")] Creating, - #[serde(rename = "ready")] Ready, - #[serde(rename = "creationFailed")] CreationFailed, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl SessionLifecycle { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Creating => "creating", + Self::Ready => "ready", + Self::CreationFailed => "creationFailed", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for SessionLifecycle { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for SessionLifecycle { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "creating" => Self::Creating, + "ready" => Self::Ready, + "creationFailed" => Self::CreationFailed, + _ => Self::Unknown(value), + }) + } } /// Bitset of summary-level session status flags. @@ -138,20 +251,59 @@ impl std::ops::Not for SessionStatus { } /// Discriminant for {@link ChatOrigin} — how a chat came into existence. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatOriginKind { /// User created the chat explicitly (e.g. via the host UI). - #[serde(rename = "user")] User, /// Forked from an existing chat at a specific turn. - #[serde(rename = "fork")] Fork, /// Created as an independent side conversation from a specific turn. - #[serde(rename = "sideChat")] SideChat, /// Spawned by a tool call running in another chat (e.g. a sub-agent delegation). - #[serde(rename = "tool")] Tool, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatOriginKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::User => "user", + Self::Fork => "fork", + Self::SideChat => "sideChat", + Self::Tool => "tool", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatOriginKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatOriginKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "user" => Self::User, + "fork" => Self::Fork, + "sideChat" => Self::SideChat, + "tool" => Self::Tool, + _ => Self::Unknown(value), + }) + } } /// How a user can interact with a chat. @@ -164,71 +316,266 @@ pub enum ChatOriginKind { /// worker chats are read-only (visible for observability) or hidden (internal /// implementation detail). The harness sets this based on the chat's role; /// the UI uses it to show appropriate controls. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatInteractivity { /// User can send messages and watch (default when absent) - #[serde(rename = "full")] Full, /// User can watch but not send messages - #[serde(rename = "read-only")] ReadOnly, /// Internal worker not shown in UI at all - #[serde(rename = "hidden")] Hidden, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatInteractivity { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Full => "full", + Self::ReadOnly => "read-only", + Self::Hidden => "hidden", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatInteractivity { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatInteractivity { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "full" => Self::Full, + "read-only" => Self::ReadOnly, + "hidden" => Self::Hidden, + _ => Self::Unknown(value), + }) + } } /// Answer lifecycle state. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatInputAnswerState { - #[serde(rename = "draft")] Draft, - #[serde(rename = "submitted")] Submitted, - #[serde(rename = "skipped")] Skipped, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatInputAnswerState { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Draft => "draft", + Self::Submitted => "submitted", + Self::Skipped => "skipped", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatInputAnswerState { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatInputAnswerState { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "draft" => Self::Draft, + "submitted" => Self::Submitted, + "skipped" => Self::Skipped, + _ => Self::Unknown(value), + }) + } } /// Answer value kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatInputAnswerValueKind { - #[serde(rename = "text")] Text, - #[serde(rename = "number")] Number, - #[serde(rename = "boolean")] Boolean, - #[serde(rename = "selected")] Selected, - #[serde(rename = "selected-many")] SelectedMany, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatInputAnswerValueKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Text => "text", + Self::Number => "number", + Self::Boolean => "boolean", + Self::Selected => "selected", + Self::SelectedMany => "selected-many", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatInputAnswerValueKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatInputAnswerValueKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "text" => Self::Text, + "number" => Self::Number, + "boolean" => Self::Boolean, + "selected" => Self::Selected, + "selected-many" => Self::SelectedMany, + _ => Self::Unknown(value), + }) + } } /// Question/input control kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatInputQuestionKind { - #[serde(rename = "text")] Text, - #[serde(rename = "number")] Number, - #[serde(rename = "integer")] Integer, - #[serde(rename = "boolean")] Boolean, - #[serde(rename = "single-select")] SingleSelect, - #[serde(rename = "multi-select")] MultiSelect, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatInputQuestionKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Text => "text", + Self::Number => "number", + Self::Integer => "integer", + Self::Boolean => "boolean", + Self::SingleSelect => "single-select", + Self::MultiSelect => "multi-select", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatInputQuestionKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatInputQuestionKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "text" => Self::Text, + "number" => Self::Number, + "integer" => Self::Integer, + "boolean" => Self::Boolean, + "single-select" => Self::SingleSelect, + "multi-select" => Self::MultiSelect, + _ => Self::Unknown(value), + }) + } } /// How a client completed an input request. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChatInputResponseKind { - #[serde(rename = "accept")] Accept, - #[serde(rename = "decline")] Decline, - #[serde(rename = "cancel")] Cancel, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChatInputResponseKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Accept => "accept", + Self::Decline => "decline", + Self::Cancel => "cancel", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChatInputResponseKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChatInputResponseKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "accept" => Self::Accept, + "decline" => Self::Decline, + "cancel" => Self::Cancel, + _ => Self::Unknown(value), + }) + } } /// Discriminant for the kinds of outstanding input a session can surface in @@ -236,109 +583,348 @@ pub enum ChatInputResponseKind { /// /// This is a general/typological union (not a lifecycle), so the discriminant is /// a `*Kind`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SessionInputRequestKind { /// A user-facing elicitation mirrored from an unresolved chat response part. - #[serde(rename = "chatInput")] ChatInput, /// A tool call awaiting parameter- or result-confirmation. - #[serde(rename = "toolConfirmation")] ToolConfirmation, /// A running tool the session wants an active client to execute. - #[serde(rename = "toolClientExecution")] ToolClientExecution, /// A tool call blocked on MCP authentication mid-execution. - #[serde(rename = "toolAuthentication")] ToolAuthentication, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl SessionInputRequestKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::ChatInput => "chatInput", + Self::ToolConfirmation => "toolConfirmation", + Self::ToolClientExecution => "toolClientExecution", + Self::ToolAuthentication => "toolAuthentication", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for SessionInputRequestKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for SessionInputRequestKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "chatInput" => Self::ChatInput, + "toolConfirmation" => Self::ToolConfirmation, + "toolClientExecution" => Self::ToolClientExecution, + "toolAuthentication" => Self::ToolAuthentication, + _ => Self::Unknown(value), + }) + } } /// How a turn ended. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TurnState { - #[serde(rename = "complete")] Complete, - #[serde(rename = "cancelled")] Cancelled, - #[serde(rename = "error")] Error, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl TurnState { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Complete => "complete", + Self::Cancelled => "cancelled", + Self::Error => "error", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for TurnState { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for TurnState { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "complete" => Self::Complete, + "cancelled" => Self::Cancelled, + "error" => Self::Error, + _ => Self::Unknown(value), + }) + } } /// Discriminant for {@link MessageOrigin} — identifies who produced a message. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MessageKind { /// Sent directly by the user. - #[serde(rename = "user")] User, /// Produced by the agent itself rather than the user — for example, an agent /// that seeds the first message of a chat it spawned. - #[serde(rename = "agent")] Agent, /// Produced by a tool rather than the user — for example, a tool that spawns a /// worker chat whose first message carries a seed prompt. - #[serde(rename = "tool")] Tool, /// A system-generated notification rather than a direct user message. - #[serde(rename = "systemNotification")] SystemNotification, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl MessageKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::User => "user", + Self::Agent => "agent", + Self::Tool => "tool", + Self::SystemNotification => "systemNotification", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for MessageKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for MessageKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "user" => Self::User, + "agent" => Self::Agent, + "tool" => Self::Tool, + "systemNotification" => Self::SystemNotification, + _ => Self::Unknown(value), + }) + } } /// Discriminant for {@link MessageAttachment} variants. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum MessageAttachmentKind { /// A simple, opaque attachment whose representation is described by the producer. - #[serde(rename = "simple")] Simple, /// An attachment whose data is embedded inline as a base64 string. - #[serde(rename = "embeddedResource")] EmbeddedResource, /// An attachment that references a resource by URI. - #[serde(rename = "resource")] Resource, /// An attachment that references annotations on an annotations channel. - #[serde(rename = "annotations")] Annotations, /// An attachment that references a bounded transcript from another chat. - #[serde(rename = "chat")] Chat, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl MessageAttachmentKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Simple => "simple", + Self::EmbeddedResource => "embeddedResource", + Self::Resource => "resource", + Self::Annotations => "annotations", + Self::Chat => "chat", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for MessageAttachmentKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for MessageAttachmentKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "simple" => Self::Simple, + "embeddedResource" => Self::EmbeddedResource, + "resource" => Self::Resource, + "annotations" => Self::Annotations, + "chat" => Self::Chat, + _ => Self::Unknown(value), + }) + } } /// Discriminant for response part types. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ResponsePartKind { - #[serde(rename = "markdown")] Markdown, - #[serde(rename = "contentRef")] ContentRef, - #[serde(rename = "toolCall")] ToolCall, - #[serde(rename = "reasoning")] Reasoning, - #[serde(rename = "systemNotification")] SystemNotification, - #[serde(rename = "inputRequest")] InputRequest, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ResponsePartKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Markdown => "markdown", + Self::ContentRef => "contentRef", + Self::ToolCall => "toolCall", + Self::Reasoning => "reasoning", + Self::SystemNotification => "systemNotification", + Self::InputRequest => "inputRequest", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ResponsePartKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ResponsePartKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "markdown" => Self::Markdown, + "contentRef" => Self::ContentRef, + "toolCall" => Self::ToolCall, + "reasoning" => Self::Reasoning, + "systemNotification" => Self::SystemNotification, + "inputRequest" => Self::InputRequest, + _ => Self::Unknown(value), + }) + } } /// Status of a tool call in the lifecycle state machine. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolCallStatus { - #[serde(rename = "streaming")] Streaming, - #[serde(rename = "pending-confirmation")] PendingConfirmation, - #[serde(rename = "running")] Running, /// Running paused because the MCP server backing this call needs /// authentication (typically step-up auth for insufficient scope, /// surfacing mid-execution). See {@link ToolCallAuthRequiredState}. - #[serde(rename = "auth-required")] AuthRequired, - #[serde(rename = "pending-result-confirmation")] PendingResultConfirmation, - #[serde(rename = "completed")] Completed, - #[serde(rename = "cancelled")] Cancelled, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolCallStatus { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Streaming => "streaming", + Self::PendingConfirmation => "pending-confirmation", + Self::Running => "running", + Self::AuthRequired => "auth-required", + Self::PendingResultConfirmation => "pending-result-confirmation", + Self::Completed => "completed", + Self::Cancelled => "cancelled", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolCallStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolCallStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "streaming" => Self::Streaming, + "pending-confirmation" => Self::PendingConfirmation, + "running" => Self::Running, + "auth-required" => Self::AuthRequired, + "pending-result-confirmation" => Self::PendingResultConfirmation, + "completed" => Self::Completed, + "cancelled" => Self::Cancelled, + _ => Self::Unknown(value), + }) + } } /// How a tool call was confirmed for execution. @@ -346,75 +932,339 @@ pub enum ToolCallStatus { /// - `NotNeeded` — No confirmation required (auto-approved) /// - `UserAction` — User explicitly approved /// - `Setting` — Approved by a persistent user setting -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolCallConfirmationReason { - #[serde(rename = "not-needed")] NotNeeded, - #[serde(rename = "user-action")] UserAction, - #[serde(rename = "setting")] Setting, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolCallConfirmationReason { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::NotNeeded => "not-needed", + Self::UserAction => "user-action", + Self::Setting => "setting", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolCallConfirmationReason { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolCallConfirmationReason { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "not-needed" => Self::NotNeeded, + "user-action" => Self::UserAction, + "setting" => Self::Setting, + _ => Self::Unknown(value), + }) + } } /// Identifies a model judge as the source of a confirmation requirement. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolCallRiskAssessmentKind { - #[serde(rename = "judge")] Judge, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolCallRiskAssessmentKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Judge => "judge", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolCallRiskAssessmentKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolCallRiskAssessmentKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "judge" => Self::Judge, + _ => Self::Unknown(value), + }) + } } /// Lifecycle status of an asynchronous model-judge confirmation decision. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolCallRiskAssessmentStatus { - #[serde(rename = "loading")] Loading, - #[serde(rename = "complete")] Complete, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolCallRiskAssessmentStatus { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Loading => "loading", + Self::Complete => "complete", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolCallRiskAssessmentStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolCallRiskAssessmentStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "loading" => Self::Loading, + "complete" => Self::Complete, + _ => Self::Unknown(value), + }) + } } /// Why a tool call was cancelled. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolCallCancellationReason { - #[serde(rename = "denied")] Denied, - #[serde(rename = "skipped")] Skipped, - #[serde(rename = "result-denied")] ResultDenied, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolCallCancellationReason { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Denied => "denied", + Self::Skipped => "skipped", + Self::ResultDenied => "result-denied", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolCallCancellationReason { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolCallCancellationReason { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "denied" => Self::Denied, + "skipped" => Self::Skipped, + "result-denied" => Self::ResultDenied, + _ => Self::Unknown(value), + }) + } } /// Whether a confirmation option represents an approval or denial action. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ConfirmationOptionKind { - #[serde(rename = "approve")] Approve, - #[serde(rename = "deny")] Deny, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ConfirmationOptionKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Approve => "approve", + Self::Deny => "deny", + Self::Unknown(value) => value.as_str(), + } + } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +impl Serialize for ConfirmationOptionKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ConfirmationOptionKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "approve" => Self::Approve, + "deny" => Self::Deny, + _ => Self::Unknown(value), + }) + } +} + +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolCallContributorKind { - #[serde(rename = "client")] Client, - #[serde(rename = "mcp")] MCP, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolCallContributorKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Client => "client", + Self::MCP => "mcp", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolCallContributorKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolCallContributorKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "client" => Self::Client, + "mcp" => Self::MCP, + _ => Self::Unknown(value), + }) + } } /// Discriminant for tool result content types. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ToolResultContentType { - #[serde(rename = "text")] Text, - #[serde(rename = "embeddedResource")] EmbeddedResource, - #[serde(rename = "resource")] Resource, - #[serde(rename = "fileEdit")] FileEdit, - #[serde(rename = "terminal")] Terminal, - #[serde(rename = "subagent")] Subagent, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ToolResultContentType { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Text => "text", + Self::EmbeddedResource => "embeddedResource", + Self::Resource => "resource", + Self::FileEdit => "fileEdit", + Self::Terminal => "terminal", + Self::Subagent => "subagent", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ToolResultContentType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ToolResultContentType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "text" => Self::Text, + "embeddedResource" => Self::EmbeddedResource, + "resource" => Self::Resource, + "fileEdit" => Self::FileEdit, + "terminal" => Self::Terminal, + "subagent" => Self::Subagent, + _ => Self::Unknown(value), + }) + } } /// Discriminant for the kind of customization. @@ -426,82 +1276,242 @@ pub enum ToolResultContentType { /// {@link CustomizationType.McpServer | `McpServer`} entries surfaced /// directly by the host. The remaining types appear only as children of /// a container. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CustomizationType { - #[serde(rename = "plugin")] Plugin, - #[serde(rename = "directory")] Directory, - #[serde(rename = "agent")] Agent, - #[serde(rename = "skill")] Skill, - #[serde(rename = "prompt")] Prompt, - #[serde(rename = "rule")] Rule, - #[serde(rename = "hook")] Hook, - #[serde(rename = "mcpServer")] McpServer, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl CustomizationType { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Plugin => "plugin", + Self::Directory => "directory", + Self::Agent => "agent", + Self::Skill => "skill", + Self::Prompt => "prompt", + Self::Rule => "rule", + Self::Hook => "hook", + Self::McpServer => "mcpServer", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for CustomizationType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for CustomizationType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "plugin" => Self::Plugin, + "directory" => Self::Directory, + "agent" => Self::Agent, + "skill" => Self::Skill, + "prompt" => Self::Prompt, + "rule" => Self::Rule, + "hook" => Self::Hook, + "mcpServer" => Self::McpServer, + _ => Self::Unknown(value), + }) + } } /// Discriminant values for {@link CustomizationLoadState}. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CustomizationLoadStatus { - #[serde(rename = "loading")] Loading, - #[serde(rename = "loaded")] Loaded, - #[serde(rename = "degraded")] Degraded, - #[serde(rename = "error")] Error, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl CustomizationLoadStatus { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Loading => "loading", + Self::Loaded => "loaded", + Self::Degraded => "degraded", + Self::Error => "error", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for CustomizationLoadStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for CustomizationLoadStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "loading" => Self::Loading, + "loaded" => Self::Loaded, + "degraded" => Self::Degraded, + "error" => Self::Error, + _ => Self::Unknown(value), + }) + } } /// Discriminant for terminal claim kinds. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TerminalClaimKind { - #[serde(rename = "client")] Client, - #[serde(rename = "session")] Session, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl TerminalClaimKind { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Client => "client", + Self::Session => "session", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for TerminalClaimKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for TerminalClaimKind { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "client" => Self::Client, + "session" => Self::Session, + _ => Self::Unknown(value), + }) + } } /// Discriminant for the {@link McpServerState} union. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum McpServerStatus { /// Server has been registered but is not yet running. - #[serde(rename = "starting")] Starting, /// Server is running and serving requests. - #[serde(rename = "ready")] Ready, /// Server is reachable but requires additional authentication before it /// can start, or before it can serve a particular request. Carries the /// RFC 9728 Protected Resource Metadata the client needs to obtain a /// token; the client then pushes the token via the existing /// `authenticate` command. - #[serde(rename = "authRequired")] AuthRequired, /// Server failed to start, crashed, or otherwise transitioned to a fatal error. - #[serde(rename = "error")] Error, /// Server has been shut down. - #[serde(rename = "stopped")] Stopped, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl McpServerStatus { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Starting => "starting", + Self::Ready => "ready", + Self::AuthRequired => "authRequired", + Self::Error => "error", + Self::Stopped => "stopped", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for McpServerStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for McpServerStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "starting" => Self::Starting, + "ready" => Self::Ready, + "authRequired" => Self::AuthRequired, + "error" => Self::Error, + "stopped" => Self::Stopped, + _ => Self::Unknown(value), + }) + } } /// Why an MCP server is currently in the {@link McpServerStatus.AuthRequired} /// state. Mirrors the three failure modes defined by the /// [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization.md). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum McpAuthRequiredReason { /// No token has been provided yet (HTTP 401, no prior token). - #[serde(rename = "required")] Required, /// A previously valid token expired or was revoked (HTTP 401). - #[serde(rename = "expired")] Expired, /// Step-up auth: a token is present but its scopes are insufficient for /// the requested operation (HTTP 403 with @@ -519,23 +1529,98 @@ pub enum McpAuthRequiredReason { /// {@link McpServerCustomization | MCP server} backing a running tool /// call so they can present an explicit "grant more access" affordance /// tied to the blocked tool call. - #[serde(rename = "insufficientScope")] InsufficientScope, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl McpAuthRequiredReason { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Required => "required", + Self::Expired => "expired", + Self::InsufficientScope => "insufficientScope", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for McpAuthRequiredReason { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for McpAuthRequiredReason { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "required" => Self::Required, + "expired" => Self::Expired, + "insufficientScope" => Self::InsufficientScope, + _ => Self::Unknown(value), + }) + } } /// Computation lifecycle of a {@link ChangesetState}. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChangesetStatus { /// The server is still computing the contents of this changeset. - #[serde(rename = "computing")] Computing, /// The changeset has been fully computed and is up-to-date. - #[serde(rename = "ready")] Ready, /// Computation failed. The cause is described by /// {@link ChangesetState.error}. - #[serde(rename = "error")] Error, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChangesetStatus { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Computing => "computing", + Self::Ready => "ready", + Self::Error => "error", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChangesetStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChangesetStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "computing" => Self::Computing, + "ready" => Self::Ready, + "error" => Self::Error, + _ => Self::Unknown(value), + }) + } } /// Execution lifecycle of a {@link ChangesetOperation}. @@ -544,47 +1629,162 @@ pub enum ChangesetStatus { /// its progress and outcome are reflected back into changeset state so that /// every subscriber observes a consistent view (e.g. a spinner on a "Create /// Pull Request" button, or an inline error after a failed "revert"). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChangesetOperationStatus { /// The operation is ready to be invoked. This is the default when /// {@link ChangesetOperation.status} is omitted. - #[serde(rename = "idle")] Idle, /// An invocation of this operation is currently in flight. - #[serde(rename = "running")] Running, /// The most recent invocation failed. The cause is described by /// {@link ChangesetOperation.error}. - #[serde(rename = "error")] Error, /// The operation is currently disabled and cannot be invoked. - #[serde(rename = "disabled")] Disabled, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChangesetOperationStatus { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Idle => "idle", + Self::Running => "running", + Self::Error => "error", + Self::Disabled => "disabled", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChangesetOperationStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChangesetOperationStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "idle" => Self::Idle, + "running" => Self::Running, + "error" => Self::Error, + "disabled" => Self::Disabled, + _ => Self::Unknown(value), + }) + } } /// Where a {@link ChangesetOperation} can be invoked. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ChangesetOperationScope { /// Applies to the whole changeset. - #[serde(rename = "changeset")] Changeset, /// Applies to a single file within the changeset. - #[serde(rename = "resource")] Resource, /// Applies to a line range within a single file. - #[serde(rename = "range")] Range, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ChangesetOperationScope { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Changeset => "changeset", + Self::Resource => "resource", + Self::Range => "range", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ChangesetOperationScope { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ChangesetOperationScope { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "changeset" => Self::Changeset, + "resource" => Self::Resource, + "range" => Self::Range, + _ => Self::Unknown(value), + }) + } } /// Discriminant for {@link ResourceChange.type}. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +/// +/// Unknown values are preserved so newer peers can extend this enum without +/// making older clients fail the containing message. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ResourceChangeType { - #[serde(rename = "added")] Added, - #[serde(rename = "updated")] Updated, - #[serde(rename = "deleted")] Deleted, + /// An unknown or future wire value, preserved verbatim. + Unknown(String), +} + +impl ResourceChangeType { + /// Returns the exact string used on the wire. + pub fn as_str(&self) -> &str { + match self { + Self::Added => "added", + Self::Updated => "updated", + Self::Deleted => "deleted", + Self::Unknown(value) => value.as_str(), + } + } +} + +impl Serialize for ResourceChangeType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +impl<'de> Deserialize<'de> for ResourceChangeType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(match value.as_str() { + "added" => Self::Added, + "updated" => Self::Updated, + "deleted" => Self::Deleted, + _ => Self::Unknown(value), + }) + } } // ─── Structs ────────────────────────────────────────────────────────── diff --git a/clients/rust/crates/ahp-types/tests/roundtrip_corpus.rs b/clients/rust/crates/ahp-types/tests/roundtrip_corpus.rs index 030a4720..0924def8 100644 --- a/clients/rust/crates/ahp-types/tests/roundtrip_corpus.rs +++ b/clients/rust/crates/ahp-types/tests/roundtrip_corpus.rs @@ -29,8 +29,8 @@ use ahp_types::{ commands::{ChangesetOperationTarget, ChatSource, Implementation, InitializeResult}, common::StringOrMarkdown, messages::JsonRpcMessage, - notifications::{PartialSessionSummary, SessionAddedParams}, - state::{ChatInputQuestion, Customization, SessionStatus, SessionSummary}, + notifications::{AuthRequiredParams, PartialSessionSummary, SessionAddedParams}, + state::{ChatInputQuestion, Customization, SessionStatus, SessionSummary, Turn}, version::{PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS}, }; use serde_json::{Number, Value}; @@ -222,6 +222,8 @@ fn decode_and_reencode(file: &str, type_name: &str, input_json: &str) -> Result< "Implementation" => round_trip!(Implementation), "InitializeResult" => round_trip!(InitializeResult), "ChatSource" => round_trip!(ChatSource), + "AuthRequiredParams" => round_trip!(AuthRequiredParams), + "Turn" => round_trip!(Turn), other => Err(format!( "{}: unknown wire type {:?}. Add a decode entry to decode_and_reencode.", file, other diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 59c62518..ccf73930 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -1132,7 +1132,7 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu } else { Some(final_answers) }; - part.response = Some(a.response); + part.response = Some(a.response.clone()); refresh_summary_status(state); touch_chat_modified(state); ReduceOutcome::Applied @@ -1142,9 +1142,10 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu id: a.id.clone(), message: a.message.clone(), }; - match a.kind { + match &a.kind { PendingMessageKind::Steering => { state.steering_message = Some(entry); + ReduceOutcome::Applied } PendingMessageKind::Queued => { let list = state.queued_messages.get_or_insert_with(Vec::new); @@ -1153,11 +1154,12 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu } else { list.push(entry); } + ReduceOutcome::Applied } + PendingMessageKind::Unknown(_) => ReduceOutcome::NoOp, } - ReduceOutcome::Applied } - StateAction::ChatPendingMessageRemoved(a) => match a.kind { + StateAction::ChatPendingMessageRemoved(a) => match &a.kind { PendingMessageKind::Steering => match &state.steering_message { Some(m) if m.id == a.id => { state.steering_message = None; @@ -1179,6 +1181,7 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu } ReduceOutcome::Applied } + PendingMessageKind::Unknown(_) => ReduceOutcome::NoOp, }, StateAction::ChatQueuedMessagesReordered(a) => { let Some(list) = state.queued_messages.as_mut() else { @@ -1267,7 +1270,7 @@ fn apply_tool_call_ready(state: &mut ChatState, a: &ChatToolCallReadyAction) -> ToolCallState::Streaming(_) | ToolCallState::Running(_) | ToolCallState::PendingConfirmation(_) => { - if let Some(confirmed) = a.confirmed { + if let Some(confirmed) = a.confirmed.clone() { ToolCallState::Running(ToolCallRunningState { tool_call_id: base.tool_call_id, tool_name: base.tool_name, @@ -1357,7 +1360,10 @@ fn apply_tool_call_confirmed( meta, invocation_message, tool_input: a.edited_tool_input.clone().or(tool_input), - confirmed: a.confirmed.unwrap_or(ToolCallConfirmationReason::NotNeeded), + confirmed: a + .confirmed + .clone() + .unwrap_or(ToolCallConfirmationReason::NotNeeded), selected_option, content: None, }) @@ -1371,7 +1377,10 @@ fn apply_tool_call_confirmed( meta, invocation_message, tool_input, - reason: a.reason.unwrap_or(ToolCallCancellationReason::Denied), + reason: a + .reason + .clone() + .unwrap_or(ToolCallCancellationReason::Denied), reason_message: a.reason_message.clone(), user_suggestion: a.user_suggestion.clone(), selected_option, @@ -1755,10 +1764,10 @@ pub fn apply_action_to_changeset( // Carry `error` only when the new status is `Error` so we don't // leave a stale error sitting on a recovered changeset. if a.status == ChangesetStatus::Error { - state.status = a.status; + state.status = a.status.clone(); state.error = a.error.clone(); } else { - state.status = a.status; + state.status = a.status.clone(); state.error = None; } ReduceOutcome::Applied @@ -1816,10 +1825,10 @@ pub fn apply_action_to_changeset( // Carry `error` only when the new status is `Error` so we don't // leave a stale error on an operation that recovered or started running. if a.status == ChangesetOperationStatus::Error { - ops[idx].status = a.status; + ops[idx].status = a.status.clone(); ops[idx].error = a.error.clone(); } else { - ops[idx].status = a.status; + ops[idx].status = a.status.clone(); ops[idx].error = None; } ReduceOutcome::Applied diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 809dd13b..42f24d4b 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -5,92 +5,286 @@ import Foundation // MARK: - ActionType /// Discriminant values for all state actions. -public enum ActionType: String, Codable, Sendable { - case rootAgentsChanged = "root/agentsChanged" - case rootActiveSessionsChanged = "root/activeSessionsChanged" - case sessionReady = "session/ready" - case sessionCreationFailed = "session/creationFailed" - case sessionChatAdded = "session/chatAdded" - case sessionChatRemoved = "session/chatRemoved" - case sessionChatUpdated = "session/chatUpdated" - case sessionDefaultChatChanged = "session/defaultChatChanged" - case chatTurnStarted = "chat/turnStarted" - case chatDelta = "chat/delta" - case chatResponsePart = "chat/responsePart" - case chatToolCallStart = "chat/toolCallStart" - case chatToolCallDelta = "chat/toolCallDelta" - case chatToolCallReady = "chat/toolCallReady" - case chatToolCallConfirmed = "chat/toolCallConfirmed" - case chatToolCallComplete = "chat/toolCallComplete" - case chatToolCallResultConfirmed = "chat/toolCallResultConfirmed" - case chatToolCallContentChanged = "chat/toolCallContentChanged" - case chatToolCallAuthRequired = "chat/toolCallAuthRequired" - case chatToolCallAuthResolved = "chat/toolCallAuthResolved" - case chatTurnComplete = "chat/turnComplete" - case chatTurnCancelled = "chat/turnCancelled" - case chatError = "chat/error" - case chatActivityChanged = "chat/activityChanged" - case chatWorkingDirectorySet = "chat/workingDirectorySet" - case chatWorkingDirectoryRemoved = "chat/workingDirectoryRemoved" - case sessionTitleChanged = "session/titleChanged" - case chatUsage = "chat/usage" - case chatReasoning = "chat/reasoning" - case sessionServerToolsChanged = "session/serverToolsChanged" - case sessionActiveClientSet = "session/activeClientSet" - case sessionActiveClientRemoved = "session/activeClientRemoved" - case sessionWorkingDirectorySet = "session/workingDirectorySet" - case sessionWorkingDirectoryRemoved = "session/workingDirectoryRemoved" - case sessionInputNeededSet = "session/inputNeededSet" - case sessionInputNeededRemoved = "session/inputNeededRemoved" - case chatPendingMessageSet = "chat/pendingMessageSet" - case chatPendingMessageRemoved = "chat/pendingMessageRemoved" - case chatQueuedMessagesReordered = "chat/queuedMessagesReordered" - case chatDraftChanged = "chat/draftChanged" - case chatInputRequested = "chat/inputRequested" - case chatInputAnswerChanged = "chat/inputAnswerChanged" - case chatInputCompleted = "chat/inputCompleted" - case sessionCustomizationsChanged = "session/customizationsChanged" - case sessionCustomizationToggled = "session/customizationToggled" - case sessionCustomizationUpdated = "session/customizationUpdated" - case sessionCustomizationRemoved = "session/customizationRemoved" - case sessionMcpServerStateChanged = "session/mcpServerStateChanged" - case sessionMcpServerStartRequested = "session/mcpServerStartRequested" - case sessionMcpServerStopRequested = "session/mcpServerStopRequested" - case chatTruncated = "chat/truncated" - case chatTurnsLoaded = "chat/turnsLoaded" - case sessionIsReadChanged = "session/isReadChanged" - case sessionIsArchivedChanged = "session/isArchivedChanged" - case sessionActivityChanged = "session/activityChanged" - case sessionChangesetsChanged = "session/changesetsChanged" - case sessionConfigChanged = "session/configChanged" - case sessionMetaChanged = "session/metaChanged" - case changesetStatusChanged = "changeset/statusChanged" - case changesetFileSet = "changeset/fileSet" - case changesetFileRemoved = "changeset/fileRemoved" - case changesetFilesReviewChanged = "changeset/filesReviewChanged" - case changesetContentChanged = "changeset/contentChanged" - case changesetOperationsChanged = "changeset/operationsChanged" - case changesetOperationStatusChanged = "changeset/operationStatusChanged" - case changesetCleared = "changeset/cleared" - case annotationsSet = "annotations/set" - case annotationsUpdated = "annotations/updated" - case annotationsRemoved = "annotations/removed" - case annotationsEntrySet = "annotations/entrySet" - case annotationsEntryRemoved = "annotations/entryRemoved" - case rootTerminalsChanged = "root/terminalsChanged" - case rootConfigChanged = "root/configChanged" - case terminalData = "terminal/data" - case terminalInput = "terminal/input" - case terminalResized = "terminal/resized" - case terminalClaimed = "terminal/claimed" - case terminalTitleChanged = "terminal/titleChanged" - case terminalCwdChanged = "terminal/cwdChanged" - case terminalExited = "terminal/exited" - case terminalCleared = "terminal/cleared" - case terminalCommandDetectionAvailable = "terminal/commandDetectionAvailable" - case terminalCommandExecuted = "terminal/commandExecuted" - case terminalCommandFinished = "terminal/commandFinished" - case resourceWatchChanged = "resourceWatch/changed" +public enum ActionType: Codable, Sendable, Hashable, RawRepresentable { + case rootAgentsChanged + case rootActiveSessionsChanged + case sessionReady + case sessionCreationFailed + case sessionChatAdded + case sessionChatRemoved + case sessionChatUpdated + case sessionDefaultChatChanged + case chatTurnStarted + case chatDelta + case chatResponsePart + case chatToolCallStart + case chatToolCallDelta + case chatToolCallReady + case chatToolCallConfirmed + case chatToolCallComplete + case chatToolCallResultConfirmed + case chatToolCallContentChanged + case chatToolCallAuthRequired + case chatToolCallAuthResolved + case chatTurnComplete + case chatTurnCancelled + case chatError + case chatActivityChanged + case chatWorkingDirectorySet + case chatWorkingDirectoryRemoved + case sessionTitleChanged + case chatUsage + case chatReasoning + case sessionServerToolsChanged + case sessionActiveClientSet + case sessionActiveClientRemoved + case sessionWorkingDirectorySet + case sessionWorkingDirectoryRemoved + case sessionInputNeededSet + case sessionInputNeededRemoved + case chatPendingMessageSet + case chatPendingMessageRemoved + case chatQueuedMessagesReordered + case chatDraftChanged + case chatInputRequested + case chatInputAnswerChanged + case chatInputCompleted + case sessionCustomizationsChanged + case sessionCustomizationToggled + case sessionCustomizationUpdated + case sessionCustomizationRemoved + case sessionMcpServerStateChanged + case sessionMcpServerStartRequested + case sessionMcpServerStopRequested + case chatTruncated + case chatTurnsLoaded + case sessionIsReadChanged + case sessionIsArchivedChanged + case sessionActivityChanged + case sessionChangesetsChanged + case sessionConfigChanged + case sessionMetaChanged + case changesetStatusChanged + case changesetFileSet + case changesetFileRemoved + case changesetFilesReviewChanged + case changesetContentChanged + case changesetOperationsChanged + case changesetOperationStatusChanged + case changesetCleared + case annotationsSet + case annotationsUpdated + case annotationsRemoved + case annotationsEntrySet + case annotationsEntryRemoved + case rootTerminalsChanged + case rootConfigChanged + case terminalData + case terminalInput + case terminalResized + case terminalClaimed + case terminalTitleChanged + case terminalCwdChanged + case terminalExited + case terminalCleared + case terminalCommandDetectionAvailable + case terminalCommandExecuted + case terminalCommandFinished + case resourceWatchChanged + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "root/agentsChanged": self = .rootAgentsChanged + case "root/activeSessionsChanged": self = .rootActiveSessionsChanged + case "session/ready": self = .sessionReady + case "session/creationFailed": self = .sessionCreationFailed + case "session/chatAdded": self = .sessionChatAdded + case "session/chatRemoved": self = .sessionChatRemoved + case "session/chatUpdated": self = .sessionChatUpdated + case "session/defaultChatChanged": self = .sessionDefaultChatChanged + case "chat/turnStarted": self = .chatTurnStarted + case "chat/delta": self = .chatDelta + case "chat/responsePart": self = .chatResponsePart + case "chat/toolCallStart": self = .chatToolCallStart + case "chat/toolCallDelta": self = .chatToolCallDelta + case "chat/toolCallReady": self = .chatToolCallReady + case "chat/toolCallConfirmed": self = .chatToolCallConfirmed + case "chat/toolCallComplete": self = .chatToolCallComplete + case "chat/toolCallResultConfirmed": self = .chatToolCallResultConfirmed + case "chat/toolCallContentChanged": self = .chatToolCallContentChanged + case "chat/toolCallAuthRequired": self = .chatToolCallAuthRequired + case "chat/toolCallAuthResolved": self = .chatToolCallAuthResolved + case "chat/turnComplete": self = .chatTurnComplete + case "chat/turnCancelled": self = .chatTurnCancelled + case "chat/error": self = .chatError + case "chat/activityChanged": self = .chatActivityChanged + case "chat/workingDirectorySet": self = .chatWorkingDirectorySet + case "chat/workingDirectoryRemoved": self = .chatWorkingDirectoryRemoved + case "session/titleChanged": self = .sessionTitleChanged + case "chat/usage": self = .chatUsage + case "chat/reasoning": self = .chatReasoning + case "session/serverToolsChanged": self = .sessionServerToolsChanged + case "session/activeClientSet": self = .sessionActiveClientSet + case "session/activeClientRemoved": self = .sessionActiveClientRemoved + case "session/workingDirectorySet": self = .sessionWorkingDirectorySet + case "session/workingDirectoryRemoved": self = .sessionWorkingDirectoryRemoved + case "session/inputNeededSet": self = .sessionInputNeededSet + case "session/inputNeededRemoved": self = .sessionInputNeededRemoved + case "chat/pendingMessageSet": self = .chatPendingMessageSet + case "chat/pendingMessageRemoved": self = .chatPendingMessageRemoved + case "chat/queuedMessagesReordered": self = .chatQueuedMessagesReordered + case "chat/draftChanged": self = .chatDraftChanged + case "chat/inputRequested": self = .chatInputRequested + case "chat/inputAnswerChanged": self = .chatInputAnswerChanged + case "chat/inputCompleted": self = .chatInputCompleted + case "session/customizationsChanged": self = .sessionCustomizationsChanged + case "session/customizationToggled": self = .sessionCustomizationToggled + case "session/customizationUpdated": self = .sessionCustomizationUpdated + case "session/customizationRemoved": self = .sessionCustomizationRemoved + case "session/mcpServerStateChanged": self = .sessionMcpServerStateChanged + case "session/mcpServerStartRequested": self = .sessionMcpServerStartRequested + case "session/mcpServerStopRequested": self = .sessionMcpServerStopRequested + case "chat/truncated": self = .chatTruncated + case "chat/turnsLoaded": self = .chatTurnsLoaded + case "session/isReadChanged": self = .sessionIsReadChanged + case "session/isArchivedChanged": self = .sessionIsArchivedChanged + case "session/activityChanged": self = .sessionActivityChanged + case "session/changesetsChanged": self = .sessionChangesetsChanged + case "session/configChanged": self = .sessionConfigChanged + case "session/metaChanged": self = .sessionMetaChanged + case "changeset/statusChanged": self = .changesetStatusChanged + case "changeset/fileSet": self = .changesetFileSet + case "changeset/fileRemoved": self = .changesetFileRemoved + case "changeset/filesReviewChanged": self = .changesetFilesReviewChanged + case "changeset/contentChanged": self = .changesetContentChanged + case "changeset/operationsChanged": self = .changesetOperationsChanged + case "changeset/operationStatusChanged": self = .changesetOperationStatusChanged + case "changeset/cleared": self = .changesetCleared + case "annotations/set": self = .annotationsSet + case "annotations/updated": self = .annotationsUpdated + case "annotations/removed": self = .annotationsRemoved + case "annotations/entrySet": self = .annotationsEntrySet + case "annotations/entryRemoved": self = .annotationsEntryRemoved + case "root/terminalsChanged": self = .rootTerminalsChanged + case "root/configChanged": self = .rootConfigChanged + case "terminal/data": self = .terminalData + case "terminal/input": self = .terminalInput + case "terminal/resized": self = .terminalResized + case "terminal/claimed": self = .terminalClaimed + case "terminal/titleChanged": self = .terminalTitleChanged + case "terminal/cwdChanged": self = .terminalCwdChanged + case "terminal/exited": self = .terminalExited + case "terminal/cleared": self = .terminalCleared + case "terminal/commandDetectionAvailable": self = .terminalCommandDetectionAvailable + case "terminal/commandExecuted": self = .terminalCommandExecuted + case "terminal/commandFinished": self = .terminalCommandFinished + case "resourceWatch/changed": self = .resourceWatchChanged + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .rootAgentsChanged: return "root/agentsChanged" + case .rootActiveSessionsChanged: return "root/activeSessionsChanged" + case .sessionReady: return "session/ready" + case .sessionCreationFailed: return "session/creationFailed" + case .sessionChatAdded: return "session/chatAdded" + case .sessionChatRemoved: return "session/chatRemoved" + case .sessionChatUpdated: return "session/chatUpdated" + case .sessionDefaultChatChanged: return "session/defaultChatChanged" + case .chatTurnStarted: return "chat/turnStarted" + case .chatDelta: return "chat/delta" + case .chatResponsePart: return "chat/responsePart" + case .chatToolCallStart: return "chat/toolCallStart" + case .chatToolCallDelta: return "chat/toolCallDelta" + case .chatToolCallReady: return "chat/toolCallReady" + case .chatToolCallConfirmed: return "chat/toolCallConfirmed" + case .chatToolCallComplete: return "chat/toolCallComplete" + case .chatToolCallResultConfirmed: return "chat/toolCallResultConfirmed" + case .chatToolCallContentChanged: return "chat/toolCallContentChanged" + case .chatToolCallAuthRequired: return "chat/toolCallAuthRequired" + case .chatToolCallAuthResolved: return "chat/toolCallAuthResolved" + case .chatTurnComplete: return "chat/turnComplete" + case .chatTurnCancelled: return "chat/turnCancelled" + case .chatError: return "chat/error" + case .chatActivityChanged: return "chat/activityChanged" + case .chatWorkingDirectorySet: return "chat/workingDirectorySet" + case .chatWorkingDirectoryRemoved: return "chat/workingDirectoryRemoved" + case .sessionTitleChanged: return "session/titleChanged" + case .chatUsage: return "chat/usage" + case .chatReasoning: return "chat/reasoning" + case .sessionServerToolsChanged: return "session/serverToolsChanged" + case .sessionActiveClientSet: return "session/activeClientSet" + case .sessionActiveClientRemoved: return "session/activeClientRemoved" + case .sessionWorkingDirectorySet: return "session/workingDirectorySet" + case .sessionWorkingDirectoryRemoved: return "session/workingDirectoryRemoved" + case .sessionInputNeededSet: return "session/inputNeededSet" + case .sessionInputNeededRemoved: return "session/inputNeededRemoved" + case .chatPendingMessageSet: return "chat/pendingMessageSet" + case .chatPendingMessageRemoved: return "chat/pendingMessageRemoved" + case .chatQueuedMessagesReordered: return "chat/queuedMessagesReordered" + case .chatDraftChanged: return "chat/draftChanged" + case .chatInputRequested: return "chat/inputRequested" + case .chatInputAnswerChanged: return "chat/inputAnswerChanged" + case .chatInputCompleted: return "chat/inputCompleted" + case .sessionCustomizationsChanged: return "session/customizationsChanged" + case .sessionCustomizationToggled: return "session/customizationToggled" + case .sessionCustomizationUpdated: return "session/customizationUpdated" + case .sessionCustomizationRemoved: return "session/customizationRemoved" + case .sessionMcpServerStateChanged: return "session/mcpServerStateChanged" + case .sessionMcpServerStartRequested: return "session/mcpServerStartRequested" + case .sessionMcpServerStopRequested: return "session/mcpServerStopRequested" + case .chatTruncated: return "chat/truncated" + case .chatTurnsLoaded: return "chat/turnsLoaded" + case .sessionIsReadChanged: return "session/isReadChanged" + case .sessionIsArchivedChanged: return "session/isArchivedChanged" + case .sessionActivityChanged: return "session/activityChanged" + case .sessionChangesetsChanged: return "session/changesetsChanged" + case .sessionConfigChanged: return "session/configChanged" + case .sessionMetaChanged: return "session/metaChanged" + case .changesetStatusChanged: return "changeset/statusChanged" + case .changesetFileSet: return "changeset/fileSet" + case .changesetFileRemoved: return "changeset/fileRemoved" + case .changesetFilesReviewChanged: return "changeset/filesReviewChanged" + case .changesetContentChanged: return "changeset/contentChanged" + case .changesetOperationsChanged: return "changeset/operationsChanged" + case .changesetOperationStatusChanged: return "changeset/operationStatusChanged" + case .changesetCleared: return "changeset/cleared" + case .annotationsSet: return "annotations/set" + case .annotationsUpdated: return "annotations/updated" + case .annotationsRemoved: return "annotations/removed" + case .annotationsEntrySet: return "annotations/entrySet" + case .annotationsEntryRemoved: return "annotations/entryRemoved" + case .rootTerminalsChanged: return "root/terminalsChanged" + case .rootConfigChanged: return "root/configChanged" + case .terminalData: return "terminal/data" + case .terminalInput: return "terminal/input" + case .terminalResized: return "terminal/resized" + case .terminalClaimed: return "terminal/claimed" + case .terminalTitleChanged: return "terminal/titleChanged" + case .terminalCwdChanged: return "terminal/cwdChanged" + case .terminalExited: return "terminal/exited" + case .terminalCleared: return "terminal/cleared" + case .terminalCommandDetectionAvailable: return "terminal/commandDetectionAvailable" + case .terminalCommandExecuted: return "terminal/commandExecuted" + case .terminalCommandFinished: return "terminal/commandFinished" + case .resourceWatchChanged: return "resourceWatch/changed" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } // MARK: - Action Infrastructure diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 4abe4305..aed60857 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -5,38 +5,178 @@ import Foundation // MARK: - Command Enums /// Discriminant for reconnect result types. -public enum ReconnectResultType: String, Codable, Sendable { - case replay = "replay" - case snapshot = "snapshot" +public enum ReconnectResultType: Codable, Sendable, Hashable, RawRepresentable { + case replay + case snapshot + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "replay": self = .replay + case "snapshot": self = .snapshot + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .replay: return "replay" + case .snapshot: return "snapshot" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// How a new chat uses its source chat and turn. -public enum ChatSourceKind: String, Codable, Sendable { +public enum ChatSourceKind: Codable, Sendable, Hashable, RawRepresentable { /// Copy source history through the referenced turn into the new chat. - case fork = "fork" + case fork /// Supply source context without copying it into the new chat's visible history. - case sideChat = "sideChat" + case sideChat + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "fork": self = .fork + case "sideChat": self = .sideChat + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .fork: return "fork" + case .sideChat: return "sideChat" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Encoding of fetched content data. -public enum ContentEncoding: String, Codable, Sendable { - case base64 = "base64" - case utf8 = "utf-8" +public enum ContentEncoding: Codable, Sendable, Hashable, RawRepresentable { + case base64 + case utf8 + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "base64": self = .base64 + case "utf-8": self = .utf8 + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .base64: return "base64" + case .utf8: return "utf-8" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// The kind of completion items being requested. -public enum CompletionItemKind: String, Codable, Sendable { +public enum CompletionItemKind: Codable, Sendable, Hashable, RawRepresentable { /// Completions for the text of a {@link Message} the user is composing. /// Each returned item carries an attachment that gets associated with the /// message when accepted. - case userMessage = "userMessage" + case userMessage + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "userMessage": self = .userMessage + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .userMessage: return "userMessage" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for {@link ResourceResolveResult.type}. -public enum ResourceType: String, Codable, Sendable { - case file = "file" - case directory = "directory" - case symlink = "symlink" +public enum ResourceType: Codable, Sendable, Hashable, RawRepresentable { + case file + case directory + case symlink + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "file": self = .file + case "directory": self = .directory + case "symlink": self = .symlink + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .file: return "file" + case .directory: return "directory" + case .symlink: return "symlink" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// How {@link ResourceWriteParams.data} is placed within the target file. @@ -58,10 +198,40 @@ public enum ResourceType: String, Codable, Sendable { /// is the byte offset at which `data` is spliced in; bytes at or after /// `position` are shifted right by `data.length`. `insert` always grows /// the file — use `truncate` to overwrite bytes in place. -public enum ResourceWriteMode: String, Codable, Sendable { - case truncate = "truncate" - case append = "append" - case insert = "insert" +public enum ResourceWriteMode: Codable, Sendable, Hashable, RawRepresentable { + case truncate + case append + case insert + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "truncate": self = .truncate + case "append": self = .append + case "insert": self = .insert + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .truncate: return "truncate" + case .append: return "append" + case .insert: return "insert" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } // MARK: - Command Types diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift index 4ffbb95c..2cd129bb 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift @@ -5,11 +5,39 @@ import Foundation // MARK: - Notification Enums /// Reason why authentication is required. -public enum AuthRequiredReason: String, Codable, Sendable { +public enum AuthRequiredReason: Codable, Sendable, Hashable, RawRepresentable { /// The client has not yet authenticated for the resource - case required = "required" + case required /// A previously valid token has expired or been revoked - case expired = "expired" + case expired + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "required": self = .required + case "expired": self = .expired + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .required: return "required" + case .expired: return "expired" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } // MARK: - Notification Types diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index fbe0604c..cdd04dc5 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -41,25 +41,113 @@ public enum StringOrMarkdown: Codable, Sendable, Equatable { // MARK: - Enums /// Policy configuration state for a model. -public enum PolicyState: String, Codable, Sendable { - case enabled = "enabled" - case disabled = "disabled" - case unconfigured = "unconfigured" +public enum PolicyState: Codable, Sendable, Hashable, RawRepresentable { + case enabled + case disabled + case unconfigured + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "enabled": self = .enabled + case "disabled": self = .disabled + case "unconfigured": self = .unconfigured + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .enabled: return "enabled" + case .disabled: return "disabled" + case .unconfigured: return "unconfigured" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for pending message kinds. -public enum PendingMessageKind: String, Codable, Sendable { +public enum PendingMessageKind: Codable, Sendable, Hashable, RawRepresentable { /// Injected into the current turn at a convenient point - case steering = "steering" + case steering /// Sent automatically as a new turn after the current turn finishes - case queued = "queued" + case queued + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "steering": self = .steering + case "queued": self = .queued + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .steering: return "steering" + case .queued: return "queued" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Session initialization state. -public enum SessionLifecycle: String, Codable, Sendable { - case creating = "creating" - case ready = "ready" - case creationFailed = "creationFailed" +public enum SessionLifecycle: Codable, Sendable, Hashable, RawRepresentable { + case creating + case ready + case creationFailed + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "creating": self = .creating + case "ready": self = .ready + case "creationFailed": self = .creationFailed + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .creating: return "creating" + case .ready: return "ready" + case .creationFailed: return "creationFailed" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Bitset of summary-level session status flags. @@ -86,15 +174,47 @@ public struct SessionStatus: OptionSet, Codable, Sendable, Hashable { } /// Discriminant for {@link ChatOrigin} — how a chat came into existence. -public enum ChatOriginKind: String, Codable, Sendable { +public enum ChatOriginKind: Codable, Sendable, Hashable, RawRepresentable { /// User created the chat explicitly (e.g. via the host UI). - case user = "user" + case user /// Forked from an existing chat at a specific turn. - case fork = "fork" + case fork /// Created as an independent side conversation from a specific turn. - case sideChat = "sideChat" + case sideChat /// Spawned by a tool call running in another chat (e.g. a sub-agent delegation). - case tool = "tool" + case tool + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "user": self = .user + case "fork": self = .fork + case "sideChat": self = .sideChat + case "tool": self = .tool + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .user: return "user" + case .fork: return "fork" + case .sideChat: return "sideChat" + case .tool: return "tool" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// How a user can interact with a chat. @@ -107,46 +227,206 @@ public enum ChatOriginKind: String, Codable, Sendable { /// worker chats are read-only (visible for observability) or hidden (internal /// implementation detail). The harness sets this based on the chat's role; /// the UI uses it to show appropriate controls. -public enum ChatInteractivity: String, Codable, Sendable { +public enum ChatInteractivity: Codable, Sendable, Hashable, RawRepresentable { /// User can send messages and watch (default when absent) - case full = "full" + case full /// User can watch but not send messages - case readOnly = "read-only" + case readOnly /// Internal worker not shown in UI at all - case hidden = "hidden" + case hidden + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "full": self = .full + case "read-only": self = .readOnly + case "hidden": self = .hidden + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .full: return "full" + case .readOnly: return "read-only" + case .hidden: return "hidden" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Answer lifecycle state. -public enum ChatInputAnswerState: String, Codable, Sendable { - case draft = "draft" - case submitted = "submitted" - case skipped = "skipped" +public enum ChatInputAnswerState: Codable, Sendable, Hashable, RawRepresentable { + case draft + case submitted + case skipped + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "draft": self = .draft + case "submitted": self = .submitted + case "skipped": self = .skipped + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .draft: return "draft" + case .submitted: return "submitted" + case .skipped: return "skipped" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Answer value kind. -public enum ChatInputAnswerValueKind: String, Codable, Sendable { - case text = "text" - case number = "number" - case boolean = "boolean" - case selected = "selected" - case selectedMany = "selected-many" +public enum ChatInputAnswerValueKind: Codable, Sendable, Hashable, RawRepresentable { + case text + case number + case boolean + case selected + case selectedMany + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "text": self = .text + case "number": self = .number + case "boolean": self = .boolean + case "selected": self = .selected + case "selected-many": self = .selectedMany + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .text: return "text" + case .number: return "number" + case .boolean: return "boolean" + case .selected: return "selected" + case .selectedMany: return "selected-many" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Question/input control kind. -public enum ChatInputQuestionKind: String, Codable, Sendable { - case text = "text" - case number = "number" - case integer = "integer" - case boolean = "boolean" - case singleSelect = "single-select" - case multiSelect = "multi-select" +public enum ChatInputQuestionKind: Codable, Sendable, Hashable, RawRepresentable { + case text + case number + case integer + case boolean + case singleSelect + case multiSelect + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "text": self = .text + case "number": self = .number + case "integer": self = .integer + case "boolean": self = .boolean + case "single-select": self = .singleSelect + case "multi-select": self = .multiSelect + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .text: return "text" + case .number: return "number" + case .integer: return "integer" + case .boolean: return "boolean" + case .singleSelect: return "single-select" + case .multiSelect: return "multi-select" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// How a client completed an input request. -public enum ChatInputResponseKind: String, Codable, Sendable { - case accept = "accept" - case decline = "decline" - case cancel = "cancel" +public enum ChatInputResponseKind: Codable, Sendable, Hashable, RawRepresentable { + case accept + case decline + case cancel + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "accept": self = .accept + case "decline": self = .decline + case "cancel": self = .cancel + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .accept: return "accept" + case .decline: return "decline" + case .cancel: return "cancel" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for the kinds of outstanding input a session can surface in @@ -154,74 +434,276 @@ public enum ChatInputResponseKind: String, Codable, Sendable { /// /// This is a general/typological union (not a lifecycle), so the discriminant is /// a `*Kind`. -public enum SessionInputRequestKind: String, Codable, Sendable { +public enum SessionInputRequestKind: Codable, Sendable, Hashable, RawRepresentable { /// A user-facing elicitation mirrored from an unresolved chat response part. - case chatInput = "chatInput" + case chatInput /// A tool call awaiting parameter- or result-confirmation. - case toolConfirmation = "toolConfirmation" + case toolConfirmation /// A running tool the session wants an active client to execute. - case toolClientExecution = "toolClientExecution" + case toolClientExecution /// A tool call blocked on MCP authentication mid-execution. - case toolAuthentication = "toolAuthentication" + case toolAuthentication + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "chatInput": self = .chatInput + case "toolConfirmation": self = .toolConfirmation + case "toolClientExecution": self = .toolClientExecution + case "toolAuthentication": self = .toolAuthentication + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .chatInput: return "chatInput" + case .toolConfirmation: return "toolConfirmation" + case .toolClientExecution: return "toolClientExecution" + case .toolAuthentication: return "toolAuthentication" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// How a turn ended. -public enum TurnState: String, Codable, Sendable { - case complete = "complete" - case cancelled = "cancelled" - case error = "error" +public enum TurnState: Codable, Sendable, Hashable, RawRepresentable { + case complete + case cancelled + case error + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "complete": self = .complete + case "cancelled": self = .cancelled + case "error": self = .error + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .complete: return "complete" + case .cancelled: return "cancelled" + case .error: return "error" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for {@link MessageOrigin} — identifies who produced a message. -public enum MessageKind: String, Codable, Sendable { +public enum MessageKind: Codable, Sendable, Hashable, RawRepresentable { /// Sent directly by the user. - case user = "user" + case user /// Produced by the agent itself rather than the user — for example, an agent /// that seeds the first message of a chat it spawned. - case agent = "agent" + case agent /// Produced by a tool rather than the user — for example, a tool that spawns a /// worker chat whose first message carries a seed prompt. - case tool = "tool" + case tool /// A system-generated notification rather than a direct user message. - case systemNotification = "systemNotification" + case systemNotification + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "user": self = .user + case "agent": self = .agent + case "tool": self = .tool + case "systemNotification": self = .systemNotification + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .user: return "user" + case .agent: return "agent" + case .tool: return "tool" + case .systemNotification: return "systemNotification" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for {@link MessageAttachment} variants. -public enum MessageAttachmentKind: String, Codable, Sendable { +public enum MessageAttachmentKind: Codable, Sendable, Hashable, RawRepresentable { /// A simple, opaque attachment whose representation is described by the producer. - case simple = "simple" + case simple /// An attachment whose data is embedded inline as a base64 string. - case embeddedResource = "embeddedResource" + case embeddedResource /// An attachment that references a resource by URI. - case resource = "resource" + case resource /// An attachment that references annotations on an annotations channel. - case annotations = "annotations" + case annotations /// An attachment that references a bounded transcript from another chat. - case chat = "chat" + case chat + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "simple": self = .simple + case "embeddedResource": self = .embeddedResource + case "resource": self = .resource + case "annotations": self = .annotations + case "chat": self = .chat + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .simple: return "simple" + case .embeddedResource: return "embeddedResource" + case .resource: return "resource" + case .annotations: return "annotations" + case .chat: return "chat" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for response part types. -public enum ResponsePartKind: String, Codable, Sendable { - case markdown = "markdown" - case contentRef = "contentRef" - case toolCall = "toolCall" - case reasoning = "reasoning" - case systemNotification = "systemNotification" - case inputRequest = "inputRequest" +public enum ResponsePartKind: Codable, Sendable, Hashable, RawRepresentable { + case markdown + case contentRef + case toolCall + case reasoning + case systemNotification + case inputRequest + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "markdown": self = .markdown + case "contentRef": self = .contentRef + case "toolCall": self = .toolCall + case "reasoning": self = .reasoning + case "systemNotification": self = .systemNotification + case "inputRequest": self = .inputRequest + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .markdown: return "markdown" + case .contentRef: return "contentRef" + case .toolCall: return "toolCall" + case .reasoning: return "reasoning" + case .systemNotification: return "systemNotification" + case .inputRequest: return "inputRequest" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Status of a tool call in the lifecycle state machine. -public enum ToolCallStatus: String, Codable, Sendable { - case streaming = "streaming" - case pendingConfirmation = "pending-confirmation" - case running = "running" +public enum ToolCallStatus: Codable, Sendable, Hashable, RawRepresentable { + case streaming + case pendingConfirmation + case running /// Running paused because the MCP server backing this call needs /// authentication (typically step-up auth for insufficient scope, /// surfacing mid-execution). See {@link ToolCallAuthRequiredState}. - case authRequired = "auth-required" - case pendingResultConfirmation = "pending-result-confirmation" - case completed = "completed" - case cancelled = "cancelled" + case authRequired + case pendingResultConfirmation + case completed + case cancelled + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "streaming": self = .streaming + case "pending-confirmation": self = .pendingConfirmation + case "running": self = .running + case "auth-required": self = .authRequired + case "pending-result-confirmation": self = .pendingResultConfirmation + case "completed": self = .completed + case "cancelled": self = .cancelled + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .streaming: return "streaming" + case .pendingConfirmation: return "pending-confirmation" + case .running: return "running" + case .authRequired: return "auth-required" + case .pendingResultConfirmation: return "pending-result-confirmation" + case .completed: return "completed" + case .cancelled: return "cancelled" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// How a tool call was confirmed for execution. @@ -229,49 +711,255 @@ public enum ToolCallStatus: String, Codable, Sendable { /// - `NotNeeded` — No confirmation required (auto-approved) /// - `UserAction` — User explicitly approved /// - `Setting` — Approved by a persistent user setting -public enum ToolCallConfirmationReason: String, Codable, Sendable { - case notNeeded = "not-needed" - case userAction = "user-action" - case setting = "setting" +public enum ToolCallConfirmationReason: Codable, Sendable, Hashable, RawRepresentable { + case notNeeded + case userAction + case setting + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "not-needed": self = .notNeeded + case "user-action": self = .userAction + case "setting": self = .setting + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .notNeeded: return "not-needed" + case .userAction: return "user-action" + case .setting: return "setting" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Identifies a model judge as the source of a confirmation requirement. -public enum ToolCallRiskAssessmentKind: String, Codable, Sendable { - case judge = "judge" +public enum ToolCallRiskAssessmentKind: Codable, Sendable, Hashable, RawRepresentable { + case judge + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "judge": self = .judge + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .judge: return "judge" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Lifecycle status of an asynchronous model-judge confirmation decision. -public enum ToolCallRiskAssessmentStatus: String, Codable, Sendable { - case loading = "loading" - case complete = "complete" +public enum ToolCallRiskAssessmentStatus: Codable, Sendable, Hashable, RawRepresentable { + case loading + case complete + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "loading": self = .loading + case "complete": self = .complete + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .loading: return "loading" + case .complete: return "complete" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Why a tool call was cancelled. -public enum ToolCallCancellationReason: String, Codable, Sendable { - case denied = "denied" - case skipped = "skipped" - case resultDenied = "result-denied" +public enum ToolCallCancellationReason: Codable, Sendable, Hashable, RawRepresentable { + case denied + case skipped + case resultDenied + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "denied": self = .denied + case "skipped": self = .skipped + case "result-denied": self = .resultDenied + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .denied: return "denied" + case .skipped: return "skipped" + case .resultDenied: return "result-denied" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Whether a confirmation option represents an approval or denial action. -public enum ConfirmationOptionKind: String, Codable, Sendable { - case approve = "approve" - case deny = "deny" +public enum ConfirmationOptionKind: Codable, Sendable, Hashable, RawRepresentable { + case approve + case deny + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "approve": self = .approve + case "deny": self = .deny + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .approve: return "approve" + case .deny: return "deny" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } -public enum ToolCallContributorKind: String, Codable, Sendable { - case client = "client" - case mCP = "mcp" +public enum ToolCallContributorKind: Codable, Sendable, Hashable, RawRepresentable { + case client + case mCP + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "client": self = .client + case "mcp": self = .mCP + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .client: return "client" + case .mCP: return "mcp" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for tool result content types. -public enum ToolResultContentType: String, Codable, Sendable { - case text = "text" - case embeddedResource = "embeddedResource" - case resource = "resource" - case fileEdit = "fileEdit" - case terminal = "terminal" - case subagent = "subagent" +public enum ToolResultContentType: Codable, Sendable, Hashable, RawRepresentable { + case text + case embeddedResource + case resource + case fileEdit + case terminal + case subagent + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "text": self = .text + case "embeddedResource": self = .embeddedResource + case "resource": self = .resource + case "fileEdit": self = .fileEdit + case "terminal": self = .terminal + case "subagent": self = .subagent + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .text: return "text" + case .embeddedResource: return "embeddedResource" + case .resource: return "resource" + case .fileEdit: return "fileEdit" + case .terminal: return "terminal" + case .subagent: return "subagent" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for the kind of customization. @@ -283,57 +971,191 @@ public enum ToolResultContentType: String, Codable, Sendable { /// {@link CustomizationType.McpServer | `McpServer`} entries surfaced /// directly by the host. The remaining types appear only as children of /// a container. -public enum CustomizationType: String, Codable, Sendable { - case plugin = "plugin" - case directory = "directory" - case agent = "agent" - case skill = "skill" - case prompt = "prompt" - case rule = "rule" - case hook = "hook" - case mcpServer = "mcpServer" +public enum CustomizationType: Codable, Sendable, Hashable, RawRepresentable { + case plugin + case directory + case agent + case skill + case prompt + case rule + case hook + case mcpServer + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "plugin": self = .plugin + case "directory": self = .directory + case "agent": self = .agent + case "skill": self = .skill + case "prompt": self = .prompt + case "rule": self = .rule + case "hook": self = .hook + case "mcpServer": self = .mcpServer + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .plugin: return "plugin" + case .directory: return "directory" + case .agent: return "agent" + case .skill: return "skill" + case .prompt: return "prompt" + case .rule: return "rule" + case .hook: return "hook" + case .mcpServer: return "mcpServer" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant values for {@link CustomizationLoadState}. -public enum CustomizationLoadStatus: String, Codable, Sendable { - case loading = "loading" - case loaded = "loaded" - case degraded = "degraded" - case error = "error" +public enum CustomizationLoadStatus: Codable, Sendable, Hashable, RawRepresentable { + case loading + case loaded + case degraded + case error + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "loading": self = .loading + case "loaded": self = .loaded + case "degraded": self = .degraded + case "error": self = .error + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .loading: return "loading" + case .loaded: return "loaded" + case .degraded: return "degraded" + case .error: return "error" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for terminal claim kinds. -public enum TerminalClaimKind: String, Codable, Sendable { - case client = "client" - case session = "session" +public enum TerminalClaimKind: Codable, Sendable, Hashable, RawRepresentable { + case client + case session + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "client": self = .client + case "session": self = .session + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .client: return "client" + case .session: return "session" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for the {@link McpServerState} union. -public enum McpServerStatus: String, Codable, Sendable { +public enum McpServerStatus: Codable, Sendable, Hashable, RawRepresentable { /// Server has been registered but is not yet running. - case starting = "starting" + case starting /// Server is running and serving requests. - case ready = "ready" + case ready /// Server is reachable but requires additional authentication before it /// can start, or before it can serve a particular request. Carries the /// RFC 9728 Protected Resource Metadata the client needs to obtain a /// token; the client then pushes the token via the existing /// `authenticate` command. - case authRequired = "authRequired" + case authRequired /// Server failed to start, crashed, or otherwise transitioned to a fatal error. - case error = "error" + case error /// Server has been shut down. - case stopped = "stopped" + case stopped + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "starting": self = .starting + case "ready": self = .ready + case "authRequired": self = .authRequired + case "error": self = .error + case "stopped": self = .stopped + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .starting: return "starting" + case .ready: return "ready" + case .authRequired: return "authRequired" + case .error: return "error" + case .stopped: return "stopped" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Why an MCP server is currently in the {@link McpServerStatus.AuthRequired} /// state. Mirrors the three failure modes defined by the /// [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization.md). -public enum McpAuthRequiredReason: String, Codable, Sendable { +public enum McpAuthRequiredReason: Codable, Sendable, Hashable, RawRepresentable { /// No token has been provided yet (HTTP 401, no prior token). - case required = "required" + case required /// A previously valid token expired or was revoked (HTTP 401). - case expired = "expired" + case expired /// Step-up auth: a token is present but its scopes are insufficient for /// the requested operation (HTTP 403 with /// `WWW-Authenticate: Bearer error="insufficient_scope"`). @@ -350,18 +1172,78 @@ public enum McpAuthRequiredReason: String, Codable, Sendable { /// {@link McpServerCustomization | MCP server} backing a running tool /// call so they can present an explicit "grant more access" affordance /// tied to the blocked tool call. - case insufficientScope = "insufficientScope" + case insufficientScope + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "required": self = .required + case "expired": self = .expired + case "insufficientScope": self = .insufficientScope + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .required: return "required" + case .expired: return "expired" + case .insufficientScope: return "insufficientScope" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Computation lifecycle of a {@link ChangesetState}. -public enum ChangesetStatus: String, Codable, Sendable { +public enum ChangesetStatus: Codable, Sendable, Hashable, RawRepresentable { /// The server is still computing the contents of this changeset. - case computing = "computing" + case computing /// The changeset has been fully computed and is up-to-date. - case ready = "ready" + case ready /// Computation failed. The cause is described by /// {@link ChangesetState.error}. - case error = "error" + case error + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "computing": self = .computing + case "ready": self = .ready + case "error": self = .error + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .computing: return "computing" + case .ready: return "ready" + case .error: return "error" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Execution lifecycle of a {@link ChangesetOperation}. @@ -370,34 +1252,126 @@ public enum ChangesetStatus: String, Codable, Sendable { /// its progress and outcome are reflected back into changeset state so that /// every subscriber observes a consistent view (e.g. a spinner on a "Create /// Pull Request" button, or an inline error after a failed "revert"). -public enum ChangesetOperationStatus: String, Codable, Sendable { +public enum ChangesetOperationStatus: Codable, Sendable, Hashable, RawRepresentable { /// The operation is ready to be invoked. This is the default when /// {@link ChangesetOperation.status} is omitted. - case idle = "idle" + case idle /// An invocation of this operation is currently in flight. - case running = "running" + case running /// The most recent invocation failed. The cause is described by /// {@link ChangesetOperation.error}. - case error = "error" + case error /// The operation is currently disabled and cannot be invoked. - case disabled = "disabled" + case disabled + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "idle": self = .idle + case "running": self = .running + case "error": self = .error + case "disabled": self = .disabled + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .idle: return "idle" + case .running: return "running" + case .error: return "error" + case .disabled: return "disabled" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Where a {@link ChangesetOperation} can be invoked. -public enum ChangesetOperationScope: String, Codable, Sendable { +public enum ChangesetOperationScope: Codable, Sendable, Hashable, RawRepresentable { /// Applies to the whole changeset. - case changeset = "changeset" + case changeset /// Applies to a single file within the changeset. - case resource = "resource" + case resource /// Applies to a line range within a single file. - case range = "range" + case range + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "changeset": self = .changeset + case "resource": self = .resource + case "range": self = .range + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .changeset: return "changeset" + case .resource: return "resource" + case .range: return "range" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } /// Discriminant for {@link ResourceChange.type}. -public enum ResourceChangeType: String, Codable, Sendable { - case added = "added" - case updated = "updated" - case deleted = "deleted" +public enum ResourceChangeType: Codable, Sendable, Hashable, RawRepresentable { + case added + case updated + case deleted + /// An unknown or future wire value, preserved verbatim. + case unknown(String) + + public init(rawValue: String) { + switch rawValue { + case "added": self = .added + case "updated": self = .updated + case "deleted": self = .deleted + default: self = .unknown(rawValue) + } + } + + public var rawValue: String { + switch self { + case .added: return "added" + case .updated: return "updated" + case .deleted: return "deleted" + case .unknown(let value): return value + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.init(rawValue: try container.decode(String.self)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValue) + } } // MARK: - State Types diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift index fc0f03fb..8a49f6f1 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift @@ -589,32 +589,40 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { case .chatPendingMessageSet(let a): let entry = PendingMessage(id: a.id, message: a.message) var next = state - if a.kind == .steering { + switch a.kind { + case .steering: next.steeringMessage = entry return next + case .queued: + var existing = next.queuedMessages ?? [] + if let idx = existing.firstIndex(where: { $0.id == a.id }) { + existing[idx] = entry + } else { + existing.append(entry) + } + next.queuedMessages = existing + return next + case .unknown: + return state } - var existing = next.queuedMessages ?? [] - if let idx = existing.firstIndex(where: { $0.id == a.id }) { - existing[idx] = entry - } else { - existing.append(entry) - } - next.queuedMessages = existing - return next case .chatPendingMessageRemoved(let a): var next = state - if a.kind == .steering { + switch a.kind { + case .steering: guard next.steeringMessage?.id == a.id else { return state } next.steeringMessage = nil return next + case .queued: + guard var existing = next.queuedMessages else { return state } + let before = existing.count + existing.removeAll { $0.id == a.id } + guard existing.count != before else { return state } + next.queuedMessages = existing.isEmpty ? nil : existing + return next + case .unknown: + return state } - guard var existing = next.queuedMessages else { return state } - let before = existing.count - existing.removeAll { $0.id == a.id } - guard existing.count != before else { return state } - next.queuedMessages = existing.isEmpty ? nil : existing - return next case .chatQueuedMessagesReordered(let a): guard let existing = state.queuedMessages else { return state } diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/TypesRoundTripFixtureTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/TypesRoundTripFixtureTests.swift index 47a9e481..01fb36ac 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/TypesRoundTripFixtureTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/TypesRoundTripFixtureTests.swift @@ -190,6 +190,10 @@ final class TypesRoundTripFixtureTests: XCTestCase { return try reencode(dec.decode(InitializeResult.self, from: inputData)) case "ChatSource": return try reencode(dec.decode(ChatSource.self, from: inputData)) + case "AuthRequiredParams": + return try reencode(dec.decode(AuthRequiredParams.self, from: inputData)) + case "Turn": + return try reencode(dec.decode(Turn.self, from: inputData)) default: throw FixtureError.message( "round-trip fixture: unknown wire type \"\(type)\". Add a decode entry to decodeAndReencode.") diff --git a/clients/typescript/test/types-round-trip.test.ts b/clients/typescript/test/types-round-trip.test.ts index 957c009a..e70b9b4f 100644 --- a/clients/typescript/test/types-round-trip.test.ts +++ b/clients/typescript/test/types-round-trip.test.ts @@ -52,7 +52,9 @@ import type { StateAction, } from '../src/types/common/actions.js'; import type { StringOrMarkdown } from '../src/types/common/state.js'; +import type { AuthRequiredParams } from '../src/types/common/notifications.js'; import type { ChangesetOperationTarget } from '../src/types/channels-changeset/commands.js'; +import type { Turn } from '../src/types/channels-chat/state.js'; import type { ChatInputQuestion, Customization, @@ -244,6 +246,8 @@ function bindToType(file: string, type: string, parsed: unknown): void { case 'Implementation': void (parsed as Implementation); break; case 'InitializeResult': void (parsed as InitializeResult); break; case 'ChatSource': void (parsed as ChatSource); break; + case 'AuthRequiredParams': void (parsed as AuthRequiredParams); break; + case 'Turn': void (parsed as Turn); break; default: throw new Error( `${file}: unknown wire type "${type}". Add a decode entry to bindToType.`, diff --git a/docs/.changes/20260729-open-enum-values.json b/docs/.changes/20260729-open-enum-values.json new file mode 100644 index 00000000..377bce54 --- /dev/null +++ b/docs/.changes/20260729-open-enum-values.json @@ -0,0 +1,5 @@ +{ + "type": "fixed", + "message": "Unknown string enum values no longer fail containing messages, round-trip unchanged, and are treated as no-ops when their semantics are unknown.", + "issues": [366] +} diff --git a/docs/specification/versioning.md b/docs/specification/versioning.md index dc716fce..8cc53b4d 100644 --- a/docs/specification/versioning.md +++ b/docs/specification/versioning.md @@ -24,7 +24,14 @@ AHP follows standard SemVer compatibility: - Two peers speaking versions `0.X.y` and `0.X.y'` (same pre-1.0 `MINOR`) are compatible. - Any other combination is **not** guaranteed to be compatible. -Within a compatible range, additive changes — new optional fields on existing types, new action types, new commands — are introduced in `PATCH` (or `MINOR`, while `MAJOR` is `0`) bumps and MUST be ignored by older peers that do not understand them. +Within a compatible range, additive changes — new optional fields on existing types, new enum values, new discriminated-union variants, new action types, and new commands — are introduced in `PATCH` (or `MINOR`, while `MAJOR` is `0`) bumps and MUST be ignored by older peers that do not understand them. + +This requirement applies at every level of a message: + +- Unknown fields on objects, including command parameters and results, MUST NOT cause decoding to fail. A peer MAY discard fields it does not understand. +- Unknown string enum values MUST NOT cause the containing message to fail. Generated clients preserve the raw string when re-encoding; consumers MUST treat its semantics as unknown. +- Unknown discriminated-union variants, including action types, MUST NOT cause the containing message to fail. Generated clients preserve the raw variant for re-encoding and reducers treat unknown actions as no-ops. +- Unknown numeric error codes MUST be accepted and surfaced as their raw integer value. ## Capabilities First, Then Required @@ -51,7 +58,7 @@ When a newer client connects to an older host: 2. The host picks the newest entry it understands and returns it. 3. The client checks the capability set advertised by the host before using newer features. 4. If a feature is unavailable, the client degrades gracefully — disabling UI affordances, falling back to older code paths, or surfacing a clear message to the user. -5. The host only sends action types known to the negotiated version. As a safety net, clients SHOULD silently ignore actions with unrecognized `type` values. +5. The host only sends action types known to the negotiated version. As a safety net, clients MUST silently ignore actions with unrecognized `type` values while preserving their raw wire representation. ## Backward Compatibility diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 4fad6f13..5cbf9b06 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -18,8 +18,11 @@ * `@JsonClassDiscriminator`, because that mode forbids the discriminator * field from existing on the variant data class — but our TS variant * interfaces include it (e.g. `MarkdownResponsePart.kind = 'markdown'`). + * - String enums → `@JvmInline value class` wrapping `String`, with + * companion-object constants, so unknown future values decode/encode + * losslessly. * - Bitset enums (JSDoc starts with "Bitset") → `@JvmInline value class` - * wrapping `Int`, with companion-object flag constants and bitwise ops, + * wrapping `UInt`, with companion-object flag constants and bitwise ops, * so unknown future bits decode/encode losslessly. * - Recursive structs → plain data classes (Kotlin classes are heap- * allocated by default, so self-reference Just Works). @@ -405,6 +408,35 @@ function generateKotlinEnum(enumDecl: EnumDeclaration): string { return lines.join('\n'); } + if (!isNumeric) { + lines.push(`@Serializable(with = ${name}Serializer::class)`); + lines.push('@JvmInline'); + lines.push(`value class ${name}(val rawValue: String) {`); + lines.push(' companion object {'); + for (const member of enumDecl.getMembers()) { + const memberName = toScreamingSnake(member.getName()); + const value = member.getValue(); + const memberDoc = member.getJsDocs()[0]?.getDescription().trim(); + if (memberDoc) { + lines.push(...emitKDoc(memberDoc, ' ')); + } + lines.push(` val ${memberName}: ${name} = ${name}(${JSON.stringify(String(value))})`); + } + lines.push(' }'); + lines.push('}'); + lines.push(''); + lines.push(`internal object ${name}Serializer : KSerializer<${name}> {`); + lines.push(` override val descriptor: SerialDescriptor =`); + lines.push(` PrimitiveSerialDescriptor("${name}", PrimitiveKind.STRING)`); + lines.push(` override fun serialize(encoder: Encoder, value: ${name}) {`); + lines.push(' encoder.encodeString(value.rawValue)'); + lines.push(' }'); + lines.push(` override fun deserialize(decoder: Decoder): ${name} =`); + lines.push(` ${name}(decoder.decodeString())`); + lines.push('}'); + return lines.join('\n'); + } + lines.push('@Serializable'); lines.push(`enum class ${name} {`); diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index eaba7592..22fc746f 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -490,16 +490,56 @@ function generateRustEnum(enumDecl: EnumDeclaration): string { } lines.push('}'); } else { - lines.push('#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]'); + lines.push('///'); + lines.push('/// Unknown values are preserved so newer peers can extend this enum without'); + lines.push('/// making older clients fail the containing message.'); + lines.push('#[derive(Debug, Clone, PartialEq, Eq, Hash)]'); lines.push(`pub enum ${name} {`); for (const mem of enumDecl.getMembers()) { const doc = mem.getJsDocs()[0]?.getDescription().trim(); if (doc) { for (const d of doc.split('\n')) lines.push(` /// ${d.trimEnd()}`); } - lines.push(` #[serde(rename = ${JSON.stringify(String(mem.getValue()))})]`); lines.push(` ${mem.getName()},`); } + lines.push(' /// An unknown or future wire value, preserved verbatim.'); + lines.push(' Unknown(String),'); + lines.push('}'); + lines.push(''); + lines.push(`impl ${name} {`); + lines.push(' /// Returns the exact string used on the wire.'); + lines.push(' pub fn as_str(&self) -> &str {'); + lines.push(' match self {'); + for (const mem of enumDecl.getMembers()) { + lines.push(` Self::${mem.getName()} => ${JSON.stringify(String(mem.getValue()))},`); + } + lines.push(' Self::Unknown(value) => value.as_str(),'); + lines.push(' }'); + lines.push(' }'); + lines.push('}'); + lines.push(''); + lines.push(`impl Serialize for ${name} {`); + lines.push(' fn serialize(&self, serializer: S) -> Result'); + lines.push(' where'); + lines.push(' S: serde::Serializer,'); + lines.push(' {'); + lines.push(' serializer.serialize_str(self.as_str())'); + lines.push(' }'); + lines.push('}'); + lines.push(''); + lines.push(`impl<'de> Deserialize<'de> for ${name} {`); + lines.push(' fn deserialize(deserializer: D) -> Result'); + lines.push(' where'); + lines.push(` D: serde::Deserializer<'de>,`); + lines.push(' {'); + lines.push(' let value = String::deserialize(deserializer)?;'); + lines.push(' Ok(match value.as_str() {'); + for (const mem of enumDecl.getMembers()) { + lines.push(` ${JSON.stringify(String(mem.getValue()))} => Self::${mem.getName()},`); + } + lines.push(' _ => Self::Unknown(value),'); + lines.push(' })'); + lines.push(' }'); lines.push('}'); } return lines.join('\n'); diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 88d307d6..03dc951c 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -332,6 +332,55 @@ function generateSwiftEnum(enumDecl: EnumDeclaration): string { return lines.join('\n'); } + if (rawType === 'String') { + lines.push(`public enum ${name}: Codable, Sendable, Hashable, RawRepresentable {`); + + for (const member of enumDecl.getMembers()) { + const memberName = swiftIdentifier(toCamelCase(member.getName())); + const memberDoc = member.getJsDocs()[0]?.getDescription().trim(); + if (memberDoc) { + for (const docLine of memberDoc.split('\n')) { + lines.push(emitSwiftDocLine(docLine, ' ')); + } + } + lines.push(` case ${memberName}`); + } + lines.push(' /// An unknown or future wire value, preserved verbatim.'); + lines.push(' case unknown(String)'); + lines.push(''); + lines.push(' public init(rawValue: String) {'); + lines.push(' switch rawValue {'); + for (const member of enumDecl.getMembers()) { + const memberName = swiftIdentifier(toCamelCase(member.getName())); + lines.push(` case ${JSON.stringify(member.getValue())}: self = .${memberName}`); + } + lines.push(' default: self = .unknown(rawValue)'); + lines.push(' }'); + lines.push(' }'); + lines.push(''); + lines.push(' public var rawValue: String {'); + lines.push(' switch self {'); + for (const member of enumDecl.getMembers()) { + const memberName = swiftIdentifier(toCamelCase(member.getName())); + lines.push(` case .${memberName}: return ${JSON.stringify(member.getValue())}`); + } + lines.push(' case .unknown(let value): return value'); + lines.push(' }'); + lines.push(' }'); + lines.push(''); + lines.push(' public init(from decoder: Decoder) throws {'); + lines.push(' let container = try decoder.singleValueContainer()'); + lines.push(' self.init(rawValue: try container.decode(String.self))'); + lines.push(' }'); + lines.push(''); + lines.push(' public func encode(to encoder: Encoder) throws {'); + lines.push(' var container = encoder.singleValueContainer()'); + lines.push(' try container.encode(rawValue)'); + lines.push(' }'); + lines.push('}'); + return lines.join('\n'); + } + lines.push(`public enum ${name}: ${rawType}, Codable, Sendable {`); for (const member of enumDecl.getMembers()) { diff --git a/types/channels-chat/reducer.ts b/types/channels-chat/reducer.ts index 332eaaec..bbb6802d 100644 --- a/types/channels-chat/reducer.ts +++ b/types/channels-chat/reducer.ts @@ -813,6 +813,9 @@ export function chatReducer(state: ChatState, action: ChatAction, log?: (msg: st if (action.kind === PendingMessageKind.Steering) { return { ...state, steeringMessage: entry }; } + if (action.kind !== PendingMessageKind.Queued) { + return state; + } const existing = state.queuedMessages ?? []; const idx = existing.findIndex(m => m.id === action.id); if (idx >= 0) { @@ -830,6 +833,9 @@ export function chatReducer(state: ChatState, action: ChatAction, log?: (msg: st } return { ...state, steeringMessage: undefined }; } + if (action.kind !== PendingMessageKind.Queued) { + return state; + } const existing = state.queuedMessages; if (!existing) { return state; diff --git a/types/test-cases/reducers/238-pending-message-set-unknown-kind-is-no-op.json b/types/test-cases/reducers/238-pending-message-set-unknown-kind-is-no-op.json new file mode 100644 index 00000000..5e379379 --- /dev/null +++ b/types/test-cases/reducers/238-pending-message-set-unknown-kind-is-no-op.json @@ -0,0 +1,53 @@ +{ + "description": "pending message set with an unknown kind is no-op", + "reducer": "chat", + "initial": { + "turns": [], + "queuedMessages": [ + { + "id": "existing", + "message": { + "text": "Existing", + "origin": { + "kind": "user" + } + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:01.000Z" + }, + "actions": [ + { + "type": "chat/pendingMessageSet", + "kind": "future-kind", + "id": "future", + "message": { + "text": "Future", + "origin": { + "kind": "user" + } + } + } + ], + "expected": { + "turns": [], + "queuedMessages": [ + { + "id": "existing", + "message": { + "text": "Existing", + "origin": { + "kind": "user" + } + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:01.000Z" + } +} diff --git a/types/test-cases/reducers/239-pending-message-removed-unknown-kind-is-no-op.json b/types/test-cases/reducers/239-pending-message-removed-unknown-kind-is-no-op.json new file mode 100644 index 00000000..c32ad580 --- /dev/null +++ b/types/test-cases/reducers/239-pending-message-removed-unknown-kind-is-no-op.json @@ -0,0 +1,65 @@ +{ + "description": "pending message removed with an unknown kind is no-op", + "reducer": "chat", + "initial": { + "turns": [], + "steeringMessage": { + "id": "shared", + "message": { + "text": "Steering", + "origin": { + "kind": "user" + } + } + }, + "queuedMessages": [ + { + "id": "shared", + "message": { + "text": "Queued", + "origin": { + "kind": "user" + } + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:01.000Z" + }, + "actions": [ + { + "type": "chat/pendingMessageRemoved", + "kind": "future-kind", + "id": "shared" + } + ], + "expected": { + "turns": [], + "steeringMessage": { + "id": "shared", + "message": { + "text": "Steering", + "origin": { + "kind": "user" + } + } + }, + "queuedMessages": [ + { + "id": "shared", + "message": { + "text": "Queued", + "origin": { + "kind": "user" + } + } + } + ], + "resource": "copilot:/test-session", + "title": "Test Session", + "status": 1, + "modifiedAt": "1970-01-01T00:00:01.000Z" + } +} diff --git a/types/test-cases/round-trips/041-auth-required-unknown-reason.json b/types/test-cases/round-trips/041-auth-required-unknown-reason.json new file mode 100644 index 00000000..e2c84c16 --- /dev/null +++ b/types/test-cases/round-trips/041-auth-required-unknown-reason.json @@ -0,0 +1,18 @@ +{ + "name": "auth-required-unknown-reason", + "group": "A", + "description": "An auth/required payload with a reason introduced by a newer peer decodes without failing and preserves the unknown enum value on re-encode.", + "type": "AuthRequiredParams", + "input": { + "channel": "ahp-root://", + "resource": "https://example.com", + "reason": "unauthorized" + }, + "acceptableOutputs": [ + { + "channel": "ahp-root://", + "resource": "https://example.com", + "reason": "unauthorized" + } + ] +} diff --git a/types/test-cases/round-trips/042-turn-unknown-enum-values.json b/types/test-cases/round-trips/042-turn-unknown-enum-values.json new file mode 100644 index 00000000..13dbaaad --- /dev/null +++ b/types/test-cases/round-trips/042-turn-unknown-enum-values.json @@ -0,0 +1,30 @@ +{ + "name": "turn-unknown-enum-values", + "group": "A", + "description": "A turn with unknown required and nested enum values decodes without failing and preserves both values on re-encode.", + "type": "Turn", + "input": { + "id": "turn-future-enums", + "message": { + "text": "Hello", + "origin": { + "kind": "integration" + } + }, + "responseParts": [], + "state": "paused" + }, + "acceptableOutputs": [ + { + "id": "turn-future-enums", + "message": { + "text": "Hello", + "origin": { + "kind": "integration" + } + }, + "responseParts": [], + "state": "paused" + } + ] +} diff --git a/types/test-cases/round-trips/043-jsonrpc-unknown-error-code.json b/types/test-cases/round-trips/043-jsonrpc-unknown-error-code.json new file mode 100644 index 00000000..0787b706 --- /dev/null +++ b/types/test-cases/round-trips/043-jsonrpc-unknown-error-code.json @@ -0,0 +1,24 @@ +{ + "name": "jsonrpc-unknown-error-code", + "group": "A", + "description": "A JSON-RPC error response with a code introduced by a newer peer decodes without failing and preserves the raw integer on re-encode.", + "type": "JsonRpcMessage", + "input": { + "jsonrpc": "2.0", + "id": 27, + "error": { + "code": -32999, + "message": "Future error" + } + }, + "acceptableOutputs": [ + { + "jsonrpc": "2.0", + "id": 27, + "error": { + "code": -32999, + "message": "Future error" + } + } + ] +}